Bug 17600: Standardize our EXPORT_OK
[srvgit] / t / db_dependent / Circulation.t
index 2cf8311..8aa5d4c 100755 (executable)
 use Modern::Perl;
 use utf8;
 
-use Test::More tests => 46;
+use Test::More tests => 55;
+use Test::Exception;
 use Test::MockModule;
+use Test::Deep qw( cmp_deeply );
+use Test::Warn;
 
 use Data::Dumper;
 use DateTime;
@@ -29,19 +32,20 @@ use t::lib::Mocks;
 use t::lib::TestBuilder;
 
 use C4::Accounts;
-use C4::Calendar;
-use C4::Circulation;
+use C4::Calendar qw( new insert_single_holiday insert_week_day_holiday delete_holiday );
+use C4::Circulation qw( AddIssue AddReturn CanBookBeRenewed GetIssuingCharges AddRenewal GetSoonestRenewDate GetLatestAutoRenewDate LostItem GetUpcomingDueIssues CanBookBeIssued AddIssuingCharge ProcessOfflinePayment transferbook updateWrongTransfer );
 use C4::Biblio;
-use C4::Items;
+use C4::Items qw( ModItemTransfer );
 use C4::Log;
-use C4::Reserves;
-use C4::Overdues qw(UpdateFine CalcFine);
+use C4::Reserves qw( AddReserve ModReserve ModReserveCancelAll ModReserveAffect CheckReserves GetOtherReserves );
+use C4::Overdues qw( CalcFine UpdateFine get_chargeable_units );
 use Koha::DateUtils;
 use Koha::Database;
-use Koha::IssuingRules;
 use Koha::Items;
+use Koha::Item::Transfers;
 use Koha::Checkouts;
 use Koha::Patrons;
+use Koha::Holds;
 use Koha::CirculationRules;
 use Koha::Subscriptions;
 use Koha::Account::Lines;
@@ -80,9 +84,9 @@ sub test_debarment_on_checkout {
     );
     my @caller      = caller;
     my $line_number = $caller[2];
-    AddIssue( $patron, $item->{barcode}, $due_date );
+    AddIssue( $patron, $item->barcode, $due_date );
 
-    my ( undef, $message ) = AddReturn( $item->{barcode}, $library->{branchcode}, undef, $return_date );
+    my ( undef, $message ) = AddReturn( $item->barcode, $library->{branchcode}, undef, $return_date );
     is( $message->{WasReturned} && exists $message->{Debarred}, 1, 'AddReturn must have debarred the patron' )
         or diag('AddReturn returned message ' . Dumper $message );
     my $debarments = Koha::Patron::Debarments::GetDebarments(
@@ -105,18 +109,24 @@ my $now_value       = dt_from_string;
 my $mocked_datetime = Test::MockModule->new('DateTime');
 $mocked_datetime->mock( 'now', sub { return $now_value->clone; } );
 
-# Start transaction
-$dbh->{RaiseError} = 1;
-
 my $cache = Koha::Caches->get_instance();
 $dbh->do(q|DELETE FROM special_holidays|);
 $dbh->do(q|DELETE FROM repeatable_holidays|);
-$cache->clear_from_cache('single_holidays');
+my $branches = Koha::Libraries->search();
+for my $branch ( $branches->next ) {
+    my $key = $branch->branchcode . "_holidays";
+    $cache->clear_from_cache($key);
+}
 
 # Start with a clean slate
 $dbh->do('DELETE FROM issues');
 $dbh->do('DELETE FROM borrowers');
 
+# Disable recording of the staff who checked out an item until we're ready for it
+t::lib::Mocks::mock_preference('RecordStaffUserOnCheckout', 0);
+
+my $module = Test::MockModule->new('C4::Context');
+
 my $library = $builder->build({
     source => 'Branch',
 });
@@ -252,32 +262,160 @@ is(
 );
 
 # Set a simple circ policy
-$dbh->do('DELETE FROM issuingrules');
-Koha::CirculationRules->search()->delete();
-$dbh->do(
-    q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed,
-                                issuelength, lengthunit,
-                                renewalsallowed, renewalperiod,
-                                norenewalbefore, auto_renew,
-                                fine, chargeperiod)
-      VALUES (?, ?, ?, ?,
-              ?, ?,
-              ?, ?,
-              ?, ?,
-              ?, ?
-             )
-    },
-    {},
-    '*', '*', '*', 25,
-    14, 'days',
-    1, 7,
-    undef, 0,
-    .10, 1
+$dbh->do('DELETE FROM circulation_rules');
+Koha::CirculationRules->set_rules(
+    {
+        categorycode => undef,
+        branchcode   => undef,
+        itemtype     => undef,
+        rules        => {
+            reservesallowed => 25,
+            issuelength     => 14,
+            lengthunit      => 'days',
+            renewalsallowed => 1,
+            renewalperiod   => 7,
+            norenewalbefore => undef,
+            auto_renew      => 0,
+            fine            => .10,
+            chargeperiod    => 1,
+        }
+    }
 );
 
+subtest "CanBookBeRenewed AllowRenewalIfOtherItemsAvailable multiple borrowers and items tests" => sub {
+    plan tests => 5;
+
+    #Can only reserve from home branch
+    Koha::CirculationRules->set_rule(
+        {
+            branchcode   => undef,
+            itemtype     => undef,
+            rule_name    => 'holdallowed',
+            rule_value   => 1
+        }
+    );
+    Koha::CirculationRules->set_rule(
+        {
+            branchcode   => undef,
+            categorycode   => undef,
+            itemtype     => undef,
+            rule_name    => 'onshelfholds',
+            rule_value   => 1
+        }
+    );
+
+    # Patrons from three different branches
+    my $patron_borrower = $builder->build_object({ class => 'Koha::Patrons' });
+    my $patron_hold_1   = $builder->build_object({ class => 'Koha::Patrons' });
+    my $patron_hold_2   = $builder->build_object({ class => 'Koha::Patrons' });
+    my $biblio = $builder->build_sample_biblio();
+
+    # Item at each patron branch
+    my $item_1 = $builder->build_sample_item({
+        biblionumber => $biblio->biblionumber,
+        homebranch   => $patron_borrower->branchcode
+    });
+    my $item_2 = $builder->build_sample_item({
+        biblionumber => $biblio->biblionumber,
+        homebranch   => $patron_hold_2->branchcode
+    });
+    my $item_3 = $builder->build_sample_item({
+        biblionumber => $biblio->biblionumber,
+        homebranch   => $patron_hold_1->branchcode
+    });
+
+    my $issue = AddIssue( $patron_borrower->unblessed, $item_1->barcode);
+    my $datedue = dt_from_string( $issue->date_due() );
+    is (defined $issue->date_due(), 1, "Item 1 checked out, due date: " . $issue->date_due() );
+
+    # Biblio-level holds
+    AddReserve(
+        {
+            branchcode       => $patron_hold_1->branchcode,
+            borrowernumber   => $patron_hold_1->borrowernumber,
+            biblionumber     => $biblio->biblionumber,
+            priority         => 1,
+            reservation_date => dt_from_string(),
+            expiration_date  => undef,
+            itemnumber       => undef,
+            found            => undef,
+        }
+    );
+    AddReserve(
+        {
+            branchcode       => $patron_hold_2->branchcode,
+            borrowernumber   => $patron_hold_2->borrowernumber,
+            biblionumber     => $biblio->biblionumber,
+            priority         => 2,
+            reservation_date => dt_from_string(),
+            expiration_date  => undef,
+            itemnumber       => undef,
+            found            => undef,
+        }
+    );
+    t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 0 );
+
+    my ( $renewokay, $error ) = CanBookBeRenewed($patron_borrower->borrowernumber, $item_1->itemnumber);
+    is( $renewokay, 0, 'Cannot renew, reserved');
+    is( $error, 'on_reserve', 'Cannot renew, reserved (returned error is on_reserve)');
+
+    t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 1 );
+
+    ( $renewokay, $error ) = CanBookBeRenewed($patron_borrower->borrowernumber, $item_1->itemnumber);
+    is( $renewokay, 1, 'Can renew, two items available for two holds');
+    is( $error, undef, 'Can renew, each reserve has an item');
+
+
+};
+
+subtest "GetIssuingCharges tests" => sub {
+    plan tests => 4;
+    my $branch_discount = $builder->build_object({ class => 'Koha::Libraries' });
+    my $branch_no_discount = $builder->build_object({ class => 'Koha::Libraries' });
+    Koha::CirculationRules->set_rule(
+        {
+            categorycode => undef,
+            branchcode   => $branch_discount->branchcode,
+            itemtype     => undef,
+            rule_name    => 'rentaldiscount',
+            rule_value   => 15
+        }
+    );
+    my $itype_charge = $builder->build_object({
+        class => 'Koha::ItemTypes',
+        value => {
+            rentalcharge => 10
+        }
+    });
+    my $itype_no_charge = $builder->build_object({
+        class => 'Koha::ItemTypes',
+        value => {
+            rentalcharge => 0
+        }
+    });
+    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
+    my $item_1 = $builder->build_sample_item({ itype => $itype_charge->itemtype });
+    my $item_2 = $builder->build_sample_item({ itype => $itype_no_charge->itemtype });
+
+    t::lib::Mocks::mock_userenv({ branchcode => $branch_no_discount->branchcode });
+    # For now the sub always uses the env branch, this should follow CircControl instead
+    my ($charge, $itemtype) = GetIssuingCharges( $item_1->itemnumber, $patron->borrowernumber);
+    is( $charge + 0, 10.00, "Charge fetched correctly when no discount exists");
+    ($charge, $itemtype) = GetIssuingCharges( $item_2->itemnumber, $patron->borrowernumber);
+    is( $charge + 0, 0.00, "Charge fetched correctly when no discount exists and no charge");
+
+    t::lib::Mocks::mock_userenv({ branchcode => $branch_discount->branchcode });
+    # For now the sub always uses the env branch, this should follow CircControl instead
+    ($charge, $itemtype) = GetIssuingCharges( $item_1->itemnumber, $patron->borrowernumber);
+    is( $charge + 0, 8.50, "Charge fetched correctly when discount exists");
+    ($charge, $itemtype) = GetIssuingCharges( $item_2->itemnumber, $patron->borrowernumber);
+    is( $charge + 0, 0.00, "Charge fetched correctly when discount exists and no charge");
+
+};
+
 my ( $reused_itemnumber_1, $reused_itemnumber_2 );
 subtest "CanBookBeRenewed tests" => sub {
-    plan tests => 71;
+    plan tests => 95;
 
     C4::Context->set_preference('ItemsDeniedRenewal','');
     # Generate test biblio
@@ -358,7 +496,8 @@ subtest "CanBookBeRenewed tests" => sub {
     my $restricted_borrowernumber = Koha::Patron->new(\%restricted_borrower_data)->store->borrowernumber;
     my $expired_borrowernumber = Koha::Patron->new(\%expired_borrower_data)->store->borrowernumber;
 
-    my $renewing_borrower = Koha::Patrons->find( $renewing_borrowernumber )->unblessed;
+    my $renewing_borrower_obj = Koha::Patrons->find( $renewing_borrowernumber );
+    my $renewing_borrower = $renewing_borrower_obj->unblessed;
     my $restricted_borrower = Koha::Patrons->find( $restricted_borrowernumber )->unblessed;
     my $expired_borrower = Koha::Patrons->find( $expired_borrowernumber )->unblessed;
 
@@ -388,13 +527,38 @@ subtest "CanBookBeRenewed tests" => sub {
 
     # Biblio-level hold, renewal test
     AddReserve(
-        $branch, $reserving_borrowernumber, $biblio->biblionumber,
-        $bibitems,  $priority, $resdate, $expdate, $notes,
-        'a title', $checkitem, $found
+        {
+            branchcode       => $branch,
+            borrowernumber   => $reserving_borrowernumber,
+            biblionumber     => $biblio->biblionumber,
+            priority         => $priority,
+            reservation_date => $resdate,
+            expiration_date  => $expdate,
+            notes            => $notes,
+            itemnumber       => $checkitem,
+            found            => $found,
+        }
     );
 
     # Testing of feature to allow the renewal of reserved items if other items on the record can fill all needed holds
-    C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
+    Koha::CirculationRules->set_rule(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rule_name    => 'onshelfholds',
+            rule_value   => '1',
+        }
+    );
+    Koha::CirculationRules->set_rule(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rule_name    => 'renewalsallowed',
+            rule_value   => '5',
+        }
+    );
     t::lib::Mocks::mock_preference('AllowRenewalIfOtherItemsAvailable', 1 );
     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
     is( $renewokay, 1, 'Bug 11634 - Allow renewal of item with unfilled holds if other available items can fill those holds');
@@ -453,9 +617,17 @@ subtest "CanBookBeRenewed tests" => sub {
 
     # Item-level hold, renewal test
     AddReserve(
-        $branch, $reserving_borrowernumber, $biblio->biblionumber,
-        $bibitems,  $priority, $resdate, $expdate, $notes,
-        'a title', $item_1->itemnumber, $found
+        {
+            branchcode       => $branch,
+            borrowernumber   => $reserving_borrowernumber,
+            biblionumber     => $biblio->biblionumber,
+            priority         => $priority,
+            reservation_date => $resdate,
+            expiration_date  => $expdate,
+            notes            => $notes,
+            itemnumber       => $item_1->itemnumber,
+            found            => $found,
+        }
     );
 
     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
@@ -466,10 +638,10 @@ subtest "CanBookBeRenewed tests" => sub {
     is( $renewokay, 1, 'Can renew item 2, item-level hold is on item 1');
 
     # Items can't fill hold for reasons
-    ModItem({ notforloan => 1 }, $biblio->biblionumber, $item_1->itemnumber);
+    $item_1->notforloan(1)->store;
     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber, 1);
     is( $renewokay, 1, 'Can renew, item is marked not for loan, hold does not block');
-    ModItem({ notforloan => 0, itype => $itemtype }, $biblio->biblionumber, $item_1->itemnumber);
+    $item_1->set({notforloan => 0, itype => $itemtype })->store;
 
     # FIXME: Add more for itemtype not for loan etc.
 
@@ -490,6 +662,7 @@ subtest "CanBookBeRenewed tests" => sub {
     is( $renewokay, 1, '(Bug 8236), Can renew, user is not restricted');
     ( $renewokay, $error ) = CanBookBeRenewed($restricted_borrowernumber, $item_5->itemnumber);
     is( $renewokay, 0, '(Bug 8236), Cannot renew, user is restricted');
+    is( $error, 'restriction', "Correct error returned");
 
     # Users cannot renew an overdue item
     my $item_6 = $builder->build_sample_item(
@@ -532,6 +705,9 @@ subtest "CanBookBeRenewed tests" => sub {
         }
     );
 
+    # Make sure fine calculation isn't skipped when adding renewal
+    t::lib::Mocks::mock_preference('CalculateFinesOnReturn', 1);
+
     t::lib::Mocks::mock_preference('RenewalLog', 0);
     my $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
     my %params_renewal = (
@@ -566,6 +742,27 @@ subtest "CanBookBeRenewed tests" => sub {
     isnt( $fines->next->status, 'UNRETURNED', 'Fine on renewed item is closed out properly' );
     $fines->delete();
 
+    t::lib::Mocks::mock_preference('OverduesBlockRenewing','allow');
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_6->itemnumber);
+    is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_7->itemnumber);
+    is( $renewokay, 1, '(Bug 8236), Can renew, this item is overdue but not pref does not block');
+
+    t::lib::Mocks::mock_preference('OverduesBlockRenewing','block');
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_6->itemnumber);
+    is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is not overdue but patron has overdues');
+    is( $error, 'overdue', "Correct error returned");
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_7->itemnumber);
+    is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue so patron has overdues');
+    is( $error, 'overdue', "Correct error returned");
+
+    t::lib::Mocks::mock_preference('OverduesBlockRenewing','blockitem');
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_6->itemnumber);
+    is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_7->itemnumber);
+    is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue');
+    is( $error, 'overdue', "Correct error returned");
+
 
     my $old_issue_log_size = Koha::ActionLogs->count( \%params_issue );
     my $old_renew_log_size = Koha::ActionLogs->count( \%params_renewal );
@@ -578,13 +775,6 @@ subtest "CanBookBeRenewed tests" => sub {
     $fines = Koha::Account::Lines->search( { borrowernumber => $renewing_borrower->{borrowernumber}, itemnumber => $item_7->itemnumber } );
     $fines->delete();
 
-    t::lib::Mocks::mock_preference('OverduesBlockRenewing','blockitem');
-    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_6->itemnumber);
-    is( $renewokay, 1, '(Bug 8236), Can renew, this item is not overdue');
-    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_7->itemnumber);
-    is( $renewokay, 0, '(Bug 8236), Cannot renew, this item is overdue');
-
-
     $hold = Koha::Holds->search({ biblionumber => $biblio->biblionumber, borrowernumber => $reserving_borrowernumber })->next;
     $hold->cancel;
 
@@ -606,10 +796,58 @@ subtest "CanBookBeRenewed tests" => sub {
     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
     is( $error, 'auto_too_soon',
         'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = undef (returned code is auto_too_soon)' );
+    AddReserve(
+        {
+            branchcode       => $branch,
+            borrowernumber   => $reserving_borrowernumber,
+            biblionumber     => $biblio->biblionumber,
+            itemnumber       => $bibitems,
+            priority         => $priority,
+            reservation_date => $resdate,
+            expiration_date  => $expdate,
+            notes            => $notes,
+            title            => 'a title',
+            itemnumber       => $item_4->itemnumber,
+            found            => $found
+        }
+    );
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+    is( $renewokay, 0, 'Still should not be able to renew' );
+    is( $error, 'on_reserve', 'returned code is on_reserve, reserve checked when not checking for cron' );
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, undef, 1 );
+    is( $renewokay, 0, 'Still should not be able to renew' );
+    is( $error, 'auto_too_soon', 'returned code is auto_too_soon, reserve not checked when checking for cron' );
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, 1 );
+    is( $renewokay, 0, 'Still should not be able to renew' );
+    is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, 1, 1 );
+    is( $renewokay, 0, 'Still should not be able to renew' );
+    is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
+    $dbh->do('UPDATE circulation_rules SET rule_value = 0 where rule_name = "norenewalbefore"');
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, 1 );
+    is( $renewokay, 0, 'Still should not be able to renew' );
+    is( $error, 'on_reserve', 'returned code is on_reserve, auto_renew only happens if not on reserve' );
+    ModReserveCancelAll($item_4->itemnumber, $reserving_borrowernumber);
+
+
+
+    $renewing_borrower_obj->autorenew_checkouts(0)->store;
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+    is( $renewokay, 1, 'No renewal before is undef, but patron opted out of auto_renewal' );
+    $renewing_borrower_obj->autorenew_checkouts(1)->store;
+
 
     # Bug 7413
     # Test premature manual renewal
