Merge pull request #91 from Pear-Trading/Release-v0.10.1

Release v0.10.1
This commit is contained in:
Finn 2017-12-21 15:51:10 +00:00 committed by GitHub
commit 0214ad4558
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 841 additions and 149 deletions

View file

@ -2,6 +2,16 @@
# Next Release # Next Release
# v0.10.1
* Added API for customer graphs
* Revamped graphs code
* Added API for customer local purchase pie charts
* Added API for customer snippets
* Added API for sector purchase list for customer dashboard
* **Admin Fix** Fixed org sector on user edit layout and text
* **Admin Feature** Added Sector U
# v0.10.0 # v0.10.0
* **API Change** Updated API for story trail maps * **API Change** Updated API for story trail maps

View file

@ -6,7 +6,6 @@ requires 'Mojo::JSON';
requires 'Email::Valid'; requires 'Email::Valid';
requires 'Geo::UK::Postcode::Regex' => '0.017'; requires 'Geo::UK::Postcode::Regex' => '0.017';
requires 'Authen::Passphrase::BlowfishCrypt'; requires 'Authen::Passphrase::BlowfishCrypt';
requires 'Time::Fake';
requires 'Scalar::Util'; requires 'Scalar::Util';
requires 'DBIx::Class'; requires 'DBIx::Class';
requires 'DBIx::Class::PassphraseColumn'; requires 'DBIx::Class::PassphraseColumn';
@ -25,6 +24,11 @@ requires 'GIS::Distance';
requires 'Text::CSV'; requires 'Text::CSV';
requires 'Try::Tiny'; requires 'Try::Tiny';
on 'test' => sub {
requires 'Test::More';
requires 'Test::MockTime';
};
feature 'schema-graph', 'Draw diagrams of Schema' => sub { feature 'schema-graph', 'Draw diagrams of Schema' => sub {
requires 'GraphViz'; requires 'GraphViz';
requires 'SQL::Translator'; requires 'SQL::Translator';
@ -38,4 +42,3 @@ feature 'postgres', 'PostgreSQL Support' => sub {
feature 'codepoint-open', 'Code Point Open manipulation' => sub { feature 'codepoint-open', 'Code Point Open manipulation' => sub {
requires 'Geo::UK::Postcode::CodePointOpen'; requires 'Geo::UK::Postcode::CodePointOpen';
}; };

View file

@ -148,6 +148,7 @@ sub startup {
$api->post('/user/account')->to('api-user#post_account_update'); $api->post('/user/account')->to('api-user#post_account_update');
$api->post('/user-history')->to('api-user#post_user_history'); $api->post('/user-history')->to('api-user#post_user_history');
$api->post('/stats')->to('api-stats#post_index'); $api->post('/stats')->to('api-stats#post_index');
$api->post('/stats/customer')->to('api-stats#post_customer');
$api->post('/stats/leaderboard')->to('api-stats#post_leaderboards'); $api->post('/stats/leaderboard')->to('api-stats#post_leaderboards');
$api->post('/stats/leaderboard/paged')->to('api-stats#post_leaderboards_paged'); $api->post('/stats/leaderboard/paged')->to('api-stats#post_leaderboards_paged');
$api->post('/outgoing-transactions')->to('api-transactions#post_transaction_list_purchases'); $api->post('/outgoing-transactions')->to('api-transactions#post_transaction_list_purchases');
@ -171,6 +172,12 @@ sub startup {
$api_v1_org->post('/employee')->to('api-organisation#post_employee_read'); $api_v1_org->post('/employee')->to('api-organisation#post_employee_read');
$api_v1_org->post('/employee/add')->to('api-organisation#post_employee_add'); $api_v1_org->post('/employee/add')->to('api-organisation#post_employee_add');
my $api_v1_cust = $api_v1->under('/customer')->to('api-v1-customer#auth');
$api_v1_cust->post('/graphs')->to('api-v1-customer-graphs#index');
$api_v1_cust->post('/snippets')->to('api-v1-customer-snippets#index');
$api_v1_cust->post('/pies')->to('api-v1-customer-pies#index');
my $admin_routes = $r->under('/admin')->to('admin#under'); my $admin_routes = $r->under('/admin')->to('admin#under');
$admin_routes->get('/home')->to('admin#home'); $admin_routes->get('/home')->to('admin#home');

View file

@ -92,6 +92,11 @@ sub update {
return $c->redirect_to( '/admin/users/' . $id ); return $c->redirect_to( '/admin/users/' . $id );
} }
my $location = $c->get_location_from_postcode(
$validation->param('postcode'),
$user->type,
);
if ( $user->type eq 'customer' ){ if ( $user->type eq 'customer' ){
try { try {
@ -100,6 +105,7 @@ sub update {
full_name => $validation->param('full_name'), full_name => $validation->param('full_name'),
display_name => $validation->param('display_name'), display_name => $validation->param('display_name'),
postcode => $validation->param('postcode'), postcode => $validation->param('postcode'),
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
}); });
$user->update({ $user->update({
email => $validation->param('email'), email => $validation->param('email'),
@ -125,6 +131,7 @@ sub update {
town => $validation->param('town'), town => $validation->param('town'),
sector => $validation->param('sector'), sector => $validation->param('sector'),
postcode => $validation->param('postcode'), postcode => $validation->param('postcode'),
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
}); });
$user->update({ $user->update({
email => $validation->param('email'), email => $validation->param('email'),

View file

@ -1,7 +1,7 @@
package Pear::LocalLoop::Controller::Api::Stats; package Pear::LocalLoop::Controller::Api::Stats;
use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Controller';
use List::Util qw/ first /; use List::Util qw/ max sum /;
has error_messages => sub { has error_messages => sub {
return { return {
@ -58,6 +58,80 @@ sub post_index {
}); });
} }
sub post_customer {
my $c = shift;
my $entity = $c->stash->{api_user}->entity;
my $duration = DateTime::Duration->new( weeks => 7 );
my $end = DateTime->today;
my $start = $end->clone->subtract_duration( $duration );
my $dtf = $c->schema->storage->datetime_parser;
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
my $week_transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
{
purchase_time => {
-between => [
$dtf->format_datetime($start),
$dtf->format_datetime($end),
],
},
buyer_id => $entity->id,
},
{
columns => [
{
quantised => 'quantised_weeks',
count => \"COUNT(*)",
}
],
group_by => 'quantised_weeks',
order_by => { '-asc' => 'quantised_weeks' },
}
);
my @all_weeks = $week_transaction_rs->all;
my $first = $all_weeks[0]->get_column('count') || 0;
my $second = $all_weeks[1]->get_column('count') || 0;
my $max = max( map { $_->get_column('count') } @all_weeks );
my $sum = sum( map { $_->get_column('count') } @all_weeks );
my $count = $week_transaction_rs->count;
my $weeks = {
first => $first,
second => $second,
max => $max,
sum => $sum,
count => $count,
};
my $sectors = { sectors => [], purchases => [] };
my $sector_purchase_rs = $entity->purchases->search({},
{
join => { 'seller' => 'organisation' },
columns => {
sector => "organisation.sector",
count => \"COUNT(*)",
},
group_by => "organisation.sector",
order_by => { '-desc' => $c->pg_or_sqlite('count',"COUNT(*)",)},
}
);
for ( $sector_purchase_rs->all ) {
push @{ $sectors->{ sectors } }, $_->get_column('sector');
push @{ $sectors->{ purchases } }, ($_->get_column('count') || 0);
}
return $c->render( json => {
success => Mojo::JSON->true,
weeks => $weeks,
sectors => $sectors,
});
}
sub post_leaderboards { sub post_leaderboards {
my $c = shift; my $c = shift;
@ -125,53 +199,74 @@ sub post_leaderboards_paged {
my $page = 1; my $page = 1;
my $today_board = $leaderboard_rs->get_latest( $validation->param('type') ); my $today_board = $leaderboard_rs->get_latest( $validation->param('type') );
my @leaderboard_array;
my $current_user_position;
my $values_count = 0;
if ( defined $today_board ) {
if ( !defined $validation->param('page') || $validation->param('page') < 1 ) { if ( !defined $validation->param('page') || $validation->param('page') < 1 ) {
my $user_position = $today_board->values->find({ entity_id => $c->stash->{api_user}->entity->id }); my $user_position = $today_board->values->find({ entity_id => $c->stash->{api_user}->entity->id });
$page = int(defined $user_position ? $user_position->{position} : 0 / 10) + 1; $page = int(defined $user_position ? $user_position->{position} : 0 / 10) + 1;
} else { } else {
$page = $validation->param('page'); $page = $validation->param('page');
} }
my $today_values = $today_board->values->search( my $today_values = $today_board->values->search(
{}, {},
{
page => $page,
rows => 10,
order_by => { -asc => 'me.position' },
columns => [
qw/
me.value
me.trend
me.position
/,
{ display_name => 'customer.display_name' },
],
join => { entity => 'customer' },
},
);
$today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' );
my @leaderboard_array = $today_values->all;
if ( $validation->param('type') =~ /total$/ ) {
@leaderboard_array = (map {
{ {
%$_, page => $page,
value => $_->{value} / 100000, rows => 10,
} order_by => { -asc => 'me.position' },
} @leaderboard_array); columns => [
qw/
me.value
me.trend
me.position
/,
{ display_name => 'customer.display_name' },
],
join => { entity => 'customer' },
},
);
$today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' );
@leaderboard_array = $today_values->all;
$values_count = $today_values->pager->total_entries;
if ( $validation->param('type') =~ /total$/ ) {
@leaderboard_array = (map {
{
%$_,
value => $_->{value} / 100000,
}
} @leaderboard_array);
}
$current_user_position = $today_values->find({ entity_id => $c->stash->{api_user}->entity->id });
} }
my $current_user_position = $today_values->find({ entity_id => $c->stash->{api_user}->entity->id });
return $c->render( json => { return $c->render( json => {
success => Mojo::JSON->true, success => Mojo::JSON->true,
leaderboard => [ @leaderboard_array ], leaderboard => [ @leaderboard_array ],
user_position => defined $current_user_position ? $current_user_position->{position} : 0, user_position => defined $current_user_position ? $current_user_position->{position} : 0,
page => $page, page => $page,
count => $today_values->pager->total_entries, count => $values_count,
}); });
} }
sub pg_or_sqlite {
my ( $c, $pg_sql, $sqlite_sql ) = @_;
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
if ( $driver eq 'Pg' ) {
return \$pg_sql;
} elsif ( $driver eq 'SQLite' ) {
return \$sqlite_sql;
} else {
$c->app->log->warn('Unknown Driver Used');
return undef;
}
}
1; 1;

View file

@ -146,12 +146,18 @@ sub post_upload {
return $c->api_validation_error if $validation->has_error; return $c->api_validation_error if $validation->has_error;
my $location = $c->get_location_from_postcode(
$validation->param('postcode'),
'organisation',
);
my $entity = $c->schema->resultset('Entity')->create_org({ my $entity = $c->schema->resultset('Entity')->create_org({
submitted_by_id => $user->id, submitted_by_id => $user->id,
name => $validation->param('organisation_name'), name => $validation->param('organisation_name'),
street_name => $validation->param('street_name'), street_name => $validation->param('street_name'),
town => $validation->param('town'), town => $validation->param('town'),
postcode => $validation->param('postcode'), postcode => $validation->param('postcode'),
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
pending => 1, pending => 1,
}); });
$organisation = $entity->organisation; $organisation = $entity->organisation;

View file

@ -0,0 +1,21 @@
package Pear::LocalLoop::Controller::Api::V1::Customer;
use Mojo::Base 'Mojolicious::Controller';
sub auth {
my $c = shift;
return 1 if $c->stash->{api_user}->type eq 'customer';
$c->render(
json => {
success => Mojo::JSON->false,
message => 'Not an Customer',
error => 'user_not_cust',
},
status => 403,
);
return 0;
}
1;

View file

@ -0,0 +1,167 @@
package Pear::LocalLoop::Controller::Api::V1::Customer::Graphs;
use Mojo::Base 'Mojolicious::Controller';
has error_messages => sub {
return {
graph => {
required => { message => 'Must request graph type', status => 400 },
in => { message => 'Unrecognised graph type', status => 400 },
},
};
};
sub index {
my $c = shift;
my $validation = $c->validation;
$validation->input( $c->stash->{api_json} );
$validation->required('graph')->in( qw/
total_last_week
avg_spend_last_week
total_last_month
avg_spend_last_month
/ );
return $c->api_validation_error if $validation->has_error;
my $graph_sub = "graph_" . $validation->param('graph');
unless ( $c->can($graph_sub) ) {
# Secondary catch in case a mistake has been made
return $c->render(
json => {
success => Mojo::JSON->false,
message => $c->error_messages->{graph}->{in}->{message},
error => 'in',
},
status => $c->error_messages->{graph}->{in}->{status},
);
}
return $c->$graph_sub;
}
sub graph_total_last_week { return shift->_purchases_total_duration( 7 ) }
sub graph_total_last_month { return shift->_purchases_total_duration( 30 ) }
sub _purchases_total_duration {
my ( $c, $day_duration ) = @_;
my $duration = DateTime::Duration->new( days => $day_duration );
my $entity = $c->stash->{api_user}->entity;
my $data = { labels => [], data => [] };
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
$data->{bounds} = {
min => $c->format_iso_datetime( $start ),
max => $c->format_iso_datetime( $end ),
};
while ( $start < $end ) {
my $next_end = $start->clone->add( days => 1 );
my $transactions = $entity->purchases
->search_between( $start, $next_end )
->get_column('value')
->sum || 0 * 1;
push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
push @{ $data->{ data } }, $transactions / 100000;
$start->add( days => 1 );
}
return $c->render(
json => {
success => Mojo::JSON->true,
graph => $data,
}
);
}
sub graph_avg_spend_last_week { return shift->_purchases_avg_spend_duration( 7 ) }
sub graph_avg_spend_last_month { return shift->_purchases_avg_spend_duration( 30 ) }
sub _purchases_avg_spend_duration {
my ( $c, $day_duration ) = @_;
my $duration = DateTime::Duration->new( days => $day_duration );
my $entity = $c->stash->{api_user}->entity;
my $data = { labels => [], data => [] };
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
$data->{bounds} = {
min => $c->format_iso_datetime( $start ),
max => $c->format_iso_datetime( $end ),
};
my $dtf = $c->schema->storage->datetime_parser;
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
my $transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
{
purchase_time => {
-between => [
$dtf->format_datetime($start),
$dtf->format_datetime($end),
],
},
buyer_id => $entity->id,
},
{
columns => [
{
quantised => 'quantised_days',
count => \"COUNT(*)",
sum_value => $c->pg_or_sqlite(
'SUM("me"."value")',
'SUM("me"."value")',
),
average_value => $c->pg_or_sqlite(
'AVG("me"."value")',
'AVG("me"."value")',
),
}
],
group_by => 'quantised_days',
order_by => { '-asc' => 'quantised_days' },
}
);
for ( $transaction_rs->all ) {
my $quantised = $c->db_datetime_parser->parse_datetime($_->get_column('quantised'));
push @{ $data->{ labels } }, $c->format_iso_datetime( $quantised );
push @{ $data->{ data } }, ($_->get_column('average_value') || 0) / 100000;
}
return $c->render(
json => {
success => Mojo::JSON->true,
graph => $data,
}
);
}
sub _get_start_end_duration {
my ( $c, $duration ) = @_;
my $end = DateTime->today;
my $start = $end->clone->subtract_duration( $duration );
return ( $start, $end );
}
sub pg_or_sqlite {
my ( $c, $pg_sql, $sqlite_sql ) = @_;
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
if ( $driver eq 'Pg' ) {
return \$pg_sql;
} elsif ( $driver eq 'SQLite' ) {
return \$sqlite_sql;
} else {
$c->app->log->warn('Unknown Driver Used');
return undef;
}
}
1;

View file

@ -0,0 +1,62 @@
package Pear::LocalLoop::Controller::Api::V1::Customer::Pies;
use Mojo::Base 'Mojolicious::Controller';
sub index {
my $c = shift;
my $entity = $c->stash->{api_user}->entity;
my $purchase_rs = $entity->purchases;
my $local_org_local_purchase = $purchase_rs->search({
"me.distance" => { '<', 20000 },
'organisation.is_local' => 1,
},
{
join => { 'seller' => 'organisation' },
}
);
my $local_org_non_local_purchase = $purchase_rs->search({
"me.distance" => { '>=', 20000 },
'organisation.is_local' => 1,
},
{
join => { 'seller' => 'organisation' },
}
);
my $non_local_org_local_purchase = $purchase_rs->search({
"me.distance" => { '<', 20000 },
'organisation.is_local' => 0,
},
{
join => { 'seller' => 'organisation' },
}
);
my $non_local_org_non_local_purchase = $purchase_rs->search({
"me.distance" => { '>=', 20000 },
'organisation.is_local' => 0,
},
{
join => { 'seller' => 'organisation' },
}
);
my $data = {
'Local shop local purchaser' => $local_org_local_purchase->count,
'Local shop non-local purchaser' => $local_org_non_local_purchase->count,
'Non-local shop local purchaser' => $non_local_org_local_purchase->count,
'Non-local shop non-local purchaser' => $non_local_org_non_local_purchase->count,
};
return $c->render(
json => {
success => Mojo::JSON->true,
pie => $data,
}
);
}
1;

View file

@ -0,0 +1,32 @@
package Pear::LocalLoop::Controller::Api::V1::Customer::Snippets;
use Mojo::Base 'Mojolicious::Controller';
sub index {
my $c = shift;
my $entity = $c->stash->{api_user}->entity;
my $data = {
user_sum => 0,
user_position => 0,
};
my $user_rs = $entity->purchases;
$data->{ user_sum } = $user_rs->get_column('value')->sum || 0;
$data->{ user_sum } /= 100000;
my $leaderboard_rs = $c->schema->resultset('Leaderboard');
my $monthly_board = $leaderboard_rs->get_latest( 'monthly_total' );
if (defined $monthly_board) {
my $monthly_values = $monthly_board->values;
$data->{ user_position } = $monthly_values ? $monthly_values->find({ entity_id => $entity->id })->position : 0;
}
return $c->render(
json => {
success => Mojo::JSON->true,
snippets => $data,
}
);
}
1;

View file

@ -78,35 +78,30 @@ sub graph_customers_range {
); );
} }
sub graph_customers_last_7_days { sub graph_customers_last_7_days { return shift->_customers_last_duration( 7 ) }
my $c = shift; sub graph_customers_last_30_days { return shift->_customers_last_duration( 30 ) }
my $duration = DateTime::Duration->new( days => 7 );
return $c->_customers_last_duration( $duration );
}
sub graph_customers_last_30_days {
my $c = shift;
my $duration = DateTime::Duration->new( days => 30 );
return $c->_customers_last_duration( $duration );
}
sub _customers_last_duration { sub _customers_last_duration {
my ( $c, $duration ) = @_; my ( $c, $day_duration ) = @_;
my $duration = DateTime::Duration->new( days => $day_duration );
my $entity = $c->stash->{api_user}->entity; my $entity = $c->stash->{api_user}->entity;
my $data = { labels => [], data => [] }; my $data = { labels => [], data => [] };
my ( $start, $end ) = $c->_get_start_end_duration( $duration ); my ( $start, $end ) = $c->_get_start_end_duration( $duration );
$data->{bounds} = {
min => $c->format_iso_datetime( $start ),
max => $c->format_iso_datetime( $end ),
};
while ( $start < $end ) { while ( $start < $end ) {
my $next_end = $start->clone->add( days => 1 ); my $next_end = $start->clone->add( days => 1 );
my $transactions = $entity->sales my $transactions = $entity->sales
->search_between( $start, $next_end ) ->search_between( $start, $next_end )
->count; ->count;
push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
push @{ $data->{ data } }, $transactions; push @{ $data->{ data } }, $transactions;
$start->add( days => 1 ); $start->add( days => 1 );
} }
@ -132,13 +127,18 @@ sub _sales_last_duration {
my ( $start, $end ) = $c->_get_start_end_duration( $duration ); my ( $start, $end ) = $c->_get_start_end_duration( $duration );
$data->{bounds} = {
min => $c->format_iso_datetime( $start ),
max => $c->format_iso_datetime( $end ),
};
while ( $start < $end ) { while ( $start < $end ) {
my $next_end = $start->clone->add( days => 1 ); my $next_end = $start->clone->add( days => 1 );
my $transactions = $entity->sales my $transactions = $entity->sales
->search_between( $start, $next_end ) ->search_between( $start, $next_end )
->get_column('value') ->get_column('value')
->sum || 0 + 0; ->sum || 0 + 0;
push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
push @{ $data->{ data } }, $transactions / 100000; push @{ $data->{ data } }, $transactions / 100000;
$start->add( days => 1 ); $start->add( days => 1 );
} }
@ -164,13 +164,18 @@ sub _purchases_last_duration {
my ( $start, $end ) = $c->_get_start_end_duration( $duration ); my ( $start, $end ) = $c->_get_start_end_duration( $duration );
$data->{bounds} = {
min => $c->format_iso_datetime( $start ),
max => $c->format_iso_datetime( $end ),
};
while ( $start < $end ) { while ( $start < $end ) {
my $next_end = $start->clone->add( days => 1 ); my $next_end = $start->clone->add( days => 1 );
my $transactions = $entity->purchases my $transactions = $entity->purchases
->search_between( $start, $next_end ) ->search_between( $start, $next_end )
->get_column('value') ->get_column('value')
->sum || 0 + 0; ->sum || 0 + 0;
push @{ $data->{ labels } }, $start->day_name; push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
push @{ $data->{ data } }, $transactions / 100000; push @{ $data->{ data } }, $transactions / 100000;
$start->add( days => 1 ); $start->add( days => 1 );
} }

View file

@ -13,8 +13,11 @@ __PACKAGE__->result_source_instance->view_definition( qq/
SELECT "value", SELECT "value",
"distance", "distance",
"purchase_time", "purchase_time",
"buyer_id",
"seller_id",
DATE_TRUNC('hour', "purchase_time") AS "quantised_hours", DATE_TRUNC('hour', "purchase_time") AS "quantised_hours",
DATE_TRUNC('day', "purchase_time") AS "quantised_days" DATE_TRUNC('day', "purchase_time") AS "quantised_days",
DATE_TRUNC('week', "purchase_time") AS "quantised_weeks"
FROM "transactions" FROM "transactions"
/); /);

View file

@ -13,8 +13,11 @@ __PACKAGE__->result_source_instance->view_definition( qq/
SELECT "value", SELECT "value",
"distance", "distance",
"purchase_time", "purchase_time",
"buyer_id",
"seller_id",
DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"purchase_time")) AS "quantised_hours", DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"purchase_time")) AS "quantised_hours",
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time")) AS "quantised_days" DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time")) AS "quantised_days",
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time", 'weekday 1')) AS "quantised_weeks"
FROM "transactions" FROM "transactions"
/); /);

View file

@ -1,5 +1,9 @@
use Mojo::Base -strict; use Mojo::Base -strict;
BEGIN {
use Test::MockTime qw/ set_absolute_time /;
}
use FindBin qw/ $Bin /; use FindBin qw/ $Bin /;
use Test::More; use Test::More;
@ -14,98 +18,61 @@ $framework->install_fixtures('users');
my $t = $framework->framework; my $t = $framework->framework;
my $schema = $t->app->schema; my $schema = $t->app->schema;
my $dtf = $schema->storage->datetime_parser;
my $org_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity; set_absolute_time('2017-01-01T00:00:00Z');
my $user_result = $schema->resultset('User')->find({ email => 'test1@example.com' })->entity;
my $start = DateTime->today->subtract( hours => 12 );
# create 40 days worth of data
for my $count ( 0 .. 40 ) {
my $trans_day = $start->clone->subtract( days => $count );
create_random_transaction( 'test1@example.com', $trans_day );
if ( $count % 2 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 3 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 4 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
}
my $session_key = $framework->login({ my $session_key = $framework->login({
email => 'test1@example.com', email => 'test1@example.com',
password => 'abc123', password => 'abc123',
}); });
$t->app->schema->resultset('Leaderboard')->create_new( 'monthly_total', DateTime->now->truncate(to => 'month' )->subtract( months => 1) ); $t->post_ok('/api/stats/customer' => json => {
session_key => $session_key,
$t->post_ok('/api/stats' => json => { session_key => $session_key } ) })
->status_is(200)->or($framework->dump_error) ->status_is(200)->or($framework->dump_error)
->json_is('/success', Mojo::JSON->true) ->json_is('/weeks', {
->json_is('/today_sum', 0) first => 2,
->json_is('/today_count', 0) second => 21,
->json_is('/week_sum', 0) max => 22,
->json_is('/week_count', 0) sum => 118,
->json_is('/month_sum', 0) count => 7,
->json_is('/month_count', 0) })
->json_is('/user_sum', 0) ->json_is('/sectors', {
->json_is('/user_count', 0) sectors => ['A'],
->json_is('/global_sum', 0) purchases => [118],
->json_is('/global_count', 0); });
for ( 1 .. 10 ) { sub create_random_transaction {
$user_result->create_related( 'purchases', { my $buyer = shift;
seller_id => $org_result->id, my $time = shift;
value => $_ * 100000,
my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity;
my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity;
$schema->resultset('Transaction')->create({
buyer => $buyer_result,
seller => $seller_result,
value => 10 * 100000,
proof_image => 'a', proof_image => 'a',
purchase_time => $time,
}); });
} }
for ( 11 .. 20 ) {
$user_result->create_related( 'purchases', {
seller_id => $org_result->id,
value => $_ * 100000,
proof_image => 'a',
purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 5 )),
});
}
for ( 21 .. 30 ) {
$user_result->create_related( 'purchases', {
seller_id => $org_result->id,
value => $_ * 100000,
proof_image => 'a',
purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 25 )),
});
}
for ( 31 .. 40 ) {
$user_result->create_related( 'purchases', {
seller_id => $org_result->id,
value => $_ * 100000,
proof_image => 'a',
purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 50 )),
});
}
for ( 41 .. 50 ) {
$org_result->create_related( 'purchases', {
seller_id => $org_result->id,
value => $_ * 100000,
proof_image => 'a',
purchase_time => $dtf->format_datetime(DateTime->today()->subtract( days => 50 )),
});
}
is $user_result->purchases->search({
purchase_time => {
-between => [
$dtf->format_datetime(DateTime->today()),
$dtf->format_datetime(DateTime->today()->add( days => 1 )),
],
},
})->get_column('value')->sum, 5500000, 'Got correct sum';
is $user_result->purchases->today_rs->get_column('value')->sum, 5500000, 'Got correct sum through rs';
$t->post_ok('/api/stats' => json => { session_key => $session_key } )
->status_is(200)
->json_is('/success', Mojo::JSON->true)
->json_is('/today_sum', 55)
->json_is('/today_count', 10)
->json_is('/week_sum', 155)
->json_is('/week_count', 10)
->json_is('/month_sum', 410)
->json_is('/month_count', 20)
->json_is('/user_sum', 820)
->json_is('/user_count', 40)
->json_is('/global_sum', 1275)
->json_is('/global_count', 50);
done_testing; done_testing;

