From d4556b786d12f5a8c70289181024056979707d93 Mon Sep 17 00:00:00 2001 From: Aleisha Amohia Date: Wed, 12 Oct 2022 04:23:42 +0000 Subject: [PATCH] Bug 23012: Apply processing fee return policy when lost item found This enhancement gives the ability to set a policy for the lost item processing fee that may get charged additional to the lost item replacement cost. The processing fee can be: - refunded - refunded if unpaid - kept To test: Set-up 1. Find an item, Item A. Go to Administration -> Item types and edit the item type for Item A. Add a default replacement cost and a processing fee and Save. 2. Go to Administration -> system preferences and set the following: - WhenLostChargeReplacementFee: Charge - BlockReturnOfLostItems: Don't block 3. Scroll down to the default lost item fee refund on return policy. Set the refund lost item replacement fee policy to 'refund lost item charge'. 4. Edit Item A and set a replacement cost. Reproduce 5. Check out Item A to Patron A. 6. Click the barcode to view Item A's information. Edit Item A and set the Lost status to 'lost'. 7. Go back to Patron A's checkouts. The item should now be checked in with two new charges applied - a lost item fee (the item's replacement cost) and a lost item processing fee (set in item types). 8. Check in Item A to mark it as found. 9. Go back to Patron A's account. Notice the lost item fee has been refunded, but the processing fee remains. 10. Manually pay or write off the processing fee. This enhancement removes the need for this manual step. 11. Apply the patch and restart services Test with lost item - refund 12. Go to Administration -> circulation and fines rules. Scroll down to the default lost item fee refund on return policy. Notice there is now a refund lost item processing fee policy. Set this to 'refund lost item processing charge'. 13. Repeat steps 6 to 9. 14. Go back to Patron A's account. Both the lost item fee and processing fee should have been refunded. 15. Repeat steps 6 to 8 (do not check it yet). 16. Go back to Patron A's account. Pay the processing fee. 17. Repeat step 9. 18. Go back to Patron A's account. Both the lost item fee and processing fee should have been refunded (you'll now be in a credit because the paid processing fee was also refunded). Test with lost item - refund_unpaid 19. Go to Administration -> circulation and fines rules. Scroll down to the default lost item fee refund on return policy. Notice there is now a refund lost item processing fee policy. Set this to 'refund lost item processing charge (only if unpaid)'. 20. Repeat steps 6 to 9. 21. Go back to Patron A's account. Both the lost item fee and processing fee should have been refunded. 22. Repeat steps 16 to 19. 23. Go back to Patron A's account. The lost item fee should have been refunded but not the processing fee, as this was already paid. Test with lost item - leave 24. Go to Administration -> circulation and fines rules. Scroll down to the default lost item fee refund on return policy. Notice there is now a refund lost item processing fee policy. Set this to 'leave lost item processing charge'. 25. Repeat steps 6 to 9. 26. Go back to Patron A's account. The lost item fee and processing fee should have been refunded but not the processing fee. Other tests 27. Confirm tests pass - t/db_dependent/Koha/Item.t - t/db_dependent/Koha/CirculationRules.t Sponsored-by: Auckland University of Technology Signed-off-by: David Nind Signed-off-by: Nick Clemens Signed-off-by: Tomas Cohen Arazi --- C4/Circulation.pm | 2 + Koha/Item.pm | 105 ++++++++++++++++++ circ/returns.pl | 3 + .../prog/en/modules/circ/returns.tt | 7 +- t/db_dependent/Koha/Item.t | 24 +++- 5 files changed, 139 insertions(+), 2 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 27351e8127..c57232bdb5 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -2220,6 +2220,8 @@ sub AddReturn { for my $message (@object_messages) { $messages->{'LostItemFeeRefunded'} = 1 if $message->message eq 'lost_refunded'; + $messages->{'ProcessingFeeRefunded'} = 1 + if $message->message eq 'processing_refunded'; $messages->{'LostItemFeeRestored'} = 1 if $message->message eq 'lost_restored'; diff --git a/Koha/Item.pm b/Koha/Item.pm index e549be6d85..37d2ee343c 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -1324,6 +1324,111 @@ sub _set_found_trigger { } } + my $processingreturn_policy = Koha::CirculationRules->get_processingreturn_policy( + { + item => $self, + return_branch => C4::Context->userenv + ? C4::Context->userenv->{'branch'} + : undef, + } + ); + + if ( $processingreturn_policy ) { + + # refund processing charge made for lost book + my $processing_charge = Koha::Account::Lines->search( + { + itemnumber => $self->itemnumber, + debit_type_code => 'PROCESSING', + status => [ undef, { '<>' => 'FOUND' } ] + }, + { + order_by => { -desc => [ 'date', 'accountlines_id' ] }, + rows => 1 + } + )->single; + + if ( $processing_charge ) { + + my $patron = $processing_charge->patron; + if ( $patron ) { + + my $account = $patron->account; + + # Credit outstanding amount + my $credit_total = $processing_charge->amountoutstanding; + + # Use cases + if ( + $processing_charge->amount > $processing_charge->amountoutstanding && + $processingreturn_policy ne "refund_unpaid" + ) { + # some amount has been cancelled. collect the offsets that are not writeoffs + # this works because the only way to subtract from this kind of a debt is + # using the UI buttons 'Pay' and 'Write off' + + # We don't credit any payments if return policy is + # "refund_unpaid" + # + # In that case only unpaid/outstanding amount + # will be credited which settles the debt without + # creating extra credits + + my $credit_offsets = $processing_charge->debit_offsets( + { + 'credit_id' => { '!=' => undef }, + 'credit.credit_type_code' => { '!=' => 'Writeoff' } + }, + { join => 'credit' } + ); + + my $total_to_refund = ( $credit_offsets->count > 0 ) ? + # credits are negative on the DB + $credit_offsets->total * -1 : + 0; + # Credit the outstanding amount, then add what has been + # paid to create a net credit for this amount + $credit_total += $total_to_refund; + } + + my $credit; + if ( $credit_total > 0 ) { + my $branchcode = + C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef; + $credit = $account->add_credit( + { + amount => $credit_total, + description => 'Item found ' . $self->itemnumber, + type => 'PROCESSING_FOUND', + interface => C4::Context->interface, + library_id => $branchcode, + item_id => $self->itemnumber, + issue_id => $processing_charge->issue_id + } + ); + + $credit->apply( { debits => [$processing_charge] } ); + $self->add_message( + { + type => 'info', + message => 'processing_refunded', + payload => { credit_id => $credit->id } + } + ); + } + + # Update the account status + $processing_charge->status('FOUND'); + $processing_charge->store(); + + # Reconcile balances if required + if ( C4::Context->preference('AccountAutoReconcile') ) { + $account->reconcile_balance; + } + } + } + } + return $self; } diff --git a/circ/returns.pl b/circ/returns.pl index 4493afb297..2c2bea8aa8 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -674,6 +674,9 @@ foreach my $code ( keys %$messages ) { elsif ( $code eq 'LostItemFeeRestored' ) { $template->param( LostItemFeeRestored => 1 ); } + elsif ( $code eq 'ProcessingFeeRefunded' ) { + $template->param( ProcessingFeeRefunded => 1 ); + } elsif ( $code eq 'ResFound' ) { ; # FIXME... anything to do here? } diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt index 6065db78b7..b7e36913f6 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -336,7 +336,7 @@

Item was lost, now found.

[% END %] [% IF LostItemFeeRefunded and not Koha.Preference('BlockReturnOfLostItems') %] -

A refund has been applied to the borrowing patron's account.

+

A refund for the lost item charge has been applied to the borrowing patron's account.

[% ELSIF LostItemFeeCharged and not Koha.Preference('BlockReturnOfLostItems') %]

A refund for the lost item charge has been applied to the borrowing patron's account, and new overdue charge has been calculated and applied.

[% ELSIF LostItemFeeRestored and not Koha.Preference('BlockReturnOfLostItems') %] @@ -347,6 +347,11 @@ [% ELSE %]

Any lost item fees for this item will remain on the patron's account.

[% END %] + [% IF ProcessingFeeRefunded and not Koha.Preference('BlockReturnOfLostItems') %] +

A refund for the lost item processing charge has been applied to the borrowing patron's account.

+ [% ELSE %] +

Any processing fees for this item will remain on the patron's account.

+ [% END %] [% END %] [% IF ( errmsgloo.withdrawn ) %] [% IF Koha.Preference('BlockReturnOfWithdrawnItems') %] diff --git a/t/db_dependent/Koha/Item.t b/t/db_dependent/Koha/Item.t index 8d52892972..aff28a5dd5 100755 --- a/t/db_dependent/Koha/Item.t +++ b/t/db_dependent/Koha/Item.t @@ -1407,7 +1407,7 @@ subtest 'store() tests' => sub { subtest '_set_found_trigger() tests' => sub { - plan tests => 6; + plan tests => 8; $schema->storage->txn_begin; @@ -1424,10 +1424,22 @@ subtest 'store() tests' => sub { } ); + # Add a lost item processing fee + my $processing_debit = $patron->account->add_debit( + { + amount => 2, + type => 'PROCESSING', + item_id => $item->id, + interface => 'intranet', + } + ); + my $lostreturn_policy = 'charge'; + my $processingreturn_policy = 'refund'; my $mocked_circ_rules = Test::MockModule->new('Koha::CirculationRules'); $mocked_circ_rules->mock( 'get_lostreturn_policy', sub { return $lostreturn_policy; } ); + $mocked_circ_rules->mock( 'get_processingreturn_policy', sub { return $processingreturn_policy; } ); # simulate it was found $item->set( { itemlost => 0 } )->store; @@ -1454,6 +1466,16 @@ subtest 'store() tests' => sub { is( $message_2->message, 'lost_charge', 'message is correct' ); is( $message_2->payload, undef, 'no payload' ); + my $message_3 = $messages->[2]; + is( $message_3->message, 'processing_refunded', 'message is correct' ); + + my $processing_credit = $processing_debit->credits->next; + is_deeply( + $message_3->payload, + { credit_id => $processing_credit->id }, + 'type is correct' + ); + $schema->storage->txn_rollback; }; -- 2.39.5