-    $dbh->do('UPDATE issuingrules SET norenewalbefore = 7');
+    Koha::CirculationRules->set_rule(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rule_name    => 'norenewalbefore',
+            rule_value   => '7',
+        }
+    );
 
     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
     is( $renewokay, 0, 'Bug 7413: Cannot renew, renewal is premature');
@@ -642,9 +880,15 @@ subtest "CanBookBeRenewed tests" => sub {
         'Bug 14101: Cannot renew, renewal is automatic and premature (returned code is auto_too_soon)'
     );
 
+    $renewing_borrower_obj->autorenew_checkouts(0)->store;
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+    is( $renewokay, 0, 'No renewal before is 7, patron opted out of auto_renewal still cannot renew early' );
+    is( $error, 'too_soon', 'Error is too_soon, no auto' );
+    $renewing_borrower_obj->autorenew_checkouts(1)->store;
+
     # Change policy so that loans can only be renewed exactly on due date (0 days prior to due date)
     # and test automatic renewal again
-    $dbh->do('UPDATE issuingrules SET norenewalbefore = 0');
+    $dbh->do(q{UPDATE circulation_rules SET rule_value = '0' WHERE rule_name = 'norenewalbefore'});
     ( $renewokay, $error ) =
       CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
@@ -652,9 +896,15 @@ subtest "CanBookBeRenewed tests" => sub {
         'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = 0 (returned code is auto_too_soon)'
     );
 
+    $renewing_borrower_obj->autorenew_checkouts(0)->store;
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+    is( $renewokay, 0, 'No renewal before is 0, patron opted out of auto_renewal still cannot renew early' );
+    is( $error, 'too_soon', 'Error is too_soon, no auto' );
+    $renewing_borrower_obj->autorenew_checkouts(1)->store;
+
     # Change policy so that loans can be renewed 99 days prior to the due date
     # and test automatic renewal again
-    $dbh->do('UPDATE issuingrules SET norenewalbefore = 99');
+    $dbh->do(q{UPDATE circulation_rules SET rule_value = '99' WHERE rule_name = 'norenewalbefore'});
     ( $renewokay, $error ) =
       CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
     is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic' );
@@ -662,81 +912,164 @@ subtest "CanBookBeRenewed tests" => sub {
         'Bug 14101: Cannot renew, renewal is automatic (returned code is auto_renew)'
     );
 
+    $renewing_borrower_obj->autorenew_checkouts(0)->store;
+    ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+    is( $renewokay, 1, 'No renewal before is 99, patron opted out of auto_renewal so can renew' );
+    $renewing_borrower_obj->autorenew_checkouts(1)->store;
+
     subtest "too_late_renewal / no_auto_renewal_after" => sub {
         plan tests => 14;
-        my $item_to_auto_renew = $builder->build(
-            {   source => 'Item',
-                value  => {
-                    biblionumber  => $biblio->biblionumber,
-                    homebranch    => $branch,
-                    holdingbranch => $branch,
-                }
+        my $item_to_auto_renew = $builder->build_sample_item(
+            {
+                biblionumber => $biblio->biblionumber,
+                library      => $branch,
             }
         );
 
         my $ten_days_before = dt_from_string->add( days => -10 );
         my $ten_days_ahead  = dt_from_string->add( days => 10 );
-        AddIssue( $renewing_borrower, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        AddIssue( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 9');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '7',
+                    no_auto_renewal_after => '9',
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 10');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '7',
+                    no_auto_renewal_after => '10',
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_late', 'Cannot auto renew, too late - no_auto_renewal_after is inclusive(returned code is auto_too_late)' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 11');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '7',
+                    no_auto_renewal_after => '11',
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_soon', 'Cannot auto renew, too soon - no_auto_renewal_after is defined(returned code is auto_too_soon)' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 11');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '11',
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0,            'Do not renew, renewal is automatic' );
         is( $error,     'auto_renew', 'Cannot renew, renew is automatic' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = NULL, no_auto_renewal_after_hard_limit = ?', undef, dt_from_string->add( days => -1 ) );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => undef,
+                    no_auto_renewal_after_hard_limit => dt_from_string->add( days => -1 ),
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = 15, no_auto_renewal_after_hard_limit = ?', undef, dt_from_string->add( days => -1 ) );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '7',
+                    no_auto_renewal_after => '15',
+                    no_auto_renewal_after_hard_limit => dt_from_string->add( days => -1 ),
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_late', 'Cannot renew, too late(returned code is auto_too_late)' );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = NULL, no_auto_renewal_after_hard_limit = ?', undef, dt_from_string->add( days => 1 ) );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => undef,
+                    no_auto_renewal_after_hard_limit => dt_from_string->add( days => 1 ),
+                }
+            }
+        );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Cannot renew, renew is automatic' );
     };
 
     subtest "auto_too_much_oweing | OPACFineNoRenewalsBlockAutoRenew & OPACFineNoRenewalsIncludeCredit" => sub {
         plan tests => 10;
-        my $item_to_auto_renew = $builder->build({
-            source => 'Item',
-            value => {
+        my $item_to_auto_renew = $builder->build_sample_item(
+            {
                 biblionumber => $biblio->biblionumber,
-                homebranch       => $branch,
-                holdingbranch    => $branch,
+                library      => $branch,
             }
-        });
+        );
 
         my $ten_days_before = dt_from_string->add( days => -10 );
         my $ten_days_ahead = dt_from_string->add( days => 10 );
-        AddIssue( $renewing_borrower, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        AddIssue( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 11');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '11',
+                }
+            }
+        );
         C4::Context->set_preference('OPACFineNoRenewalsBlockAutoRenew','1');
         C4::Context->set_preference('OPACFineNoRenewals','10');
         C4::Context->set_preference('OPACFineNoRenewalsIncludeCredit','1');
@@ -747,12 +1080,12 @@ subtest "CanBookBeRenewed tests" => sub {
                 amount      => $fines_amount,
                 interface   => 'test',
                 type        => 'OVERDUE',
-                item_id     => $item_to_auto_renew->{itemnumber},
+                item_id     => $item_to_auto_renew->itemnumber,
                 description => "Some fines"
             }
         )->status('RETURNED')->store;
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 5' );
 
@@ -761,12 +1094,12 @@ subtest "CanBookBeRenewed tests" => sub {
                 amount      => $fines_amount,
                 interface   => 'test',
                 type        => 'OVERDUE',
-                item_id     => $item_to_auto_renew->{itemnumber},
+                item_id     => $item_to_auto_renew->itemnumber,
                 description => "Some fines"
             }
         )->status('RETURNED')->store;
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, patron has 10' );
 
@@ -775,12 +1108,12 @@ subtest "CanBookBeRenewed tests" => sub {
                 amount      => $fines_amount,
                 interface   => 'test',
                 type        => 'OVERDUE',
-                item_id     => $item_to_auto_renew->{itemnumber},
+                item_id     => $item_to_auto_renew->itemnumber,
                 description => "Some fines"
             }
         )->status('RETURNED')->store;
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, patron has 15' );
 
@@ -793,13 +1126,13 @@ subtest "CanBookBeRenewed tests" => sub {
             }
         )->store;
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Can auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit'  );
 
         C4::Context->set_preference('OPACFineNoRenewalsIncludeCredit','0');
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_too_much_oweing', 'Cannot auto renew, OPACFineNoRenewals=10, OPACFineNoRenewalsIncludeCredit=1, patron has 15 debt, 5 credit'  );
 
@@ -809,16 +1142,24 @@ subtest "CanBookBeRenewed tests" => sub {
 
     subtest "auto_account_expired | BlockExpiredPatronOpacActions" => sub {
         plan tests => 6;
-        my $item_to_auto_renew = $builder->build({
-            source => 'Item',
-            value => {
+        my $item_to_auto_renew = $builder->build_sample_item(
+            {
                 biblionumber => $biblio->biblionumber,
-                homebranch       => $branch,
-                holdingbranch    => $branch,
+                library      => $branch,
             }
-        });
+        );
 
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 11');
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => 10,
+                    no_auto_renewal_after => 11,
+                }
+            }
+        );
 
         my $ten_days_before = dt_from_string->add( days => -10 );
         my $ten_days_ahead = dt_from_string->add( days => 10 );
@@ -827,9 +1168,9 @@ subtest "CanBookBeRenewed tests" => sub {
         # => auto renew is allowed
         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 0);
         my $patron = $expired_borrower;
-        my $checkout = AddIssue( $patron, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        my $checkout = AddIssue( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Can auto renew, patron is expired but BlockExpiredPatronOpacActions=0' );
         Koha::Checkouts->find( $checkout->issue_id )->delete;
@@ -839,9 +1180,9 @@ subtest "CanBookBeRenewed tests" => sub {
         # => auto renew is not allowed
         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
         $patron = $expired_borrower;
-        $checkout = AddIssue( $patron, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        $checkout = AddIssue( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_account_expired', 'Can not auto renew, lockExpiredPatronOpacActions=1 and patron is expired' );
         Koha::Checkouts->find( $checkout->issue_id )->delete;
@@ -851,9 +1192,9 @@ subtest "CanBookBeRenewed tests" => sub {
         # => auto renew is allowed
         t::lib::Mocks::mock_preference('BlockExpiredPatronOpacActions', 1);
         $patron = $renewing_borrower;
-        $checkout = AddIssue( $patron, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        $checkout = AddIssue( $patron, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
         ( $renewokay, $error ) =
-          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->{itemnumber} );
+          CanBookBeRenewed( $patron->{borrowernumber}, $item_to_auto_renew->itemnumber );
         is( $renewokay, 0, 'Do not renew, renewal is automatic' );
         is( $error, 'auto_renew', 'Can auto renew, BlockExpiredPatronOpacActions=1 but patron is not expired' );
         Koha::Checkouts->find( $checkout->issue_id )->delete;
@@ -861,61 +1202,155 @@ subtest "CanBookBeRenewed tests" => sub {
 
     subtest "GetLatestAutoRenewDate" => sub {
         plan tests => 5;
-        my $item_to_auto_renew = $builder->build(
-            {   source => 'Item',
-                value  => {
-                    biblionumber  => $biblio->biblionumber,
-                    homebranch    => $branch,
-                    holdingbranch => $branch,
-                }
+        my $item_to_auto_renew = $builder->build_sample_item(
+            {
+                biblionumber => $biblio->biblionumber,
+                library      => $branch,
             }
         );
 
         my $ten_days_before = dt_from_string->add( days => -10 );
         my $ten_days_ahead  = dt_from_string->add( days => 10 );
-        AddIssue( $renewing_borrower, $item_to_auto_renew->{barcode}, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 7, no_auto_renewal_after = NULL, no_auto_renewal_after_hard_limit = NULL');
-        my $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+        AddIssue( $renewing_borrower, $item_to_auto_renew->barcode, $ten_days_ahead, undef, $ten_days_before, undef, { auto_renew => 1 } );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '7',
+                    no_auto_renewal_after => '',
+                    no_auto_renewal_after_hard_limit => undef,
+                }
+            }
+        );
+        my $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $latest_auto_renew_date, undef, 'GetLatestAutoRenewDate should return undef if no_auto_renewal_after or no_auto_renewal_after_hard_limit are not defined' );
         my $five_days_before = dt_from_string->add( days => -5 );
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 5, no_auto_renewal_after_hard_limit = NULL');
-        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '5',
+                    no_auto_renewal_after_hard_limit => undef,
+                }
+            }
+        );
+        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $latest_auto_renew_date->truncate( to => 'minute' ),
             $five_days_before->truncate( to => 'minute' ),
             'GetLatestAutoRenewDate should return -5 days if no_auto_renewal_after = 5 and date_due is 10 days before'
         );
         my $five_days_ahead = dt_from_string->add( days => 5 );
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 15, no_auto_renewal_after_hard_limit = NULL');
-        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+        $dbh->do(q{UPDATE circulation_rules SET rule_value = '10' WHERE rule_name = 'norenewalbefore'});
+        $dbh->do(q{UPDATE circulation_rules SET rule_value = '15' WHERE rule_name = 'no_auto_renewal_after'});
+        $dbh->do(q{UPDATE circulation_rules SET rule_value = NULL WHERE rule_name = 'no_auto_renewal_after_hard_limit'});
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '15',
+                    no_auto_renewal_after_hard_limit => undef,
+                }
+            }
+        );
+        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $latest_auto_renew_date->truncate( to => 'minute' ),
             $five_days_ahead->truncate( to => 'minute' ),
             'GetLatestAutoRenewDate should return +5 days if no_auto_renewal_after = 15 and date_due is 10 days before'
         );
         my $two_days_ahead = dt_from_string->add( days => 2 );
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = NULL, no_auto_renewal_after_hard_limit = ?', undef, dt_from_string->add( days => 2 ) );
-        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '',
+                    no_auto_renewal_after_hard_limit => dt_from_string->add( days => 2 ),
+                }
+            }
+        );
+        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $latest_auto_renew_date->truncate( to => 'day' ),
             $two_days_ahead->truncate( to => 'day' ),
             'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is defined and not no_auto_renewal_after'
         );
-        $dbh->do('UPDATE issuingrules SET norenewalbefore = 10, no_auto_renewal_after = 15, no_auto_renewal_after_hard_limit = ?', undef, dt_from_string->add( days => 2 ) );
-        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->{itemnumber} );
+        Koha::CirculationRules->set_rules(
+            {
+                categorycode => undef,
+                branchcode   => undef,
+                itemtype     => undef,
+                rules        => {
+                    norenewalbefore       => '10',
+                    no_auto_renewal_after => '15',
+                    no_auto_renewal_after_hard_limit => dt_from_string->add( days => 2 ),
+                }
+            }
+        );
+        $latest_auto_renew_date = GetLatestAutoRenewDate( $renewing_borrowernumber, $item_to_auto_renew->itemnumber );
         is( $latest_auto_renew_date->truncate( to => 'day' ),
             $two_days_ahead->truncate( to => 'day' ),
             'GetLatestAutoRenewDate should return +2 days if no_auto_renewal_after_hard_limit is < no_auto_renewal_after'
         );
 
     };
-
     # Too many renewals
 
     # set policy to forbid renewals
-    $dbh->do('UPDATE issuingrules SET norenewalbefore = NULL, renewalsallowed = 0');
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                norenewalbefore => undef,
+                renewalsallowed => 0,
+            }
+        }
+    );
 
     ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
     is( $renewokay, 0, 'Cannot renew, 0 renewals allowed');
     is( $error, 'too_many', 'Cannot renew, 0 renewals allowed (returned code is too_many)');
 
+    # Too many unseen renewals
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                unseen_renewals_allowed => 2,
+                renewalsallowed => 10,
+            }
+        }
+    );
+    t::lib::Mocks::mock_preference('UnseenRenewals', 1);
+    $dbh->do('UPDATE issues SET unseen_renewals = 2 where borrowernumber = ? AND itemnumber = ?', undef, ($renewing_borrowernumber, $item_1->itemnumber));
+    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
+    is( $renewokay, 0, 'Cannot renew, 0 unseen renewals allowed');
+    is( $error, 'too_unseen', 'Cannot renew, returned code is too_unseen');
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                norenewalbefore => undef,
+                renewalsallowed => 0,
+            }
+        }
+    );
+    t::lib::Mocks::mock_preference('UnseenRenewals', 0);
+
     # Test WhenLostForgiveFine and WhenLostChargeReplacementFee
     t::lib::Mocks::mock_preference('WhenLostForgiveFine','1');
     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','1');
