From 93f999951e4f8087254321a4be17f05fa294070f Mon Sep 17 00:00:00 2001
From: Nick Clemens <nick@bywatersolutions.com>
Date: Tue, 26 Sep 2023 17:29:28 +0000
Subject: [PATCH] Bug 34924: Handle final renewal errors

This patch updates Koha::Checkout->attempt_auto_renew to renew when the 'final' errors are passed, and to pass the error value back to the cronjob for processing

The sample notice for AUTO_RENEW and AUTO_RENEW_DGST are both updated to handle the new error

On the next cron the error will be updated to too_many or too_unseen, but a notice will not be sent.

To test:
 0 - Run reset_all to install the updated sample notices or copy the text
 1 - Set system preference UnseenRenewals to 'Allow'
 2 - Setup a circ rule to allow 2 renewals, 2 unseen renewals
 3 - Checkout an item to a patron who has an email defined and auto renewals selected in messaging preferences
 4 - Update the issue to be due/overdue:
    UPDATE issues SET date_due=DATE_SUB(NOW(), INTERVAL 1 DAYS);
 5 - perl misc/cronjobs/automatic_renewals.pl -v -c
 6 - Confirm patron notified and issue renewed
 7 - Set issue due/overdue again
 8 - perl misc/cronjobs/automatic_renewals.pl -v -c
 9 - Confirm patron notified of final unseen renewal
10 - perl misc/cronjobs/automatic_renewals.pl -v -c
11 - Confirm issue not renewed, patron not notified
12 - Update circ rules t all 4 renewals total
13 - Force renewal on staff side to clear unseen
14 - perl misc/cronjobs/automatic_renewals.pl -v -c
15 - Confirm patron notified of final renewal (allowed 4, 2 unseen from cron, 1 manual, this unseen from cron)
16 - perl misc/cronjobs/automatic_renewals.pl -v -c
17 - Confirm patron not notified, issue not renewed
---
 Koha/Checkout.pm                              | 15 +++++--
 .../mysql/en/mandatory/sample_notices.yml     | 12 ++++++
 t/db_dependent/Circulation.t                  |  2 +-
 t/db_dependent/Koha/Checkouts.t               | 39 +++++++++++++++++--
 4 files changed, 60 insertions(+), 8 deletions(-)

