From 41c45081408eadb33391dd6aeb8c95417306971a 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 PUT /accountlines/(:accountlines_id)/payment (pay towards accountline) PUT /accountlines/(:borrowernumber)/amountpayment (pay towards borrower) PUT /accountlines/(:accountlines_id)/partialpayment (pay accountline partially) Test plan: 1. Open a browser tab on Koha staff and log in (to create CGISESSID cookie). You should have permission updatecharges. 2. Create a fine to any patron and get the accountlines_id. 3. Send PUT 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 PUT request to http://yourlibrary/api/v1/accountlines/ZZZ/amountpayment with body defined in definitions/partialpayAccountlineBody.json. Replace ZZZ with the borrowernumber that you created two fines to. Set amount to 10. 7. Check that the two accountlines are paid. 8. Repeat step 2. 9. Send PUT request to http://yourlibrary/api/v1/accountlines/YYY/partialpayment with body defined in definitions/partialpayAccountlineBody.json. Replace YYY with the accountlines_id you created in step 8. Set amount to half of the amount of 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 --- Koha/REST/V1/Accountline.pm | 83 ++++++++++++++++ api/v1/definitions/amountpaid.json | 9 ++ api/v1/definitions/index.json | 2 + api/v1/definitions/partialpayAccountlineBody.json | 11 +++ api/v1/swagger.json | 115 ++++++++++++++++++++++ t/db_dependent/api/v1/accountlines.t | 114 ++++++++++++++++++++- 6 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 api/v1/definitions/amountpaid.json create mode 100644 api/v1/definitions/partialpayAccountlineBody.json diff --git a/Koha/REST/V1/Accountline.pm b/Koha/REST/V1/Accountline.pm index 7f33511..6e802a2 100644 --- a/Koha/REST/V1/Accountline.pm +++ b/Koha/REST/V1/Accountline.pm @@ -20,7 +20,10 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; use C4::Auth qw( haspermission ); +use C4::Accounts qw( makepayment makepartialpayment recordpayment ); +use C4::Members qw( GetMember ); use Koha::Accountlines; +use Scalar::Util qw( looks_like_number ); sub list { my ($c, $args, $cb) = @_; @@ -58,4 +61,84 @@ sub edit { return $c->$cb($accountline->unblessed(), 200); } + +sub pay { + my ($c, $args, $cb) = @_; + + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, {updatecharges => 1})) { + return $c->$cb({error => "You don't have the required permission"}, 403); + } + + my $accountline = Koha::Accountlines->find($args->{accountlines_id}); + unless ($accountline) { + return $c->$cb({error => "Accountline not found"}, 404); + } + + makepayment($accountline->accountlines_id, + $accountline->borrowernumber, + $accountline->accountno, + $accountline->amount); + + $accountline = Koha::Accountlines->find($args->{accountlines_id}); + return $c->$cb($accountline->unblessed(), 200); +} + +sub partialpay { + my ($c, $args, $cb) = @_; + + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, {updatecharges => 1})) { + return $c->$cb({error => "You don't have the required permission"}, 403); + } + + my $accountline = Koha::Accountlines->find($args->{accountlines_id}); + unless ($accountline) { + return $c->$cb({error => "Accountline 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); + } + + makepartialpayment($accountline->accountlines_id, + $accountline->borrowernumber, + $accountline->accountno, + $amount, '', '', $note); + + $accountline = Koha::Accountlines->find($args->{accountlines_id}); + return $c->$cb($accountline->unblessed(), 200); +} + + +sub payamount { + my ($c, $args, $cb) = @_; + + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, {updatecharges => 1})) { + return $c->$cb({error => "You don't have the required permission"}, 403); + } + + my $borrower = GetMember(borrowernumber => $args->{borrowernumber}); + unless ($borrower) { + return $c->$cb({error => "Borrower 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); + } + + recordpayment($borrower->{borrowernumber}, $amount, '', $note); + + return $c->$cb({amount => $amount}, 200); +} + 1; diff --git a/api/v1/definitions/amountpaid.json b/api/v1/definitions/amountpaid.json new file mode 100644 index 0000000..589183f --- /dev/null +++ b/api/v1/definitions/amountpaid.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "amount": { + "type": "number", + "description": "Amount paid" + } + } +} diff --git a/api/v1/definitions/index.json b/api/v1/definitions/index.json index 198349a..29a3179 100644 --- a/api/v1/definitions/index.json +++ b/api/v1/definitions/index.json @@ -1,6 +1,8 @@ { + "amountpaid": { "$ref": "amountpaid.json" }, "accountline": { "$ref": "accountline.json" }, "editAccountlineBody": { "$ref": "editAccountlineBody.json" }, + "partialpayAccountlineBody": { "$ref": "partialpayAccountlineBody.json" }, "patron": { "$ref": "patron.json" }, "holds": { "$ref": "holds.json" }, "hold": { "$ref": "hold.json" }, diff --git a/api/v1/definitions/partialpayAccountlineBody.json b/api/v1/definitions/partialpayAccountlineBody.json new file mode 100644 index 0000000..a49f229 --- /dev/null +++ b/api/v1/definitions/partialpayAccountlineBody.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "amount": { + "description": "Amount to pay" + }, + "note": { + "description": "Payment note" + } + } +} diff --git a/api/v1/swagger.json b/api/v1/swagger.json index e74cd66..03462a5 100644 --- a/api/v1/swagger.json +++ b/api/v1/swagger.json @@ -298,6 +298,121 @@ } } } + }, + "/accountlines/{accountlines_id}/payment": { + "put": { + "operationId": "payAccountlines", + "tags": ["accountlines"], + "produces": [ + "application/json" + ], + "parameters": [ + { "$ref": "#/parameters/accountlinesIdPathParam" } + ], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Paid accountline", + "schema": { "$ref": "#/definitions/accountline" } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "#/definitions/error" } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "#/definitions/error" + } + }, + "404": { + "description": "Accountline not found", + "schema": { "$ref": "#/definitions/error" } + } + } + } + }, + "/accountlines/{accountlines_id}/partialpayment": { + "put": { + "operationId": "partialpayAccountlines", + "tags": ["accountlines"], + "produces": [ + "application/json" + ], + "parameters": [ + { "$ref": "#/parameters/accountlinesIdPathParam" }, + { + "name": "body", + "in": "body", + "description": "A JSON object containing fields to modify", + "required": true, + "schema": { "$ref": "#/definitions/partialpayAccountlineBody" } + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Paid accountline", + "schema": { "$ref": "#/definitions/accountline" } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "#/definitions/error" } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "#/definitions/error" + } + }, + "404": { + "description": "Accountline not found", + "schema": { "$ref": "#/definitions/error" } + } + } + } + }, + "/accountlines/{borrowernumber}/amountpayment": { + "put": { + "operationId": "payamountAccountlines", + "tags": ["accountlines"], + "produces": [ + "application/json" + ], + "parameters": [ + { "$ref": "#/parameters/borrowernumberPathParam" }, + { + "name": "body", + "in": "body", + "description": "A JSON object containing fields to modify", + "required": true, + "schema": { "$ref": "#/definitions/partialpayAccountlineBody" } + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Amount paid", + "schema": { "$ref": "#/definitions/amountpaid" } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { "$ref": "#/definitions/error" } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "#/definitions/error" + } + }, + "404": { + "description": "Borrower not found", + "schema": { "$ref": "#/definitions/error" } + } + } + } } }, "definitions": { diff --git a/t/db_dependent/api/v1/accountlines.t b/t/db_dependent/api/v1/accountlines.t index 30b7a08..d13a447 100644 --- a/t/db_dependent/api/v1/accountlines.t +++ b/t/db_dependent/api/v1/accountlines.t @@ -17,7 +17,7 @@ use Modern::Perl; -use Test::More tests => 18; +use Test::More tests => 46; use Test::Mojo; use t::lib::TestBuilder; @@ -44,6 +44,12 @@ $t->get_ok('/api/v1/accountlines') $t->put_ok("/api/v1/accountlines/11224409" => json => {'amount' => -5}) ->status_is(403); +$t->put_ok("/api/v1/accountlines/11224408/payment") + ->status_is(403); + +$t->put_ok("/api/v1/accountlines/11224407/partialpayment" => json => {'amount' => 8}) + ->status_is(403); + my $loggedinuser = $builder->build({ source => 'Borrower', value => { @@ -73,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(''); @@ -113,7 +119,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'}); @@ -124,7 +129,6 @@ my $accountline_to_edit = Koha::Accountlines->search({'borrowernumber' => $borro $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'}); @@ -138,5 +142,105 @@ is($accountline_edited->{amountoutstanding}, '-19.000000'); # Payment tests +$tx = $t->ua->build_tx(PUT => "/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::Accountlines->search({'borrowernumber' => $borrowernumber, 'amount' => 20})->unblessed()->[0]; +$tx = $t->ua->build_tx(PUT => "/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); + +my $accountline_paid = Koha::Accountlines->search({'borrowernumber' => $borrowernumber, 'amount' => -20})->unblessed()->[0]; +ok($accountline_paid); + +# Partial payment tests +$put_data = { + 'amount' => 17, + 'note' => 'Partial payment' +}; + +$tx = $t->ua->build_tx( + PUT => "/api/v1/accountlines/11224419/partialpayment" + => json => $put_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::Accountlines->search({'borrowernumber' => $borrowernumber, 'amount' => 80})->unblessed()->[0]; + +$tx = $t->ua->build_tx(PUT => "/api/v1/accountlines/$accountline_to_partiallypay->{accountlines_id}/partialpayment" => 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(PUT => "/api/v1/accountlines/$accountline_to_partiallypay->{accountlines_id}/partialpayment" => json => $put_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::Accountlines->search({'borrowernumber' => $borrowernumber, 'amount' => 80})->unblessed()->[0]; +is($accountline_to_partiallypay->{amountoutstanding}, '63.000000'); + +my $accountline_partiallypaid = Koha::Accountlines->search({'borrowernumber' => $borrowernumber, 'amount' => 17})->unblessed()->[0]; +ok($accountline_partiallypaid); + +# Pay amount tests +my $borrower3 = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + } +}); +my $borrowernumber3 = $borrower3->{borrowernumber}; + +$dbh->do(q| + INSERT INTO accountlines (borrowernumber, amount, accounttype, amountoutstanding) + VALUES (?, 26, 'A', 26) + |, undef, $borrowernumber3); + +$t->put_ok("/api/v1/accountlines/$borrowernumber3/amountpayment" => json => {'amount' => 8}) + ->status_is(403); + +my $put_data2 = { + 'amount' => 24, + 'note' => 'Partial payment' +}; + +$tx = $t->ua->build_tx(PUT => "/api/v1/accountlines/8789798797/amountpayment" => json => $put_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(PUT => "/api/v1/accountlines/$borrowernumber3/amountpayment" => 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(PUT => "/api/v1/accountlines/$borrowernumber3/amountpayment" => 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(PUT => "/api/v1/accountlines/$borrowernumber3/amountpayment" => json => $put_data2); +$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_partiallypaid = Koha::Accountlines->search({'borrowernumber' => $borrowernumber3, 'amount' => 26})->unblessed()->[0]; + +is($accountline_partiallypaid->{amountoutstanding}, '2.000000'); $dbh->rollback; -- 1.9.1