136
t/api/v1/customer/graphs.t Normal file
View file

@ -0,0 +1,136 @@
use Mojo::Base -strict;
use FindBin qw/ $Bin /;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
use DateTime;
my $framework = Test::Pear::LocalLoop->new(
etc_dir => "$Bin/../../../etc",
);
$framework->install_fixtures('users');
my $t = $framework->framework;
my $schema = $t->app->schema;
my $start = DateTime->today->subtract( hours => 12 );
# create 30 days worth of data
for my $count ( 0 .. 29 ) {
my $trans_day = $start->clone->subtract( days => $count );
create_random_transaction( 'test1@example.com', $trans_day );
if ( $count % 2 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 3 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 4 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
}
my $session_key = $framework->login({
email => 'test1@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/customer/graphs' => json => {
session_key => $session_key,
graph => 'total_last_week',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 6 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 6 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 20, 40, 20, 30, 30, 40, 10 ],
});
$t->post_ok('/api/v1/customer/graphs' => json => {
session_key => $session_key,
graph => 'avg_spend_last_week',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 6 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 6 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 10, 10, 10, 10, 10, 10, 10 ],
});
$t->post_ok('/api/v1/customer/graphs' => json => {
session_key => $session_key,
graph => 'total_last_month',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 29 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 29 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10 ],
});
$t->post_ok('/api/v1/customer/graphs' => json => {
session_key => $session_key,
graph => 'avg_spend_last_month',
})
->status_is(200)->or($framework->dump_error)
->json_is('/graph', {
labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 29 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 29 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ],
});
$framework->logout( $session_key );
$session_key = $framework->login({
email => 'org@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/customer/graphs' => json => {
session_key => $session_key,
graph => 'avg_spend_last_week',
})
->status_is(403)
->json_is('/success', Mojo::JSON->false)
->json_is('/error', 'user_not_cust');
sub create_random_transaction {
my $buyer = shift;
my $time = shift;
my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity;
my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity;
$schema->resultset('Transaction')->create({
buyer => $buyer_result,
seller => $seller_result,
value => 10 * 100000,
proof_image => 'a',
purchase_time => $time,
});
}
done_testing;

67
t/api/v1/customer/pies.t Normal file
View file

@ -0,0 +1,67 @@
use Mojo::Base -strict;
use FindBin qw/ $Bin /;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
use DateTime;
my $framework = Test::Pear::LocalLoop->new(
etc_dir => "$Bin/../../../etc",
);
$framework->install_fixtures('users');
my $t = $framework->framework;
my $schema = $t->app->schema;
my $start = DateTime->today->subtract( hours => 12 );
# create 30 days worth of data
for my $count ( 0 .. 29 ) {
my $trans_day = $start->clone->subtract( days => $count );
create_random_transaction( 'test1@example.com', $trans_day );
if ( $count % 2 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 3 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
if ( $count % 4 ) {
create_random_transaction( 'test1@example.com', $trans_day );
}
}
my $session_key = $framework->login({
email => 'test1@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/customer/pies' => json => {
session_key => $session_key,
})
->status_is(200)->or($framework->dump_error)
->json_is('/pie', {
'Local shop local purchaser' => 0,
'Local shop non-local purchaser' => 0,
'Non-local shop local purchaser' => 0,
'Non-local shop non-local purchaser' => 0,
});
sub create_random_transaction {
my $buyer = shift;
my $time = shift;
my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity;
my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity;
$schema->resultset('Transaction')->create({
buyer => $buyer_result,
seller => $seller_result,
value => 10 * 100000,
proof_image => 'a',
purchase_time => $time,
});
}
done_testing;

View file

@ -0,0 +1,74 @@
use Mojo::Base -strict;
use FindBin qw/ $Bin /;
use Test::More;
use Mojo::JSON;
use Test::Pear::LocalLoop;
use DateTime;
my $framework = Test::Pear::LocalLoop->new(
etc_dir => "$Bin/../../../etc",
);
$framework->install_fixtures('users');
my $t = $framework->framework;
my $schema = $t->app->schema;
$t->app->schema->resultset('Leaderboard')->create_new( 'monthly_total', DateTime->now->truncate(to => 'month' )->subtract( months => 1) );
my $start = DateTime->today->subtract( hours => 12 );
# create 30 days worth of data
for my $count ( 0 .. 60 ) {
my $trans_day = $start->clone->subtract( days => $count );
create_random_transaction( 'test1@example.com', $trans_day );
if ( $count % 2 ) {
create_random_transaction( 'test2@example.com', $trans_day );
}
if ( $count % 3 ) {
create_random_transaction( 'test3@example.com', $trans_day );
}
if ( $count % 4 ) {
create_random_transaction( 'test4@example.com', $trans_day );
}
}
my $session_key = $framework->login({
email => 'test1@example.com',
password => 'abc123',
});
$t->post_ok('/api/v1/customer/snippets' => json => {
session_key => $session_key,
})
->status_is(200)->or($framework->dump_error)
->json_is('/snippets', {
user_sum => 610,
user_position => 1,
});
$framework->logout( $session_key );
$session_key = $framework->login({
email => 'test1@example.com',
password => 'abc123',
});
sub create_random_transaction {
my $buyer = shift;
my $time = shift;
my $buyer_result = $schema->resultset('User')->find({ email => $buyer })->entity;
my $seller_result = $schema->resultset('Organisation')->find({ name => 'Test Org' })->entity;
$schema->resultset('Transaction')->create({
buyer => $buyer_result,
seller => $seller_result,
value => 10 * 100000,
proof_image => 'a',
purchase_time => $time,
});
}
done_testing;

View file

@ -44,7 +44,13 @@ $t->post_ok('/api/v1/organisation/graphs' => json => {
}) })
->status_is(200)->or($framework->dump_error) ->status_is(200)->or($framework->dump_error)
->json_is('/graph', { ->json_is('/graph', {
labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 6 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 6 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 2, 4, 2, 3, 3, 4, 1 ], data => [ 2, 4, 2, 3, 3, 4, 1 ],
}); });
@ -54,7 +60,13 @@ $t->post_ok('/api/v1/organisation/graphs' => json => {
}) })
->status_is(200)->or($framework->dump_error) ->status_is(200)->or($framework->dump_error)
->json_is('/graph', { ->json_is('/graph', {
labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 29 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 29 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ], data => [ 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 2, 4, 2, 3, 3, 4, 1 ],
}); });
@ -64,7 +76,13 @@ $t->post_ok('/api/v1/organisation/graphs' => json => {
}) })
->status_is(200)->or($framework->dump_error) ->status_is(200)->or($framework->dump_error)
->json_is('/graph', { ->json_is('/graph', {
labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 6 ) ], labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 6 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 6 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 20, 40, 20, 30, 30, 40, 10 ], data => [ 20, 40, 20, 30, 30, 40, 10 ],
}); });
@ -74,7 +92,13 @@ $t->post_ok('/api/v1/organisation/graphs' => json => {
}) })
->status_is(200)->or($framework->dump_error) ->status_is(200)->or($framework->dump_error)
->json_is('/graph', { ->json_is('/graph', {
labels => [ map { $start->clone->subtract( days => $_ )->day_name } reverse ( 0 .. 29 ) ], labels => [ map { $t->app->format_iso_datetime(
$start->clone->subtract( days => $_ )->subtract( hours => 12 )
) } reverse ( 0 .. 29 ) ],
bounds => {
min => $t->app->format_iso_datetime($start->clone->subtract( days => 29 )->subtract( hours => 12 ) ),
max => $t->app->format_iso_datetime($start->clone->add( hours => 12 )),
},
data => [ 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10 ], data => [ 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10, 40, 30, 30, 20, 40, 20, 40, 20, 30, 30, 40, 10 ],
}); });

