diff --git a/CHANGELOG.md b/CHANGELOG.md index f712caa..fb8bb99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ # Next Release +# v0.10.6 + +* Fixed organisation submission +* Changed category listing code +* Made transaction upload code more lenient +* Added API ability to edit and delete transactions +* Added test for above +* Made test dumping more sane +* Fixed quantised transaction calcuations for weeks on sqlite +* Amended customer snippet, category list and customer stats tests + # v0.10.5 * **Admin Feature** Removed generic Transaction List, replaced with a new diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 5bd5e26..c996567 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -154,6 +154,8 @@ sub startup { $api->post('/stats/leaderboard')->to('api-stats#post_leaderboards'); $api->post('/stats/leaderboard/paged')->to('api-stats#post_leaderboards_paged'); $api->post('/outgoing-transactions')->to('api-transactions#post_transaction_list_purchases'); + $api->post('/recurring-transactions')->to('api-transactions#update_recurring'); + $api->post('/recurring-transactions/delete')->to('api-transactions#delete_recurring'); my $api_v1 = $api->under('/v1'); diff --git a/lib/Pear/LocalLoop/Controller/Api/Categories.pm b/lib/Pear/LocalLoop/Controller/Api/Categories.pm index 9913f42..ed5b0c1 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Categories.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Categories.pm @@ -31,9 +31,9 @@ sub post_category_list { value => { sum => 'value' }, category_id => 'category_id', essential => 'essential', - } + }, ], - group_by => [ qw/ category_id quantised_weeks / ], + group_by => [ qw/ category_id quantised_weeks essential value / ], order_by => { '-desc' => 'value' }, } ); @@ -43,7 +43,7 @@ sub post_category_list { for my $cat_trans ( $month_transaction_category_rs->all ) { my $quantised = $c->db_datetime_parser->parse_datetime($cat_trans->get_column('quantised')); my $days = $c->format_iso_date( $quantised ) || 0; - my $category = $cat_trans->get_column('category_id') || 0; + my $category = $cat_trans->get_column('category_id') || undef; my $value = ($cat_trans->get_column('value') || 0) / 100000; $data->{categories}->{$days} = [] unless exists $data->{categories}->{$days}; push @{ $data->{categories}->{$days} }, { diff --git a/lib/Pear/LocalLoop/Controller/Api/Transactions.pm b/lib/Pear/LocalLoop/Controller/Api/Transactions.pm index 4a0ae8c..84bb0da 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Transactions.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Transactions.pm @@ -8,6 +8,21 @@ has error_messages => sub { required => { message => 'No email sent.', status => 400 }, email => { message => 'Email is invalid.', status => 400 }, }, + value => { + required => { message => 'transaction amount is missing', status => 400 }, + number => { message => 'transaction amount does not look like a number', status => 400 }, + gt_num => { message => 'transaction amount cannot be equal to or less than zero', status => 400 }, + }, + apply_time => { + required => { message => 'purchase time is missing', status => 400 }, + is_full_iso_datetime => { message => 'time is in incorrect format', status => 400 }, + }, + id => { + required => { message => 'Recurring Transaction not found', status => 400 }, + }, + category => { + in_resultset => { message => 'Category is invalid', status => 400 }, + }, }; }; @@ -30,7 +45,11 @@ sub post_transaction_list_purchases { }, ); -# purchase_time needs timezone attached to it + my $recurring_transactions = $c->schema->resultset('TransactionRecurring')->search({ + buyer_id => $user->id, + }); + + # purchase_time needs timezone attached to it my @transaction_list = ( map {{ seller => $_->seller->name, @@ -39,11 +58,112 @@ sub post_transaction_list_purchases { }} $transactions->all ); + my @recurring_transaction_list = ( + map {{ + id => $_->id, + seller => $_->seller->name, + value => $_->value / 100000, + start_time => $c->format_iso_datetime($_->start_time), + last_updated => $c->format_iso_datetime($_->last_updated) || undef, + essential => $_->essential, + category => $_->category_id || 0, + recurring_period => $_->recurring_period, + }} $recurring_transactions->all + ); + return $c->render( json => { success => Mojo::JSON->true, transactions => \@transaction_list, + recurring_transactions => \@recurring_transaction_list, page_no => $transactions->pager->total_entries, }); } +sub update_recurring { + my $c = shift; + + my $user = $c->stash->{api_user}; + + my $validation = $c->validation; + $validation->input( $c->stash->{api_json} ); + $validation->required('id'); + + return $c->api_validation_error if $validation->has_error; + + my $id = $validation->param('id'); + + my $recur_transaction = $c->schema->resultset('TransactionRecurring')->find($id); + unless ( $recur_transaction ) { + return $c->render( + json => { + success => Mojo::JSON->false, + message => 'Error Finding Recurring Transaction', + error => 'recurring_error', + }, + status => 400, + ); + } + + $validation->required('recurring_period'); + $validation->required('apply_time')->is_full_iso_datetime; + $validation->optional('category')->in_resultset( 'id', $c->schema->resultset('Category')); + $validation->optional('essential'); + $validation->required('value'); + + return $c->api_validation_error if $validation->has_error; + + my $apply_time = $c->parse_iso_datetime($validation->param('apply_time')); + + $c->schema->storage->txn_do( sub { + $recur_transaction->update({ + start_time => $c->format_db_datetime($apply_time), + last_updated => undef, + category_id => $validation->param('category'), + essential => $validation->param('essential'), + value => $validation->param('value') * 100000, + recurring_period => $validation->param('recurring_period'), + }); + }); + + return $c->render( json => { + success => Mojo::JSON->true, + message => 'Recurring Transaction Updated Successfully', + }); + +} + +sub delete_recurring { + my $c = shift; + + my $user = $c->stash->{api_user}; + + my $validation = $c->validation; + $validation->input( $c->stash->{api_json} ); + $validation->required('id'); + + return $c->api_validation_error if $validation->has_error; + + my $id = $validation->param('id'); + + my $recur_transaction = $c->schema->resultset('TransactionRecurring')->find($id); + unless ( $recur_transaction ) { + return $c->render( + json => { + success => Mojo::JSON->false, + message => 'Error Finding Recurring Transaction', + error => 'recurring_error', + }, + status => 400, + ); + } + + $recur_transaction->delete; + + return $c->render( json => { + success => Mojo::JSON->true, + message => 'Recurring Transaction Deleted Successfully', + }); + +} + 1; diff --git a/lib/Pear/LocalLoop/Controller/Api/Upload.pm b/lib/Pear/LocalLoop/Controller/Api/Upload.pm index dea5678..31fa234 100644 --- a/lib/Pear/LocalLoop/Controller/Api/Upload.pm +++ b/lib/Pear/LocalLoop/Controller/Api/Upload.pm @@ -147,7 +147,7 @@ sub post_upload { # Unknown Organisation $validation->required('organisation_name'); $validation->optional('street_name'); - $validation->required('town'); + $validation->optional('town'); $validation->optional('postcode')->postcode; return $c->api_validation_error if $validation->has_error; @@ -243,18 +243,21 @@ sub post_category { my $c = shift; my $self = $c; - my $categories = { ids => [], names => [] }; - my $category_rs = $c->schema->resultset('Category'); - for ( $category_rs->all ) { - push @{ $categories->{ ids } }, $_->get_column('id'); - push @{ $categories->{ names } }, $_->get_column('name'); - } + # for ( $category_rs->all ) { + # push @{ $categories->{ ids } }, $_->get_column('id'); + # push @{ $categories->{ names } }, $_->get_column('name'); + # } + my %category_list = ( + map { + $_->id => $_->name, + } $category_rs->all + ); return $self->render( json => { success => Mojo::JSON->true, - categories => $categories, + categories => \%category_list, }); } diff --git a/lib/Pear/LocalLoop/Plugin/Datetime.pm b/lib/Pear/LocalLoop/Plugin/Datetime.pm index 1f79e45..c6f2e55 100644 --- a/lib/Pear/LocalLoop/Plugin/Datetime.pm +++ b/lib/Pear/LocalLoop/Plugin/Datetime.pm @@ -66,6 +66,7 @@ sub register { $app->helper( format_iso_datetime => sub { my ( $c, $datetime_obj ) = @_; + return unless defined $datetime_obj; return $c->iso_datetime_parser->format_datetime( $datetime_obj, ); diff --git a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm index b288c65..0d2739b 100644 --- a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm +++ b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategoryPg.pm @@ -10,18 +10,18 @@ __PACKAGE__->table('view_quantised_transactions'); __PACKAGE__->result_source_instance->is_virtual(1); __PACKAGE__->result_source_instance->view_definition( qq/ -SELECT "transactions.value", - "transactions.distance", - "transactions.purchase_time", - "transactions.buyer_id", - "transactions.seller_id", - "transactions.essential", - "transaction_category.category_id", - DATE_TRUNC('hour', "transactions.purchase_time") AS "quantised_hours", - DATE_TRUNC('day', "transactions.purchase_time") AS "quantised_days", - DATE_TRUNC('week', "transactions.purchase_time") AS "quantised_weeks" +SELECT "transactions"."value", + "transactions"."distance", + "transactions"."purchase_time", + "transactions"."buyer_id", + "transactions"."seller_id", + "transactions"."essential", + "transaction_category"."category_id", + DATE_TRUNC('hour', "transactions"."purchase_time") AS "quantised_hours", + DATE_TRUNC('day', "transactions"."purchase_time") AS "quantised_days", + DATE_TRUNC('week', "transactions"."purchase_time") AS "quantised_weeks" FROM "transactions" -LEFT JOIN "transaction_category" ON "transactions.id" = "transaction_category.transaction_id" +LEFT JOIN "transaction_category" ON "transactions"."id" = "transaction_category"."transaction_id" /); 1; diff --git a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm index 7992458..958ea59 100644 --- a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm +++ b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionCategorySQLite.pm @@ -19,7 +19,7 @@ SELECT "transactions"."value", "transaction_category"."category_id", DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"transactions"."purchase_time")) AS "quantised_hours", DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time")) AS "quantised_days", - DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time", 'weekday 1')) AS "quantised_weeks" + DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time", 'weekday 0','-6 days')) AS "quantised_weeks" FROM "transactions" LEFT JOIN "transaction_category" ON "transactions"."id" = "transaction_category"."transaction_id" /); diff --git a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionSQLite.pm b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionSQLite.pm index abf95dd..1c908fd 100644 --- a/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionSQLite.pm +++ b/lib/Pear/LocalLoop/Schema/Result/ViewQuantisedTransactionSQLite.pm @@ -17,7 +17,7 @@ SELECT "value", "seller_id", 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", 'weekday 1')) AS "quantised_weeks" + DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time",'weekday 0','-6 days')) AS "quantised_weeks" FROM "transactions" /); diff --git a/lib/Test/Pear/LocalLoop.pm b/lib/Test/Pear/LocalLoop.pm index 490aa1a..deffc44 100644 --- a/lib/Test/Pear/LocalLoop.pm +++ b/lib/Test/Pear/LocalLoop.pm @@ -114,6 +114,8 @@ sub dump_error { my $self = shift; if ( my $error = $self->tx->res->dom->at('pre[id="error"]') ) { diag $error->text; + } elsif ( my $route_error = $self->tx->res->dom->at('div[id="routes"] > p') ) { + diag $route_error->content; } else { diag $self->tx->res->to_string; } diff --git a/t/api/categories.t b/t/api/categories.t index 0f48620..10d11ab 100644 --- a/t/api/categories.t +++ b/t/api/categories.t @@ -19,6 +19,11 @@ $framework->install_fixtures('users'); my $t = $framework->framework; my $schema = $t->app->schema; +$schema->resultset('Category')->create({ + id => 1, + name => 'test', +}); + set_absolute_time('2017-01-02T00:00:00Z'); my $start = DateTime->today->subtract( hours => 12 ); @@ -52,7 +57,7 @@ $t->post_ok('/api/stats/category' => json => { categories => { "2016-12-05" => [{ days => "2016-12-05", - value => 30, + value => 210, category => 1, }], "2016-12-12" => [{ @@ -62,7 +67,7 @@ $t->post_ok('/api/stats/category' => json => { }], "2016-12-19" => [{ days => "2016-12-19", - value => 220, + value => 210, category => 1, }], "2016-12-26" => [{ @@ -70,28 +75,20 @@ $t->post_ok('/api/stats/category' => json => { value => 190, category => 1, }], - "2017-01-02" => [{ - days => "2017-01-02", - value => 170, - category => 1, - }], }, essentials => { "2016-12-05" => { - value => 30, + value => 210, }, "2016-12-12" => { value => 200, }, "2016-12-19" => { - value => 220, + value => 210, }, "2016-12-26" => { value => 190, }, - "2017-01-02" => { - value => 170, - }, } })->or($framework->dump_error); @@ -99,6 +96,7 @@ 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; my $test_transaction = $schema->resultset('Transaction')->create({ @@ -109,6 +107,7 @@ sub create_random_transaction { purchase_time => $time, essential => 1, }); + $schema->resultset('TransactionCategory')->create({ category_id => 1, transaction_id => $test_transaction->id, diff --git a/t/api/stats.t b/t/api/stats.t index 67e8705..2fa8a9a 100644 --- a/t/api/stats.t +++ b/t/api/stats.t @@ -49,11 +49,11 @@ $t->post_ok('/api/stats/customer' => json => { }) ->status_is(200)->or($framework->dump_error) ->json_is('/weeks', { - first => 2, - second => 21, + first => 20, + second => 20, max => 22, sum => 118, - count => 7, + count => 6, }) ->or($framework->dump_error) ->json_is('/sectors', { diff --git a/t/api/transactions.t b/t/api/transactions.t index 849a2a8..1cd62f4 100644 --- a/t/api/transactions.t +++ b/t/api/transactions.t @@ -38,8 +38,6 @@ my $session_key = $framework->login({ password => 'abc123', }); -use Data::Dumper; - $t->post_ok('/api/outgoing-transactions' => json => { session_key => $session_key, }) @@ -50,6 +48,36 @@ $t->post_ok('/api/outgoing-transactions' => json => { ->json_has('/transactions/1/value') ->json_has('/transactions/1/purchase_time'); +my $test_purchase_time = "2017-08-14T11:29:07.965+01:00"; + +$t->post_ok('/api/recurring-transactions' => json => { + session_key => $session_key, + id => 1, + apply_time => $test_purchase_time, + essential => "false", + value => 5, + recurring_period => 'daily', + }) + ->status_is(200)->or($framework->dump_error) + ->json_is({ + success => Mojo::JSON->true, + message => 'Recurring Transaction Updated Successfully', + }); + +is $schema->resultset('TransactionRecurring')->count, 87; + +$t->post_ok('/api/recurring-transactions/delete' => json => { + session_key => $session_key, + id => 1, + }) + ->status_is(200)->or($framework->dump_error) + ->json_is({ + success => Mojo::JSON->true, + message => 'Recurring Transaction Deleted Successfully', + }); + +is $schema->resultset('TransactionRecurring')->count, 86; + sub create_random_transaction { my $buyer = shift; @@ -64,6 +92,14 @@ sub create_random_transaction { proof_image => 'a', purchase_time => $time, }); + $schema->resultset('TransactionRecurring')->create({ + buyer => $buyer_result, + seller => $seller_result, + value => 10, + start_time => $time, + essential => 1, + recurring_period => 'weekly', + }); } done_testing; diff --git a/t/api/v1/customer/snippets.t b/t/api/v1/customer/snippets.t index 4a3ae8c..07c3e9a 100644 --- a/t/api/v1/customer/snippets.t +++ b/t/api/v1/customer/snippets.t @@ -1,5 +1,9 @@ use Mojo::Base -strict; +BEGIN { + use Test::MockTime qw/ set_absolute_time /; +} + use FindBin qw/ $Bin /; use Test::More; @@ -15,7 +19,7 @@ $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) ); +set_absolute_time('2017-01-02T00:00:00Z'); my $start = DateTime->today->subtract( hours => 12 ); @@ -25,15 +29,19 @@ for my $count ( 0 .. 60 ) { create_random_transaction( 'test1@example.com', $trans_day ); if ( $count % 2 ) { - create_random_transaction( 'test2@example.com', $trans_day ); + create_random_transaction( 'test1@example.com', $trans_day ); } if ( $count % 3 ) { - create_random_transaction( 'test3@example.com', $trans_day ); + create_random_transaction( 'test1@example.com', $trans_day ); } if ( $count % 4 ) { - create_random_transaction( 'test4@example.com', $trans_day ); + create_random_transaction( 'test1@example.com', $trans_day ); } } +my $lb_start = $start->clone->truncate( to => 'month' )->subtract( months => 1); + +#use Devel::Dwarn; Dwarn({ $_->get_columns }) for $schema->resultset('Transaction')->all; +$schema->resultset('Leaderboard')->create_new( 'monthly_total', $lb_start ); my $session_key = $framework->login({ email => 'test1@example.com', @@ -45,7 +53,7 @@ $t->post_ok('/api/v1/customer/snippets' => json => { }) ->status_is(200)->or($framework->dump_error) ->json_is('/snippets', { - user_sum => 610, + user_sum => 1760, user_position => 1, });