@@ -, +, @@ cookie). You must have permission updatecharges. without body where YYY is the accountlines_id you created in step 2. http://yourlibrary/api/v1/patrons/ZZZ/payment with body {"amount": 10} Replace ZZZ with the borrowernumber for which you have created two fines 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. amount. 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(-) --- a/Koha/REST/V1/Accountline.pm +++ a/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; --- a/Koha/REST/V1/Patron.pm +++ a/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; --- a/api/v1/swagger/paths.json +++ a/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" } } --- a/api/v1/swagger/paths/accountlines.json +++ a/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" + } + } + } } } --- a/api/v1/swagger/paths/patrons.json +++ a/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" + } + } + } } } --- a/t/db_dependent/api/v1/accountlines.t +++ a/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; --- a/t/db_dependent/api/v1/patrons.t +++ a/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; --