diff --git a/Koha/Checkout.pm b/Koha/Checkout.pm
index dab86db7b2..a63ec71ab8 100644
--- a/Koha/Checkout.pm
+++ b/Koha/Checkout.pm
@@ -168,7 +168,8 @@ sub attempt_auto_renew {
 
     # CanBookBeRenewed returns 'auto_renew' when the renewal should be done by this script
     my ( $ok, $error ) = C4::Circulation::CanBookBeRenewed( $self->patron, $self, undef, 1 );
-    if ( $error eq 'auto_renew' ) {
+    my $store_error;
+    if ( $error eq 'auto_renew' || $error eq 'auto_renew_final' || $error eq 'auto_unseen_final' ) {
         if ($confirm) {
             my $date_due = C4::Circulation::AddRenewal(
                 {
@@ -179,14 +180,20 @@ sub attempt_auto_renew {
                     automatic      => 1,
                 }
             );
-            $self->auto_renew_error(undef)->store;
+            $store_error = $error eq 'auto_renew' ? undef : $error;
+            $self->auto_renew_error($store_error)->store;
         }
-        return ( 1, undef, 1 );
+        return ( 1, $store_error, 1 );
     } else {
         my $updated = 0;
         if ( !$self->auto_renew_error || $error ne $self->auto_renew_error ) {
+            $updated = 1
+                unless (
+                $self->auto_renew_error
+                && (   $self->auto_renew_error eq 'auto_renew_final' && $error eq 'too_many'
+                    || $self->auto_renew_error eq 'auto_unseen_final' && $error eq 'too_unseen' )
+                );
             $self->auto_renew_error($error)->store if $confirm;
-            $updated = 1;
         }
         return ( 0, $error, $updated );
     }
diff --git a/installer/data/mysql/en/mandatory/sample_notices.yml b/installer/data/mysql/en/mandatory/sample_notices.yml
index 2fcaacb3e6..12715d9f83 100644
--- a/installer/data/mysql/en/mandatory/sample_notices.yml
+++ b/installer/data/mysql/en/mandatory/sample_notices.yml
@@ -2102,6 +2102,12 @@ tables:
             - "This item must be renewed at the library."
             - "[% ELSIF checkout.auto_renew_error == 'auto_account_expired' %]"
             - "Your account has expired."
+            - "[% ELSIF checkout.auto_renew_error == 'auto_renew_final' %]"
+            - "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]"
+            - "This item has reached the maximum number of automatic renewals and will no longer be renewed."
+            - "[% ELSIF checkout.auto_renew_error == 'auto_unseen_final' %]"
+            - "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]"
+            - "This item has reached the maximum number of unseen renewals and will need to be renewed at the library."
             - "[% END %]"
             - "[% ELSE %]"
             - "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]"
@@ -2176,6 +2182,12 @@ tables:
             - "Your total unpaid fines are too high."
             - "[% ELSIF checkout.auto_renew_error == 'too_unseen' %]"
             - "This item must be renewed at the library."
+            - "[% ELSIF checkout.auto_renew_error == 'auto_renew_final' %]"
+            - "was renewed until [% checkout.date_due | $KohaDates as_due_date => 1%]"
+            - "This item has reached the maximum number of automatic renewals and will no longer be renewed."
+            - "[% ELSIF checkout.auto_renew_error == 'auto_unseen_final' %]"
+            - "was renewed until [% checkout.date_due | $KohaDates as_due_date => 1%]"
+            - "This item has reached the maximum number of unseen renewals and will need to be renewed at the library."
             - "[% END %]"
             - "[% END %]"
 
diff --git a/t/db_dependent/Circulation.t b/t/db_dependent/Circulation.t
index 93229c2de9..374ab20b26 100755
--- a/t/db_dependent/Circulation.t
+++ b/t/db_dependent/Circulation.t
@@ -1389,7 +1389,7 @@ subtest "CanBookBeRenewed tests" => sub {
         $issue->unseen_renewals(1)->renewals_count(1)->store;
 
         ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrower_obj, $issue );
-        is( $renewokay, 0,                  'No success for auto_renewal' );
+        is( $renewokay, 0,                   'No success for auto_renewal' );
         is( $error,     'auto_unseen_final', 'Final unseen renewal reported, not final overall' );
 
         # Final unseen renewal
diff --git a/t/db_dependent/Koha/Checkouts.t b/t/db_dependent/Koha/Checkouts.t
index c776985fb1..9afb25d253 100755
--- a/t/db_dependent/Koha/Checkouts.t
+++ b/t/db_dependent/Koha/Checkouts.t
@@ -412,15 +412,14 @@ subtest 'automatic_checkin' => sub {
 
 subtest 'attempt_auto_renew' => sub {
 
-    plan tests => 17;
+    plan tests => 33;
 
     $schema->storage->txn_begin;
 
     my $renew_error = 'auto_renew';
     my $module      = Test::MockModule->new('C4::Circulation');
     $module->mock( 'CanBookBeRenewed', sub { return ( 1, $renew_error ) } );
-    my $around_now = dt_from_string();
-    $module->mock( 'AddRenewal', sub { warn "AddRenewal called" } );
+    $module->mock( 'AddRenewal',       sub { warn "AddRenewal called" } );
     my $checkout = $builder->build_object(
         {
             class => 'Koha::Checkouts',
@@ -452,6 +451,8 @@ subtest 'attempt_auto_renew' => sub {
     is( $error, undef, "No error when renewed" );
     ok( $updated, "Issue reported as updated when renewed" );
 
+    $module->mock( 'AddRenewal', sub { return; } );
+
     $renew_error = 'anything_else';
     ( $success, $error, $updated ) = $checkout->attempt_auto_renew();
     ok( !$success, "Success is untrue for any other status" );
@@ -471,5 +472,37 @@ subtest 'attempt_auto_renew' => sub {
     ( $success, $error, $updated ) = $checkout->attempt_auto_renew();
     ok( !$updated, "Issue not reported as updated when status has not changed" );
 
+    $renew_error = "auto_unseen_final";
+    ( $success, $error, $updated ) = $checkout->attempt_auto_renew( { confirm => 1 } );
+    ok( $success, "Issue is renewed when error is 'auto_unseen_final'" );
+    is( $error, 'auto_unseen_final', "Error of finality reported when renewed" );
+    ok( $updated, "Issue reported as updated when renewed" );
+    $checkout->discard_changes();
+    is( $checkout->auto_renew_error, 'auto_unseen_final', "Error updated" );
+
+    $renew_error = "too_unseen";
+    ( $success, $error, $updated ) = $checkout->attempt_auto_renew( { confirm => 1 } );
+    ok( !$success, "Issue is not renewed when error is 'too_unseen'" );
+    is( $error, 'too_unseen', "Error reported correctly" );
+    ok( !$updated, "Issue not reported as updated when moved from final to too unseen" );
+    $checkout->discard_changes();
+    is( $checkout->auto_renew_error, 'too_unseen', "Error updated" );
+
+    $renew_error = "auto_renew_final";
+    ( $success, $error, $updated ) = $checkout->attempt_auto_renew( { confirm => 1 } );
+    ok( $success, "Issue is renewed when error is 'auto_renew_final'" );
+    is( $error, 'auto_renew_final', "Error of finality reported when renewed" );
+    ok( $updated, "Issue reported as updated when renewed" );
+    $checkout->discard_changes();
+    is( $checkout->auto_renew_error, 'auto_renew_final', "Error updated" );
+
+    $renew_error = "too_many";
+    ( $success, $error, $updated ) = $checkout->attempt_auto_renew( { confirm => 1 } );
+    ok( !$success, "Issue is not renewed when error is 'too_many'" );
+    is( $error, 'too_many', "Error reported correctly" );
+    ok( !$updated, "Issue not reported as updated when moved from final to too many" );
+    $checkout->discard_changes();
+    is( $checkout->auto_renew_error, 'too_many', "Error updated" );
+
     $schema->storage->txn_rollback;
 };
-- 
2.30.2