From 8eb9ba80c8abf3b50cccbe93e7017c4384c2739c Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
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 <tomascohen@theke.io>
---
 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 35b75e7782c..2f620bd4d14 100644
--- a/C4/Circulation.pm
+++ b/C4/Circulation.pm
@@ -1545,51 +1545,31 @@ sub AddIssue {
             $item_object->store({log_action => 0});
             ModDateLastSeen( $item_object->itemnumber );
 
-            # 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.
@@ -2093,46 +2073,32 @@ sub AddReturn {
     my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost ); # 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;
             }
         }
     }
@@ -2587,66 +2553,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 5114a87db34..803580e1d0a 100644
--- a/Koha/Item.pm
+++ b/Koha/Item.pm
@@ -823,8 +823,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
@@ -833,80 +832,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 fa1e3e1d136..6456d4f0d04 100755
--- a/t/db_dependent/Circulation.t
+++ b/t/db_dependent/Circulation.t
@@ -2666,71 +2666,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 5cc3f3702eb..0d204d8245f 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.28.0