From 2a777e8ff7ffb281a8034a1905d23c97a099bbdb Mon Sep 17 00:00:00 2001 From: Alex Arnaud Date: Thu, 12 Nov 2015 14:44:47 +0100 Subject: [PATCH] [SIGNED-OFF] 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 Signed-off-by: Josef Moravec --- 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 | 57 +++++++++++++++++++++++++++++-- 7 files changed, 299 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 477980e..3fbb05f 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, 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 6b3f51f..1a7188c 100644 --- a/t/db_dependent/api/v1/patrons.t +++ b/t/db_dependent/api/v1/patrons.t @@ -17,7 +17,7 @@ use Modern::Perl; -use Test::More tests => 21; +use Test::More tests => 32; use Test::Mojo; use t::lib::TestBuilder; @@ -26,6 +26,7 @@ use C4::Context; use Koha::Database; use Koha::Patron; +use Koha::Account::Lines; my $builder = t::lib::TestBuilder->new(); @@ -106,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) } }); @@ -131,4 +132,56 @@ $t->request_ok($tx) ->json_is('/surname' => $borrower->{ surname }) ->json_is('/lost' => 1 ); + +# 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