@@ -, +, @@ found - refunded - refunded if unpaid - kept - WhenLostChargeReplacementFee: Charge - BlockReturnOfLostItems: Don't block - t/db_dependent/Koha/Item.t - t/db_dependent/Koha/CirculationRules.t --- 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(-) --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -2226,6 +2226,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'; --- a/Koha/Item.pm +++ a/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; } --- a/circ/returns.pl +++ a/circ/returns.pl @@ -673,6 +673,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? } --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ a/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') %] --- a/t/db_dependent/Koha/Item.t +++ a/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; }; --