From 126b48fe49686aa04477106a210442e9b9054959 Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Wed, 22 Apr 2020 16:47:17 +0000
Subject: [PATCH] Bug 19532: Expiry and overdue cronjobs, and overdue fine
 calculation

plus tests for overdue fines in t/db_dependent/Circulation/CalcFine.t
---
 C4/Overdues.pm                           | 23 +++++++++++-
 misc/cronjobs/recalls/expire_recalls.pl  | 63 ++++++++++++++++++++++++++++++++
 misc/cronjobs/recalls/overdue_recalls.pl | 41 +++++++++++++++++++++
 t/db_dependent/Circulation/CalcFine.t    | 56 +++++++++++++++++++++++++++-
 4 files changed, 181 insertions(+), 2 deletions(-)
 create mode 100755 misc/cronjobs/recalls/expire_recalls.pl
 create mode 100755 misc/cronjobs/recalls/overdue_recalls.pl

diff --git a/C4/Overdues.pm b/C4/Overdues.pm
index a9de67d1ee5..35680f954a0 100644
--- a/C4/Overdues.pm
+++ b/C4/Overdues.pm
@@ -36,6 +36,7 @@ use Koha::DateUtils;
 use Koha::Account::Lines;
 use Koha::Account::Offsets;
 use Koha::Libraries;
+use Koha::Recalls;
 
 use vars qw(@ISA @EXPORT);
 
@@ -243,6 +244,7 @@ sub CalcFine {
                 'fine',
                 'overduefinescap',
                 'cap_fine_to_replacement_price',
+                'recall_overdue_fine',
             ]
         }
     );
@@ -262,7 +264,26 @@ sub CalcFine {
         # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
         # if chargeperiod_charge_at = 0, we charge at the end of each charge period
         $charge_periods = defined $issuing_rule->{chargeperiod_charge_at} && $issuing_rule->{chargeperiod_charge_at} == 1 ? ceil($charge_periods) : floor($charge_periods);
-        $amount = $charge_periods * $issuing_rule->{fine};
+
+        # check if item has been recalled. recall should have been marked Overdue by cronjob, so only look at overdue recalls
+        # only charge using recall_overdue_fine if there is an item-level recall for this particular item, OR a biblio-level recall
+        my @recalls = Koha::Recalls->search({ biblionumber => $item->{biblionumber}, old => undef, status => 'O' });
+        my $bib_level_recall = 1 if scalar @recalls > 0;
+        foreach my $recall ( @recalls ) {
+            if ( $recall->item_level_recall and $recall->itemnumber == $item->{itemnumber} and $issuing_rule->{recall_overdue_fine} ) {
+                $bib_level_recall = 0;
+                $amount = $charge_periods * $issuing_rule->{recall_overdue_fine};
+                last;
+            }
+        }
+        if ( $bib_level_recall and $issuing_rule->{recall_overdue_fine} ) {
+            # biblio-level recall
+            $amount = $charge_periods * $issuing_rule->{recall_overdue_fine};
+        }
+        if ( scalar @recalls == 0 ) {
+            # no recall, use normal fine amount
+            $amount = $charge_periods * $issuing_rule->{fine};
+        }
     } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
 
     $amount = $issuing_rule->{overduefinescap} if $issuing_rule->{overduefinescap} && $amount > $issuing_rule->{overduefinescap};