@@ -934,13 +1369,13 @@ subtest "CanBookBeRenewed tests" => sub {
     my $line = Koha::Account::Lines->search({ borrowernumber => $renewing_borrower->{borrowernumber} })->next();
     is( $line->debit_type_code, 'OVERDUE', 'Account line type is OVERDUE' );
     is( $line->status, 'UNRETURNED', 'Account line status is UNRETURNED' );
-    is( $line->amountoutstanding, '15.000000', 'Account line amount outstanding is 15.00' );
-    is( $line->amount, '15.000000', 'Account line amount is 15.00' );
+    is( $line->amountoutstanding+0, 15, 'Account line amount outstanding is 15.00' );
+    is( $line->amount+0, 15, 'Account line amount is 15.00' );
     is( $line->issue_id, $issue->id, 'Account line issue id matches' );
 
     my $offset = Koha::Account::Offsets->search({ debit_id => $line->id })->next();
     is( $offset->type, 'OVERDUE', 'Account offset type is Fine' );
-    is( $offset->amount, '15.000000', 'Account offset amount is 15.00' );
+    is( $offset->amount+0, 15, 'Account offset amount is 15.00' );
 
     t::lib::Mocks::mock_preference('WhenLostForgiveFine','0');
     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','0');
@@ -961,7 +1396,7 @@ subtest "CanBookBeRenewed tests" => sub {
         undef, $renewing_borrower->{borrowernumber}
     );
 
-    is( $total_due, '15.000000', 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
+    is( $total_due+0, 15, 'Borrower only charged replacement fee with both WhenLostForgiveFine and WhenLostChargeReplacementFee enabled' );
 
     C4::Context->dbh->do("DELETE FROM accountlines");
 
@@ -994,13 +1429,6 @@ subtest "CanBookBeRenewed tests" => sub {
     my $units = C4::Overdues::get_chargeable_units('days', $future, $now, $library2->{branchcode});
     ok( $units == 0, '_get_chargeable_units returns 0 for items not past due date (Bug 12596)' );
 
-    # Users cannot renew any item if there is an overdue item
-    t::lib::Mocks::mock_preference('OverduesBlockRenewing','block');
-    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_6->itemnumber);
-    is( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
-    ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_7->itemnumber);
-    is( $renewokay, 0, '(Bug 8236), Cannot renew, one of the items is overdue');
-
     my $manager = $builder->build_object({ class => "Koha::Patrons" });
     t::lib::Mocks::mock_userenv({ patron => $manager,branchcode => $manager->branchcode });
     t::lib::Mocks::mock_preference('WhenLostChargeReplacementFee','1');
@@ -1145,27 +1573,23 @@ subtest "Bug 13841 - Do not create new 0 amount fines" => sub {
 subtest "AllowRenewalIfOtherItemsAvailable tests" => sub {
     $dbh->do('DELETE FROM issues');
     $dbh->do('DELETE FROM items');
-    $dbh->do('DELETE FROM issuingrules');
-    Koha::CirculationRules->search()->delete();
-    $dbh->do(
-        q{
-        INSERT INTO issuingrules ( categorycode, branchcode, itemtype, reservesallowed, issuelength, lengthunit, renewalsallowed, renewalperiod,
-                    norenewalbefore, auto_renew, fine, chargeperiod ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
-        },
-        {},
-        '*', '*', '*', 25,
-        14,  'days',
-        1,   7,
-        undef,  0,
-        .10, 1
-    );
+    $dbh->do('DELETE FROM circulation_rules');
     Koha::CirculationRules->set_rules(
         {
-            categorycode => '*',
-            itemtype     => '*',
-            branchcode   => '*',
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
             rules        => {
-                maxissueqty => 20
+                reservesallowed => 25,
+                issuelength     => 14,
+                lengthunit      => 'days',
+                renewalsallowed => 1,
+                renewalperiod   => 7,
+                norenewalbefore => undef,
+                auto_renew      => 0,
+                fine            => .10,
+                chargeperiod    => 1,
+                maxissueqty     => 20
             }
         }
     );
@@ -1209,33 +1633,72 @@ subtest "AllowRenewalIfOtherItemsAvailable tests" => sub {
     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with no hold on the record' );
 
     AddReserve(
-        $library2->{branchcode}, $borrowernumber2, $biblio->biblionumber,
-        '',  1, undef, undef, '',
-        undef, undef, undef
+        {
+            branchcode     => $library2->{branchcode},
+            borrowernumber => $borrowernumber2,
+            biblionumber   => $biblio->biblionumber,
+            priority       => 1,
+        }
     );
 
-    C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 0");
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                onshelfholds => 0,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfholds are disabled' );
 
-    C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 0");
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                onshelfholds => 0,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
 
-    C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                onshelfholds => 1,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
     is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
 
-    C4::Context->dbh->do("UPDATE issuingrules SET onshelfholds = 1");
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                onshelfholds => 1,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
     is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
 
     # Setting item not checked out to be not for loan but holdable
-    ModItem({ notforloan => -1 }, $biblio->biblionumber, $item_2->itemnumber);
+    $item_2->notforloan(-1)->store;
 
     ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
     is( $renewokay, 0, 'Bug 14337 - Verify the borrower can not renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled but the only available item is notforloan' );
@@ -1322,11 +1785,11 @@ subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
             homebranch    => $homebranch->{branchcode},
             holdingbranch => $holdingbranch->{branchcode},
         }
-    )->unblessed;
+    );
 
     set_userenv($holdingbranch);
 
-    my $issue = AddIssue( $patron_1->unblessed, $item->{barcode} );
+    my $issue = AddIssue( $patron_1->unblessed, $item->barcode );
     is( ref($issue), 'Koha::Checkout', 'AddIssue should return a Koha::Checkout object' );
 
     my ( $error, $question, $alerts );
@@ -1339,16 +1802,16 @@ subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
     ok( $error->{UNKNOWN_BARCODE}, '"KohaIsAwesome" is not a valid barcode as expected.' );
     ## Can be issued from homebranch
     set_userenv($homebranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
     ## Can be issued from holdingbranch
     set_userenv($holdingbranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
     ## Can be issued from another branch
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
 
@@ -1356,18 +1819,18 @@ subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
     ## Cannot be issued from homebranch
     set_userenv($homebranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
     is( $error->{branch_to_return},         $holdingbranch->{branchcode}, 'branch_to_return matched holdingbranch' );
     ## Can be issued from holdinbranch
     set_userenv($holdingbranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
     ## Cannot be issued from another branch
     set_userenv($otherbranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
     is( $error->{branch_to_return},         $holdingbranch->{branchcode}, 'branch_to_return matches holdingbranch' );
@@ -1376,18 +1839,18 @@ subtest 'CanBookBeIssued & AllowReturnToBranch' => sub {
     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
     ## Can be issued from holdinbranch
     set_userenv($homebranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$error) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER must be set' );
     ## Cannot be issued from holdinbranch
     set_userenv($holdingbranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
     is( $error->{branch_to_return},         $homebranch->{branchcode}, 'branch_to_return matches homebranch' );
     ## Cannot be issued from holdinbranch
     set_userenv($otherbranch);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron_2, $item->barcode );
     is( keys(%$question) + keys(%$alerts), 0, 'There should not be any errors or alerts (impossible)' . str($error, $question, $alerts) );
     is( exists $error->{RETURN_IMPOSSIBLE}, 1, 'RETURN_IMPOSSIBLE must be set' );
     is( $error->{branch_to_return},         $homebranch->{branchcode}, 'branch_to_return matches homebranch' );
@@ -1409,12 +1872,12 @@ subtest 'AddIssue & AllowReturnToBranch' => sub {
             homebranch    => $homebranch->{branchcode},
             holdingbranch => $holdingbranch->{branchcode},
         }
-    )->unblessed;
+    );
 
     set_userenv($holdingbranch);
 
     my $ref_issue = 'Koha::Checkout';
-    my $issue = AddIssue( $patron_1, $item->{barcode} );
+    my $issue = AddIssue( $patron_1, $item->barcode );
 
     my ( $error, $question, $alerts );
 
@@ -1422,42 +1885,42 @@ subtest 'AddIssue & AllowReturnToBranch' => sub {
     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
     ## Can be issued from homebranch
     set_userenv($homebranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from homebranch');
-    set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from homebranch');
+    set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
     ## Can be issued from holdinbranch
     set_userenv($holdingbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from holdingbranch');
-    set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from holdingbranch');
+    set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
     ## Can be issued from another branch
     set_userenv($otherbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from otherbranch');
-    set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - anywhere | Can be issued from otherbranch');
+    set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
 
     # AllowReturnToBranch == holdinbranch
     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'holdingbranch' );
     ## Cannot be issued from homebranch
     set_userenv($homebranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from homebranch');
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from homebranch');
     ## Can be issued from holdingbranch
     set_userenv($holdingbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue, 'AllowReturnToBranch - holdingbranch | Can be issued from holdingbranch');
-    set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - holdingbranch | Can be issued from holdingbranch');
+    set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
     ## Cannot be issued from another branch
     set_userenv($otherbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from otherbranch');
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - holdingbranch | Cannot be issued from otherbranch');
 
     # AllowReturnToBranch == homebranch
     t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'homebranch' );
     ## Can be issued from homebranch
     set_userenv($homebranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), $ref_issue, 'AllowReturnToBranch - homebranch | Can be issued from homebranch' );
-    set_userenv($holdingbranch); AddIssue( $patron_1, $item->{barcode} ); # Reinsert the original issue
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), $ref_issue, 'AllowReturnToBranch - homebranch | Can be issued from homebranch' );
+    set_userenv($holdingbranch); AddIssue( $patron_1, $item->barcode ); # Reinsert the original issue
     ## Cannot be issued from holdinbranch
     set_userenv($holdingbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from holdingbranch' );
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from holdingbranch' );
     ## Cannot be issued from another branch
     set_userenv($otherbranch);
-    is ( ref( AddIssue( $patron_2, $item->{barcode} ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from otherbranch' );
+    is ( ref( AddIssue( $patron_2, $item->barcode ) ), '', 'AllowReturnToBranch - homebranch | Cannot be issued from otherbranch' );
     # TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
 };
 
@@ -1470,38 +1933,38 @@ subtest 'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
         {
             library => $library->{branchcode},
         }
-    )->unblessed;
+    );
     my $item_2 = $builder->build_sample_item(
         {
             library => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
     my ( $error, $question, $alerts );
 
     # Patron cannot issue item_1, they have overdues
     my $yesterday = DateTime->today( time_zone => C4::Context->tz() )->add( days => -1 );
-    my $issue = AddIssue( $patron->unblessed, $item_1->{barcode}, $yesterday );    # Add an overdue
+    my $issue = AddIssue( $patron->unblessed, $item_1->barcode, $yesterday );    # Add an overdue
 
     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'confirmation' );
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
     is( keys(%$error) + keys(%$alerts),  0, 'No key for error and alert' . str($error, $question, $alerts) );
     is( $question->{USERBLOCKEDOVERDUE}, 1, 'OverduesBlockCirc=confirmation, USERBLOCKEDOVERDUE should be set for question' );
 
     t::lib::Mocks::mock_preference( 'OverduesBlockCirc', 'block' );
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
     is( $error->{USERBLOCKEDOVERDUE},      1, 'OverduesBlockCirc=block, USERBLOCKEDOVERDUE should be set for error' );
 
     # Patron cannot issue item_1, they are debarred
     my $tomorrow = DateTime->today( time_zone => C4::Context->tz() )->add( days => 1 );
     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->borrowernumber, expiration => $tomorrow } );
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
     is( $error->{USERBLOCKEDWITHENDDATE}, output_pref( { dt => $tomorrow, dateformat => 'sql', dateonly => 1 } ), 'USERBLOCKEDWITHENDDATE should be tomorrow' );
 
     Koha::Patron::Debarments::AddDebarment( { borrowernumber => $patron->borrowernumber } );
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
     is( keys(%$question) + keys(%$alerts),  0, 'No key for question and alert ' . str($error, $question, $alerts) );
     is( $error->{USERBLOCKEDNOENDDATE},    '9999-12-31', 'USERBLOCKEDNOENDDATE should be 9999-12-31 for unlimited debarments' );
 };
@@ -1532,9 +1995,9 @@ subtest 'CanBookBeIssued + Statistic patrons "X"' => sub {
         {
             library => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
-    my ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_1->{barcode} );
+    my ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_1->barcode );
     is( $error->{STATS}, 1, '"Error" flag "STATS" must be set if CanBookBeIssued is called with a statistic patron (category_type=X)' );
 
     # TODO There are other tests to provide here
@@ -1594,9 +2057,17 @@ subtest 'MultipleReserves' => sub {
     );
     my $reserving_borrowernumber1 = Koha::Patron->new(\%reserving_borrower_data1)->store->borrowernumber;
     AddReserve(
-        $branch, $reserving_borrowernumber1, $biblio->biblionumber,
-        $bibitems,  $priority, $resdate, $expdate, $notes,
-        'a title', $checkitem, $found
+        {
+            branchcode       => $branch,
+            borrowernumber   => $reserving_borrowernumber1,
+            biblionumber     => $biblio->biblionumber,
+            priority         => $priority,
+            reservation_date => $resdate,
+            expiration_date  => $expdate,
+            notes            => $notes,
+            itemnumber       => $checkitem,
+            found            => $found,
+        }
     );
 
     my %reserving_borrower_data2 = (
@@ -1607,9 +2078,17 @@ subtest 'MultipleReserves' => sub {
     );
     my $reserving_borrowernumber2 = Koha::Patron->new(\%reserving_borrower_data2)->store->borrowernumber;
     AddReserve(
-        $branch, $reserving_borrowernumber2, $biblio->biblionumber,
-        $bibitems,  $priority, $resdate, $expdate, $notes,
-        'a title', $checkitem, $found
+        {
+            branchcode       => $branch,
+            borrowernumber   => $reserving_borrowernumber2,
+            biblionumber     => $biblio->biblionumber,
+            priority         => $priority,
+            reservation_date => $resdate,
+            expiration_date  => $expdate,
+            notes            => $notes,
+            itemnumber       => $checkitem,
+            found            => $found,
+        }
     );
 
     {
@@ -1648,37 +2127,53 @@ subtest 'CanBookBeIssued + AllowMultipleIssuesOnABiblio' => sub {
             biblionumber => $biblionumber,
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
     my $item_2 = $builder->build_sample_item(
         {
             biblionumber => $biblionumber,
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
     my ( $error, $question, $alerts );
-    my $issue = AddIssue( $patron->unblessed, $item_1->{barcode}, dt_from_string->add( days => 1 ) );
+    my $issue = AddIssue( $patron->unblessed, $item_1->barcode, dt_from_string->add( days => 1 ) );
 
     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
-    is( keys(%$error) + keys(%$alerts),  0, 'No error or alert should be raised' . str($error, $question, $alerts) );
-    is( $question->{BIBLIO_ALREADY_ISSUED}, 1, 'BIBLIO_ALREADY_ISSUED question flag should be set if AllowMultipleIssuesOnABiblio=0 and issue already exists' . str($error, $question, $alerts) );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
+    cmp_deeply(
+        { error => $error, alerts => $alerts },
+        { error => {}, alerts => {} },
+        'No error or alert should be raised'
+    );
+    is( $question->{BIBLIO_ALREADY_ISSUED}, 1, 'BIBLIO_ALREADY_ISSUED question flag should be set if AllowMultipleIssuesOnABiblio=0 and issue already exists' );
 
     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
-    is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if AllowMultipleIssuesOnABiblio=1' . str($error, $question, $alerts) );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
+    cmp_deeply(
+        { error => $error, question => $question, alerts => $alerts },
+        { error => {}, question => {}, alerts => {} },
+        'No BIBLIO_ALREADY_ISSUED flag should be set if AllowMultipleIssuesOnABiblio=1'
+    );
 
     # Add a subscription
     Koha::Subscription->new({ biblionumber => $biblionumber })->store;
 
     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 0);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
-    is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription' . str($error, $question, $alerts) );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
+    cmp_deeply(
+        { error => $error, question => $question, alerts => $alerts },
+        { error => {}, question => {}, alerts => {} },
+        'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
+    );
 
     t::lib::Mocks::mock_preference('AllowMultipleIssuesOnABiblio', 1);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->{barcode} );
-    is( keys(%$error) + keys(%$question) + keys(%$alerts),  0, 'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription' . str($error, $question, $alerts) );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron, $item_2->barcode );
+    cmp_deeply(
+        { error => $error, question => $question, alerts => $alerts },
+        { error => {}, question => {}, alerts => {} },
+        'No BIBLIO_ALREADY_ISSUED flag should be set if it is a subscription'
+    );
 };
 
 subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
@@ -1698,38 +2193,40 @@ subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
             biblionumber => $biblionumber,
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
     my $item_2 = $builder->build_sample_item(
         {
             biblionumber => $biblionumber,
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
-    # And the issuing rule
-    Koha::IssuingRules->search->delete;
-    my $rule = Koha::IssuingRule->new(
+    # And the circulation rule
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
         {
-            categorycode => '*',
-            itemtype     => '*',
-            branchcode   => '*',
-            issuelength  => 1,
-            firstremind  => 1,        # 1 day of grace
-            finedays     => 2,        # 2 days of fine per day of overdue
-            lengthunit   => 'days',
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                issuelength => 1,
+                firstremind => 1,        # 1 day of grace
+                finedays    => 2,        # 2 days of fine per day of overdue
+                lengthunit  => 'days',
+            }
         }
     );
-    $rule->store();
 
     # Patron cannot issue item_1, they have overdues
-    my $five_days_ago = dt_from_string->subtract( days => 5 );
-    my $ten_days_ago  = dt_from_string->subtract( days => 10 );
-    AddIssue( $patron, $item_1->{barcode}, $five_days_ago );    # Add an overdue
-    AddIssue( $patron, $item_2->{barcode}, $ten_days_ago )
+    my $now = dt_from_string;
+    my $five_days_ago = $now->clone->subtract( days => 5 );
+    my $ten_days_ago  = $now->clone->subtract( days => 10 );
+    AddIssue( $patron, $item_1->barcode, $five_days_ago );    # Add an overdue
+    AddIssue( $patron, $item_2->barcode, $ten_days_ago )
       ;    # Add another overdue
 
     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '0' );
-    AddReturn( $item_1->{barcode}, $library->{branchcode}, undef, dt_from_string );
+    AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
     my $debarments = Koha::Patron::Debarments::GetDebarments(
         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
     is( scalar(@$debarments), 1 );
@@ -1738,20 +2235,20 @@ subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
     # Same for the others
     my $expected_expiration = output_pref(
         {
-            dt         => dt_from_string->add( days => ( 5 - 1 ) * 2 ),
+            dt         => $now->clone->add( days => ( 5 - 1 ) * 2 ),
             dateformat => 'sql',
             dateonly   => 1
         }
     );
     is( $debarments->[0]->{expiration}, $expected_expiration );
 
-    AddReturn( $item_2->{barcode}, $library->{branchcode}, undef, dt_from_string );
+    AddReturn( $item_2->barcode, $library->{branchcode}, undef, $now );
     $debarments = Koha::Patron::Debarments::GetDebarments(
         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
     is( scalar(@$debarments), 1 );
     $expected_expiration = output_pref(
         {
-            dt         => dt_from_string->add( days => ( 10 - 1 ) * 2 ),
+            dt         => $now->clone->add( days => ( 10 - 1 ) * 2 ),
             dateformat => 'sql',
             dateonly   => 1
         }
@@ -1762,29 +2259,29 @@ subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
 
     t::lib::Mocks::mock_preference( 'CumulativeRestrictionPeriods', '1' );
-    AddIssue( $patron, $item_1->{barcode}, $five_days_ago );    # Add an overdue
-    AddIssue( $patron, $item_2->{barcode}, $ten_days_ago )
+    AddIssue( $patron, $item_1->barcode, $five_days_ago );    # Add an overdue
+    AddIssue( $patron, $item_2->barcode, $ten_days_ago )
       ;    # Add another overdue
-    AddReturn( $item_1->{barcode}, $library->{branchcode}, undef, dt_from_string );
+    AddReturn( $item_1->barcode, $library->{branchcode}, undef, $now );
     $debarments = Koha::Patron::Debarments::GetDebarments(
         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
     is( scalar(@$debarments), 1 );
     $expected_expiration = output_pref(
         {
-            dt         => dt_from_string->add( days => ( 5 - 1 ) * 2 ),
+            dt         => $now->clone->add( days => ( 5 - 1 ) * 2 ),
             dateformat => 'sql',
             dateonly   => 1
         }
     );
     is( $debarments->[0]->{expiration}, $expected_expiration );
 
-    AddReturn( $item_2->{barcode}, $library->{branchcode}, undef, dt_from_string );
+    AddReturn( $item_2->barcode, $library->{branchcode}, undef, $now );
     $debarments = Koha::Patron::Debarments::GetDebarments(
         { borrowernumber => $patron->{borrowernumber}, type => 'SUSPENSION' } );
     is( scalar(@$debarments), 1 );
     $expected_expiration = output_pref(
         {
-            dt => dt_from_string->add( days => ( 5 - 1 ) * 2 + ( 10 - 1 ) * 2 ),
+            dt => $now->clone->add( days => ( 5 - 1 ) * 2 + ( 10 - 1 ) * 2 ),
             dateformat => 'sql',
             dateonly   => 1
         }
@@ -1793,7 +2290,7 @@ subtest 'AddReturn + CumulativeRestrictionPeriods' => sub {
 };
 
 subtest 'AddReturn + suspension_chargeperiod' => sub {
-    plan tests => 21;
+    plan tests => 27;
 
     my $library = $builder->build( { source => 'Branch' } );
     my $patron  = $builder->build( { source => 'Borrower', value => { categorycode => $patron_category->{categorycode} } } );
@@ -1808,28 +2305,30 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
             biblionumber => $biblionumber,
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
     # And the issuing rule
-    Koha::IssuingRules->search->delete;
-    my $rule = Koha::IssuingRule->new(
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
         {
             categorycode => '*',
             itemtype     => '*',
             branchcode   => '*',
-            issuelength  => 1,
-            firstremind  => 0,        # 0 day of grace
-            finedays     => 2,        # 2 days of fine per day of overdue
-            suspension_chargeperiod => 1,
-            lengthunit   => 'days',
+            rules        => {
+                issuelength => 1,
+                firstremind => 0,    # 0 day of grace
+                finedays    => 2,    # 2 days of fine per day of overdue
+                suspension_chargeperiod => 1,
+                lengthunit              => 'days',
+            }
         }
     );
-    $rule->store();
 
-    my $five_days_ago = dt_from_string->subtract( days => 5 );
+    my $now = dt_from_string;
+    my $five_days_ago = $now->clone->subtract( days => 5 );
     # We want to charge 2 days every day, without grace
     # With 5 days of overdue: 5 * Z
-    my $expected_expiration = dt_from_string->add( days => ( 5 * 2 ) / 1 );
+    my $expected_expiration = $now->clone->add( days => ( 5 * 2 ) / 1 );
     test_debarment_on_checkout(
         {
             item            => $item_1,
@@ -1840,10 +2339,51 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
         }
     );
 
+    # Same with undef firstremind
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => '*',
+            itemtype     => '*',
+            branchcode   => '*',
+            rules        => {
+                issuelength => 1,
+                firstremind => undef,    # 0 day of grace
+                finedays    => 2,    # 2 days of fine per day of overdue
+                suspension_chargeperiod => 1,
+                lengthunit              => 'days',
+            }
+        }
+    );
+    {
+    my $now = dt_from_string;
+    my $five_days_ago = $now->clone->subtract( days => 5 );
+    # We want to charge 2 days every day, without grace
+    # With 5 days of overdue: 5 * Z
+    my $expected_expiration = $now->clone->add( days => ( 5 * 2 ) / 1 );
+    test_debarment_on_checkout(
+        {
+            item            => $item_1,
+            library         => $library,
+            patron          => $patron,
+            due_date        => $five_days_ago,
+            expiration_date => $expected_expiration,
+        }
+    );
+    }
     # We want to charge 2 days every 2 days, without grace
     # With 5 days of overdue: (5 * 2) / 2
-    $rule->suspension_chargeperiod(2)->store;
-    $expected_expiration = dt_from_string->add( days => floor( 5 * 2 ) / 2 );
+    Koha::CirculationRules->set_rule(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rule_name    => 'suspension_chargeperiod',
+            rule_value   => '2',
+        }
+    );
+
+    $expected_expiration = $now->clone->add( days => floor( 5 * 2 ) / 2 );
     test_debarment_on_checkout(
         {
             item            => $item_1,
@@ -1856,9 +2396,18 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
 
     # We want to charge 2 days every 3 days, with 1 day of grace
     # With 5 days of overdue: ((5-1) / 3 ) * 2
-    $rule->suspension_chargeperiod(3)->store;
-    $rule->firstremind(1)->store;
-    $expected_expiration = dt_from_string->add( days => floor( ( ( 5 - 1 ) / 3 ) * 2 ) );
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                suspension_chargeperiod => 3,
+                firstremind             => 1,
+            }
+        }
+    );
+    $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 1 ) / 3 ) * 2 ) );
     test_debarment_on_checkout(
         {
             item            => $item_1,
@@ -1871,15 +2420,24 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
 
     # Use finesCalendar to know if holiday must be skipped to calculate the due date
     # We want to charge 2 days every days, with 0 day of grace (to not burn brains)
-    $rule->finedays(2)->store;
-    $rule->suspension_chargeperiod(1)->store;
-    $rule->firstremind(0)->store;
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                finedays                => 2,
+                suspension_chargeperiod => 1,
+                firstremind             => 0,
+            }
+        }
+    );
     t::lib::Mocks::mock_preference('finesCalendar', 'noFinesWhenClosed');
     t::lib::Mocks::mock_preference('SuspensionsCalendar', 'noSuspensionsWhenClosed');
 
     # Adding a holiday 2 days ago
     my $calendar = C4::Calendar->new(branchcode => $library->{branchcode});
-    my $two_days_ago = dt_from_string->subtract( days => 2 );
+    my $two_days_ago = $now->clone->subtract( days => 2 );
     $calendar->insert_single_holiday(
         day             => $two_days_ago->day,
         month           => $two_days_ago->month,
@@ -1888,7 +2446,7 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
         description     => 'holidayDesc 2 days ago'
     );
     # With 5 days of overdue, only 4 (x finedays=2) days must charged (one was an holiday)
-    $expected_expiration = dt_from_string->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) );
+    $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) );
     test_debarment_on_checkout(
         {
             item            => $item_1,
@@ -1900,7 +2458,7 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
     );
 
     # Adding a holiday 2 days ahead, with finesCalendar=noFinesWhenClosed it should be skipped
-    my $two_days_ahead = dt_from_string->add( days => 2 );
+    my $two_days_ahead = $now->clone->add( days => 2 );
     $calendar->insert_single_holiday(
         day             => $two_days_ahead->day,
         month           => $two_days_ahead->month,
@@ -1910,7 +2468,7 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
     );
 
     # Same as above, but we should skip D+2
-    $expected_expiration = dt_from_string->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) + 1 );
+    $expected_expiration = $now->clone->add( days => floor( ( ( 5 - 0 - 1 ) / 1 ) * 2 ) + 1 );
     test_debarment_on_checkout(
         {
             item            => $item_1,
@@ -1946,10 +2504,22 @@ subtest 'AddReturn + suspension_chargeperiod' => sub {
             item            => $item_1,
             library         => $library,
             patron          => $patron,
-            return_date     => dt_from_string->add(days => 5),
-            expiration_date => dt_from_string->add(days => 5 + (5 * 2 - 1) ),
+            return_date     => $now->clone->add(days => 5),
+            expiration_date => $now->clone->add(days => 5 + (5 * 2 - 1) ),
+        }
+    );
+
+    test_debarment_on_checkout(
+        {
+            item            => $item_1,
+            library         => $library,
+            patron          => $patron,
+            due_date        => $now->clone->add(days => 1),
+            return_date     => $now->clone->add(days => 5),
+            expiration_date => $now->clone->add(days => 5 + (4 * 2 - 1) ),
         }
     );
+
 };
 
 subtest 'CanBookBeIssued + AutoReturnCheckedOutItems' => sub {
@@ -1981,17 +2551,17 @@ subtest 'CanBookBeIssued + AutoReturnCheckedOutItems' => sub {
         {
             library      => $library->branchcode,
         }
-    )->unblessed;
+    );
 
     my ( $error, $question, $alerts );
