From 9aed8c7e9ceb8db8fa886c86725003e69747689a Mon Sep 17 00:00:00 2001
From: Aleisha Amohia <aleishaamohia@hotmail.com>
Date: Fri, 8 Oct 2021 16:09:11 +1300
Subject: [PATCH] Bug 31415: Script to automate converting reserves to recalls

This patch adds a script which can be used to automatically convert the
oldest reserve on a record to a recall, if there are a specified minimum
number of reserves on the record.

To test:
1) Enable UseRecalls and configure circulation rules relevant to
recalls. Keep the 'on shelf recalls allowed' setting to 'any' for the
sake of testing ('all' would require all items to be checked out for the
reserve to be converted to a recall)
2) Check out items from multiple records (at least 2 different records)
to Patron A.
3) Place multiple reserves on each record (at least 2 reserves).
4) Place multiple reserves on another record which has no items checked
out.
5) We now have multiple records where the oldest reserve is eligible for
recall, plus a record where the oldest reserve is NOT eligible for
recall (because no items are checked out).
6) In your terminal, get into the shell and run the script. -v means
verbose and will print the reserves that are converted into recalls.
--min 1 means there must be more than 1 reserve on the record to be
considered.
  sudo koha-shell INSTANCE
  perl misc/cronjobs/recalls/convert_reserves_to_recalls.pl -v --min 1
7) Confirm that, for the records where items are checked out and there
are multiple reserves, the oldest reserve was converted into a recall
8) Confirm that, for the record where no items are checked out, the
reserves all remain and not converted into a recall.
9) Confirm documentation for the script looks good
  perldoc misc/cronjobs/recalls/convert_reserves_to_recalls.pl

Sponsored-by: Auckland University of Technology
Signed-off-by: David Nind <david@davidnind.com>
---
 .../recalls/convert_reserves_to_recalls.pl    | 135 ++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100755 misc/cronjobs/recalls/convert_reserves_to_recalls.pl

diff --git a/misc/cronjobs/recalls/convert_reserves_to_recalls.pl b/misc/cronjobs/recalls/convert_reserves_to_recalls.pl
new file mode 100755
index 0000000000..e47ffff821
--- /dev/null
+++ b/misc/cronjobs/recalls/convert_reserves_to_recalls.pl
@@ -0,0 +1,135 @@
+#!/usr/bin/perl
+
+# Copyright 2021 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>.
+
+use Modern::Perl;
+use Getopt::Long;
+
+BEGIN {
+    # find Koha's Perl modules
+    # test carefully before changing this
+    use FindBin;
+    eval { require "$FindBin::Bin/../kohalib.pl" };
+}
+
+use Koha::Script -cron;
+use Koha::DateUtils qw( dt_from_string );
+use Koha::Recalls;
+use C4::Log;
+
+cronlogaction();
+
+=head1 NAME
+
+convert_reserves_to_recalls.pl
+
+=head1 SYNOPSIS
+
+  convert_reserves_to_recalls.pl [-v] [--min X]
+
+This script converts reserves to recalls when a record has a set minimum number of reserves placed.
+
+If a record is found to have the specified minimum number of reserves, the script will find the oldest reserve and convert it to a recall, as long as it would be a legal recall.
+
+It will only convert the single oldest reserve. Once converted, the script will move on to the next record, it will not continue to convert reserves on this record.
+
+  Options:
+    -v          verbose
+    --min X     minimum number of reserves
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-v> | B<--verbose>
+
+Verbose. Without this flag set, only fatal errors are reported.
+
+=item B<-m> | B<--min>
+
+The minimum number of reserves that may exist on a record for it to be selected for conversion
+
+=back
+
+=cut
+
+my $min = 5;
+my $verbose;
+
+GetOptions(
+    'min|m=i' => \$min,
+    'verbose|v' => \$verbose
+) or pod2usage(1);
+
+# get reserves where there is more than $min reserves on one biblio
+my @bib_reserves = Koha::Holds->search({}, {
+    select => [
+        'biblionumber',
+        { count => 'biblionumber', -as => 'bibcount' }
+    ],
+    group_by => 'biblionumber',
+    having => { 'bibcount' => { '>=', $min } },
+})->as_list;
+
+my $count = 0;
+foreach my $bib ( @bib_reserves ) {
+    $bib = $bib->unblessed;
+    if ( $bib->{bibcount} >= $min ) {
+        # If there are $min or more holds on the same record, convert the oldest hold to a recall
+
+        my @reserves = Koha::Holds->search({ biblionumber => $bib->{biblionumber} }, { order_by => { -asc => 'reservedate' } })->as_list;
+        my $reserve_to_convert = $reserves[0];
+        foreach my $res ( @reserves ) {
+            if ( dt_from_string($res->reservedate) < dt_from_string($reserve_to_convert->reservedate) ) {
+                $reserve_to_convert = $res;
+            }
+        }
+
+        my $patron = Koha::Patrons->find( $reserve_to_convert->borrowernumber );
+        my $biblio = Koha::Biblios->find( $reserve_to_convert->biblionumber );
+        my $item;
+        my $can_convert;
+        if ( $reserve_to_convert->item_level_hold ) {
+            $item = Koha::Items->find( $reserve_to_convert->itemnumber );
+            if ( $item->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) {
+                $can_convert = 1;
+            }
+        } else {
+            if ( $biblio->can_be_recalled({ patron => $patron, hold_convert => 1 }) ) {
+                $can_convert = 1;
+            }
+        }
+        if ( $can_convert ) {
+            my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
+                patron => $patron,
+                biblio => $biblio,
+                branchcode => $reserve_to_convert->branchcode,
+                item => $item,
+                expirationdate => $reserve_to_convert->expirationdate,
+                interface => 'commandline',
+            });
+            $reserve_to_convert->cancel({ cancellation_reason => 'RECALLED' });
+            $count++;
+            if ( $verbose ) {
+                my $reserve_id = $reserve_to_convert->reserve_id;
+                my $biblionumber = $reserve_to_convert->biblionumber;
+                print "$count. Hold converted to recall (reserve_id: $reserve_id, biblionumber: $biblionumber).\n";
+            }
+        }
+    }
+}
-- 
2.30.2