From 1a79257a0552a4c4899b090be19a6436c6564181 Mon Sep 17 00:00:00 2001
From: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Date: Wed, 13 Jan 2016 19:17:49 +0000
Subject: [PATCH] Bug 15581: Add a circ rule to limit the auto renewals given a
 delay

This patch adds a new circulation rule (no_auto_renewal_after) to block/allow
auto renewals after a given delay.
For instance, if the issue date is 10 days before today, and
no_auto_renewal_after is set to 10, tomorrow the issue won't be auto
renewed.

Test plan:
0/ Execute the update DB entry
Note: You will have to manually change data in your DB, make sure you
have access to the sql cli.
1/ Define a rule with no_auto_renewal_after (10 for instance) and
norenewalbefore (5 for instance).
(This new rule will behave the same as norenewalbefore: the unit depends
on the lengthunit value).
The automatic renewals will be done from 5 to 10 days ahead.
2/ Modify the issues.issuedate, to simulate a checkout in the past:
    UPDATE issues
    SET issuedate = "yyyy-mm-dd hh:mm:ss"
    WHERE itemnumber = YOUR_ITEMNUMBER;
with issuedate = 2 days before for instance
3/ Execute the automatic renewals cronjob script (misc/cronjobs/automatic_renewals.pl)
Confirm that the issue has not been renewed (too soon)
4/ Repeat step 2 with a due date set as 11 days before
5/ Execute the automatic renewals cronjob script (misc/cronjobs/automatic_renewals.pl)
Confirm that the issue has not been renewed (too late)
6/ Repeat step 2 with a due date set as 7 days before
7/ Execute the automatic renewals cronjob script (misc/cronjobs/automatic_renewals.pl)
Confirm that the issue has been renewed (issues.renewals has been
incremented and date_due has been updated according your circ rules).

Sponsored-by: University of the Arts London
Signed-off-by: Jonathan Field <jonathan.field@ptfs-europe.com>
---
 C4/Circulation.pm                                  | 18 ++++++++-
 admin/smart-rules.pl                               |  3 ++
 installer/data/mysql/atomicupdate/bug_15581.sql    |  1 +
 installer/data/mysql/kohastructure.sql             |  1 +
 .../prog/en/modules/admin/smart-rules.tt           |  4 ++
 .../intranet-tmpl/prog/en/modules/circ/renew.tt    | 12 ++++++
 t/db_dependent/Circulation.t                       | 43 +++++++++++++++++++++-
 7 files changed, 80 insertions(+), 2 deletions(-)
 create mode 100644 installer/data/mysql/atomicupdate/bug_15581.sql

diff --git a/C4/Circulation.pm b/C4/Circulation.pm
index cfe3442..e22c8b6 100644
--- a/C4/Circulation.pm
+++ b/C4/Circulation.pm
@@ -2850,6 +2850,22 @@ sub CanBookBeRenewed {
         return ( 0, 'overdue');
     }
 