-    my $issue = AddIssue( $patron1->unblessed, $item->{barcode} );
+    my $issue = AddIssue( $patron1->unblessed, $item->barcode );
 
     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 0);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->barcode );
     is( $question->{ISSUED_TO_ANOTHER}, 1, 'ISSUED_TO_ANOTHER question flag should be set if AutoReturnCheckedOutItems is disabled and item is checked out to another' );
 
     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 1);
-    ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->{barcode} );
+    ( $error, $question, $alerts ) = CanBookBeIssued( $patron2, $item->barcode );
     is( $alerts->{RETURNED_FROM_ANOTHER}->{patron}->borrowernumber, $patron1->borrowernumber, 'RETURNED_FROM_ANOTHER alert flag should be set if AutoReturnCheckedOutItems is enabled and item is checked out to another' );
 
     t::lib::Mocks::mock_preference('AutoReturnCheckedOutItems', 0);
@@ -1999,8 +2569,9 @@ subtest 'CanBookBeIssued + AutoReturnCheckedOutItems' => sub {
 
 
 subtest 'AddReturn | is_overdue' => sub {
-    plan tests => 5;
+    plan tests => 9;
 
+    t::lib::Mocks::mock_preference('MarkLostItemsAsReturned', 'batchmod|moredetail|cronjob|additem|pendingreserves|onpayment');
     t::lib::Mocks::mock_preference('CalculateFinesOnReturn', 1);
     t::lib::Mocks::mock_preference('finesMode', 'production');
     t::lib::Mocks::mock_preference('MaxFine', '100');
@@ -2015,57 +2586,59 @@ subtest 'AddReturn | is_overdue' => sub {
             library      => $library->{branchcode},
             replacementprice => 7
         }
-    )->unblessed;
+    );
 
-    Koha::IssuingRules->search->delete;
-    my $rule = Koha::IssuingRule->new(
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
         {
-            categorycode => '*',
-            itemtype     => '*',
-            branchcode   => '*',
-            issuelength  => 6,
-            lengthunit   => 'days',
-            fine         => 1, # Charge 1 every day of overdue
-            chargeperiod => 1,
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                issuelength  => 6,
+                lengthunit   => 'days',
+                fine         => 1,        # Charge 1 every day of overdue
+                chargeperiod => 1,
+            }
         }
     );
-    $rule->store();
 
     my $now   = dt_from_string;
-    my $one_day_ago   = dt_from_string->subtract( days => 1 );
-    my $five_days_ago = dt_from_string->subtract( days => 5 );
-    my $ten_days_ago  = dt_from_string->subtract( days => 10 );
+    my $one_day_ago   = $now->clone->subtract( days => 1 );
+    my $two_days_ago  = $now->clone->subtract( days => 2 );
+    my $five_days_ago = $now->clone->subtract( days => 5 );
+    my $ten_days_ago  = $now->clone->subtract( days => 10 );
     $patron = Koha::Patrons->find( $patron->{borrowernumber} );
 
     # No return date specified, today will be used => 10 days overdue charged
-    AddIssue( $patron->unblessed, $item->{barcode}, $ten_days_ago ); # date due was 10d ago
-    AddReturn( $item->{barcode}, $library->{branchcode} );
+    AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
+    AddReturn( $item->barcode, $library->{branchcode} );
     is( int($patron->account->balance()), 10, 'Patron should have a charge of 10 (10 days x 1)' );
     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
 
     # specify return date 5 days before => no overdue charged
-    AddIssue( $patron->unblessed, $item->{barcode}, $five_days_ago ); # date due was 5d ago
-    AddReturn( $item->{barcode}, $library->{branchcode}, undef, $ten_days_ago );
+    AddIssue( $patron->unblessed, $item->barcode, $five_days_ago ); # date due was 5d ago
+    AddReturn( $item->barcode, $library->{branchcode}, undef, $ten_days_ago );
     is( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
 
     # specify return date 5 days later => 5 days overdue charged
-    AddIssue( $patron->unblessed, $item->{barcode}, $ten_days_ago ); # date due was 10d ago
-    AddReturn( $item->{barcode}, $library->{branchcode}, undef, $five_days_ago );
+    AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
+    AddReturn( $item->barcode, $library->{branchcode}, undef, $five_days_ago );
     is( int($patron->account->balance()), 5, 'AddReturn: pass return_date => overdue' );
     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
 
     # specify return date 5 days later, specify exemptfine => no overdue charge
-    AddIssue( $patron->unblessed, $item->{barcode}, $ten_days_ago ); # date due was 10d ago
-    AddReturn( $item->{barcode}, $library->{branchcode}, 1, $five_days_ago );
+    AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago ); # date due was 10d ago
+    AddReturn( $item->barcode, $library->{branchcode}, 1, $five_days_ago );
     is( int($patron->account->balance()), 0, 'AddReturn: pass return_date => no overdue' );
     Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
 
