From 36723ce5e7a4d10c3b3b1f81e8c2940eadbac699 Mon Sep 17 00:00:00 2001 From: Alex Arnaud Date: Thu, 12 Nov 2015 14:44:47 +0100 Subject: [PATCH] Bug 15165 - Add API routes to pay accountlines POST /accountlines/(:accountlines_id)/payment (pay towards accountline) POST /patrons/(:borrowernumber)/payment (pay towards borrower) Test plan: 1. Open a browser tab on Koha staff and log in (to create CGISESSID cookie). You must have permission updatecharges. 2. Create a fine to any patron and get the accountlines_id. 3. Send POST request to http://yourlibrary/api/v1/accountlines/YYY/payment without body where YYY is the accountlines_id you created in step 2. 4. Check that the accountline that you created in step 2 is paid. 5. Create two payments with amount 5.00 (with no other outstanding payments) 6. Send POST request to http://yourlibrary/api/v1/patrons/ZZZ/payment with body {"amount": 10} Replace ZZZ with the borrowernumber for which you have created two fines 7. Check that the two accountlines are paid. 8. Repeat step 2. 9. Send POST request to http://yourlibrary/api/v1/accountlines/YYY/payment with body {"amount": XXX} Replace YYY with the accountlines_id you created in step 8. Set amount (XXX) to the half of the amount of the fine you created in step 8. 10. Check that the fine is still outstanding with half of the original amount. 11. Run unit tests at t/db_dependent/api/v1/accountlines.t and t/db_dependent/api/v1/patrons.t --- Koha/REST/V1/Accountline.pm | 37 ++++++++++++++++++++ Koha/REST/V1/Patron.pm | 34 +++++++++++++++++++ api/v1/swagger/paths.json | 6 ++++ api/v1/swagger/paths/accountlines.json | 55 ++++++++++++++++++++++++++++++ api/v1/swagger/paths/patrons.json | 55 ++++++++++++++++++++++++++++++ t/db_dependent/api/v1/accountlines.t | 62 +++++++++++++++++++++++++++++++--- t/db_dependent/api/v1/patrons.t | 58 +++++++++++++++++++++++++++++-- 7 files changed, 300 insertions(+), 7 deletions(-) diff --git a/Koha/REST/V1/Accountline.pm b/Koha/REST/V1/Accountline.pm index 23c2491..9ee8aa8 100644 --- a/Koha/REST/V1/Accountline.pm +++ b/Koha/REST/V1/Accountline.pm @@ -19,8 +19,11 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; +use Scalar::Util qw( looks_like_number ); + use C4::Auth qw( haspermission ); use Koha::Account::Lines; +use Koha::Account; sub list { my ($c, $args, $cb) = @_; @@ -48,4 +51,38 @@ sub edit { return $c->$cb($accountline->unblessed(), 200); } + +sub pay { + my ($c, $args, $cb) = @_; + + my $accountline = Koha::Account::Lines->find($args->{accountlines_id}); + unless ($accountline) { + return $c->$cb({error => "Accountline not found"}, 404); + } + + my $body = $c->req->json; + my $amount = defined $body->{amount}; + my $note = $body->{note} || ''; + + if ($amount && !looks_like_number($amount)) { + return $c->$cb({error => "Invalid amount"}, 400); + } + + Koha::Account->new( + { + patron_id => $accountline->borrowernumber, + } + )->pay( + { + lines => [$accountline], + amount => $amount, + note => $note, + } + ); + + $accountline = Koha::Account::Lines->find($args->{accountlines_id}); + return $c->$cb($accountline->unblessed(), 200); +} + + 1; diff --git a/Koha/REST/V1/Patron.pm b/Koha/REST/V1/Patron.pm index b97a154..73259c5 100644 --- a/Koha/REST/V1/Patron.pm +++ b/Koha/REST/V1/Patron.pm @@ -19,7 +19,11 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; +use Scalar::Util qw( looks_like_number ); + +use C4::Auth qw( haspermission ); use Koha::Patrons; +use Koha::Account; sub list { my ($c, $args, $cb) = @_; @@ -44,4 +48,34 @@ sub get { return $c->$cb($patron->unblessed, 200); } +sub pay { + my ($c, $args, $cb) = @_; + + my $patron = Koha::Patrons->find($args->{borrowernumber}); + unless ($patron) { + return $c->$cb({error => "Patron not found"}, 404); + } + + my $body = $c->req->json; + my $amount = $body->{amount}; + my $note = $body->{note} || ''; + + unless ($amount && looks_like_number($amount)) { + return $c->$cb({error => "Invalid amount"}, 400); + } + + Koha::Account->new( + { + patron_id => $args->{borrowernumber}, + } + )->pay( + { + amount => $amount, + note => $note, + } + ); + + return $c->$cb('', 204); +} + 1; diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index 8cfcd39..6ac3307 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -11,6 +11,9 @@ "/accountlines/{accountlines_id}": { "$ref": "paths/accountlines.json#/~1accountlines~1{accountlines_id}" }, + "/accountlines/{accountlines_id}/payment": { + "$ref": "paths/accountlines.json#/~1accountlines~1{accountlines_id}~1payment" + }, "/holds": { "$ref": "paths/holds.json#/~1holds" }, @@ -22,5 +25,8 @@ }, "/patrons/{borrowernumber}": { "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}" + }, + "/patrons/{borrowernumber}/payment": { + "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}~1payment" } } diff --git a/api/v1/swagger/paths/accountlines.json b/api/v1/swagger/paths/accountlines.json index d89056d..89ebb23 100644 --- a/api/v1/swagger/paths/accountlines.json +++ b/api/v1/swagger/paths/accountlines.json @@ -93,5 +93,60 @@ } } } + }, + "/accountlines/{accountlines_id}/payment": { + "post": { + "operationId": "payAccountlines", + "tags": ["accountlines"], + "produces": [ + "application/json" + ], + "parameters": [ + { "$ref": "../parameters.json#/accountlinesIdPathParam" }, + { + "name": "body", + "in": "body", + "description": "A JSON object containing fields to modify", + "schema": { + "type": "object", + "properties": { + "amount": { + "description": "Amount to pay" + }, + "note": { + "description": "Payment note" + } + } + } + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Paid accountline", + "schema": { "$ref": "../definitions.json#/accountline" } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "../definitions.json#/error" } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "404": { + "description": "Accountline not found", + "schema": { "$ref": "../definitions.json#/error" } + } + }, + "x-koha-authorization": { + "permissions": { + "updatecharges": "1" + } + } + } } } diff --git a/api/v1/swagger/paths/patrons.json b/api/v1/swagger/paths/patrons.json index 67632e1..5b3253a 100644 --- a/api/v1/swagger/paths/patrons.json +++ b/api/v1/swagger/paths/patrons.json @@ -69,5 +69,60 @@ } } } + }, + "/patrons/{borrowernumber}/payment": { + "post": { + "operationId": "payForPatron", + "tags": ["accountlines"], + "produces": [ + "application/json" + ], + "parameters": [ + { "$ref": "../parameters.json#/borrowernumberPathParam" }, + { + "name": "body", + "in": "body", + "description": "A JSON object containing fields to modify", + "required": true, + "schema": { + "type": "object", + "properties": { + "amount": { + "description": "Amount to pay" + }, + "note": { + "description": "Payment note" + } + } + } + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "204": { + "description": "Success" + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "../definitions.json#/error" } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "404": { + "description": "Borrower not found", + "schema": { "$ref": "../definitions.json#/error" } + } + }, + "x-koha-authorization": { + "permissions": { + "updatecharges": "1" + } + } + } } } diff --git a/t/db_dependent/api/v1/accountlines.t b/t/db_dependent/api/v1/accountlines.t index d395603..4d918dd 100644 --- a/t/db_dependent/api/v1/accountlines.t +++ b/t/db_dependent/api/v1/accountlines.t @@ -17,7 +17,8 @@ use Modern::Perl; -use Test::More tests => 22; + +use Test::More tests => 37; use Test::Mojo; use t::lib::TestBuilder; @@ -45,6 +46,9 @@ $t->get_ok('/api/v1/accountlines') $t->put_ok("/api/v1/accountlines/11224409" => json => {'amount' => -5}) ->status_is(401); +$t->post_ok("/api/v1/accountlines/11224408/payment") + ->status_is(401); + my $loggedinuser = $builder->build({ source => 'Borrower', value => { @@ -75,8 +79,8 @@ my $borrowernumber2 = $borrower2->{borrowernumber}; $dbh->do(q| DELETE FROM accountlines |); $dbh->do(q| - INSERT INTO accountlines (borrowernumber, amount, accounttype) - VALUES (?, 20, 'A'), (?, 40, 'F'), (?, 80, 'F'), (?, 10, 'F') + INSERT INTO accountlines (borrowernumber, amount, accounttype, amountoutstanding) + VALUES (?, 20, 'A', 20), (?, 40, 'F', 40), (?, 80, 'F', 80), (?, 10, 'F', 10) |, undef, $borrowernumber, $borrowernumber, $borrowernumber, $borrowernumber2); my $session = C4::Auth::get_session(''); @@ -127,7 +131,6 @@ my $put_data = { $tx = $t->ua->build_tx( PUT => "/api/v1/accountlines/11224409" - => {Accept => '*/*'} => json => $put_data); $tx->req->cookies({name => 'CGISESSID', value => $session->id}); $tx->req->env({REMOTE_ADDR => '127.0.0.1'}); @@ -144,7 +147,6 @@ $t->request_ok($tx) $tx = $t->ua->build_tx( PUT => "/api/v1/accountlines/$accountline_to_edit->{accountlines_id}" - => {Accept => '*/*'} => json => $put_data); $tx->req->cookies({name => 'CGISESSID', value => $session->id}); $tx->req->env({REMOTE_ADDR => '127.0.0.1'}); @@ -158,5 +160,55 @@ is($accountline_edited->{amountoutstanding}, '-19.000000'); # Payment tests +$tx = $t->ua->build_tx(POST => "/api/v1/accountlines/4562765765/payment"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(404); + +my $accountline_to_pay = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber, 'amount' => 20})->unblessed()->[0]; +$tx = $t->ua->build_tx(POST => "/api/v1/accountlines/$accountline_to_pay->{accountlines_id}/payment"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(200); +#$t->content_is('toto'); + +my $accountline_paid = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber, 'amount' => -20})->unblessed()->[0]; +ok($accountline_paid); + +# Partial payment tests +my $post_data = { + 'amount' => 17, + 'note' => 'Partial payment' +}; + +$tx = $t->ua->build_tx( + POST => "/api/v1/accountlines/11224419/payment" + => json => $post_data); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(404); + +my $accountline_to_partiallypay = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber, 'amount' => 80})->unblessed()->[0]; + +$tx = $t->ua->build_tx(POST => "/api/v1/accountlines/$accountline_to_partiallypay->{accountlines_id}/payment" => json => {amount => 'foo'}); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(400); + +$tx = $t->ua->build_tx(POST => "/api/v1/accountlines/$accountline_to_partiallypay->{accountlines_id}/payment" => json => $post_data); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(200); + +$accountline_to_partiallypay = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber, 'amount' => 80})->unblessed()->[0]; +is($accountline_to_partiallypay->{amountoutstanding}, '63.000000'); + +my $accountline_partiallypaid = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber, 'amount' => -17})->unblessed()->[0]; +ok($accountline_partiallypaid); $dbh->rollback; diff --git a/t/db_dependent/api/v1/patrons.t b/t/db_dependent/api/v1/patrons.t index f4b9410..390b198 100644 --- a/t/db_dependent/api/v1/patrons.t +++ b/t/db_dependent/api/v1/patrons.t @@ -17,7 +17,8 @@ use Modern::Perl; -use Test::More tests => 20; + +use Test::More tests => 31; use Test::Mojo; use t::lib::TestBuilder; @@ -26,6 +27,7 @@ use C4::Context; use Koha::Database; use Koha::Patron; +use Koha::Account::Lines; my $builder = t::lib::TestBuilder->new(); @@ -105,7 +107,7 @@ my $loggedinuser = $builder->build({ value => { branchcode => $branchcode, categorycode => $categorycode, - flags => 16 # borrowers flag + flags => 1040 # borrowers and updatecharges (2^4 | 2^10) } }); @@ -129,4 +131,56 @@ $t->request_ok($tx) ->json_is('/borrowernumber' => $borrower->{ borrowernumber }) ->json_is('/surname' => $borrower->{ surname }); + +# Payment tests +my $borrower2 = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + } +}); +my $borrowernumber2 = $borrower2->{borrowernumber}; + +$dbh->do(q| + INSERT INTO accountlines (borrowernumber, amount, accounttype, amountoutstanding) + VALUES (?, 26, 'A', 26) + |, undef, $borrowernumber2); + +$t->post_ok("/api/v1/patrons/$borrowernumber2/payment" => json => {'amount' => 8}) + ->status_is(401); + +my $post_data2 = { + 'amount' => 24, + 'note' => 'Partial payment' +}; + +$tx = $t->ua->build_tx(POST => "/api/v1/patrons/8789798797/payment" => json => $post_data2); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(404); + +$tx = $t->ua->build_tx(POST => "/api/v1/patrons/$borrowernumber2/payment" => json => {amount => 0}); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(400); + +$tx = $t->ua->build_tx(POST => "/api/v1/patrons/$borrowernumber2/payment" => json => {amount => 'foo'}); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(400); + +$tx = $t->ua->build_tx(POST => "/api/v1/patrons/$borrowernumber2/payment" => json => $post_data2); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$tx->req->env({REMOTE_ADDR => '127.0.0.1'}); +$t->request_ok($tx) + ->status_is(204); + +my $accountline_partiallypaid = Koha::Account::Lines->search({'borrowernumber' => $borrowernumber2, 'amount' => 26})->unblessed()->[0]; + +is($accountline_partiallypaid->{amountoutstanding}, '2.000000'); + $dbh->rollback; -- 2.1.4