+    if ( $itemissue->{auto_renew}
+        and defined $issuingrule->{no_auto_renewal_after}
+                and $issuingrule->{no_auto_renewal_after} ne "" ) {
+
+        # Get issue_date and add no_auto_renewal_after
+        # If this is greater than today, it's too late for renewal.
+        my $maximum_renewal_date = dt_from_string($itemissue->{issuedate});
+        $maximum_renewal_date->add(
+            $issuingrule->{lengthunit} => $issuingrule->{no_auto_renewal_after}
+        );
+        my $now = dt_from_string;
+        if ( $now >= $maximum_renewal_date ) {
+            return ( 0, "auto_too_late" );
+        }
+    }
+
     if ( defined $issuingrule->{norenewalbefore}
         and $issuingrule->{norenewalbefore} ne "" )
     {
@@ -2879,7 +2895,7 @@ sub CanBookBeRenewed {
 
     # Fallback for automatic renewals:
     # If norenewalbefore is undef, don't renew before due date.
-    elsif ( $itemissue->{auto_renew} ) {
+    if ( $itemissue->{auto_renew} ) {
         my $now = dt_from_string;
         return ( 0, "auto_renew" )
           if $now >= $itemissue->{date_due};
diff --git a/admin/smart-rules.pl b/admin/smart-rules.pl
index 1aa6b89..8d62c2c 100755
--- a/admin/smart-rules.pl
+++ b/admin/smart-rules.pl
@@ -136,6 +136,8 @@ elsif ($op eq 'add') {
     my $norenewalbefore  = $input->param('norenewalbefore');
     $norenewalbefore = undef if $norenewalbefore =~ /^\s*$/;
     my $auto_renew = $input->param('auto_renew') eq 'yes' ? 1 : 0;
+    my $no_auto_renewal_after = $input->param('no_auto_renewal_after');
+    $no_auto_renewal_after = undef if $no_auto_renewal_after =~ /^\s*$/;
     my $reservesallowed  = $input->param('reservesallowed');
     my $holds_per_record  = $input->param('holds_per_record');
     my $onshelfholds     = $input->param('onshelfholds') || 0;
@@ -172,6 +174,7 @@ elsif ($op eq 'add') {
         renewalperiod                 => $renewalperiod,
         norenewalbefore               => $norenewalbefore,
         auto_renew                    => $auto_renew,
+        no_auto_renewal_after         => $no_auto_renewal_after,
         reservesallowed               => $reservesallowed,
         holds_per_record              => $holds_per_record,
         issuelength                   => $issuelength,
diff --git a/installer/data/mysql/atomicupdate/bug_15581.sql b/installer/data/mysql/atomicupdate/bug_15581.sql
new file mode 100644
index 0000000..8548968
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/bug_15581.sql
@@ -0,0 +1 @@
+ALTER TABLE issuingrules ADD COLUMN no_auto_renewal_after INT(4) DEFAULT NULL AFTER auto_renew;
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 2142657..225afd9 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -869,6 +869,7 @@ CREATE TABLE `issuingrules` ( -- circulation and fine rules
   `renewalperiod` int(4) default NULL, -- renewal period in the unit set in issuingrules.lengthunit
   `norenewalbefore` int(4) default NULL, -- no renewal allowed until X days or hours before due date.
   `auto_renew` BOOLEAN default FALSE, -- automatic renewal
+  `no_auto_renewal_after` int(4) default NULL, -- no auto renewal allowed after X days or hours after the issue date
   `reservesallowed` smallint(6) NOT NULL default "0", -- how many holds are allowed
   `holds_per_record` SMALLINT(6) NOT NULL DEFAULT 1, -- How many holds a patron can have on a given bib
   `branchcode` varchar(10) NOT NULL default '', -- the branch this rule is for (branches.branchcode)
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
index 9e87404..cb5db63 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
@@ -185,6 +185,7 @@ $(document).ready(function() {
                 <th>Renewal period</th>
                 <th>No renewal before</th>
                 <th>Automatic renewal</th>
+                <th>No automatic renewal after</th>
                 <th>Holds allowed (count)</th>
                 <th>Holds per record (count)</th>
                 <th>On shelf holds allowed</th>
@@ -264,6 +265,7 @@ $(document).ready(function() {
                                 No
                                 [% END %]
                             </td>
+                            <td>[% rule.no_auto_renewal_after %]</td>
 							<td>[% rule.reservesallowed %]</td>
                                                         <td>[% rule.holds_per_record %]</td>
                                                         <td>
@@ -339,6 +341,7 @@ $(document).ready(function() {
                             <option value="yes">Yes</option>
                         </select>
                     </td>
+                    <td><input type="text" name="no_auto_renewal_after" id="no_auto_renewal_after" size="3" /></td>
                     <td><input type="text" name="reservesallowed" id="reservesallowed" size="2" /></td>
                     <td><input type="text" name="holds_per_record" id="holds_per_record" size="2" /></td>
                     <td>
@@ -383,6 +386,7 @@ $(document).ready(function() {
                       <th>Renewal period</th>
                       <th>No renewal before</th>
                       <th>Automatic renewal</th>
+                      <th>No automatic renewal after</th>
                       <th>Holds allowed (count)</th>
                       <th>Holds per record (count)</th>
                       <th>On shelf holds allowed</th>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
index 947767d..edbaaf1 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt
@@ -77,6 +77,18 @@
                                     </form>
                                 [% END %]
 
+                            [% ELSIF error == "auto_too_late" %]
+
+                                <p>[% item.biblio.title %] [% item.biblioitem.subtitle %] ( [% item.barcode %] ) has been scheduled for automatic renewal and cannot be renewed since [% latestrenewdate | $KohaDates %]. </p>
+
+                                [% IF Koha.Preference('AllowRenewalLimitOverride') %]
+                                    <form method="post" action="/cgi-bin/koha/circ/renew.pl">
+                                        <input type="hidden" name="barcode" value="[% item.barcode %]"/>
+                                        <input type="hidden" name="override_limit" value="1" />
+                                        <input type="submit" class="approve" value="Override and renew" />
+                                    </form>
+                                [% END %]
+
                             [% ELSIF error == "auto_renew" %]
 
                                 <p>[% item.biblio.title %] [% item.biblioitem.subtitle %] ( [% item.barcode %] ) has been scheduled for automatic renewal. </p>
diff --git a/t/db_dependent/Circulation.t b/t/db_dependent/Circulation.t
index 18d1c87..6c32a0d 100755
--- a/t/db_dependent/Circulation.t
+++ b/t/db_dependent/Circulation.t
@@ -29,7 +29,7 @@ use Koha::Database;
 
 use t::lib::TestBuilder;
 
-use Test::More tests => 88;
+use Test::More tests => 89;
 
 BEGIN {
     use_ok('C4::Circulation');
@@ -550,6 +550,47 @@ C4::Context->dbh->do("DELETE FROM accountlines");
         'Bug 14101: Cannot renew, renewal is automatic (returned code is auto_renew)'
     );
 
+    subtest "too_late_renewal / no_auto_renewal_after" => sub {
+        plan tests => 8;
+        my $item_to_auto_renew = $builder->build(
+            {   source => 'Item',
+                value  => {
+                    biblionumber  => $biblionumber,
+                    homebranch    => $branch,
+                    holdingbranch => $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 = 9');
+        ( $renewokay, $error ) =
+          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');
+        ( $renewokay, $error ) =
+          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');
+        ( $renewokay, $error ) =
+          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');
+        ( $renewokay, $error ) =
+          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' );
+    };
+
     # Too many renewals
 
     # set policy to forbid renewals
-- 
2.1.4