-    subtest 'bug 22877' => sub {
+    subtest 'bug 22877 | Lost item return' => sub {
 
         plan tests => 3;
 
-        my $issue = AddIssue( $patron->unblessed, $item->{barcode}, $ten_days_ago );    # date due was 10d ago
+        my $issue = AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago );    # date due was 10d ago
 
         # Fake fines cronjob on this checkout
         my ($fine) =
@@ -2074,7 +2647,7 @@ subtest 'AddReturn | is_overdue' => sub {
         UpdateFine(
             {
                 issue_id       => $issue->issue_id,
-                itemnumber     => $item->{itemnumber},
+                itemnumber     => $item->itemnumber,
                 borrowernumber => $patron->borrowernumber,
                 amount         => $fine,
                 due            => output_pref($ten_days_ago)
@@ -2084,409 +2657,686 @@ subtest 'AddReturn | is_overdue' => sub {
             10, "Overdue fine of 10 days overdue" );
 
         # Fake longoverdue with charge and not marking returned
-        LostItem( $item->{itemnumber}, 'cronjob', 0 );
+        LostItem( $item->itemnumber, 'cronjob', 0 );
         is( int( $patron->account->balance() ),
             17, "Lost fine of 7 plus 10 days overdue" );
 
         # Now we return it today
-        AddReturn( $item->{barcode}, $library->{branchcode} );
+        AddReturn( $item->barcode, $library->{branchcode} );
         is( int( $patron->account->balance() ),
             17, "Should have a single 10 days overdue fine and lost charge" );
-      }
-};
 
-subtest '_FixAccountForLostAndReturned' => sub {
+        # Cleanup
+        Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
+    };
 
-    plan tests => 5;
+    subtest 'bug 8338 | backdated return resulting in zero amount fine' => sub {
 
-    t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 );
-    t::lib::Mocks::mock_preference( 'WhenLostForgiveFine',          0 );
+        plan tests => 17;
 
-    my $processfee_amount  = 20;
-    my $replacement_amount = 99.00;
-    my $item_type          = $builder->build_object(
-        {   class => 'Koha::ItemTypes',
-            value => {
-                notforloan         => undef,
-                rentalcharge       => 0,
-                defaultreplacecost => undef,
-                processfee         => $processfee_amount,
-                rentalcharge_daily => 0,
-            }
-        }
-    );
-    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
+        t::lib::Mocks::mock_preference('CalculateFinesOnBackdate', 1);
 
-    my $biblio = $builder->build_sample_biblio({ author => 'Hall, Daria' });
+        my $issue = AddIssue( $patron->unblessed, $item->barcode, $one_day_ago );    # date due was 1d ago
 
-    subtest 'Full write-off tests' => sub {
+        # Fake fines cronjob on this checkout
+        my ($fine) =
+          CalcFine( $item, $patron->categorycode, $library->{branchcode},
+            $one_day_ago, $now );
+        UpdateFine(
+            {
+                issue_id       => $issue->issue_id,
+                itemnumber     => $item->itemnumber,
+                borrowernumber => $patron->borrowernumber,
+                amount         => $fine,
+                due            => output_pref($one_day_ago)
+            }
+        );
+        is( int( $patron->account->balance() ),
+            1, "Overdue fine of 1 day overdue" );
 
-        plan tests => 12;
+        # Backdated return (dropbox mode example - charge should be removed)
+        AddReturn( $item->barcode, $library->{branchcode}, 1, $one_day_ago );
+        is( int( $patron->account->balance() ),
+            0, "Overdue fine should be annulled" );
+        my $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
+        is( $lines->count, 0, "Overdue fine accountline has been removed");
 
-        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
-        my $manager = $builder->build_object({ class => "Koha::Patrons" });
-        t::lib::Mocks::mock_userenv({ patron => $manager,branchcode => $manager->branchcode });
+        $issue = AddIssue( $patron->unblessed, $item->barcode, $two_days_ago );    # date due was 2d ago
 
-        my $item = $builder->build_sample_item(
+        # Fake fines cronjob on this checkout
+        ($fine) =
+          CalcFine( $item, $patron->categorycode, $library->{branchcode},
+            $two_days_ago, $now );
+        UpdateFine(
             {
-                biblionumber     => $biblio->biblionumber,
-                library          => $library->branchcode,
-                replacementprice => $replacement_amount,
-                itype            => $item_type->itemtype,
+                issue_id       => $issue->issue_id,
+                itemnumber     => $item->itemnumber,
+                borrowernumber => $patron->borrowernumber,
+                amount         => $fine,
+                due            => output_pref($one_day_ago)
             }
         );
+        is( int( $patron->account->balance() ),
+            2, "Overdue fine of 2 days overdue" );
 
-        AddIssue( $patron->unblessed, $item->barcode );
-
-        # Simulate item marked as lost
-        ModItem( { itemlost => 3 }, $biblio->biblionumber, $item->itemnumber );
-        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 amountoutstanding is generated' );
-        is( $lost_fee_line->status,
-            undef, 'The LOST status was not set' );
-
-        my $account = $patron->account;
-        my $debts   = $account->outstanding_debits;
-
-        # Write off the debt
-        my $credit = $account->add_credit(
-            {   amount => $account->balance,
-                type   => 'WRITEOFF',
+        # Payment made against fine
+        $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
+        my $debit = $lines->next;
+        my $credit = $patron->account->add_credit(
+            {
+                amount    => 2,
+                type      => 'PAYMENT',
                 interface => 'test',
             }
         );
-        $credit->apply( { debits => [ $debts->as_list ], offset_type => 'Writeoff' } );
+        $credit->apply(
+            { debits => [ $debit ], offset_type => 'Payment' } );
 
-        my $credit_return_id = C4::Circulation::_FixAccountForLostAndReturned( $item->itemnumber, $patron->id );
-        is( $credit_return_id, undef, 'No LOST_RETURN account line added' );
-
-        $lost_fee_line->discard_changes; # reload from DB
-        is( $lost_fee_line->amountoutstanding + 0, 0, 'Lost fee has no outstanding amount' );
-        is( $lost_fee_line->debit_type_code,
-            'LOST', 'Lost fee now still has account type of LOST' );
-        is( $lost_fee_line->status, 'RETURNED', "Lost fee now has account status of RETURNED");
-
-        is( $patron->account->balance, -0, 'The patron balance is 0, everything was written off' );
+        is( int( $patron->account->balance() ),
+            0, "Overdue fine should be paid off" );
+        $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber });
+        is ( $lines->count, 2, "Overdue (debit) and Payment (credit) present");
+        my $line = $lines->next;
+        is( $line->amount+0, 2, "Overdue fine amount remains as 2 days");
+        is( $line->amountoutstanding+0, 0, "Overdue fine amountoutstanding reduced to 0");
+
+        # Backdated return (dropbox mode example - charge should be removed)
+        AddReturn( $item->barcode, $library->{branchcode}, undef, $one_day_ago );
+        is( int( $patron->account->balance() ),
+            -1, "Refund credit has been applied" );
+        $lines = Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber }, { order_by => { '-asc' => 'accountlines_id' }});
+        is( $lines->count, 3, "Overdue (debit), Payment (credit) and Refund (credit) are all present");
+
+        $line = $lines->next;
+        is($line->amount+0,1, "Overdue fine amount has been reduced to 1");
+        is($line->amountoutstanding+0,0, "Overdue fine amount outstanding remains at 0");
+        is($line->status,'RETURNED', "Overdue fine is fixed");
+        $line = $lines->next;
+        is($line->amount+0,-2, "Original payment amount remains as 2");
+        is($line->amountoutstanding+0,0, "Original payment remains applied");
+        $line = $lines->next;
+        is($line->amount+0,-1, "Refund amount correctly set to 1");
+        is($line->amountoutstanding+0,-1, "Refund amount outstanding unspent");
+
+        # Cleanup
+        Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
     };
 
-    subtest 'Full payment tests' => sub {
+    subtest 'bug 25417 | backdated return + exemptfine' => sub {
 
-        plan tests => 13;
+        plan tests => 2;
 
-        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
+        t::lib::Mocks::mock_preference('CalculateFinesOnBackdate', 1);
 
-        my $item = $builder->build_sample_item(
-            {
-                biblionumber     => $biblio->biblionumber,
-                library          => $library->branchcode,
-                replacementprice => $replacement_amount,
-                itype            => $item_type->itemtype
-            }
-        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode, $one_day_ago );    # date due was 1d ago
 
-        AddIssue( $patron->unblessed, $item->barcode );
-
-        # Simulate item marked as lost
-        ModItem( { itemlost => 1 }, $biblio->biblionumber, $item->itemnumber );
-        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;
-
-        # Write off the debt
-        my $credit = $account->add_credit(
-            {   amount => $account->balance,
-                type   => 'PAYMENT',
-                interface => 'test',
+        # Fake fines cronjob on this checkout
+        my ($fine) =
+          CalcFine( $item, $patron->categorycode, $library->{branchcode},
+            $one_day_ago, $now );
+        UpdateFine(
+            {
+                issue_id       => $issue->issue_id,
+                itemnumber     => $item->itemnumber,
+                borrowernumber => $patron->borrowernumber,
+                amount         => $fine,
+                due            => output_pref($one_day_ago)
             }
         );
-        $credit->apply( { debits => [ $debts->as_list ], offset_type => 'Payment' } );
-
-        my $credit_return_id = C4::Circulation::_FixAccountForLostAndReturned( $item->itemnumber, $patron->id );
-        my $credit_return = Koha::Account::Lines->find($credit_return_id);
-
-        is( $credit_return->credit_type_code, 'LOST_RETURN', 'An account line of type LOST_RETURN is added' );
-        is( $credit_return->amount + 0,
-            -99.00, 'The account line of type LOST_RETURN has an amount of -99' );
-        is( $credit_return->amountoutstanding + 0,
-            -99.00, 'The account line of type LOST_RETURN has an amountoutstanding of -99' );
+        is( int( $patron->account->balance() ),
+            1, "Overdue fine of 1 day overdue" );
 
-        $lost_fee_line->discard_changes;
-        is( $lost_fee_line->amountoutstanding + 0, 0, 'Lost fee has no outstanding amount' );
-        is( $lost_fee_line->debit_type_code,
-            'LOST', 'Lost fee now still has account type of LOST' );
-        is( $lost_fee_line->status, 'RETURNED', "Lost fee now has account status of RETURNED");
+        # Backdated return (dropbox mode example - charge should no longer exist)
+        AddReturn( $item->barcode, $library->{branchcode}, 1, $one_day_ago );
+        is( int( $patron->account->balance() ),
+            0, "Overdue fine should be annulled" );
 
-        is( $patron->account->balance,
-            -99, 'The patron balance is -99, a credit that equals the lost fee payment' );
+        # Cleanup
+        Koha::Account::Lines->search({ borrowernumber => $patron->borrowernumber })->delete;
     };
 
-    subtest 'Test without payment or write off' => sub {
+    subtest 'bug 24075 | backdated return with return datetime matching due datetime' => sub {
+        plan tests => 7;
 
-        plan tests => 13;
+        t::lib::Mocks::mock_preference( 'CalculateFinesOnBackdate', 1 );
 
-        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
+        my $due_date = dt_from_string;
+        my $issue = AddIssue( $patron->unblessed, $item->barcode, $due_date );
 
-        my $item = $builder->build_sample_item(
+        # Add fine
+        UpdateFine(
             {
-                biblionumber     => $biblio->biblionumber,
-                library          => $library->branchcode,
-                replacementprice => 23.00,
-                replacementprice => $replacement_amount,
-                itype            => $item_type->itemtype
+                issue_id       => $issue->issue_id,
+                itemnumber     => $item->itemnumber,
+                borrowernumber => $patron->borrowernumber,
+                amount         => 0.25,
+                due            => output_pref($due_date)
             }
         );
+        is( $patron->account->balance(),
+            0.25, 'Overdue fine of $0.25 recorded' );
 
-        AddIssue( $patron->unblessed, $item->barcode );
-
-        # Simulate item marked as lost
-        ModItem( { itemlost => 3 }, $biblio->biblionumber, $item->itemnumber );
-        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 $credit_return_id = C4::Circulation::_FixAccountForLostAndReturned( $item->itemnumber, $patron->id );
-        my $credit_return = Koha::Account::Lines->find($credit_return_id);
-
-        is( $credit_return->credit_type_code, 'LOST_RETURN', 'An account line of type LOST_RETURN is added' );
-        is( $credit_return->amount + 0, -99.00, 'The account line of type LOST_RETURN has an amount of -99' );
-        is( $credit_return->amountoutstanding + 0, 0, 'The account line of type LOST_RETURN has an amountoutstanding of 0' );
-
-        $lost_fee_line->discard_changes;
-        is( $lost_fee_line->amountoutstanding + 0, 0, 'Lost fee has no outstanding amount' );
-        is( $lost_fee_line->debit_type_code,
-            'LOST', 'Lost fee now still has account type of LOST' );
-        is( $lost_fee_line->status, 'RETURNED', "Lost fee now has account status of RETURNED");
-
-        is( $patron->account->balance, 20, 'The patron balance is 20, still owes the processing fee' );
-    };
+        # Backdate return to exact due date and time
+        my ( undef, $message ) =
+          AddReturn( $item->barcode, $library->{branchcode},
+            undef, $due_date );
 
-    subtest 'Test with partial payement and write off, and remaining debt' => sub {
+        my $accountline =
+          Koha::Account::Lines->find( { issue_id => $issue->id } );
+        ok( !$accountline, 'accountline removed as expected' );
 
-        plan tests => 16;
+        # Re-issue
+        $issue = AddIssue( $patron->unblessed, $item->barcode, $due_date );
 
-        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
-        my $item = $builder->build_sample_item(
+        # Add fine
+        UpdateFine(
             {
-                biblionumber     => $biblio->biblionumber,
-                library          => $library->branchcode,
-                replacementprice => $replacement_amount,
-                itype            => $item_type->itemtype
+                issue_id       => $issue->issue_id,
+                itemnumber     => $item->itemnumber,
+                borrowernumber => $patron->borrowernumber,
+                amount         => .25,
+                due            => output_pref($due_date)
             }
         );
+        is( $patron->account->balance(),
+            0.25, 'Overdue fine of $0.25 recorded' );
 
-        AddIssue( $patron->unblessed, $item->barcode );
-
-        # Simulate item marked as lost
-        ModItem( { itemlost => 1 }, $biblio->biblionumber, $item->itemnumber );
-        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;
-        is( $account->balance, $processfee_amount + $replacement_amount, 'Balance is PROCESSING + L' );
-
-        # Partially pay fee
-        my $payment_amount = 27;
-        my $payment        = $account->add_credit(
-            {   amount => $payment_amount,
-                type   => 'PAYMENT',
-                interface => 'test',
+        # Partial pay accruing fine
+        my $lines = Koha::Account::Lines->search(
+            {
+                borrowernumber => $patron->borrowernumber,
+                issue_id       => $issue->id
             }
         );
-
-        $payment->apply( { debits => [ $lost_fee_line ], offset_type => 'Payment' } );
-
-        # Partially write off fee
-        my $write_off_amount = 25;
-        my $write_off        = $account->add_credit(
-            {   amount => $write_off_amount,
-                type   => 'WRITEOFF',
+        my $debit  = $lines->next;
+        my $credit = $patron->account->add_credit(
+            {
+                amount    => .20,
+                type      => 'PAYMENT',
                 interface => 'test',
             }
         );
-        $write_off->apply( { debits => [ $lost_fee_line ], offset_type => 'Writeoff' } );
-
-        is( $account->balance,
-            $processfee_amount + $replacement_amount - $payment_amount - $write_off_amount,
-            'Payment and write off applied'
-        );
-
-        # Store the amountoutstanding value
-        $lost_fee_line->discard_changes;
-        my $outstanding = $lost_fee_line->amountoutstanding;
-
-        my $credit_return_id = C4::Circulation::_FixAccountForLostAndReturned( $item->itemnumber, $patron->id );
-        my $credit_return = Koha::Account::Lines->find($credit_return_id);
-
-        is( $account->balance, $processfee_amount - $payment_amount, 'Balance is PROCESSING - PAYMENT (LOST_RETURN)' );
+        $credit->apply( { debits => [$debit], offset_type => 'Payment' } );
 
-        $lost_fee_line->discard_changes;
-        is( $lost_fee_line->amountoutstanding + 0, 0, 'Lost fee has no outstanding amount' );
-        is( $lost_fee_line->debit_type_code,
-            'LOST', 'Lost fee now still has account type of LOST' );
-        is( $lost_fee_line->status, 'RETURNED', "Lost fee now has account status of RETURNED");
+        is( $patron->account->balance(), .05, 'Overdue fine reduced to $0.05' );
 
-        is( $credit_return->credit_type_code, 'LOST_RETURN', 'An account line of type LOST_RETURN is added' );
-        is( $credit_return->amount + 0,
-            ($payment_amount + $outstanding ) * -1,
-            'The account line of type LOST_RETURN has an amount equal to the payment + outstanding'
-        );
-        is( $credit_return->amountoutstanding + 0,
-            $payment_amount * -1,
-            'The account line of type LOST_RETURN has an amountoutstanding equal to the payment'
-        );
+        # Backdate return to exact due date and time
+        ( undef, $message ) =
+          AddReturn( $item->barcode, $library->{branchcode},
+            undef, $due_date );
 
-        is( $account->balance,
-            $processfee_amount - $payment_amount,
-            'The patron balance is the difference between the PROCESSING and the credit'
+        $lines = Koha::Account::Lines->search(
+            {
+                borrowernumber => $patron->borrowernumber,
+                issue_id       => $issue->id
+            }
         );
+        $accountline = $lines->next;
+        is( $accountline->amountoutstanding + 0,
+            0, 'Partially paid fee amount outstanding was reduced to 0' );
+        is( $accountline->amount + 0,
+            0, 'Partially paid fee amount was reduced to 0' );
+        is( $patron->account->balance(), -0.20, 'Patron refund recorded' );
+
+        # Cleanup
+        Koha::Account::Lines->search(
+            { borrowernumber => $patron->borrowernumber } )->delete;
     };
 
-    subtest 'Partial payement, existing debits and AccountAutoReconcile' => sub {
-
-        plan tests => 8;
+    subtest 'enh 23091 | Lost item return policies' => sub {
+        plan tests => 4;
 
-        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
-        my $barcode = 'KD123456793';
-        my $replacement_amount = 100;
-        my $processfee_amount  = 20;
+        my $manager = $builder->build_object({ class => "Koha::Patrons" });
 
-        my $item_type          = $builder->build_object(
-            {   class => 'Koha::ItemTypes',
-                value => {
-                    notforloan         => undef,
-                    rentalcharge       => 0,
-                    defaultreplacecost => undef,
-                    processfee         => 0,
-                    rentalcharge_daily => 0,
+        my $branchcode_false =
+          $builder->build( { source => 'Branch' } )->{branchcode};
+        my $specific_rule_false = $builder->build(
+            {
+                source => 'CirculationRule',
+                value  => {
+                    branchcode   => $branchcode_false,
+                    categorycode => undef,
+                    itemtype     => undef,
+                    rule_name    => 'lostreturn',
+                    rule_value   => 0
                 }
             }
         );
-        my ( undef, undef, $item_id ) = AddItem(
-            {   homebranch       => $library->branchcode,
-                holdingbranch    => $library->branchcode,
-                barcode          => $barcode,
-                replacementprice => $replacement_amount,
-                itype            => $item_type->itemtype
-            },
-            $biblio->biblionumber
+        my $branchcode_refund =
+          $builder->build( { source => 'Branch' } )->{branchcode};
+        my $specific_rule_refund = $builder->build(
+            {
+                source => 'CirculationRule',
+                value  => {
+                    branchcode   => $branchcode_refund,
+                    categorycode => undef,
+                    itemtype     => undef,
+                    rule_name    => 'lostreturn',
+                    rule_value   => 'refund'
+                }
+            }
         );
-
-        AddIssue( $patron->unblessed, $barcode );
-
-        # Simulate item marked as lost
-        ModItem( { itemlost => 1 }, $biblio->biblionumber, $item_id );
-        LostItem( $item_id, 1 );
-
-        my $lost_fee_lines = Koha::Account::Lines->search(
-            { borrowernumber => $patron->id, itemnumber => $item_id, 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;
-        is( $account->balance, $replacement_amount, 'Balance is L' );
-
-        # Partially pay fee
-        my $payment_amount = 27;
-        my $payment        = $account->add_credit(
-            {   amount => $payment_amount,
-                type   => 'PAYMENT',
-                interface => 'test',
+        my $branchcode_restore =
+          $builder->build( { source => 'Branch' } )->{branchcode};
+        my $specific_rule_restore = $builder->build(
+            {
+                source => 'CirculationRule',
+                value  => {
+                    branchcode   => $branchcode_restore,
+                    categorycode => undef,
+                    itemtype     => undef,
+                    rule_name    => 'lostreturn',
+                    rule_value   => 'restore'
+                }
             }
         );
-        $payment->apply({ debits => [ $lost_fee_line ], offset_type => 'Payment' });
-
-        is( $account->balance,
-            $replacement_amount - $payment_amount,
-            'Payment applied'
+        my $branchcode_charge =
+          $builder->build( { source => 'Branch' } )->{branchcode};
+        my $specific_rule_charge = $builder->build(
+            {
+                source => 'CirculationRule',
+                value  => {
+                    branchcode   => $branchcode_charge,
+                    categorycode => undef,
+                    itemtype     => undef,
+                    rule_name    => 'lostreturn',
+                    rule_value   => 'charge'
+                }
+            }
         );
 
-        my $manual_debit_amount = 80;
-        $account->add_debit( { amount => $manual_debit_amount, type => 'OVERDUE', interface =>'test' } );
-
-        is( $account->balance, $manual_debit_amount + $replacement_amount - $payment_amount, 'Manual debit applied' );
+        my $replacement_amount = 99.00;
+        t::lib::Mocks::mock_preference( 'AllowReturnToBranch', 'anywhere' );
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee', 1 );
+        t::lib::Mocks::mock_preference( 'WhenLostForgiveFine',          0 );
+        t::lib::Mocks::mock_preference( 'BlockReturnOfLostItems',       0 );
+        t::lib::Mocks::mock_preference( 'RefundLostOnReturnControl',
+            'CheckinLibrary' );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge',
+            undef );
 
-        t::lib::Mocks::mock_preference( 'AccountAutoReconcile', 1 );
+        subtest 'lostreturn | false' => sub {
+            plan tests => 12;
 
-        my $credit_return_id = C4::Circulation::_FixAccountForLostAndReturned( $item_id, $patron->id );
-        my $credit_return = Koha::Account::Lines->find($credit_return_id);
+            t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_false });
 
-        is( $account->balance, $manual_debit_amount - $payment_amount, 'Balance is PROCESSING - payment (LOST_RETURN)' );
-
-        my $manual_debit = Koha::Account::Lines->search({ borrowernumber => $patron->id, debit_type_code => 'OVERDUE', status => 'UNRETURNED' })->next;
-        is( $manual_debit->amountoutstanding + 0, $manual_debit_amount - $payment_amount, 'reconcile_balance was called' );
+            my $item = $builder->build_sample_item(
+                {
+                    replacementprice => $replacement_amount
+                }
+            );
+
+            # Issue the item
+            my $issue = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago );
+
+            # Fake fines cronjob on this checkout
+            my ($fine) =
+              CalcFine( $item, $patron->categorycode, $library->{branchcode},
+                $ten_days_ago, $now );
+            UpdateFine(
+                {
+                    issue_id       => $issue->issue_id,
+                    itemnumber     => $item->itemnumber,
+                    borrowernumber => $patron->borrowernumber,
+                    amount         => $fine,
+                    due            => output_pref($ten_days_ago)
+                }
+            );
+            my $overdue_fees = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'OVERDUE'
+                }
+            );
+            is( $overdue_fees->count, 1, 'Overdue item fee produced' );
+            my $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                10, 'The right OVERDUE amount is generated' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The right OVERDUE amountoutstanding is generated' );
+
+            # Simulate item marked as lost
+            $item->itemlost(3)->store;
+            C4::Circulation::LostItem( $item->itemnumber, 1 );
+
+            my $lost_fee_lines = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'LOST'
+                }
+            );
+            is( $lost_fee_lines->count, 1, '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 amountoutstanding is generated' );
+            is( $lost_fee_line->status, undef, 'The LOST status was not set' );
+
+            # Return lost item
+            my ( $returned, $message ) =
+              AddReturn( $item->barcode, $branchcode_false, undef, $five_days_ago );
+
+            $overdue_fee->discard_changes;
+            is( $overdue_fee->amount + 0,
+                10, 'The OVERDUE amount is left intact' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The OVERDUE amountoutstanding is left intact' );
+
+            $lost_fee_line->discard_changes;
+            is( $lost_fee_line->amount + 0,
+                $replacement_amount, 'The LOST amount is left intact' );
+            is( $lost_fee_line->amountoutstanding + 0,
+                $replacement_amount,
+                'The LOST amountoutstanding is left intact' );
+            # FIXME: Should we set the LOST fee status to 'FOUND' regardless of whether we're refunding or not?
+            is( $lost_fee_line->status, undef, 'The LOST status was not set' );
+        };
+
+        subtest 'lostreturn | refund' => sub {
+            plan tests => 12;
+
+            t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_refund });
+
+            my $item = $builder->build_sample_item(
+                {
+                    replacementprice => $replacement_amount
+                }
+            );
+
+            # Issue the item
+            my $issue = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago );
+
+            # Fake fines cronjob on this checkout
+            my ($fine) =
+              CalcFine( $item, $patron->categorycode, $library->{branchcode},
+                $ten_days_ago, $now );
+            UpdateFine(
+                {
+                    issue_id       => $issue->issue_id,
+                    itemnumber     => $item->itemnumber,
+                    borrowernumber => $patron->borrowernumber,
+                    amount         => $fine,
+                    due            => output_pref($ten_days_ago)
+                }
+            );
+            my $overdue_fees = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'OVERDUE'
+                }
+            );
+            is( $overdue_fees->count, 1, 'Overdue item fee produced' );
+            my $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                10, 'The right OVERDUE amount is generated' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The right OVERDUE amountoutstanding is generated' );
+
+            # Simulate item marked as lost
+            $item->itemlost(3)->store;
+            C4::Circulation::LostItem( $item->itemnumber, 1 );
+
+            my $lost_fee_lines = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'LOST'
+                }
+            );
+            is( $lost_fee_lines->count, 1, '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 amountoutstanding is generated' );
+            is( $lost_fee_line->status, undef, 'The LOST status was not set' );
+
+            # Return the lost item
+            my ( undef, $message ) =
+              AddReturn( $item->barcode, $branchcode_refund, undef, $five_days_ago );
+
+            $overdue_fee->discard_changes;
+            is( $overdue_fee->amount + 0,
+                10, 'The OVERDUE amount is left intact' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The OVERDUE amountoutstanding is left intact' );
+
+            $lost_fee_line->discard_changes;
+            is( $lost_fee_line->amount + 0,
+                $replacement_amount, 'The LOST amount is left intact' );
+            is( $lost_fee_line->amountoutstanding + 0,
+                0,
+                'The LOST amountoutstanding is refunded' );
+            is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
+        };
+
+        subtest 'lostreturn | restore' => sub {
+            plan tests => 13;
+
+            t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_restore });
+
+            my $item = $builder->build_sample_item(
+                {
+                    replacementprice => $replacement_amount
+                }
+            );
+
+            # Issue the item
+            my $issue = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode , $ten_days_ago);
+
+            # Fake fines cronjob on this checkout
+            my ($fine) =
+              CalcFine( $item, $patron->categorycode, $library->{branchcode},
+                $ten_days_ago, $now );
+            UpdateFine(
+                {
+                    issue_id       => $issue->issue_id,
+                    itemnumber     => $item->itemnumber,
+                    borrowernumber => $patron->borrowernumber,
+                    amount         => $fine,
+                    due            => output_pref($ten_days_ago)
+                }
+            );
+            my $overdue_fees = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'OVERDUE'
+                }
+            );
+            is( $overdue_fees->count, 1, 'Overdue item fee produced' );
+            my $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                10, 'The right OVERDUE amount is generated' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The right OVERDUE amountoutstanding is generated' );
+
+            # Simulate item marked as lost
+            $item->itemlost(3)->store;
+            C4::Circulation::LostItem( $item->itemnumber, 1 );
+
+            my $lost_fee_lines = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'LOST'
+                }
+            );
+            is( $lost_fee_lines->count, 1, '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 amountoutstanding is generated' );
+            is( $lost_fee_line->status, undef, 'The LOST status was not set' );
+
+            # Simulate refunding overdue fees upon marking item as lost
+            my $overdue_forgive = $patron->account->add_credit(
+                {
+                    amount     => 10.00,
+                    user_id    => $manager->borrowernumber,
+                    library_id => $branchcode_restore,
+                    interface  => 'test',
+                    type       => 'FORGIVEN',
+                    item_id    => $item->itemnumber
+                }
+            );
+            $overdue_forgive->apply(
+                { debits => [$overdue_fee], offset_type => 'Forgiven' } );
+            $overdue_fee->discard_changes;
+            is($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
+
+            # Do nothing
+            my ( undef, $message ) =
+              AddReturn( $item->barcode, $branchcode_restore, undef, $five_days_ago );
+
+            $overdue_fee->discard_changes;
+            is( $overdue_fee->amount + 0,
+                10, 'The OVERDUE amount is left intact' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The OVERDUE amountoutstanding is restored' );
+
+            $lost_fee_line->discard_changes;
+            is( $lost_fee_line->amount + 0,
+                $replacement_amount, 'The LOST amount is left intact' );
+            is( $lost_fee_line->amountoutstanding + 0,
+                0,
+                'The LOST amountoutstanding is refunded' );
+            is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
+        };
+
+        subtest 'lostreturn | charge' => sub {
+            plan tests => 16;
+
+            t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $branchcode_charge });
+
+            my $item = $builder->build_sample_item(
+                {
+                    replacementprice => $replacement_amount
+                }
+            );
+
+            # Issue the item
+            my $issue = C4::Circulation::AddIssue( $patron->unblessed, $item->barcode, $ten_days_ago );
+
+            # Fake fines cronjob on this checkout
+            my ($fine) =
+              CalcFine( $item, $patron->categorycode, $library->{branchcode},
+                $ten_days_ago, $now );
+            UpdateFine(
+                {
+                    issue_id       => $issue->issue_id,
+                    itemnumber     => $item->itemnumber,
+                    borrowernumber => $patron->borrowernumber,
+                    amount         => $fine,
+                    due            => output_pref($ten_days_ago)
+                }
+            );
+            my $overdue_fees = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'OVERDUE'
+                }
+            );
+            is( $overdue_fees->count, 1, 'Overdue item fee produced' );
+            my $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                10, 'The right OVERDUE amount is generated' );
+            is( $overdue_fee->amountoutstanding + 0,
+                10,
+                'The right OVERDUE amountoutstanding is generated' );
+
+            # Simulate item marked as lost
+            $item->itemlost(3)->store;
+            C4::Circulation::LostItem( $item->itemnumber, 1 );
+
+            my $lost_fee_lines = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'LOST'
+                }
+            );
+            is( $lost_fee_lines->count, 1, '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 amountoutstanding is generated' );
+            is( $lost_fee_line->status, undef, 'The LOST status was not set' );
+
+            # Simulate refunding overdue fees upon marking item as lost
+            my $overdue_forgive = $patron->account->add_credit(
+                {
+                    amount     => 10.00,
+                    user_id    => $manager->borrowernumber,
+                    library_id => $branchcode_charge,
+                    interface  => 'test',
+                    type       => 'FORGIVEN',
+                    item_id    => $item->itemnumber
+                }
+            );
+            $overdue_forgive->apply(
+                { debits => [$overdue_fee], offset_type => 'Forgiven' } );
+            $overdue_fee->discard_changes;
+            is($overdue_fee->amountoutstanding + 0, 0, 'Overdue fee forgiven');
+
+            # Do nothing
+            my ( undef, $message ) =
+              AddReturn( $item->barcode, $branchcode_charge, undef, $five_days_ago );
+
+            $lost_fee_line->discard_changes;
+            is( $lost_fee_line->amount + 0,
+                $replacement_amount, 'The LOST amount is left intact' );
+            is( $lost_fee_line->amountoutstanding + 0,
+                0,
+                'The LOST amountoutstanding is refunded' );
+            is( $lost_fee_line->status, 'FOUND', 'The LOST status was set to FOUND' );
+
+            $overdue_fees = Koha::Account::Lines->search(
+                {
+                    borrowernumber  => $patron->id,
+                    itemnumber      => $item->itemnumber,
+                    debit_type_code => 'OVERDUE'
+                },
+                {
+                    order_by => { '-asc' => 'accountlines_id'}
+                }
+            );
+            is( $overdue_fees->count, 2, 'A second OVERDUE fee has been added' );
+            $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                10, 'The original OVERDUE amount is left intact' );
+            is( $overdue_fee->amountoutstanding + 0,
+                0,
+                'The original OVERDUE amountoutstanding is left as forgiven' );
+            $overdue_fee = $overdue_fees->next;
+            is( $overdue_fee->amount + 0,
+                5, 'The new OVERDUE amount is correct for the backdated return' );
+            is( $overdue_fee->amountoutstanding + 0,
+                5,
+                'The new OVERDUE amountoutstanding is correct for the backdated return' );
+        };
     };
 };
 
 subtest '_FixOverduesOnReturn' => sub {
-    plan tests => 11;
+    plan tests => 14;
 
     my $manager = $builder->build_object({ class => "Koha::Patrons" });
     t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $manager->branchcode });
