From d643bf75a032fddc4d7cefb8489182ae13b899a5 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 3 Sep 2020 17:12:47 +0100 Subject: [PATCH] Bug 23091: Move _Restore to Koha::Item store trigger Signed-off-by: Tomas Cohen Arazi Signed-off-by: Jonathan Druart --- C4/Circulation.pm | 178 +++++-------------- Koha/Item.pm | 191 ++++++++++++-------- t/db_dependent/Circulation.t | 65 ------- t/db_dependent/Koha/Items.t | 332 ++++++++++++++++++++++++++++++++++- 4 files changed, 493 insertions(+), 273 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 3d405402f3..26fea55766 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -1541,51 +1541,31 @@ sub AddIssue { $item_object->datelastseen( dt_from_string()->ymd() ); $item_object->store({log_action => 0}); - # If the item was lost, it has now been found, restore/charge the overdue if necessary + # If the item was lost, it has now been found, charge the overdue if necessary if ($was_lost) { - my $lostreturn_policy = - Koha::CirculationRules->get_lostreturn_policy( - { - return_branch => C4::Context->userenv->{branch}, - item => $item_object - } - ); - - if ($lostreturn_policy) { - - if ( $lostreturn_policy eq 'charge' ) { - $actualissue //= Koha::Old::Checkouts->search( - { itemnumber => $item_unblessed->{itemnumber} }, - { - order_by => { '-desc' => 'returndate' }, - rows => 1 - } - )->single; - unless ( exists( $borrower->{branchcode} ) ) { - my $patron = $actualissue->patron; - $borrower = $patron->unblessed; + if ( $item_object->{_charge} ) { + $actualissue //= Koha::Old::Checkouts->search( + { itemnumber => $item_unblessed->{itemnumber} }, + { + order_by => { '-desc' => 'returndate' }, + rows => 1 } - _CalculateAndUpdateFine( - { - issue => $actualissue, - item => $item_unblessed, - borrower => $borrower, - return_date => $issuedate - } - ); - _FixOverduesOnReturn( $borrower->{borrowernumber}, - $item_object->itemnumber, undef, 'RENEWED' ); - } - elsif ( $lostreturn_policy eq 'restore' ) { - _RestoreOverdueForLostAndFound( - $item_object->itemnumber ); - } - - if ( C4::Context->preference('AccountAutoReconcile') ) { - $account->reconcile_balance; + )->single; + unless ( exists( $borrower->{branchcode} ) ) { + my $patron = $actualissue->patron; + $borrower = $patron->unblessed; } + _CalculateAndUpdateFine( + { + issue => $actualissue, + item => $item_unblessed, + borrower => $borrower, + return_date => $issuedate + } + ); + _FixOverduesOnReturn( $borrower->{borrowernumber}, + $item_object->itemnumber, undef, 'RENEWED' ); } - } # If it costs to borrow this book, charge it to the patron's account. @@ -2094,46 +2074,32 @@ sub AddReturn { my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost, { skip_record_index => 1 } ); # will unset itemlost if needed # fix up the accounts..... - if ( $item_was_lost ) { + if ($item_was_lost) { $messages->{'WasLost'} = 1; unless ( C4::Context->preference("BlockReturnOfLostItems") ) { $messages->{'LostItemFeeRefunded'} = $updated_item->{_refunded}; - - my $lostreturn_policy = - Koha::CirculationRules->get_lostreturn_policy( - { - return_branch => C4::Context->userenv->{branch}, - item => $updated_item + $messages->{'LostItemFeeRestored'} = $updated_item->{_restored}; + + if ( $updated_item->{_charge} ) { + $issue //= Koha::Old::Checkouts->search( + { itemnumber => $item->itemnumber }, + { order_by => { '-desc' => 'returndate' }, rows => 1 } ) + ->single; + unless ( exists( $patron_unblessed->{branchcode} ) ) { + my $patron = $issue->patron; + $patron_unblessed = $patron->unblessed; } - ); - - if ($lostreturn_policy) { - - if ( $lostreturn_policy eq 'charge' ) { - $issue //= Koha::Old::Checkouts->search( - { itemnumber => $item->itemnumber }, - { order_by => { '-desc' => 'returndate' }, rows => 1 } - )->single; - unless (exists($patron_unblessed->{branchcode})) { - my $patron = $issue->patron; - $patron_unblessed = $patron->unblessed; + _CalculateAndUpdateFine( + { + issue => $issue, + item => $item->unblessed, + borrower => $patron_unblessed, + return_date => $return_date } - _CalculateAndUpdateFine( - { - issue => $issue, - item => $item->unblessed, - borrower => $patron_unblessed, - return_date => $return_date - } - ); - _FixOverduesOnReturn( $patron_unblessed->{borrowernumber}, - $item->itemnumber, undef, 'RETURNED' ); - $messages->{'LostItemFeeCharged'} = 1; - } - elsif ( $lostreturn_policy eq 'restore' ) { - _RestoreOverdueForLostAndFound( $item->itemnumber ); - $messages->{'LostItemFeeRestored'} = 1; - } + ); + _FixOverduesOnReturn( $patron_unblessed->{borrowernumber}, + $item->itemnumber, undef, 'RETURNED' ); + $messages->{'LostItemFeeCharged'} = 1; } } } @@ -2593,66 +2559,6 @@ sub _FixOverduesOnReturn { return $result; } -=head2 _RestoreOverdueForLostAndFound - - &_RestoreOverdueForLostAndFound( $itemnumber ); - -C<$itemnumber> itemnumber - -Internal function - -=cut - -sub _RestoreOverdueForLostAndFound { - my ( $item ) = @_; - - unless( $item ) { - warn "_RestoreOverdueForLostAndFound() not supplied valid itemnumber"; - return; - } - - my $schema = Koha::Database->schema; - - my $result = $schema->txn_do( - sub { - # check for lost overdue fine - my $accountlines = Koha::Account::Lines->search( - { - itemnumber => $item, - debit_type_code => 'OVERDUE', - status => 'LOST' - }, - { - order_by => { '-desc' => 'date' }, - rows => 1 - } - ); - return 0 unless $accountlines->count; # no warning, there's just nothing to fix - - # Update status of fine - my $overdue = $accountlines->next; - $overdue->status('RETURNED')->store(); - - # Find related forgive credit - my $refunds = $overdue->credits( - { - credit_type_code => 'FORGIVEN', - itemnumber => $item, - status => [ { '!=' => 'VOID' }, undef ] - }, - { order_by => { '-desc' => 'date' }, rows => 1 } - ); - return 0 unless $refunds->count; # no warning, there's just nothing to fix - - # Revert the forgive credit - my $refund = $refunds->next; - return $refund->void(); - } - ); - - return $result; -} - =head2 _GetCircControlBranch my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower); diff --git a/Koha/Item.pm b/Koha/Item.pm index bedf4d1856..707bef5a41 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -825,8 +825,7 @@ sub _set_found_trigger { return $self unless $lost_age_in_days < $no_refund_after_days; } - return $self - unless Koha::CirculationRules->get_lostreturn_policy( + my $lostreturn_policy = Koha::CirculationRules->get_lostreturn_policy( { item => $self, return_branch => C4::Context->userenv @@ -835,80 +834,130 @@ sub _set_found_trigger { } ); - # check for charge made for lost book - my $accountlines = Koha::Account::Lines->search( - { - itemnumber => $self->itemnumber, - debit_type_code => 'LOST', - status => [ undef, { '<>' => 'FOUND' } ] - }, - { - order_by => { -desc => [ 'date', 'accountlines_id' ] } - } - ); - - return $self unless $accountlines->count > 0; - - my $accountline = $accountlines->next; - my $total_to_refund = 0; + if ( $lostreturn_policy ) { - return $self unless $accountline->borrowernumber; - - my $patron = Koha::Patrons->find( $accountline->borrowernumber ); - return $self - unless $patron; # Patron has been deleted, nobody to credit the return to - # FIXME Should not we notify this somewhere - - my $account = $patron->account; - - # Use cases - if ( $accountline->amount > $accountline->amountoutstanding ) { - - # 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' - my $credits_offsets = Koha::Account::Offsets->search( + # refund charge made for lost book + my $lost_charge = Koha::Account::Lines->search( { - debit_id => $accountline->id, - credit_id => { '!=' => undef }, # it is not the debit itself - type => { '!=' => 'Writeoff' }, - amount => { '<' => 0 } # credits are negative on the DB - } - ); - - $total_to_refund = ( $credits_offsets->count > 0 ) - ? $credits_offsets->total * -1 # credits are negative on the DB - : 0; - } - - my $credit_total = $accountline->amountoutstanding + $total_to_refund; - - my $credit; - if ( $credit_total > 0 ) { - my $branchcode = - C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef; - $credit = $account->add_credit( + itemnumber => $self->itemnumber, + debit_type_code => 'LOST', + status => [ undef, { '<>' => 'FOUND' } ] + }, { - amount => $credit_total, - description => 'Item found ' . $self->itemnumber, - type => 'LOST_FOUND', - interface => C4::Context->interface, - library_id => $branchcode, - item_id => $self->itemnumber, - issue_id => $accountline->issue_id + order_by => { -desc => [ 'date', 'accountlines_id' ] }, + rows => 1 } - ); - - $credit->apply( { debits => [$accountline] } ); - $self->{_refunded} = 1; - } - - # Update the account status - $accountline->status('FOUND'); - $accountline->store(); + )->single; + + if ( $lost_charge ) { + + my $patron = $lost_charge->patron; + if ( $patron ) { + + my $account = $patron->account; + my $total_to_refund = 0; + + # Use cases + if ( $lost_charge->amount > $lost_charge->amountoutstanding ) { + + # 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' + my $credits_offsets = Koha::Account::Offsets->search( + { + debit_id => $lost_charge->id, + credit_id => { '!=' => undef }, # it is not the debit itself + type => { '!=' => 'Writeoff' }, + amount => { '<' => 0 } # credits are negative on the DB + } + ); + + $total_to_refund = ( $credits_offsets->count > 0 ) + ? $credits_offsets->total * -1 # credits are negative on the DB + : 0; + } + + my $credit_total = $lost_charge->amountoutstanding + $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 => 'LOST_FOUND', + interface => C4::Context->interface, + library_id => $branchcode, + item_id => $self->itemnumber, + issue_id => $lost_charge->issue_id + } + ); + + $credit->apply( { debits => [$lost_charge] } ); + $self->{_refunded} = 1; + } + + # Update the account status + $lost_charge->status('FOUND'); + $lost_charge->store(); + + # Reconcile balances if required + if ( C4::Context->preference('AccountAutoReconcile') ) { + $account->reconcile_balance; + } + } + } - if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) { - $account->reconcile_balance; + # restore fine for lost book + if ( $lostreturn_policy eq 'restore' ) { + my $lost_overdue = Koha::Account::Lines->search( + { + itemnumber => $self->itemnumber, + debit_type_code => 'OVERDUE', + status => 'LOST' + }, + { + order_by => { '-desc' => 'date' }, + rows => 1 + } + )->single; + + if ( $lost_overdue ) { + + my $patron = $lost_overdue->patron; + if ($patron) { + my $account = $patron->account; + + # Update status of fine + $lost_overdue->status('FOUND')->store(); + + # Find related forgive credit + my $refund = $lost_overdue->credits( + { + credit_type_code => 'FORGIVEN', + itemnumber => $self->itemnumber, + status => [ { '!=' => 'VOID' }, undef ] + }, + { order_by => { '-desc' => 'date' }, rows => 1 } + )->single; + + if ( $refund ) { + # Revert the forgive credit + $refund->void(); + $self->{_restored} = 1; + } + + # Reconcile balances if required + if ( C4::Context->preference('AccountAutoReconcile') ) { + $account->reconcile_balance; + } + } + } + } elsif ( $lostreturn_policy eq 'charge' ) { + $self->{_charge} = 1; + } } return $self; diff --git a/t/db_dependent/Circulation.t b/t/db_dependent/Circulation.t index 35063635c8..0e173017c8 100755 --- a/t/db_dependent/Circulation.t +++ b/t/db_dependent/Circulation.t @@ -2698,71 +2698,6 @@ subtest 'AddReturn | is_overdue' => sub { }; }; -subtest '_RestoreOverdueForLostAndFound' => sub { - - plan tests => 7; - - my $manager = $builder->build_object( { class => "Koha::Patrons" } ); - t::lib::Mocks::mock_userenv( - { patron => $manager, branchcode => $manager->branchcode } ); - - my $patron = $builder->build_object( { class => "Koha::Patrons" } ); - my $item = $builder->build_sample_item(); - - # No fine found - my $result = C4::Circulation::_RestoreOverdueForLostAndFound( $item->itemnumber); - is($result, 0, "0 returned when no overdue found"); - - # Fine not forgiven - my $account = $patron->account; - my $overdue = $account->add_debit( - { - amount => 30.00, - user_id => $manager->borrowernumber, - library_id => $library2->{branchcode}, - interface => 'test', - item_id => $item->itemnumber, - type => 'OVERDUE', - } - )->store(); - $overdue->status('LOST')->store(); - - $result = C4::Circulation::_RestoreOverdueForLostAndFound( $item->itemnumber); - is($result, 0, "0 returned when overdue found without forgival"); - $overdue->discard_changes; - is($overdue->status, 'RETURNED', 'Overdue status updated to RETURNED'); - - # Reset status - $overdue->status('LOST')->store(); - - # Fine forgiven - my $credit = $account->add_credit( - { - amount => 30.00, - user_id => $manager->borrowernumber, - library_id => $library2->{branchcode}, - interface => 'test', - type => 'FORGIVEN', - item_id => $item->itemnumber - } - ); - $credit->apply( { debits => [$overdue], offset_type => 'Forgiven' } ); - - $result = C4::Circulation::_RestoreOverdueForLostAndFound( $item->itemnumber); - - is( ref($result), 'Koha::Account::Line', 'Return a Koha::Account::Line object on sucess'); - $overdue->discard_changes; - is($overdue->status, 'RETURNED', 'Overdue status updated to RETURNED'); - is($overdue->amountoutstanding, $overdue->amount, 'Overdue outstanding restored'); - - # Missing parameters - warning_like { - C4::Circulation::_RestoreOverdueForLostAndFound( undef ) - } - qr/_RestoreOverdueForLostAndFound\(\) not supplied valid itemnumber/, - "parameter warning received for missing itemnumbernumber"; -}; - subtest '_FixOverduesOnReturn' => sub { plan tests => 14; diff --git a/t/db_dependent/Koha/Items.t b/t/db_dependent/Koha/Items.t index 5cc3f3702e..0d204d8245 100755 --- a/t/db_dependent/Koha/Items.t +++ b/t/db_dependent/Koha/Items.t @@ -138,7 +138,7 @@ subtest 'store' => sub { }; subtest '_lost_found_trigger' => sub { - plan tests => 7; + plan tests => 10; t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 ); t::lib::Mocks::mock_preference( 'WhenLostForgiveFine', 0 ); @@ -761,6 +761,336 @@ subtest 'store' => sub { }; + subtest 'restore fine | no overdue' => sub { + + plan tests => 8; + + my $manager = + $builder->build_object( { class => "Koha::Patrons" } ); + t::lib::Mocks::mock_userenv( + { patron => $manager, branchcode => $manager->branchcode } ); + + # Set lostreturn_policy to 'restore' for tests + my $specific_rule_restore = $builder->build( + { + source => 'CirculationRule', + value => { + branchcode => $manager->branchcode, + categorycode => undef, + itemtype => undef, + rule_name => 'lostreturn', + rule_value => 'restore' + } + } + ); + + my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); + + my $item = $builder->build_sample_item( + { + biblionumber => $biblio->biblionumber, + library => $library->branchcode, + replacementprice => $replacement_amount, + itype => $item_type->itemtype + } + ); + + my $issue = + C4::Circulation::AddIssue( $patron->unblessed, $item->barcode ); + + # Simulate item marked as lost + $item->itemlost(1)->store; + C4::Circulation::LostItem( $item->itemnumber, 1 ); + + my $processing_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'PROCESSING' + } + ); + is( $processing_fee_lines->count, + 1, 'Only one processing fee produced' ); + my $processing_fee_line = $processing_fee_lines->next; + is( $processing_fee_line->amount + 0, + $processfee_amount, + 'The right PROCESSING amount is generated' ); + is( $processing_fee_line->amountoutstanding + 0, + $processfee_amount, + 'The right PROCESSING amountoutstanding is generated' ); + + my $lost_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'LOST' + } + ); + is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' ); + my $lost_fee_line = $lost_fee_lines->next; + is( $lost_fee_line->amount + 0, + $replacement_amount, 'The right LOST amount is generated' ); + is( $lost_fee_line->amountoutstanding + 0, + $replacement_amount, + 'The right LOST amountountstanding is generated' ); + + my $account = $patron->account; + my $debts = $account->outstanding_debits; + + # Pay off the debt + my $credit = $account->add_credit( + { + amount => $account->balance, + type => 'PAYMENT', + interface => 'test', + } + ); + $credit->apply( + { debits => [ $debts->as_list ], offset_type => 'Payment' } ); + + # Simulate item marked as found + $item->itemlost(0)->store; + is( $item->{_refunded}, 1, 'Refund triggered' ); + is( $item->{_restored}, undef, 'Restore not triggered when there is no overdue fine found' ); + }; + + subtest 'restore fine | unforgiven overdue' => sub { + + plan tests => 10; + + # Set lostreturn_policy to 'restore' for tests + my $manager = + $builder->build_object( { class => "Koha::Patrons" } ); + t::lib::Mocks::mock_userenv( + { patron => $manager, branchcode => $manager->branchcode } ); + my $specific_rule_restore = $builder->build( + { + source => 'CirculationRule', + value => { + branchcode => $manager->branchcode, + categorycode => undef, + itemtype => undef, + rule_name => 'lostreturn', + rule_value => 'restore' + } + } + ); + + my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); + + my $item = $builder->build_sample_item( + { + biblionumber => $biblio->biblionumber, + library => $library->branchcode, + replacementprice => $replacement_amount, + itype => $item_type->itemtype + } + ); + + my $issue = + C4::Circulation::AddIssue( $patron->unblessed, $item->barcode ); + + # Simulate item marked as lost + $item->itemlost(1)->store; + C4::Circulation::LostItem( $item->itemnumber, 1 ); + + my $processing_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'PROCESSING' + } + ); + is( $processing_fee_lines->count, + 1, 'Only one processing fee produced' ); + my $processing_fee_line = $processing_fee_lines->next; + is( $processing_fee_line->amount + 0, + $processfee_amount, + 'The right PROCESSING amount is generated' ); + is( $processing_fee_line->amountoutstanding + 0, + $processfee_amount, + 'The right PROCESSING amountoutstanding is generated' ); + + my $lost_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'LOST' + } + ); + is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' ); + my $lost_fee_line = $lost_fee_lines->next; + is( $lost_fee_line->amount + 0, + $replacement_amount, 'The right LOST amount is generated' ); + is( $lost_fee_line->amountoutstanding + 0, + $replacement_amount, + 'The right LOST amountountstanding is generated' ); + + my $account = $patron->account; + my $debts = $account->outstanding_debits; + + # Pay off the debt + my $credit = $account->add_credit( + { + amount => $account->balance, + type => 'PAYMENT', + interface => 'test', + } + ); + $credit->apply( + { debits => [ $debts->as_list ], offset_type => 'Payment' } ); + + # Fine not forgiven + my $overdue = $account->add_debit( + { + amount => 30.00, + user_id => $manager->borrowernumber, + library_id => $library->branchcode, + interface => 'test', + item_id => $item->itemnumber, + type => 'OVERDUE', + } + )->store(); + $overdue->status('LOST')->store(); + $overdue->discard_changes; + is( $overdue->status, 'LOST', + 'Overdue status set to LOST' ); + + # Simulate item marked as found + $item->itemlost(0)->store; + is( $item->{_refunded}, 1, 'Refund triggered' ); + is( $item->{_restored}, undef, 'Restore not triggered when overdue was not forgiven' ); + $overdue->discard_changes; + is( $overdue->status, 'FOUND', + 'Overdue status updated to FOUND' ); + }; + + subtest 'restore fine | forgiven overdue' => sub { + + plan tests => 12; + + # Set lostreturn_policy to 'restore' for tests + my $manager = + $builder->build_object( { class => "Koha::Patrons" } ); + t::lib::Mocks::mock_userenv( + { patron => $manager, branchcode => $manager->branchcode } ); + my $specific_rule_restore = $builder->build( + { + source => 'CirculationRule', + value => { + branchcode => $manager->branchcode, + categorycode => undef, + itemtype => undef, + rule_name => 'lostreturn', + rule_value => 'restore' + } + } + ); + + my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); + + my $item = $builder->build_sample_item( + { + biblionumber => $biblio->biblionumber, + library => $library->branchcode, + replacementprice => $replacement_amount, + itype => $item_type->itemtype + } + ); + + my $issue = + C4::Circulation::AddIssue( $patron->unblessed, $item->barcode ); + + # Simulate item marked as lost + $item->itemlost(1)->store; + C4::Circulation::LostItem( $item->itemnumber, 1 ); + + my $processing_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'PROCESSING' + } + ); + is( $processing_fee_lines->count, + 1, 'Only one processing fee produced' ); + my $processing_fee_line = $processing_fee_lines->next; + is( $processing_fee_line->amount + 0, + $processfee_amount, + 'The right PROCESSING amount is generated' ); + is( $processing_fee_line->amountoutstanding + 0, + $processfee_amount, + 'The right PROCESSING amountoutstanding is generated' ); + + my $lost_fee_lines = Koha::Account::Lines->search( + { + borrowernumber => $patron->id, + itemnumber => $item->itemnumber, + debit_type_code => 'LOST' + } + ); + is( $lost_fee_lines->count, 1, 'Only one lost item fee produced' ); + my $lost_fee_line = $lost_fee_lines->next; + is( $lost_fee_line->amount + 0, + $replacement_amount, 'The right LOST amount is generated' ); + is( $lost_fee_line->amountoutstanding + 0, + $replacement_amount, + 'The right LOST amountountstanding is generated' ); + + my $account = $patron->account; + my $debts = $account->outstanding_debits; + + # Pay off the debt + my $credit = $account->add_credit( + { + amount => $account->balance, + type => 'PAYMENT', + interface => 'test', + } + ); + $credit->apply( + { debits => [ $debts->as_list ], offset_type => 'Payment' } ); + + # Add overdue + my $overdue = $account->add_debit( + { + amount => 30.00, + user_id => $manager->borrowernumber, + library_id => $library->branchcode, + interface => 'test', + item_id => $item->itemnumber, + type => 'OVERDUE', + } + )->store(); + $overdue->status('LOST')->store(); + is( $overdue->status, 'LOST', + 'Overdue status set to LOST' ); + + # Forgive fine + $credit = $account->add_credit( + { + amount => 30.00, + user_id => $manager->borrowernumber, + library_id => $library->branchcode, + interface => 'test', + type => 'FORGIVEN', + item_id => $item->itemnumber + } + ); + $credit->apply( + { debits => [$overdue], offset_type => 'Forgiven' } ); + + # Simulate item marked as found + $item->itemlost(0)->store; + is( $item->{_refunded}, 1, 'Refund triggered' ); + is( $item->{_restored}, 1, 'Restore triggered when overdue was forgiven' ); + $overdue->discard_changes; + is( $overdue->status, 'FOUND', 'Overdue status updated to FOUND' ); + is( $overdue->amountoutstanding, $overdue->amount, 'Overdue outstanding has been restored' ); + $credit->discard_changes; + is( $credit->status, 'VOID', 'Overdue Forgival has been marked as VOID'); + }; + subtest 'Continue when userenv is not set' => sub { plan tests => 1; -- 2.39.5