View file

@ -91,6 +91,7 @@ my $entity5 = {
street_name => 'Test Street', street_name => 'Test Street',
town => 'Lancaster', town => 'Lancaster',
postcode => 'LA1 1AA', postcode => 'LA1 1AA',
sector => 'A',
}, },
user => { user => {
email => 'org@example.com', email => 'org@example.com',
@ -124,4 +125,3 @@ $fixtures->dump({
schema => $schema, schema => $schema,
directory => "$Bin/../data/" . $data_set, directory => "$Bin/../data/" . $data_set,
}); });

View file

@ -7,7 +7,7 @@ $HASH1 = {
pending => 0, pending => 0,
postcode postcode
=> 'LA1 1AA', => 'LA1 1AA',
sector => undef, sector => 'A',
street_name street_name
=> 'Test Street', => 'Test Street',
submitted_by_id submitted_by_id

View file

@ -81,8 +81,10 @@
<input id="town" type="text" class="form-control" placeholder="Town" name="town" value="<%= $org_rs->town %>"> <input id="town" type="text" class="form-control" placeholder="Town" name="town" value="<%= $org_rs->town %>">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="town">Town</label> <label for="town">Sector</label>
<input id="town" type="sector" class="form-control" placeholder="Sector Area Code" name="sector" value="<%= $org_rs->sector %>"> <select id="sector" type="sector" class="form-control" placeholder="Sector Area Code" name="sector">
%= include 'partials/sector_options', selected_sector => $org_rs->sector || '';
</select>
</div> </div>
</div> </div>
% } else { % } else {

View file

@ -19,3 +19,4 @@
<option value='R'<%= $selected_sector eq 'R' ? ' selected' : '' %>>Arts, Entertainment & Recreation</option> <option value='R'<%= $selected_sector eq 'R' ? ' selected' : '' %>>Arts, Entertainment & Recreation</option>
<option value='S'<%= $selected_sector eq 'S' ? ' selected' : '' %>>Other Service Activities</option> <option value='S'<%= $selected_sector eq 'S' ? ' selected' : '' %>>Other Service Activities</option>
<option value='T'<%= $selected_sector eq 'T' ? ' selected' : '' %>>Household Domestic Business</option> <option value='T'<%= $selected_sector eq 'T' ? ' selected' : '' %>>Household Domestic Business</option>
<option value='U'<%= $selected_sector eq 'U' ? ' selected' : '' %>>Extraterritorial Organisations and bodies</option>