@@ -2523,7 +3373,7 @@ subtest '_FixOverduesOnReturn' => sub {
 
     $accountline->_result()->discard_changes();
 
-    is( $accountline->amountoutstanding, '99.000000', 'Fine has the same amount outstanding as previously' );
+    is( $accountline->amountoutstanding+0, 99, 'Fine has the same amount outstanding as previously' );
     isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
     is( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
 
@@ -2543,59 +3393,35 @@ subtest '_FixOverduesOnReturn' => sub {
 
     is( $accountline->amountoutstanding + 0, 0, 'Fine amountoutstanding has been reduced to 0' );
     isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
-    is( $accountline->status, 'FORGIVEN', 'Open fine ( account type OVERDUE ) has been set to fine forgiven ( status FORGIVEN )');
+    is( $accountline->status, 'RETURNED', 'Open fine ( account type OVERDUE ) has been set to returned ( status RETURNED )');
     is( ref $offset, "Koha::Account::Offset", "Found matching offset for fine reduction via forgiveness" );
     is( $offset->amount + 0, -99, "Amount of offset is correct" );
     my $credit = $offset->credit;
     is( ref $credit, "Koha::Account::Line", "Found matching credit for fine forgiveness" );
     is( $credit->amount + 0, -99, "Credit amount is set correctly" );
     is( $credit->amountoutstanding + 0, 0, "Credit amountoutstanding is correctly set to 0" );
-};
-
-subtest '_FixAccountForLostAndReturned returns undef if patron is deleted' => sub {
-    plan tests => 1;
-
-    my $manager = $builder->build_object({ class => "Koha::Patrons" });
-    t::lib::Mocks::mock_userenv({ patron => $manager, branchcode => $manager->branchcode });
-
-    my $biblio = $builder->build_sample_biblio({ author => 'Hall, Kylie' });
-
-    my $branchcode  = $library2->{branchcode};
-
-    my $item = $builder->build_sample_item(
-        {
-            biblionumber     => $biblio->biblionumber,
-            library          => $branchcode,
-            replacementprice => 99.00,
-            itype            => $itemtype,
-        }
-    );
 
-    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
-
-    ## Start with basic call, should just close out the open fine
-    my $accountline = Koha::Account::Line->new(
+    # Bug 25417 - Only forgive fines where there is an amount outstanding to forgive
+    $accountline->set(
         {
-            borrowernumber => $patron->id,
-            debit_type_code    => 'LOST',
-            status         => undef,
-            itemnumber     => $item->itemnumber,
-            amount => 99.00,
-            amountoutstanding => 99.00,
-            interface => 'test',
+            debit_type_code    => 'OVERDUE',
+            status         => 'UNRETURNED',
+            amountoutstanding => 0.00,
         }
     )->store();
+    $offset->delete;
 
-    $patron->delete();
-
-    my $return_value = C4::Circulation::_FixAccountForLostAndReturned( $patron->id, $item->itemnumber );
-
-    is( $return_value, undef, "_FixAccountForLostAndReturned returns undef if patron is deleted" );
+    C4::Circulation::_FixOverduesOnReturn( $patron->{borrowernumber}, $item->itemnumber, 1, 'RETURNED' );
 
+    $accountline->_result()->discard_changes();
+    $offset = Koha::Account::Offsets->search({ debit_id => $accountline->id, type => 'Forgiven' })->next();
+    is( $offset, undef, "No offset created when trying to forgive fine with no outstanding balance" );
+    isnt( $accountline->status, 'UNRETURNED', 'Open fine ( account type OVERDUE ) has been closed out ( status not UNRETURNED )');
+    is( $accountline->status, 'RETURNED', 'Passed status has been used to set as RETURNED )');
 };
 
 subtest 'Set waiting flag' => sub {
-    plan tests => 4;
+    plan tests => 11;
 
     my $library_1 = $builder->build( { source => 'Branch' } );
     my $patron_1  = $builder->build( { source => 'Borrower', value => { branchcode => $library_1->{branchcode}, categorycode => $patron_category->{categorycode} } } );
@@ -2606,103 +3432,149 @@ subtest 'Set waiting flag' => sub {
         {
             library      => $library_1->{branchcode},
         }
-    )->unblessed;
+    );
 
     set_userenv( $library_2 );
     my $reserve_id = AddReserve(
-        $library_2->{branchcode}, $patron_2->{borrowernumber}, $item->{biblionumber},
-        '', 1, undef, undef, '', undef, $item->{itemnumber},
+        {
+            branchcode     => $library_2->{branchcode},
+            borrowernumber => $patron_2->{borrowernumber},
+            biblionumber   => $item->biblionumber,
+            priority       => 1,
+            itemnumber     => $item->itemnumber,
+        }
     );
 
     set_userenv( $library_1 );
     my $do_transfer = 1;
-    my ( $res, $rr ) = AddReturn( $item->{barcode}, $library_1->{branchcode} );
-    ModReserveAffect( $item->{itemnumber}, undef, $do_transfer, $reserve_id );
+    my ( $res, $rr ) = AddReturn( $item->barcode, $library_1->{branchcode} );
+    ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
     my $hold = Koha::Holds->find( $reserve_id );
     is( $hold->found, 'T', 'Hold is in transit' );
 
-    my ( $status ) = CheckReserves($item->{itemnumber});
-    is( $status, 'Reserved', 'Hold is not waiting yet');
+    my ( $status ) = CheckReserves($item->itemnumber);
+    is( $status, 'Transferred', 'Hold is not waiting yet');
 
     set_userenv( $library_2 );
     $do_transfer = 0;
-    AddReturn( $item->{barcode}, $library_2->{branchcode} );
-    ModReserveAffect( $item->{itemnumber}, undef, $do_transfer, $reserve_id );
+    AddReturn( $item->barcode, $library_2->{branchcode} );
+    ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
     $hold = Koha::Holds->find( $reserve_id );
     is( $hold->found, 'W', 'Hold is waiting' );
-    ( $status ) = CheckReserves($item->{itemnumber});
+    ( $status ) = CheckReserves($item->itemnumber);
     is( $status, 'Waiting', 'Now the hold is waiting');
+
+    #Bug 21944 - Waiting transfer checked in at branch other than pickup location
+    set_userenv( $library_1 );
+    (undef, my $messages, undef, undef ) = AddReturn ( $item->barcode, $library_1->{branchcode} );
+    $hold = Koha::Holds->find( $reserve_id );
+    is( $hold->found, undef, 'Hold is no longer marked waiting' );
+    is( $hold->priority, 1,  "Hold is now priority one again");
+    is( $hold->waitingdate, undef, "Hold no longer has a waiting date");
+    is( $hold->itemnumber, $item->itemnumber, "Hold has retained its' itemnumber");
+    is( $messages->{ResFound}->{ResFound}, "Reserved", "Hold is still returned");
+    is( $messages->{ResFound}->{found}, undef, "Hold is no longer marked found in return message");
+    is( $messages->{ResFound}->{priority}, 1, "Hold is priority 1 in return message");
 };
 
 subtest 'Cancel transfers on lost items' => sub {
-    plan tests => 5;
-    my $library_1 = $builder->build( { source => 'Branch' } );
-    my $patron_1 = $builder->build( { source => 'Borrower', value => { branchcode => $library_1->{branchcode}, categorycode => $patron_category->{categorycode} } } );
-    my $library_2 = $builder->build( { source => 'Branch' } );
-    my $patron_2  = $builder->build( { source => 'Borrower', value => { branchcode => $library_2->{branchcode}, categorycode => $patron_category->{categorycode} } } );
-    my $biblio = $builder->build_sample_biblio({branchcode => $library->{branchcode}});
-    my $item   = $builder->build_sample_item({
-        biblionumber  => $biblio->biblionumber,
-        library    => $library_1->{branchcode},
-    });
+    plan tests => 6;
 
-    set_userenv( $library_2 );
-    my $reserve_id = AddReserve(
-        $library_2->{branchcode}, $patron_2->{borrowernumber}, $item->biblionumber, '', 1, undef, undef, '', undef, $item->itemnumber,
+    my $library_to = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $item   = $builder->build_sample_item();
+    my $holdingbranch = $item->holdingbranch;
+    # Historic transfer (datearrived is defined)
+    my $old_transfer = $builder->build_object(
+        {
+            class => 'Koha::Item::Transfers',
+            value => {
+                itemnumber    => $item->itemnumber,
+                frombranch    => $holdingbranch,
+                tobranch      => $library_to->branchcode,
+                reason        => 'Manual',
+                datesent      => \'NOW()',
+                datearrived   => \'NOW()',
+                datecancelled => undef,
+                daterequested => \'NOW()'
+            }
+        }
+    );
+    # Queued transfer (datesent is undefined)
+    my $transfer_1 = $builder->build_object(
+        {
+            class => 'Koha::Item::Transfers',
+            value => {
+                itemnumber    => $item->itemnumber,
+                frombranch    => $holdingbranch,
+                tobranch      => $library_to->branchcode,
+                reason        => 'Manual',
+                datesent      => undef,
+                datearrived   => undef,
+                datecancelled => undef,
+                daterequested => \'NOW()'
+            }
+        }
+    );
+    # In transit transfer (datesent is defined, datearrived and datecancelled are both undefined)
+    my $transfer_2 = $builder->build_object(
+        {
+            class => 'Koha::Item::Transfers',
+            value => {
+                itemnumber    => $item->itemnumber,
+                frombranch    => $holdingbranch,
+                tobranch      => $library_to->branchcode,
+                reason        => 'Manual',
+                datesent      => \'NOW()',
+                datearrived   => undef,
+                datecancelled => undef,
+                daterequested => \'NOW()'
+            }
+        }
     );
 
-    #Return book and add transfer
-    set_userenv( $library_1 );
-    my $do_transfer = 1;
-    my ( $res, $rr ) = AddReturn( $item->barcode, $library_1->{branchcode} );
-    ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
-    C4::Circulation::transferbook( $library_2->{branchcode}, $item->barcode );
-    my $hold = Koha::Holds->find( $reserve_id );
-    is( $hold->found, 'T', 'Hold is in transit' );
-
-    #Check transfer exists and the items holding branch is the transfer destination branch before marking it as lost
-    my ($datesent,$frombranch,$tobranch) = GetTransfers($item->itemnumber);
-    is( $tobranch, $library_2->{branchcode}, 'The transfer record exists in the branchtransfers table');
-    my $itemcheck = Koha::Items->find($item->itemnumber);
-    is( $itemcheck->holdingbranch, $library_1->{branchcode}, 'Items holding branch is the transfers origin branch before it is marked as lost' );
-
-    #Simulate item being marked as lost and confirm the transfer is deleted and the items holding branch is the transfers source branch
-    ModItem( { itemlost => 1 }, $item->biblionumber, $item->itemnumber );
+    # Simulate item being marked as lost
+    $item->itemlost(1)->store;
     LostItem( $item->itemnumber, 'test', 1 );
-    ($datesent,$frombranch,$tobranch) = GetTransfers($item->itemnumber);
-    is( $tobranch, undef, 'The transfer on the lost item has been deleted as the LostItemCancelOutstandingTransfer is enabled');
-    $itemcheck = Koha::Items->find($item->itemnumber);
-    is( $itemcheck->holdingbranch, $library_1->{branchcode}, 'Lost item with cancelled hold has holding branch equallying the transfers source branch' );
+
+    $transfer_1->discard_changes;
+    isnt($transfer_1->datecancelled, undef, "Queud transfer was cancelled upon item lost");
+    is($transfer_1->cancellation_reason, 'ItemLost', "Cancellation reason was set to 'ItemLost'");
+    $transfer_2->discard_changes;
+    isnt($transfer_2->datecancelled, undef, "Active transfer was cancelled upon item lost");
+    is($transfer_2->cancellation_reason, 'ItemLost', "Cancellation reason was set to 'ItemLost'");
+    $old_transfer->discard_changes;
+    is($old_transfer->datecancelled, undef, "Old transfers are unaffected");
+    $item->discard_changes;
+    is($item->holdingbranch, $holdingbranch, "Items holding branch remains unchanged");
 };
 
 subtest 'CanBookBeIssued | is_overdue' => sub {
     plan tests => 3;
 
     # Set a simple circ policy
-    $dbh->do('DELETE FROM issuingrules');
-    $dbh->do(
-    q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed,
-                                    issuelength, lengthunit,
-                                    renewalsallowed, renewalperiod,
-                                    norenewalbefore, auto_renew,
-                                    fine, chargeperiod)
-          VALUES (?, ?, ?, ?,
-                  ?, ?,
-                  ?, ?,
-                  ?, ?,
-                  ?, ?
-                 )
-        },
-        {},
-        '*',   '*', '*', 25,
-        14,  'days',
-        1,     7,
-        undef, 0,
-        .10,   1
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            branchcode   => undef,
+            itemtype     => undef,
+            rules        => {
+                maxissueqty     => 1,
+                reservesallowed => 25,
+                issuelength     => 14,
+                lengthunit      => 'days',
+                renewalsallowed => 1,
+                renewalperiod   => 7,
+                norenewalbefore => undef,
+                auto_renew      => 0,
+                fine            => .10,
+                chargeperiod    => 1,
+            }
+        }
     );
 
-    my $five_days_go = output_pref({ dt => dt_from_string->add( days => 5 ), dateonly => 1});
-    my $ten_days_go  = output_pref({ dt => dt_from_string->add( days => 10), dateonly => 1 });
+    my $now   = dt_from_string;
+    my $five_days_go = output_pref({ dt => $now->clone->add( days => 5 ), dateonly => 1});
+    my $ten_days_go  = output_pref({ dt => $now->clone->add( days => 10), dateonly => 1 });
     my $library = $builder->build( { source => 'Branch' } );
     my $patron  = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
 
@@ -2710,12 +3582,12 @@ subtest 'CanBookBeIssued | is_overdue' => sub {
         {
             library      => $library->{branchcode},
         }
-    )->unblessed;
+    );
 