diff --git a/misc/cronjobs/recalls/expire_recalls.pl b/misc/cronjobs/recalls/expire_recalls.pl
new file mode 100755
index 00000000000..82f03a3a98e
--- /dev/null
+++ b/misc/cronjobs/recalls/expire_recalls.pl
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+# Copyright 2020 Aleisha Amohia <aleisha@catalyst.net.nz>
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+BEGIN {
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+# set overdue recalls as overdue. This includes:
+# - recalls that have been requested and not fulfilled and have passed their expiration date
+# - recalls that have been awaiting pickup for longer than the specified recall_shelf_time circulation rule, or the RecallMaxPickUpDelay if circ rule is unset
+
+use Koha::Script -cron;
+use Koha::DateUtils;
+use Koha::Recalls;
+use C4::Log;
+
+cronlogaction();
+
+my @recalls = Koha::Recalls->search({ old => undef });
+foreach my $recall (@recalls) {
+    if ( ( $recall->requested or $recall->overdue ) and $recall->expirationdate and dt_from_string( $recall->expirationdate ) < dt_from_string() ){
+        $recall->set_expired({ interface => 'COMMANDLINE' });
+    }
+    if ( $recall->is_waiting ) {
+        my $recall_shelf_time = Koha::CirculationRules->get_effective_rule({
+            categorycode => $recall->patron->categorycode,
+            itemtype => $recall->item->effective_itemtype,
+            branchcode => $recall->branchcode,
+            rule_name => 'recall_shelf_time',
+        });
+        my $waitingdate = dt_from_string( $recall->waitingdate );
+        my $now = dt_from_string();
+        my $days_waiting = $now->subtract_datetime( $waitingdate );
+        if ( defined $recall_shelf_time and $recall_shelf_time->rule_value > 0 ) {
+            if ( $days_waiting->days > $recall_shelf_time->rule_value ) {
+                $recall->set_expired({ interface => 'COMMANDLINE' });
+            }
+        } else {
+            if ( $days_waiting->days > C4::Context->preference('RecallMaxPickUpDelay') ) {
+                $recall->set_expired({ interface => 'COMMANDLINE' });
+            }
+        }
+    }
+}
diff --git a/misc/cronjobs/recalls/overdue_recalls.pl b/misc/cronjobs/recalls/overdue_recalls.pl
new file mode 100755
index 00000000000..d6209d37ccf
--- /dev/null
+++ b/misc/cronjobs/recalls/overdue_recalls.pl
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+# Copyright 2020 Aleisha Amohia <aleisha@catalyst.net.nz>
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+BEGIN {
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+# set overdue recalls as overdue
+
+use Koha::Script -cron;
+use Koha::DateUtils;
+use Koha::Recalls;
+use C4::Log;
+
+cronlogaction();
+
+my @recalls = Koha::Recalls->search({ status => 'R' });
+foreach my $recall (@recalls){
+    if ( $recall->checkout && dt_from_string( $recall->checkout->date_due ) <= dt_from_string() ){
+        $recall->set_overdue;
+    }
+}
diff --git a/t/db_dependent/Circulation/CalcFine.t b/t/db_dependent/Circulation/CalcFine.t
index f09a0e011db..c26aa82ce76 100644
--- a/t/db_dependent/Circulation/CalcFine.t
+++ b/t/db_dependent/Circulation/CalcFine.t
@@ -2,7 +2,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 3;
+use Test::More tests => 4;
 
 use C4::Context;
 use C4::Overdues;
@@ -204,6 +204,60 @@ subtest 'Test cap_fine_to_replacement_pricew with overduefinescap' => sub {
     teardown();
 };
 
+subtest 'Recall overdue fines' => sub {
+    plan tests => 2;
+
+    Koha::CirculationRules->set_rules(
+        {
+            branchcode   => undef,
+            categorycode => undef,
+            itemtype     => undef,
+            rules        => {
+                fine                          => '1.00',
+                lengthunit                    => 'days',
+                finedays                      => 0,
+                firstremind                   => 0,
+                chargeperiod                  => 1,
+                recall_overdue_fine           => '5.00',
+            },
+        }
+    );
+
+    my $start_dt = DateTime->new(
+        year       => 2000,
+        month      => 1,
+        day        => 1,
+    );
+
+    my $end_dt = DateTime->new(
+        year       => 2000,
+        month      => 1,
+        day        => 6,
+    );
+
+    my $recall = Koha::Recall->new({
+        borrowernumber => $patron->{borrowernumber},
+        recalldate => dt_from_string,
+        biblionumber => $item->{biblionumber},
+        branchcode => $branch->{branchcode},
+        status => 'R',
+        itemnumber => $item->{itemnumber},
+        expirationdate => undef,
+        item_level_recall => 1
+    })->store;
+    $recall->set_overdue;
+
+    my ($amount) = CalcFine( $item, $patron->{categorycode}, $branch->{branchcode}, $start_dt, $end_dt );
+    is( int($amount), 25, 'Use recall fine amount specified in circulation rules' );
+
+    $recall->set_finished;
+    ($amount) = CalcFine( $item, $patron->{categorycode}, $branch->{branchcode}, $start_dt, $end_dt );
+    is( int($amount), 5, 'With no recall, use normal fine amount' );
+
+
+    teardown();
+};
+
 sub teardown {
     $dbh->do(q|DELETE FROM circulation_rules|);
 }
-- 
2.11.0