Further work on import functions

This commit is contained in:
Tom Bloor 2017-11-13 13:30:33 +00:00
parent 593efcedfa
commit 9096bef00d
9 changed files with 382 additions and 0 deletions

View file

@ -22,6 +22,8 @@ requires 'Module::Runtime';
requires 'DBIx::Class::DeploymentHandler'; requires 'DBIx::Class::DeploymentHandler';
requires 'DBIx::Class::Fixtures'; requires 'DBIx::Class::Fixtures';
requires 'GIS::Distance'; requires 'GIS::Distance';
requires 'Text::CSV';
requires 'Try::Tiny';
feature 'schema-graph', 'Draw diagrams of Schema' => sub { feature 'schema-graph', 'Draw diagrams of Schema' => sub {
requires 'GraphViz'; requires 'GraphViz';

View file

@ -200,6 +200,12 @@ sub startup {
$admin_routes->get('/reports/transactions')->to('admin-reports#transaction_data'); $admin_routes->get('/reports/transactions')->to('admin-reports#transaction_data');
$admin_routes->get('/import')->to('admin-import#index');
$admin_routes->get('/import/add')->to('admin-import#get_add');
$admin_routes->post('/import/add')->to('admin-import#post_add');
$admin_routes->get('/import/:set_id')->to('admin-import#list');
$admin_routes->get('/import/:set_id/:value_id')->to('admin-import#get_value');
$admin_routes->post('/import/:set_id/:value_id')->to('admin-import#post_value');
# 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');

View file

@ -1,9 +1,147 @@
package Pear::LocalLoop::Controller::Admin::Import; package Pear::LocalLoop::Controller::Admin::Import;
use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Controller';
use Text::CSV;
use Try::Tiny;
has result_set => sub {
my $c = shift;
return $c->schema->resultset('ImportSet');
};
sub index { sub index {
my $c = shift; my $c = shift;
my $import_rs = $c->result_set->search(
undef,
{
page => $c->param('page') || 1,
rows => 10,
order_by => { -desc => 'date' },
},
);
$c->stash( import_rs => $import_rs );
}
sub list {
my $c = shift;
my $set_id = $c->param('set_id');
my $import_set = $c->result_set->find($set_id);
my $import_value_rs = $c->result_set->get_values($set_id);
my $import_users_rs = $c->result_set->get_users($set_id);
my $import_org_rs = $c->result_set->get_orgs($set_id);
$c->stash(
import_set => $import_set,
import_value_rs => $import_value_rs,
import_users_rs => $import_users_rs,
import_org_rs => $import_org_rs,
);
}
sub get_add {
my $c = shift;
}
sub post_add {
my $c = shift;
my $csv_data = $c->param('csv');
my $date_format = $c->param('date_format');
my $csv = Text::CSV->new({
binary => 1,
allow_whitespace => 1,
});
open my $fh, '<', \$csv_data;
# List context returns the actual headers
my @csv_headers;
my $error;
try {
@csv_headers = $csv->header( $fh );
} catch {
$error = $_;
};
if ( defined $error ) {
$c->flash( error => $error, csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
# Text::CSV Already errors on duplicate columns, so this is fine
my @required = grep {/^user$|^value$|^date$|^organisation$/} @csv_headers;
unless ( scalar( @required ) == 4 ) {
$c->flash( error => 'Required columns not available', csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
my $csv_output = $csv->getline_hr_all( $fh );
unless ( scalar( @$csv_output ) ) {
$c->flash( error => "No data found", csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
for my $data ( @$csv_output ) {
Dwarn $data;
for my $key ( qw/ user value organisation / ) {
unless ( defined $data->{$key} ) {
$c->flash( error => "Undefined [$key] data found", csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
}
if ( defined $data->{date} ) {
my $dtp = DateTime::Format::Strptime->new( pattern => $date_format );
my $dt_obj = $dtp->parse_datetime($data->{date});
unless ( defined $dt_obj ) {
$c->flash( error => "Undefined or incorrect format for [date] data found", csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
$data->{date} = $dt_obj;
}
}
my $value_set;
$c->schema->txn_do(
sub {
$value_set = $c->result_set->create({});
$value_set->values->populate(
[
[ qw/ user_name purchase_value purchase_date org_name / ],
( map { [ @{$_}{qw/ user value date organisation /} ] } @$csv_output ),
]
);
}
);
unless ( defined $value_set ) {
$c->flash( error => 'Error creating new Value Set', csv_data => $csv_data, date_format => $date_format );
$c->redirect_to( '/admin/import/add' );
return;
}
$c->flash( success => 'Created Value Set' );
$c->redirect_to( '/admin/import/' . $value_set->id );
}
sub get_value {
my $c = shift;
my $set_id = $c->param('set_id');
}
sub post_value {
my $c = shift;
my $set_id = $c->param('set_id');
} }
1; 1;

View file

@ -0,0 +1,43 @@
package Pear::LocalLoop::Schema::ResultSet::ImportSet;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';
sub get_values {
my $self = shift;
my $id = shift;
return $self->find($id)->search_related(
'values',
undef,
{
order_by => { -asc => 'id' },
},
);
}
sub get_users {
my $self = shift;
my $id = shift;
return $self->get_values($id)->search({},
{
group_by => 'user_name',
},
);
}
sub get_orgs {
my $self = shift;
my $id = shift;
return $self->get_values($id)->search({},
{
group_by => 'org_name',
},
);
}
1;

View file

@ -0,0 +1,43 @@
% layout 'admin';
% title 'Import';
% 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">
<h3>Add CSV Import</h3>
<p>
Copy and paste a CSV in to import it. The first line should contain the
headers The following headers are recognised:
</p>
<ul>
<li>user</li>
<li>value</li>
<li>date</li>
<li>organisation</li>
</ul>
<p>
Any columns which are not in the list above will be ignored and can be
called anything you want, as long as they are not in the list above. They
must also be unique.
</p>
<p>
For date format, consult <a href="https://metacpan.org/pod/DateTime::Format::Strptime#STRPTIME-PATTERN-TOKENS">here</a> for the patterns
</p>
</div>
<div class="col-12">
<form action="<%= url_for %>" method="post">
<input type="text" class="form-control mb-3" name="date_format" required value="<%=flash 'date_format' %>"/>
<textarea class="form-control mb-3" rows="15" name="csv"><%= flash 'csv_data' %></textarea>
<button class="btn btn-success btn-block" type="submit">Save</button>
</form>
</div>
</div>

View file

View file

@ -0,0 +1,45 @@
% layout 'admin';
% title 'Import';
% 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">
<div class="card">
<h3 class="card-header">
CSV Import
<a href="<%= url_for . '/add' %>" class="btn btn-success" style="float: right">Import Data</a>
</h3>
</div>
</div>
% for my $import ( $import_rs->all ) {
<div class="col col-md-4 mb-3">
<div class="card">
<div class="card-header">
<span class="font-bold"><%= $import->id %></span>
%= format_human_datetime $import->date;
</div>
<div class="card-body">
</div>
<div class="card-footer text-right">
<a href="<%= url_for . '/' . $import->id %>" class="card-link">
Edit Import
</a>
</div>
</div>
</div>
% }
</div>
<div class="row">
<div class="col">
%= bootstrap_pagination( $c->param('page') || 1, $import_rs->pager->last_page, { class => 'justify-content-center' } );
</div>
</div>

View file

@ -0,0 +1,102 @@
% layout 'admin';
% title 'Import';
% 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 col-md-6 mb-3">
<div class="card">
<h3 class="card-header">
User Assignments
</h3>
<div class="card-body">
Unique users in this Import, and their assigned entity
</div>
<div class="list-group list-group-flush">
% for my $user ( $import_users_rs->all ) {
<div class="list-group-item">
<div class="row">
<div class="col-4">
%= $user->user_name
</div>
<div class="col-4">
<span class="text-muted font-italic">Unassigned</span>
</div>
<div class="col-4">
<a class="btn btn-primary">Select</a>
</div>
</div>
</div>
% }
</div>
</div>
</div>
<div class="col-12 col-md-6 mb-3">
<div class="card">
<h3 class="card-header">
Org Assignments
</h3>
<div class="card-body">
Unique orgs in this Import, and their assigned entity
</div>
<div class="list-group list-group-flush">
% for my $org ( $import_org_rs->all ) {
<div class="list-group-item">
<div class="row">
<div class="col-4">
%= $org->org_name
</div>
<div class="col-4">
<span class="text-muted font-italic">Unassigned</span>
</div>
<div class="col-4">
<a class="btn btn-primary">Select</a>
</div>
</div>
</div>
% }
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<h3 class="card-header">
%= format_human_datetime $import_set->date;
</h3>
<div class="card-body">
Content listed in original order of import
</div>
<div class="list-group list-group-flush">
% for my $import_value ( $import_value_rs->all ) {
<div class="list-group-item">
<div class="row">
<div class="col-2">
<%= $import_value->user_name %>
</div>
<div class="col-3">
<%= format_human_datetime $import_value->purchase_date %>
</div>
<div class="col-2">
<%= $import_value->purchase_value %>
</div>
<div class="col-3">
<%= $import_value->org_name %>
</div>
<div class="col-2">
<a href="#" class="btn btn-danger">Ignore</a>
</div>
</div>
</div>
% }
</div>
</div>
</div>
</div>

View file

@ -50,6 +50,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link<%= title eq 'Organisations' ? ' active' : '' %>" href="<%= url_for '/admin/organisations' %>">Organisations</a> <a class="nav-link<%= title eq 'Organisations' ? ' active' : '' %>" href="<%= url_for '/admin/organisations' %>">Organisations</a>
</li> </li>
<li class="nav-item">
<a class="nav-link<%= title eq 'Import' ? ' active' : '' %>" href="<%= url_for '/admin/import' %>">Import</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="<%= url_for '/admin/logout' %>">Logout</a> <a class="nav-link" href="<%= url_for '/admin/logout' %>">Logout</a>
</li> </li>