-    my $issue = AddIssue( $patron->unblessed, $item->{barcode}, $five_days_go ); # date due was 10d ago
-    my $actualissue = Koha::Checkouts->find( { itemnumber => $item->{itemnumber} } );
+    my $issue = AddIssue( $patron->unblessed, $item->barcode, $five_days_go ); # date due was 10d ago
+    my $actualissue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
     is( output_pref({ str => $actualissue->date_due, dateonly => 1}), $five_days_go, "First issue works");
-    my ($issuingimpossible, $needsconfirmation) = CanBookBeIssued($patron,$item->{barcode},$ten_days_go, undef, undef, undef);
+    my ($issuingimpossible, $needsconfirmation) = CanBookBeIssued($patron,$item->barcode,$ten_days_go, undef, undef, undef);
     is( $needsconfirmation->{RENEW_ISSUE}, 1, "This is a renewal");
     is( $needsconfirmation->{TOO_MANY}, undef, "Not too many, is a renewal");
 };
@@ -2726,17 +3598,23 @@ subtest 'ItemsDeniedRenewal preference' => sub {
     C4::Context->set_preference('ItemsDeniedRenewal','');
 
     my $idr_lib = $builder->build_object({ class => 'Koha::Libraries'});
-    $dbh->do(
-        q{
-        INSERT INTO issuingrules ( categorycode, branchcode, itemtype, reservesallowed, issuelength, lengthunit, renewalsallowed, renewalperiod,
-                    norenewalbefore, auto_renew, fine, chargeperiod ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
-        },
-        {},
-        '*', $idr_lib->branchcode, '*', 25,
-        14,  'days',
-        10,   7,
-        undef,  0,
-        .10, 1
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => '*',
+            itemtype     => '*',
+            branchcode   => $idr_lib->branchcode,
+            rules        => {
+                reservesallowed => 25,
+                issuelength     => 14,
+                lengthunit      => 'days',
+                renewalsallowed => 10,
+                renewalperiod   => 7,
+                norenewalbefore => undef,
+                auto_renew      => 0,
+                fine            => .10,
+                chargeperiod    => 1,
+            }
+        }
     );
 
     my $deny_book = $builder->build_object({ class => 'Koha::Items', value => {
@@ -2985,7 +3863,7 @@ subtest 'AddReturn should clear items.onloan for unissued items' => sub {
 
 subtest 'AddRenewal and AddIssuingCharge tests' => sub {
 
-    plan tests => 10;
+    plan tests => 13;
 
 
     t::lib::Mocks::mock_preference('item-level_itypes', 1);
@@ -3011,16 +3889,16 @@ subtest 'AddRenewal and AddIssuingCharge tests' => sub {
     });
 
     my $biblio = $builder->build_sample_biblio({ title=> $title, author => $author });
-    my ( undef, undef, $item_id ) = AddItem(
+    my $item_id = Koha::Item->new(
         {
+            biblionumber     => $biblio->biblionumber,
             homebranch       => $library->id,
             holdingbranch    => $library->id,
             barcode          => $barcode,
             replacementprice => 23.00,
             itype            => $itemtype->id
         },
-        $biblio->biblionumber
-    );
+    )->store->itemnumber;
     my $item = Koha::Items->find( $item_id );
 
     my $context = Test::MockModule->new('C4::Context');
@@ -3028,6 +3906,11 @@ subtest 'AddRenewal and AddIssuingCharge tests' => sub {
 
     # Check the item out
     AddIssue( $patron->unblessed, $item->barcode );
+
+    throws_ok {
+        AddRenewal( $patron->borrowernumber, $item->itemnumber, $library->id, undef, {break=>"the_renewal"} );
+    } 'Koha::Exceptions::Checkout::FailedRenewal', 'Exception is thrown when renewal update to issues fails';
+
     t::lib::Mocks::mock_preference( 'RenewalLog', 0 );
     my $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
     my %params_renewal = (
@@ -3062,11 +3945,31 @@ subtest 'AddRenewal and AddIssuingCharge tests' => sub {
     is( $line->description, '', 'AddRenewal does not set a hardcoded description for the accountline' );
 
     t::lib::Mocks::mock_preference( 'RenewalLog', 1 );
-    $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
+
+    $context = Test::MockModule->new('C4::Context');
+    $context->mock( userenv => { branch => undef, interface => 'CRON'} ); #Test statistical logging of renewal via cron (atuo_renew)
+
+    my $now = dt_from_string;
+    $date = output_pref( { dt => $now, dateonly => 1, dateformat => 'iso' } );
     $old_log_size = Koha::ActionLogs->count( \%params_renewal );
+    my $sth = $dbh->prepare("SELECT COUNT(*) FROM statistics WHERE itemnumber = ? AND branch = ?");
+    $sth->execute($item->id, $library->id);
+    my ($old_stats_size) = $sth->fetchrow_array;
     AddRenewal( $patron->id, $item->id, $library->id );
     $new_log_size = Koha::ActionLogs->count( \%params_renewal );
+    $sth->execute($item->id, $library->id);
+    my ($new_stats_size) = $sth->fetchrow_array;
     is( $new_log_size, $old_log_size + 1, 'renew log successfully added' );
+    is( $new_stats_size, $old_stats_size + 1, 'renew statistic successfully added with passed branch' );
+
+    AddReturn( $item->id, $library->id, undef, $date );
+    AddIssue( $patron->unblessed, $item->barcode, $now );
+    AddRenewal( $patron->id, $item->id, $library->id, undef, undef, 1 );
+    my $lines_skipped = Koha::Account::Lines->search({
+        borrowernumber => $patron->id,
+        itemnumber     => $item->id
+    });
+    is( $lines_skipped->count, 5, 'Passing skipfinecalc causes fine calculation on renewal to be skipped' );
 
 };
 
@@ -3093,7 +3996,7 @@ subtest 'ProcessOfflinePayment() tests' => sub {
 };
 
 subtest 'Incremented fee tests' => sub {
-    plan tests => 20;
+    plan tests => 19;
 
     my $dt = dt_from_string();
     Time::Fake->offset( $dt->epoch );
@@ -3103,7 +4006,6 @@ subtest 'Incremented fee tests' => sub {
     my $library =
       $builder->build_object( { class => 'Koha::Libraries' } )->store;
 
-    my $module = new Test::MockModule('C4::Context');
     $module->mock( 'userenv', sub { { branch => $library->id } } );
 
     my $patron = $builder->build_object(
@@ -3117,9 +4019,10 @@ subtest 'Incremented fee tests' => sub {
         {
             class => 'Koha::ItemTypes',
             value => {
-                notforloan         => undef,
-                rentalcharge       => 0,
-                rentalcharge_daily => 1.000000
+                notforloan                   => undef,
+                rentalcharge                 => 0,
+                rentalcharge_daily           => 1,
+                rentalcharge_daily_calendar  => 0
             }
         }
     )->store;
@@ -3131,44 +4034,45 @@ subtest 'Incremented fee tests' => sub {
         }
     );
 
-    is( $itemtype->rentalcharge_daily,
-        '1.000000', 'Daily rental charge stored and retreived correctly' );
+    is( $itemtype->rentalcharge_daily+0,
+        1, 'Daily rental charge stored and retreived correctly' );
     is( $item->effective_itemtype, $itemtype->id,
         "Itemtype set correctly for item" );
 
-    my $dt_from     = dt_from_string();
-    my $dt_to       = dt_from_string()->add( days => 7 );
-    my $dt_to_renew = dt_from_string()->add( days => 13 );
+    my $now         = dt_from_string;
+    my $dt_from     = $now->clone;
+    my $dt_to       = $now->clone->add( days => 7 );
+    my $dt_to_renew = $now->clone->add( days => 13 );
 
     # Daily Tests
-    t::lib::Mocks::mock_preference( 'finesCalendar', 'ignoreCalendar' );
     my $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     my $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '7.000000',
-"Daily rental charge calculated correctly with finesCalendar = ignoreCalendar"
+    is( $accountline->amount+0, 7,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0"
     );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '6.000000',
-"Daily rental charge calculated correctly with finesCalendar = ignoreCalendar, for renewal"
+    is( $accountline->amount+0, 6,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 0, for renewal"
     );
     $accountline->delete();
     $issue->delete();
 
     t::lib::Mocks::mock_preference( 'finesCalendar', 'noFinesWhenClosed' );
+    $itemtype->rentalcharge_daily_calendar(1)->store();
     $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '7.000000',
-"Daily rental charge calculated correctly with finesCalendar = noFinesWhenClosed"
+    is( $accountline->amount+0, 7,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1"
     );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '6.000000',
-"Daily rental charge calculated correctly with finesCalendar = noFinesWhenClosed, for renewal"
+    is( $accountline->amount+0, 6,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1, for renewal"
     );
     $accountline->delete();
     $issue->delete();
@@ -3188,20 +4092,20 @@ subtest 'Incremented fee tests' => sub {
     $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '6.000000',
-"Daily rental charge calculated correctly with finesCalendar = noFinesWhenClosed and closed $closed_day_name"
+    is( $accountline->amount+0, 6,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name"
     );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
-    is( $accountline->amount, '5.000000',
-"Daily rental charge calculated correctly with finesCalendar = noFinesWhenClosed and closed $closed_day_name, for renewal"
+    is( $accountline->amount+0, 5,
+"Daily rental charge calculated correctly with rentalcharge_daily_calendar = 1 and closed $closed_day_name, for renewal"
     );
     $accountline->delete();
     $issue->delete();
 
-    $itemtype->rentalcharge('2.000000')->store;
-    is( $itemtype->rentalcharge, '2.000000',
+    $itemtype->rentalcharge(2)->store;
+    is( $itemtype->rentalcharge+0, 2,
         'Rental charge updated and retreived correctly' );
     $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
@@ -3216,54 +4120,53 @@ subtest 'Incremented fee tests' => sub {
         "Fixed charge and accrued charge recorded distinctly, for renewal" );
     $accountlines->delete();
     $issue->delete();
-    $itemtype->rentalcharge('00.000000')->store;
-    is( $itemtype->rentalcharge, '00.000000',
+    $itemtype->rentalcharge(0)->store;
+    is( $itemtype->rentalcharge+0, 0,
         'Rental charge reset and retreived correctly' );
 
     # Hourly
-    my $issuingrule = Koha::IssuingRules->get_effective_issuing_rule(
+    Koha::CirculationRules->set_rule(
         {
             categorycode => $patron->categorycode,
             itemtype     => $itemtype->id,
-            branchcode   => $library->id
+            branchcode   => $library->id,
+            rule_name    => 'lengthunit',
+            rule_value   => 'hours',
         }
     );
-    $issuingrule->lengthunit('hours')->store();
-    is( $issuingrule->lengthunit, 'hours',
-        'Issuingrule updated and retrieved correctly' );
 
     $itemtype->rentalcharge_hourly('0.25')->store();
     is( $itemtype->rentalcharge_hourly,
         '0.25', 'Hourly rental charge stored and retreived correctly' );
 
-    $dt_to       = dt_from_string()->add( hours => 168 );
-    $dt_to_renew = dt_from_string()->add( hours => 312 );
+    $dt_to       = $now->clone->add( hours => 168 );
+    $dt_to_renew = $now->clone->add( hours => 312 );
 
-    t::lib::Mocks::mock_preference( 'finesCalendar', 'ignoreCalendar' );
+    $itemtype->rentalcharge_hourly_calendar(0)->store();
     $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 42,
-        "Hourly rental charge calculated correctly with finesCalendar = ignoreCalendar (168h * 0.25u)" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0 (168h * 0.25u)" );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 36,
-        "Hourly rental charge calculated correctly with finesCalendar = ignoreCalendar, for renewal (312h - 168h * 0.25u)" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 0, for renewal (312h - 168h * 0.25u)" );
     $accountline->delete();
     $issue->delete();
 
-    t::lib::Mocks::mock_preference( 'finesCalendar', 'noFinesWhenClosed' );
+    $itemtype->rentalcharge_hourly_calendar(1)->store();
     $issue =
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 36,
-        "Hourly rental charge calculated correctly with finesCalendar = noFinesWhenClosed and closed $closed_day_name (168h - 24h * 0.25u)" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name (168h - 24h * 0.25u)" );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 30,
-        "Hourly rental charge calculated correctly with finesCalendar = noFinesWhenClosed and closed $closed_day_name, for renewal (312h - 168h - 24h * 0.25u" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 and closed $closed_day_name, for renewal (312h - 168h - 24h * 0.25u" );
     $accountline->delete();
     $issue->delete();
 
@@ -3272,15 +4175,14 @@ subtest 'Incremented fee tests' => sub {
       AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 42,
-        "Hourly rental charge calculated correctly with finesCalendar = noFinesWhenClosed (168h - 0h * 0.25u" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1 (168h - 0h * 0.25u" );
     $accountline->delete();
     AddRenewal( $patron->id, $item->id, $library->id, $dt_to_renew, $dt_to );
     $accountline = Koha::Account::Lines->find( { itemnumber => $item->id } );
     is( $accountline->amount + 0, 36,
-        "Hourly rental charge calculated correctly with finesCalendar = noFinesWhenClosed, for renewal (312h - 168h - 0h * 0.25u)" );
+        "Hourly rental charge calculated correctly with rentalcharge_hourly_calendar = 1, for renewal (312h - 168h - 0h * 0.25u)" );
     $accountline->delete();
     $issue->delete();
-    $issuingrule->lengthunit('days')->store();
     Time::Fake->reset;
 };
 
@@ -3310,80 +4212,717 @@ subtest 'CanBookBeIssued & RentalFeesCheckoutConfirmation' => sub {
         }
     );
 
-    my $biblioitem = $builder->build( { source => 'Biblioitem' } );
-    my $item = $builder->build_object(
+    my $item = $builder->build_sample_item(
         {
-            class => 'Koha::Items',
-            value  => {
-                homebranch    => $library->id,
-                holdingbranch => $library->id,
-                notforloan    => 0,
-                itemlost      => 0,
-                withdrawn     => 0,
-                itype         => $itemtype->id,
-                biblionumber  => $biblioitem->{biblionumber},
-                biblioitemnumber => $biblioitem->{biblioitemnumber},
-            }
+            library    => $library->id,
+            notforloan => 0,
+            itemlost   => 0,
+            withdrawn  => 0,
+            itype      => $itemtype->id,
         }
     )->store;
 
     my ( $issuingimpossible, $needsconfirmation );
     my $dt_from = dt_from_string();
-    my $dt_due = dt_from_string()->add( days => 3 );
+    my $dt_due = $dt_from->clone->add( days => 3 );
 
-    $itemtype->rentalcharge('1.000000')->store;
+    $itemtype->rentalcharge(1)->store;
     ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
     is_deeply( $needsconfirmation, { RENTALCHARGE => '1.00' }, 'Item needs rentalcharge confirmation to be issued' );
     $itemtype->rentalcharge('0')->store;
-    $itemtype->rentalcharge_daily('1.000000')->store;
+    $itemtype->rentalcharge_daily(1)->store;
     ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
     is_deeply( $needsconfirmation, { RENTALCHARGE => '3' }, 'Item needs rentalcharge confirmation to be issued, increment' );
     $itemtype->rentalcharge_daily('0')->store;
 };
 
-subtest "Test Backdating of Returns" => sub {
-    plan tests => 2;
+subtest 'CanBookBeIssued & CircConfirmItemParts' => sub {
+    plan tests => 1;
+
+    t::lib::Mocks::mock_preference('CircConfirmItemParts', 1);
+
+    my $patron = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    )->store;
+
+    my $item = $builder->build_sample_item(
+        {
+            materials => 'includes DVD',
+        }
+    )->store;
+
+    my $dt_due = dt_from_string->add( days => 3 );
+
+    my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $item->barcode, $dt_due, undef, undef, undef );
+    is_deeply( $needsconfirmation, { ADDITIONAL_MATERIALS => 'includes DVD' }, 'Item needs confirmation of additional parts' );
+};
+
+subtest 'Do not return on renewal (LOST charge)' => sub {
+    plan tests => 1;
+
+    t::lib::Mocks::mock_preference('MarkLostItemsAsReturned', 'onpayment');
+    my $library = $builder->build_object( { class => "Koha::Libraries" } );
+    my $manager = $builder->build_object( { class => "Koha::Patrons" } );
+    t::lib::Mocks::mock_userenv({ patron => $manager,branchcode => $manager->branchcode });
+
+    my $biblio = $builder->build_sample_biblio;
 
-    my $branch = $library2->{branchcode};
-    my $biblio = $builder->build_sample_biblio();
     my $item = $builder->build_sample_item(
         {
             biblionumber     => $biblio->biblionumber,
-            library          => $branch,
+            library          => $library->branchcode,
+            replacementprice => 99.00,
             itype            => $itemtype,
         }
     );
 
-    my %a_borrower_data = (
-        firstname =>  'Kyle',
-        surname => 'Hall',
-        categorycode => $patron_category->{categorycode},
-        branchcode => $branch,
-    );
-    my $borrowernumber = Koha::Patron->new(\%a_borrower_data)->store->borrowernumber;
-    my $borrower = Koha::Patrons->find( $borrowernumber )->unblessed;
+    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
+    AddIssue( $patron->unblessed, $item->barcode );
 
-    my $due_date = dt_from_string;
-    my $issue = AddIssue( $borrower, $item->barcode, $due_date );
-    UpdateFine(
+    my $accountline = Koha::Account::Line->new(
         {
-            issue_id          => $issue->id(),
+            borrowernumber    => $patron->borrowernumber,
+            debit_type_code   => 'LOST',
+            status            => undef,
             itemnumber        => $item->itemnumber,
-            borrowernumber    => $borrowernumber,
-            amount            => .25,
-            amountoutstanding => .25,
-            type              => q{}
+            amount            => 12,
+            amountoutstanding => 12,
+            interface         => 'something',
+        }
+    )->store();
+
+    # AddRenewal doesn't call _FixAccountForLostAndFound
+    AddIssue( $patron->unblessed, $item->barcode );
+
+    is( $patron->checkouts->count, 1,
+        'Renewal should not return the item even if a LOST payment has been made earlier'
+    );
+};
+
+subtest 'Filling a hold should cancel existing transfer' => sub {
+    plan tests => 4;
+
+    t::lib::Mocks::mock_preference('AutomaticItemReturn', 1);
+
+    my $libraryA = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $libraryB = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => {
+                categorycode => $patron_category->{categorycode},
+                branchcode => $libraryA->branchcode,
+            }
+        }
+    )->store;
+
+    my $item = $builder->build_sample_item({
+        homebranch => $libraryB->branchcode,
+    });
+
+    my ( undef, $message ) = AddReturn( $item->barcode, $libraryA->branchcode, undef, undef );
+    is( Koha::Item::Transfers->search({ itemnumber => $item->itemnumber, datearrived => undef })->count, 1, "We generate a transfer on checkin");
+    AddReserve({
+        branchcode     => $libraryA->branchcode,
+        borrowernumber => $patron->borrowernumber,
+        biblionumber   => $item->biblionumber,
+        itemnumber     => $item->itemnumber
+    });
+    my $reserves = Koha::Holds->search({ itemnumber => $item->itemnumber });
+    is( $reserves->count, 1, "Reserve is placed");
+    ( undef, $message ) = AddReturn( $item->barcode, $libraryA->branchcode, undef, undef );
+    my $reserve = $reserves->next;
+    ModReserveAffect( $item->itemnumber, $patron->borrowernumber, 0, $reserve->reserve_id );
+    $reserve->discard_changes;
+    ok( $reserve->found eq 'W', "Reserve is marked waiting" );
+    is( Koha::Item::Transfers->search({ itemnumber => $item->itemnumber, datearrived => undef })->count, 0, "No outstanding transfers when hold is waiting");
+};
+
+subtest 'Tests for NoRefundOnLostReturnedItemsAge with AddReturn' => sub {
+
+    plan tests => 4;
+
+    t::lib::Mocks::mock_preference('BlockReturnOfLostItems', 0);
+    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron  = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    );
+
+    my $biblionumber = $builder->build_sample_biblio(
+        {
+            branchcode => $library->branchcode,
+        }
+    )->biblionumber;
+
+    # And the circulation rule
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                issuelength => 14,
+                lengthunit  => 'days',
+            }
+        }
+    );
+    $builder->build(
+        {
+            source => 'CirculationRule',
+            value  => {
+                branchcode   => undef,
+                categorycode => undef,
+                itemtype     => undef,
+                rule_name    => 'lostreturn',
+                rule_value   => 'refund'
+            }
+        }
+    );
+
+    subtest 'NoRefundOnLostReturnedItemsAge = undef' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', undef );
+
+        my $lost_on = dt_from_string->subtract( days => 7 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
+        $a->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 6 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
+        $a->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 7 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
+        $a->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 8 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        );
+        $a = $a->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        my ( $doreturn, $messages ) = AddReturn( $item->barcode, $library->branchcode, undef, dt_from_string );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
+        $a->delete;
+    };
+};
+
+subtest 'Tests for NoRefundOnLostReturnedItemsAge with AddIssue' => sub {
+
+    plan tests => 4;
+
+    t::lib::Mocks::mock_preference('BlockReturnOfLostItems', 0);
+    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron  = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    );
+    my $patron2  = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    );
+
+    my $biblionumber = $builder->build_sample_biblio(
+        {
+            branchcode => $library->branchcode,
+        }
+    )->biblionumber;
+
+    # And the circulation rule
+    Koha::CirculationRules->search->delete;
+    Koha::CirculationRules->set_rules(
+        {
+            categorycode => undef,
+            itemtype     => undef,
+            branchcode   => undef,
+            rules        => {
+                issuelength => 14,
+                lengthunit  => 'days',
+            }
+        }
+    );
+    $builder->build(
+        {
+            source => 'CirculationRule',
+            value  => {
+                branchcode   => undef,
+                categorycode => undef,
+                itemtype     => undef,
+                rule_name    => 'lostreturn',
+                rule_value   => 'refund'
+            }
+        }
+    );
+
+    subtest 'NoRefundOnLostReturnedItemsAge = undef' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', undef );
+
+        my $lost_on = dt_from_string->subtract( days => 7 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        $issue = AddIssue( $patron2->unblessed, $item->barcode );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
+        $a->delete;
+        $issue->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge > length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 6 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        $issue = AddIssue( $patron2->unblessed, $item->barcode );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 0, "Lost fee was refunded" );
+        $a->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge = length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 7 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        )->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        $issue = AddIssue( $patron2->unblessed, $item->barcode );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
+        $a->delete;
+    };
+
+    subtest 'NoRefundOnLostReturnedItemsAge < length of days item has been lost' => sub {
+        plan tests => 3;
+
+        t::lib::Mocks::mock_preference( 'WhenLostChargeReplacementFee',   1 );
+        t::lib::Mocks::mock_preference( 'NoRefundOnLostReturnedItemsAge', 7 );
+
+        my $lost_on = dt_from_string->subtract( days => 8 )->date;
+
+        my $item = $builder->build_sample_item(
+            {
+                biblionumber     => $biblionumber,
+                library          => $library->branchcode,
+                replacementprice => '42',
+            }
+        );
+        my $issue = AddIssue( $patron->unblessed, $item->barcode );
+        LostItem( $item->itemnumber, 'cli', 0 );
+        $item->_result->itemlost(1);
+        $item->_result->itemlost_on( $lost_on );
+        $item->_result->update();
+
+        my $a = Koha::Account::Lines->search(
+            {
+                itemnumber     => $item->id,
+                borrowernumber => $patron->borrowernumber
+            }
+        );
+        $a = $a->next;
+        ok( $a, "Found accountline for lost fee" );
+        is( $a->amountoutstanding + 0, 42, "Lost fee charged correctly" );
+        $issue = AddIssue( $patron2->unblessed, $item->barcode );
+        $a = $a->get_from_storage;
+        is( $a->amountoutstanding + 0, 42, "Lost fee was not refunded" );
+        $a->delete;
+    };
+};
+
+subtest 'transferbook tests' => sub {
+    plan tests => 9;
+
+    throws_ok
+    { C4::Circulation::transferbook({}); }
+    'Koha::Exceptions::MissingParameter',
+    'Koha::Patron->store raises an exception on missing params';
+
+    throws_ok
+    { C4::Circulation::transferbook({to_branch=>'anything'}); }
+    'Koha::Exceptions::MissingParameter',
+    'Koha::Patron->store raises an exception on missing params';
+
+    throws_ok
+    { C4::Circulation::transferbook({from_branch=>'anything'}); }
+    'Koha::Exceptions::MissingParameter',
+    'Koha::Patron->store raises an exception on missing params';
+
+    my ($doreturn,$messages) = C4::Circulation::transferbook({to_branch=>'there',from_branch=>'here'});
+    is( $doreturn, 0, "No return without barcode");
+    ok( exists $messages->{BadBarcode}, "We get a BadBarcode message if no barcode passed");
+    is( $messages->{BadBarcode}, undef, "No barcode passed means undef BadBarcode" );
+
+    ($doreturn,$messages) = C4::Circulation::transferbook({to_branch=>'there',from_branch=>'here',barcode=>'BadBarcode'});
+    is( $doreturn, 0, "No return without barcode");
+    ok( exists $messages->{BadBarcode}, "We get a BadBarcode message if no barcode passed");
+    is( $messages->{BadBarcode}, 'BadBarcode', "No barcode passed means undef BadBarcode" );
+
+};
+
+subtest 'Checkout should correctly terminate a transfer' => sub {
+    plan tests => 7;
+
+    my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron_1 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { branchcode => $library_1->branchcode }
+        }
+    );
+    my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron_2 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { branchcode => $library_2->branchcode }
+        }
+    );
+
+    my $item = $builder->build_sample_item(
+        {
+            library => $library_1->branchcode,
+        }
+    );
+
+    t::lib::Mocks::mock_userenv( { branchcode => $library_1->branchcode } );
+    my $reserve_id = AddReserve(
+        {
+            branchcode     => $library_2->branchcode,
+            borrowernumber => $patron_2->borrowernumber,
+            biblionumber   => $item->biblionumber,
+            itemnumber     => $item->itemnumber,
+            priority       => 1,
+        }
+    );
+
+    my $do_transfer = 1;
+    ModItemTransfer( $item->itemnumber, $library_1->branchcode,
+        $library_2->branchcode, 'Manual' );
+    ModReserveAffect( $item->itemnumber, undef, $do_transfer, $reserve_id );
+    GetOtherReserves( $item->itemnumber )
+      ;    # To put the Reason, it's what does returns.pl...
+    my $hold = Koha::Holds->find($reserve_id);
+    is( $hold->found, 'T', 'Hold is in transit' );
+    my $transfer = $item->get_transfer;
+    is( $transfer->frombranch, $library_1->branchcode );
+    is( $transfer->tobranch,   $library_2->branchcode );
+    is( $transfer->reason,     'Reserve' );
+
+    t::lib::Mocks::mock_userenv( { branchcode => $library_2->branchcode } );
+    AddIssue( $patron_1->unblessed, $item->barcode );
+    $transfer = $transfer->get_from_storage;
+    isnt( $transfer->datearrived, undef );
+    $hold = $hold->get_from_storage;
+    is( $hold->found, undef, 'Hold is waiting' );
+    is( $hold->priority, 1, );
+};
+
+subtest 'AddIssue records staff who checked out item if appropriate' => sub  {
+    plan tests => 2;
+
+    $module->mock( 'userenv', sub { { branch => $library->{id} } } );
+
+    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    );
+    my $issuer = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { categorycode => $patron_category->{categorycode} }
+        }
+    );
+    my $item = $builder->build_sample_item(
+        {
+            library  => $library->{branchcode}
+        }
+    );
+
+    $module->mock( 'userenv', sub { { branch => $library->id, number => $issuer->{borrowernumber} } } );
+
+    my $dt_from = dt_from_string();
+    my $dt_to   = dt_from_string()->add( days => 7 );
+
+    my $issue = AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
+
+    is( $issue->issuer, undef, "Staff who checked out the item not recorded when RecordStaffUserOnCheckout turned off" );
+
+    t::lib::Mocks::mock_preference('RecordStaffUserOnCheckout', 1);
+
+    my $issue2 =
+      AddIssue( $patron->unblessed, $item->barcode, $dt_to, undef, $dt_from );
+
+    is( $issue->issuer, $issuer->{borrowernumber}, "Staff who checked out the item recorded when RecordStaffUserOnCheckout turned on" );
+};
+
+subtest "Item's onloan value should be set if checked out item is checked out to a different patron" => sub {
+    plan tests => 2;
+
+    my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $patron_1 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { branchcode => $library_1->branchcode }
+        }
+    );
+    my $patron_2 = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { branchcode => $library_1->branchcode }
+        }
+    );
+
+    my $item = $builder->build_sample_item(
+        {
+            library => $library_1->branchcode,
         }
     );
 
