In progress commit
This commit is contained in:
parent
71d4a83400
commit
b00e9e838c
19 changed files with 478 additions and 79 deletions
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
1
cpanfile
1
cpanfile
|
@ -23,6 +23,7 @@ requires 'DBIx::Class::Fixtures';
|
||||||
requires 'GIS::Distance';
|
requires 'GIS::Distance';
|
||||||
requires 'Text::CSV';
|
requires 'Text::CSV';
|
||||||
requires 'Try::Tiny';
|
requires 'Try::Tiny';
|
||||||
|
requires 'Throwable::Error';
|
||||||
|
|
||||||
on 'test' => sub {
|
on 'test' => sub {
|
||||||
requires 'Test::More';
|
requires 'Test::More';
|
||||||
|
|
|
@ -244,6 +244,9 @@ sub startup {
|
||||||
|
|
||||||
$admin_routes->get('/import/:set_id/ignore/:value_id')->to('admin-import#ignore_value');
|
$admin_routes->get('/import/:set_id/ignore/:value_id')->to('admin-import#ignore_value');
|
||||||
$admin_routes->get('/import/:set_id/import')->to('admin-import#run_import');
|
$admin_routes->get('/import/:set_id/import')->to('admin-import#run_import');
|
||||||
|
|
||||||
|
$admin_routes->get('/import_from')->to('admin-import_from#index');
|
||||||
|
|
||||||
# my $user_routes = $r->under('/')->to('root#under');
|
# my $user_routes = $r->under('/')->to('root#under');
|
||||||
|
|
||||||
# $user_routes->get('/home')->to('root#home');
|
# $user_routes->get('/home')->to('root#home');
|
||||||
|
|
4
lib/Pear/LocalLoop/Controller/Admin/ImportFrom.pm
Normal file
4
lib/Pear/LocalLoop/Controller/Admin/ImportFrom.pm
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package Pear::LocalLoop::Controller::Admin::ImportFrom;
|
||||||
|
use Mojo::Base 'Mojolicious::Controller';
|
||||||
|
|
||||||
|
1;
|
10
lib/Pear/LocalLoop/Error.pm
Normal file
10
lib/Pear/LocalLoop/Error.pm
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package Pear::LocalLoop::Error;
|
||||||
|
use Moo;
|
||||||
|
extends 'Throwable::Error';
|
||||||
|
|
||||||
|
package Pear::LocalLoop::ImplementationError;
|
||||||
|
use Moo;
|
||||||
|
use namespace::clean;
|
||||||
|
extends Pear::LocalLoop::Error;
|
||||||
|
|
||||||
|
1;
|
23
lib/Pear/LocalLoop/Import/LCCCsv.pm
Normal file
23
lib/Pear/LocalLoop/Import/LCCCsv.pm
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package Pear::LocalLoop::Import::LCCCsv;
|
||||||
|
use Moo;
|
||||||
|
use Pear::LocalLoop::Error;
|
||||||
|
|
||||||
|
with qw/
|
||||||
|
Pear::LocalLoop::Import::Role::ExternalName
|
||||||
|
Pear::LocalLoop::Import::Role::Schema
|
||||||
|
Pear::LocalLoop::Import::Role::CSV
|
||||||
|
/;
|
||||||
|
|
||||||
|
has external_name => (
|
||||||
|
is => 'ro',
|
||||||
|
default => 'LCC CSV',
|
||||||
|
);
|
||||||
|
|
||||||
|
has csv_required_columns => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
Pear::LocalLoop::ImplementationError->throw("Must be implemented by child class");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
16
lib/Pear/LocalLoop/Import/LCCCsv/Suppliers.pm
Normal file
16
lib/Pear/LocalLoop/Import/LCCCsv/Suppliers.pm
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package Pear::LocalLoop::Import::LCCCsv::Suppliers;
|
||||||
|
use Moo;
|
||||||
|
|
||||||
|
extends qw/Pear::LocalLoop::Import::LCCCsv/;
|
||||||
|
|
||||||
|
sub import {
|
||||||
|
my $self = shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _row_to_result {
|
||||||
|
my ( $self, $row ) = @_;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
10
lib/Pear/LocalLoop/Import/LCCCsv/Transactions.pm
Normal file
10
lib/Pear/LocalLoop/Import/LCCCsv/Transactions.pm
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package Pear::LocalLoop::Import::LCCCsv::Transactions;
|
||||||
|
use Moo;
|
||||||
|
|
||||||
|
extends qw/Pear::LocalLoop::Import::LCCCsv/;
|
||||||
|
|
||||||
|
sub import {
|
||||||
|
my $self = shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
46
lib/Pear/LocalLoop/Import/Role/CSV.pm
Normal file
46
lib/Pear/LocalLoop/Import/Role/CSV.pm
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package Pear::LocalLoop::Import::Role::CSV;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Moo::Role;
|
||||||
|
use Text::CSV;
|
||||||
|
|
||||||
|
requires 'csv_required_columns';
|
||||||
|
|
||||||
|
has csv_file => (
|
||||||
|
is => 'ro',
|
||||||
|
required => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
has _csv_filehandle => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
open my $fh, '<', $self->csv_file;
|
||||||
|
return $fh;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
has text_csv_options => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
return {
|
||||||
|
binary => 1,
|
||||||
|
allow_whitespace => 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
has _text_csv => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
return Text::CSV->new(shift->text_csv_options);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
has csv_data => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
my $self = shift;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
19
lib/Pear/LocalLoop/Import/Role/ExternalName.pm
Normal file
19
lib/Pear/LocalLoop/Import/Role/ExternalName.pm
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package Pear::LocalLoop::Import::Role::ExternalName;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Moo::Role;
|
||||||
|
|
||||||
|
requires qw/
|
||||||
|
external_name
|
||||||
|
schema
|
||||||
|
/;
|
||||||
|
|
||||||
|
has external_result => (
|
||||||
|
is => 'lazy',
|
||||||
|
builder => sub {
|
||||||
|
my $self = shift;
|
||||||
|
return $self->resultset('ExternalReference')->find_or_create({ name => $self->external_name });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
11
lib/Pear/LocalLoop/Import/Role/Schema.pm
Normal file
11
lib/Pear/LocalLoop/Import/Role/Schema.pm
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package Pear::LocalLoop::Import::Role::Schema;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Moo::Role;
|
||||||
|
|
||||||
|
has schema => (
|
||||||
|
is => 'ro',
|
||||||
|
required => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
39
lib/Pear/LocalLoop/Schema/Result/ExternalReference.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/ExternalReference.pm
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::ExternalReference;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table("external_references");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_auto_increment => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"name" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key("id");
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint([ qw/name/ ]);
|
||||||
|
|
||||||
|
__PACKAGE__->has_many(
|
||||||
|
'transactions',
|
||||||
|
"Pear::LocalLoop::Schema::Result::TransactionExternal",
|
||||||
|
{ 'foreign.external_reference_id' => 'self.id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->has_many(
|
||||||
|
'organisations',
|
||||||
|
"Pear::LocalLoop::Schema::Result::OrganisationExternal",
|
||||||
|
{ 'foreign.external_reference_id' => 'self.id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
|
@ -70,16 +70,31 @@ __PACKAGE__->add_columns(
|
||||||
},
|
},
|
||||||
latitude => {
|
latitude => {
|
||||||
data_type => 'decimal',
|
data_type => 'decimal',
|
||||||
size => [8,5],
|
size => [ 8, 5 ],
|
||||||
is_nullable => 1,
|
is_nullable => 1,
|
||||||
default_value => undef,
|
default_value => undef,
|
||||||
},
|
},
|
||||||
longitude => {
|
longitude => {
|
||||||
data_type => 'decimal',
|
data_type => 'decimal',
|
||||||
size => [8,5],
|
size => [ 8, 5 ],
|
||||||
is_nullable => 1,
|
is_nullable => 1,
|
||||||
default_value => undef,
|
default_value => undef,
|
||||||
},
|
},
|
||||||
|
type_id => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 1,
|
||||||
|
is_foreign_key => 1,
|
||||||
|
},
|
||||||
|
social_type_id => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 1,
|
||||||
|
is_foreign_key => 1,
|
||||||
|
},
|
||||||
|
is_anchor => {
|
||||||
|
data_type => 'boolean',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'FALSE',
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
__PACKAGE__->set_primary_key('id');
|
__PACKAGE__->set_primary_key('id');
|
||||||
|
@ -103,27 +118,32 @@ __PACKAGE__->filter_column(
|
||||||
},
|
},
|
||||||
is_local => {
|
is_local => {
|
||||||
filter_to_storage => 'to_bool',
|
filter_to_storage => 'to_bool',
|
||||||
|
},
|
||||||
|
is_anchor => {
|
||||||
|
filter_to_storage => 'to_bool',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
# Only works when calling ->deploy, but atleast helps for tests
|
# Only works when calling ->deploy, but atleast helps for tests
|
||||||
sub sqlt_deploy_hook {
|
sub sqlt_deploy_hook {
|
||||||
my ( $source_instance, $sqlt_table ) = @_;
|
my ($source_instance, $sqlt_table) = @_;
|
||||||
my $pending_field = $sqlt_table->get_field('pending');
|
my $pending_field = $sqlt_table->get_field('pending');
|
||||||
if ( $sqlt_table->schema->translator->producer_type =~ /SQLite$/ ) {
|
if ($sqlt_table->schema->translator->producer_type =~ /SQLite$/) {
|
||||||
$pending_field->{default_value} = 0;
|
$pending_field->{default_value} = 0;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$pending_field->{default_value} = \"false";
|
$pending_field->{default_value} = \"false";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub to_bool {
|
sub to_bool {
|
||||||
my ( $self, $val ) = @_;
|
my ($self, $val) = @_;
|
||||||
return if ! defined $val;
|
return if !defined $val;
|
||||||
my $driver_name = $self->result_source->schema->storage->dbh->{Driver}->{Name};
|
my $driver_name = $self->result_source->schema->storage->dbh->{Driver}->{Name};
|
||||||
if ( $driver_name eq 'SQLite' ) {
|
if ($driver_name eq 'SQLite') {
|
||||||
return $val ? 1 : 0;
|
return $val ? 1 : 0;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return $val ? 'true' : 'false';
|
return $val ? 'true' : 'false';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
49
lib/Pear/LocalLoop/Schema/Result/OrganisationExternal.pm
Normal file
49
lib/Pear/LocalLoop/Schema/Result/OrganisationExternal.pm
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::OrganisationExternal;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table("organisations_external");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_auto_increment => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"org_id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_foreign_key => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"external_reference_id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_foreign_key => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"external_id" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key("id");
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint([ qw/external_reference_id external_id/ ]);
|
||||||
|
|
||||||
|
__PACKAGE__->belongs_to(
|
||||||
|
"organisation",
|
||||||
|
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||||
|
{ 'foreign.id' => 'self.org_id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->belongs_to(
|
||||||
|
"external_reference",
|
||||||
|
"Pear::LocalLoop::Schema::Result::ExternalReference",
|
||||||
|
{ 'foreign.id' => 'self.external_reference_id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
39
lib/Pear/LocalLoop/Schema/Result/OrganisationSocialType.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/OrganisationSocialType.pm
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::OrganisationSocialType;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table("organisation_social_types");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_auto_increment => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"key" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"name" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key("id");
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint([ qw/key/ ]);
|
||||||
|
|
||||||
|
__PACKAGE__->has_many(
|
||||||
|
"organisations",
|
||||||
|
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||||
|
{ 'foreign.social_type_id' => 'self.id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
||||||
|
1;
|
38
lib/Pear/LocalLoop/Schema/Result/OrganisationType.pm
Normal file
38
lib/Pear/LocalLoop/Schema/Result/OrganisationType.pm
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::OrganisationType;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table("organisation_types");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_auto_increment => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"key" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"name" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key("id");
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint([ qw/key/ ]);
|
||||||
|
|
||||||
|
__PACKAGE__->has_many(
|
||||||
|
"organisations",
|
||||||
|
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||||
|
{ 'foreign.type_id' => 'self.id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
49
lib/Pear/LocalLoop/Schema/Result/TransactionExternal.pm
Normal file
49
lib/Pear/LocalLoop/Schema/Result/TransactionExternal.pm
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package Pear::LocalLoop::Schema::Result::TransactionExternal;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use base 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
__PACKAGE__->table("transactions_external");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
"id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_auto_increment => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"transaction_id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_foreign_key => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"external_reference_id" => {
|
||||||
|
data_type => "integer",
|
||||||
|
is_foreign_key => 1,
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
"external_id" => {
|
||||||
|
data_type => "varchar",
|
||||||
|
size => 255,
|
||||||
|
is_nullable => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key("id");
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint([ qw/external_reference_id external_id/ ]);
|
||||||
|
|
||||||
|
__PACKAGE__->belongs_to(
|
||||||
|
"transaction",
|
||||||
|
"Pear::LocalLoop::Schema::Result::Transaction",
|
||||||
|
{ 'foreign.id' => 'self.transaction_id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->belongs_to(
|
||||||
|
"external_reference",
|
||||||
|
"Pear::LocalLoop::Schema::Result::ExternalReference",
|
||||||
|
{ 'foreign.id' => 'self.external_reference_id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
1;
|
|
@ -43,19 +43,4 @@ __PACKAGE__->belongs_to(
|
||||||
{ 'foreign.id' => 'self.transaction_id' },
|
{ 'foreign.id' => 'self.transaction_id' },
|
||||||
);
|
);
|
||||||
|
|
||||||
__PACKAGE__->might_have(
|
|
||||||
"category",
|
|
||||||
"Pear::LocalLoop::Schema::Result::TransactionCategory" => "transaction_id",
|
|
||||||
);
|
|
||||||
|
|
||||||
sub sqlt_deploy_hook {
|
|
||||||
my ( $source_instance, $sqlt_table ) = @_;
|
|
||||||
my $pending_field = $sqlt_table->get_field('essential');
|
|
||||||
if ( $sqlt_table->schema->translator->producer_type =~ /SQLite$/ ) {
|
|
||||||
$pending_field->{default_value} = 0;
|
|
||||||
} else {
|
|
||||||
$pending_field->{default_value} = \"false";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
32
templates/admin/import_from/index.html.ep
Normal file
32
templates/admin/import_from/index.html.ep
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
% layout 'admin';
|
||||||
|
% title 'Import From';
|
||||||
|
% content_for javascript => begin
|
||||||
|
% end
|
||||||
|
% if (my $error = flash 'error') {
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<strong>Error!</strong> <%= $error %>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
% elsif (my $success = flash 'success') {
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<strong>Success!</strong> <%= $success %>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<h3 class="float-left">Import From</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<p>Various import options depending on source. Each one is custom.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-4 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">LCC Procurement Import</h5>
|
||||||
|
<div class="card-text">Creditor reports from LCC</div>
|
||||||
|
<a href="#" class="card-link">Suppliers</a>
|
||||||
|
<a href="#" class="card-link">Transactions</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Reference in a new issue