+    AddIssue( $patron_1->unblessed, $item->barcode );
+    ok( $item->get_from_storage->onloan, "Item's onloan column is set after initial checkout" );
+    AddIssue( $patron_2->unblessed, $item->barcode );
+    ok( $item->get_from_storage->onloan, "Item's onloan column is set after second checkout" );
+};
 
-    my ( undef, $message ) = AddReturn( $item->barcode, $branch, undef, $due_date );
+subtest "updateWrongTransfer tests" => sub {
+    plan tests => 5;
 
-    my $accountline = Koha::Account::Lines->find( { issue_id => $issue->id } );
-    is( $accountline->amountoutstanding, '0.000000', 'Fee amount outstanding was reduced to 0' );
-    is( $accountline->amount, '0.000000', 'Fee amount was reduced to 0' );
+    my $library1 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $library2 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $library3 = $builder->build_object( { class => 'Koha::Libraries' } );
+    my $item     = $builder->build_sample_item(
+        {
+            homebranch    => $library1->branchcode,
+            holdingbranch => $library2->branchcode,
+            datelastseen  => undef
+        }
+    );
+
+    my $transfer = $builder->build_object(
+        {
+            class => 'Koha::Item::Transfers',
+            value => {
+                itemnumber    => $item->itemnumber,
+                frombranch    => $library2->branchcode,
+                tobranch      => $library1->branchcode,
+                daterequested => dt_from_string,
+                datesent      => dt_from_string,
+                datecancelled => undef,
+                datearrived   => undef,
+                reason        => 'Manual'
+            }
+        }
+    );
+    is( ref($transfer), 'Koha::Item::Transfer', 'Mock transfer added' );
+
+    my $new_transfer = C4::Circulation::updateWrongTransfer($item->itemnumber, $library1->branchcode);
+    is(ref($new_transfer), 'Koha::Item::Transfer', "updateWrongTransfer returns a 'Koha::Item::Transfer' object");
+    ok( !$new_transfer->in_transit, "New transfer is NOT created as in transit (or cancelled)");
+
+    my $original_transfer = $transfer->get_from_storage;
+    ok( defined($original_transfer->datecancelled), "Original transfer was cancelled");
+    is( $original_transfer->cancellation_reason, 'WrongTransfer', "Original transfer cancellation reason is 'WrongTransfer'");
 };
 
 $schema->storage->txn_rollback;
 C4::Context->clear_syspref_cache();
-$cache->clear_from_cache('single_holidays');
+$branches = Koha::Libraries->search();
+for my $branch ( $branches->next ) {
+    my $key = $branch->branchcode . "_holidays";
+    $cache->clear_from_cache($key);
+}