View | Details | Raw Unified | Return to bug 8367
Collapse All | Expand All

(-)a/C4/Letters.pm (-15 lines)
Lines 798-818 sub _parseletter_sth { Link Here
798
sub _parseletter {
798
sub _parseletter {
799
    my ( $letter, $table, $values ) = @_;
799
    my ( $letter, $table, $values ) = @_;
800
800
801
    if ( $table eq 'reserves' && $values->{'waitingdate'} ) {
802
        my @waitingdate = split /-/, $values->{'waitingdate'};
803
804
        $values->{'expirationdate'} = '';
805
        if( C4::Context->preference('ExpireReservesMaxPickUpDelay') &&
806
        C4::Context->preference('ReservesMaxPickUpDelay') ) {
807
            my $dt = dt_from_string();
808
            $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') );
809
            $values->{'expirationdate'} = output_pref({ dt => $dt, dateonly => 1 });
810
        }
811
812
        $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 });
813
814
    }
815
816
    if ($letter->{content} && $letter->{content} =~ /<<today>>/) {
801
    if ($letter->{content} && $letter->{content} =~ /<<today>>/) {
817
        my $todaysdate = output_pref( DateTime->now() );
802
        my $todaysdate = output_pref( DateTime->now() );
818
        $letter->{content} =~ s/<<today>>/$todaysdate/go;
803
        $letter->{content} =~ s/<<today>>/$todaysdate/go;
(-)a/C4/Reserves.pm (-28 / +184 lines)
Lines 167-192 sub AddReserve { Link Here
167
	# Make room in reserves for this before those of a later reserve date
167
	# Make room in reserves for this before those of a later reserve date
168
	$priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
168
	$priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
169
    }
169
    }
170
    my $waitingdate;
170
    my ($waitingdate, $lastpickupdate);
171
171
172
    my $item = C4::Items::GetItem( $checkitem );
172
    # If the reserv had the waiting status, we had the value of the resdate
173
    # If the reserv had the waiting status, we had the value of the resdate
173
    if ( $found eq 'W' ) {
174
    if ( $found eq 'W' ) {
174
        $waitingdate = $resdate;
175
        $waitingdate = $resdate;
176
177
        #The reserve-object doesn't exist yet in DB, so we must supply what information we have to GetLastPickupDate() so it can do it's work.
178
        my $reserve = {borrowernumber => $borrowernumber, waitingdate => $waitingdate, branchcode => $branch};
179
        $lastpickupdate = GetLastPickupDate( $reserve, $item );
175
    }
180
    }
176
181
177
    # updates take place here
182
    # updates take place here
178
    my $query = qq{
183
    my $query = qq{
179
        INSERT INTO reserves
184
        INSERT INTO reserves
180
            (borrowernumber,biblionumber,reservedate,branchcode,
185
            (borrowernumber,biblionumber,reservedate,branchcode,
181
            priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
186
            priority,reservenotes,itemnumber,found,waitingdate,expirationdate,lastpickupdate)
182
        VALUES
187
        VALUES
183
             (?,?,?,?,?,
188
             (?,?,?,?,?,
184
             ?,?,?,?,?)
189
             ?,?,?,?,?,?)
185
             };
190
             };
186
    my $sth = $dbh->prepare($query);
191
    my $sth = $dbh->prepare($query);
187
    $sth->execute(
192
    $sth->execute(
188
        $borrowernumber, $biblionumber, $resdate, $branch,      $priority,
193
        $borrowernumber, $biblionumber, $resdate, $branch,      $priority,
189
        $notes,          $checkitem,    $found,   $waitingdate, $expdate
194
        $notes,          $checkitem,    $found,   $waitingdate, $expdate,
195
        $lastpickupdate
190
    );
196
    );
191
    my $reserve_id = $sth->{mysql_insertid};
197
    my $reserve_id = $sth->{mysql_insertid};
192
    # add a reserve fee if needed
198
    # add a reserve fee if needed
Lines 738-744 sub GetReservesForBranch { Link Here
738
    my $dbh = C4::Context->dbh;
744
    my $dbh = C4::Context->dbh;
739
745
740
    my $query = "
746
    my $query = "
741
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
747
        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate, lastpickupdate
742
        FROM   reserves 
748
        FROM   reserves 
743
        WHERE   priority='0'
749
        WHERE   priority='0'
744
        AND found='W'
750
        AND found='W'
Lines 762-767 sub GetReservesForBranch { Link Here
762
    return (@transreserv);
768
    return (@transreserv);
763
}
769
}
764
770
771
=head2 GetLastPickupDate
772
773
  my $lastpickupdate = GetLastPickupDate($reserve, $item);
774
  my $lastpickupdate = GetLastPickupDate($reserve, $item, $borrower);
775
  my $lastpickupdate = GetLastPickupDate(undef,    $item);
776
777
Gets the last pickup date from the issuingrules for the given reserves-row and sets the
778
$reserve->{lastpickupdate}.-value.
779
If the reserves-row is not passed, function tries to figure it out from the item-row.
780
781
Calculating the last pickup date respects Calendar holidays and skips to the next open day.
782
783
If the issuingrule's holdspickupwait is 0 or less, means that the lastpickupdate-feature
784
is disabled for this kind of material.
785
786
@PARAM1 koha.reserves-row
787
@PARAM2 koha.items-row, If the reserve is not given, an item must be given to be
788
                        able to find a reservation
789
@PARAM3 koha.borrowers-row, OPTIONAL
790
RETURNS DateTime, depicting the last pickup date.
791
        OR undef if there is an error or if the issuingrules disable this feature for this material.
792
=cut
793
794
sub GetLastPickupDate {
795
    my ($reserve, $item, $borrower) = @_;
796
797
    ##Verify parameters
798
    if ( not defined $reserve and not defined $item ) {
799
        warn "C4::Reserves::GetMaxPickupDate(), is called without a reserve and an item";
800
        return;
801
    }
802
    if ( defined $reserve and not defined $item ) {
803
        $item = C4::Items::GetItem( $reserve->{itemnumber} );
804
    }
805
    unless ( defined $reserve ) {
806
        my $reserve = GetReservesFromItemnumber( $item->{itemnumber} );
807
    }
808
809
    my $date = $reserve->{waitingdate};
810
    unless ( $date ) { #It is possible that a reserve is just caught and it doesn't have a waitingdate yet.
811
        $date = DateTime->now( time_zone => C4::Context->tz() ); #So default to NOW()
812
    }
813
    else {
814
        $date = (ref $reserve->{waitingdate} eq 'DateTime') ? $reserve->{waitingdate}  :  dt_from_string($reserve->{waitingdate});
815
    }
816
    $borrower = C4::Members::GetMember( 'borrowernumber' => $reserve->{borrowernumber} ) unless $borrower;
817
818
    ##Get churning the LastPickupDate
819
    my $controlbranch = GetReservesControlBranch( $item, $borrower );
820
821
    my $issuingrule = C4::Circulation::GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $controlbranch );
822
823
    if ( defined($issuingrule)
824
        && defined $issuingrule->{holdspickupwait}
825
        && $issuingrule->{holdspickupwait} > 0 ) { #If holdspickupwait is <= 0, it means this feature is disabled for this type of material.
826
827
        $date->add( days => $issuingrule->{holdspickupwait} );
828
829
        my $calendar = Koha::Calendar->new( branchcode => $reserve->{'branchcode'} );
830
        my $is_holiday = $calendar->is_holiday( $date );
831
832
        while ( $is_holiday ) {
833
            $date->add( days => 1 );
834
            $is_holiday = $calendar->is_holiday( $date );
835
        }
836
837
        $reserve->{lastpickupdate} = $date->ymd();
838
        return $date;
839
    }
840
    #Without explicitly setting the return value undef, the lastpickupdate-column is
841
    #  set as 0000-00-00 instead of NULL in DBD::MySQL.
842
    #This causes this hold to always expire any lastpickupdate check,
843
    #  effectively canceling it as soon as cancel_expired_holds.pl is ran.
844
    #This is exactly the opposite of disabling the autoexpire feature.
845
    #So please let me explicitly return undef, because Perl cant always handle everything.
846
    delete $reserve->{lastpickupdate};
847
    return undef;
848
}
849
=head2 GetReservesControlBranch
850
851
  $branchcode = &GetReservesControlBranch($borrower, $item)
852
853
Returns the branchcode to consider to check hold rules against
854
855
=cut
856
857
sub GetReservesControlBranch {
858
    my ( $borrower, $item ) = @_;
859
    my $controlbranch = C4::Context->preference('ReservesControlBranch');
860
    my $hbr           = C4::Context->preference('HomeOrHoldingBranch') || "homebranch";
861
    my $branchcode    = "*";
862
    if ( $controlbranch eq "ItemHomeLibrary" ) {
863
        $branchcode = $item->{$hbr};
864
    } elsif ( $controlbranch eq "PatronLibrary" ) {
865
        $branchcode = $borrower->{branchcode};
866
    }
867
    return $branchcode;
868
}
869
765
=head2 GetReserveStatus
870
=head2 GetReserveStatus
766
871
767
  $reservestatus = GetReserveStatus($itemnumber);
872
  $reservestatus = GetReserveStatus($itemnumber);
Lines 971-999 sub CancelExpiredReserves { Link Here
971
1076
972
    # Cancel reserves that have been waiting too long
1077
    # Cancel reserves that have been waiting too long
973
    if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) {
1078
    if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) {
974
        my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
975
        my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
976
1079
977
        my $today = dt_from_string();
1080
        my $query = "SELECT * FROM reserves WHERE NOW() > lastpickupdate AND found = 'W' AND priority = 0";
978
979
        my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
980
        $sth = $dbh->prepare( $query );
1081
        $sth = $dbh->prepare( $query );
981
        $sth->execute( $max_pickup_delay );
1082
        $sth->execute();
982
1083
983
        while ( my $res = $sth->fetchrow_hashref ) {
1084
        while ( my $res = $sth->fetchrow_hashref ) {
984
            my $do_cancel = 1;
985
            unless ( $cancel_on_holidays ) {
986
                my $calendar = Koha::Calendar->new( branchcode => $res->{'branchcode'} );
987
                my $is_holiday = $calendar->is_holiday( $today );
988
989
                if ( $is_holiday ) {
990
                    $do_cancel = 0;
991
                }
992
            }
993
994
            if ( $do_cancel ) {
995
                CancelReserve({ reserve_id => $res->{'reserve_id'}, charge_cancel_fee => 1 });
1085
                CancelReserve({ reserve_id => $res->{'reserve_id'}, charge_cancel_fee => 1 });
996
            }
997
        }
1086
        }
998
    }
1087
    }
999
1088
Lines 1239-1248 sub ModReserveStatus { Link Here
1239
    #first : check if we have a reservation for this item .
1328
    #first : check if we have a reservation for this item .
1240
    my ($itemnumber, $newstatus) = @_;
1329
    my ($itemnumber, $newstatus) = @_;
1241
    my $dbh = C4::Context->dbh;
1330
    my $dbh = C4::Context->dbh;
1331
    
1332
    my $now = dt_from_string;
1333
    my $reserve = $dbh->selectrow_hashref(q{
1334
        SELECT *
1335
        FROM reserves
1336
        WHERE itemnumber = ?
1337
            AND found IS NULL
1338
            AND priority = 0
1339
    }, {}, $itemnumber);
1340
    return unless $reserve;
1341
    
1342
    my $lastpickupdate = GetLastPickupDate( $reserve );
1242
1343
1243
    my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1344
    my $query = q{
1345
        UPDATE reserves
1346
        SET found = ?,
1347
            waitingdate = ?,
1348
            lastpickupdate = ?
1349
        WHERE itemnumber = ?
1350
            AND found IS NULL
1351
            AND priority = 0
1352
    };
1244
    my $sth_set = $dbh->prepare($query);
1353
    my $sth_set = $dbh->prepare($query);
1245
    $sth_set->execute( $newstatus, $itemnumber );
1354
    $sth_set->execute( $newstatus, $now, $lastpickupdate, $itemnumber );
1246
1355
1247
    if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1356
    if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1248
      CartToShelf( $itemnumber );
1357
      CartToShelf( $itemnumber );
Lines 1295-1315 sub ModReserveAffect { Link Here
1295
        WHERE borrowernumber = ?
1404
        WHERE borrowernumber = ?
1296
          AND biblionumber = ?
1405
          AND biblionumber = ?
1297
    ";
1406
    ";
1407
        $sth = $dbh->prepare($query);
1408
        $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1298
    }
1409
    }
1299
    else {
1410
    else {
1300
    # affect the reserve to Waiting as well.
1411
    # affect the reserve to Waiting as well.
1412
        my $item = C4::Items::GetItem( $itemnumber );
1413
        my $lastpickupdate = GetLastPickupDate( $request, $item );
1301
        $query = "
1414
        $query = "
1302
            UPDATE reserves
1415
            UPDATE reserves
1303
            SET     priority = 0,
1416
            SET     priority = 0,
1304
                    found = 'W',
1417
                    found = 'W',
1305
                    waitingdate = NOW(),
1418
                    waitingdate = NOW(),
1419
                    lastpickupdate = ?,
1306
                    itemnumber = ?
1420
                    itemnumber = ?
1307
            WHERE borrowernumber = ?
1421
            WHERE borrowernumber = ?
1308
              AND biblionumber = ?
1422
              AND biblionumber = ?
1309
        ";
1423
        ";
1424
        $sth = $dbh->prepare($query);
1425
        $sth->execute( $lastpickupdate, $itemnumber, $borrowernumber,$biblionumber);
1310
    }
1426
    }
1311
    $sth = $dbh->prepare($query);
1427
1312
    $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1313
    _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1428
    _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1314
    _FixPriority( { biblionumber => $biblionumber } );
1429
    _FixPriority( { biblionumber => $biblionumber } );
1315
    if ( C4::Context->preference("ReturnToShelvingCart") ) {
1430
    if ( C4::Context->preference("ReturnToShelvingCart") ) {
Lines 1385-1390 sub GetReserveInfo { Link Here
1385
                   reserves.biblionumber,
1500
                   reserves.biblionumber,
1386
                   reserves.branchcode,
1501
                   reserves.branchcode,
1387
                   reserves.waitingdate,
1502
                   reserves.waitingdate,
1503
                   reserves.lastpickupdate,
1388
                   notificationdate,
1504
                   notificationdate,
1389
                   reminderdate,
1505
                   reminderdate,
1390
                   priority,
1506
                   priority,
Lines 1821-1826 sub _Findgroupreserve { Link Here
1821
               reserves.borrowernumber      AS borrowernumber,
1937
               reserves.borrowernumber      AS borrowernumber,
1822
               reserves.reservedate         AS reservedate,
1938
               reserves.reservedate         AS reservedate,
1823
               reserves.branchcode          AS branchcode,
1939
               reserves.branchcode          AS branchcode,
1940
               reserves.lastpickupdate      AS lastpickupdate,
1824
               reserves.cancellationdate    AS cancellationdate,
1941
               reserves.cancellationdate    AS cancellationdate,
1825
               reserves.found               AS found,
1942
               reserves.found               AS found,
1826
               reserves.reservenotes        AS reservenotes,
1943
               reserves.reservenotes        AS reservenotes,
Lines 1855-1860 sub _Findgroupreserve { Link Here
1855
               reserves.borrowernumber      AS borrowernumber,
1972
               reserves.borrowernumber      AS borrowernumber,
1856
               reserves.reservedate         AS reservedate,
1973
               reserves.reservedate         AS reservedate,
1857
               reserves.branchcode          AS branchcode,
1974
               reserves.branchcode          AS branchcode,
1975
               reserves.lastpickupdate      AS lastpickupdate,
1858
               reserves.cancellationdate    AS cancellationdate,
1976
               reserves.cancellationdate    AS cancellationdate,
1859
               reserves.found               AS found,
1977
               reserves.found               AS found,
1860
               reserves.reservenotes        AS reservenotes,
1978
               reserves.reservenotes        AS reservenotes,
Lines 1889-1894 sub _Findgroupreserve { Link Here
1889
               reserves.reservedate                AS reservedate,
2007
               reserves.reservedate                AS reservedate,
1890
               reserves.waitingdate                AS waitingdate,
2008
               reserves.waitingdate                AS waitingdate,
1891
               reserves.branchcode                 AS branchcode,
2009
               reserves.branchcode                 AS branchcode,
2010
               reserves.lastpickupdate             AS lastpickupdate,
1892
               reserves.cancellationdate           AS cancellationdate,
2011
               reserves.cancellationdate           AS cancellationdate,
1893
               reserves.found                      AS found,
2012
               reserves.found                      AS found,
1894
               reserves.reservenotes               AS reservenotes,
2013
               reserves.reservenotes               AS reservenotes,
Lines 2145-2150 sub MoveReserve { Link Here
2145
    }
2264
    }
2146
}
2265
}
2147
2266
2267
=head MoveWaitingdate
2268
2269
  #Move waitingdate two months and fifteen days forward.
2270
  my $dateDuration = DateTime::Duration->new( months => 2, days => 15 );
2271
  $reserve = MoveWaitingdate( $reserve, $dateDuration);
2272
2273
  #Move waitingdate one year and eleven days backwards.
2274
  my $dateDuration = DateTime::Duration->new( years => -1, days => -11 );
2275
  $reserve = MoveWaitingdate( $reserve, $dateDuration);
2276
2277
Moves the waitingdate and updates the lastpickupdate to match.
2278
If waitingdate is not defined, uses today.
2279
Is intended to be used from automated tests, because under normal library
2280
operations there should be NO REASON to move the waitingdate.
2281
2282
@PARAM1 koha.reserves-row, with waitingdate set.
2283
@PARAM2 DateTime::Duration, with the desired offset.
2284
RETURNS koha.reserve-row, with keys waitingdate and lastpickupdate updated.
2285
=cut
2286
sub MoveWaitingdate {
2287
    my ($reserve, $dateDuration) = @_;
2288
2289
    my $dt = dt_from_string( $reserve->{waitingdate} );
2290
    $dt->add_duration( $dateDuration );
2291
    $reserve->{waitingdate} = $dt->ymd();
2292
2293
    GetLastPickupDate( $reserve ); #Update the $reserve->{lastpickupdate}
2294
2295
    #UPDATE the DB part
2296
    my $dbh = C4::Context->dbh();
2297
    my $sth = $dbh->prepare( "UPDATE reserves SET waitingdate=?, lastpickupdate=? WHERE reserve_id=?" );
2298
    $sth->execute( $reserve->{waitingdate}, $reserve->{lastpickupdate}, $reserve->{reserve_id} );
2299
2300
    return $reserve;
2301
}
2302
2148
=head2 MergeHolds
2303
=head2 MergeHolds
2149
2304
2150
  MergeHolds($dbh,$to_biblio, $from_biblio);
2305
  MergeHolds($dbh,$to_biblio, $from_biblio);
Lines 2241-2247 sub RevertWaitingStatus { Link Here
2241
    SET
2396
    SET
2242
      priority = 1,
2397
      priority = 1,
2243
      found = NULL,
2398
      found = NULL,
2244
      waitingdate = NULL
2399
      waitingdate = NULL,
2400
      lastpickupdate = NULL,
2245
    WHERE
2401
    WHERE
2246
      reserve_id = ?
2402
      reserve_id = ?
2247
    ";
2403
    ";
(-)a/admin/smart-rules.pl (+2 lines)
Lines 117-122 elsif ($op eq 'add') { Link Here
117
    $norenewalbefore = undef if $norenewalbefore eq '0' or $norenewalbefore =~ /^\s*$/;
117
    $norenewalbefore = undef if $norenewalbefore eq '0' or $norenewalbefore =~ /^\s*$/;
118
    my $auto_renew = $input->param('auto_renew') eq 'yes' ? 1 : 0;
118
    my $auto_renew = $input->param('auto_renew') eq 'yes' ? 1 : 0;
119
    my $reservesallowed  = $input->param('reservesallowed');
119
    my $reservesallowed  = $input->param('reservesallowed');
120
    my $holdspickupwait = $input->param('holdspickupwait');
120
    my $onshelfholds     = $input->param('onshelfholds') || 0;
121
    my $onshelfholds     = $input->param('onshelfholds') || 0;
121
    $maxissueqty =~ s/\s//g;
122
    $maxissueqty =~ s/\s//g;
122
    $maxissueqty = undef if $maxissueqty !~ /^\d+/;
123
    $maxissueqty = undef if $maxissueqty !~ /^\d+/;
Lines 148-153 elsif ($op eq 'add') { Link Here
148
        norenewalbefore    => $norenewalbefore,
149
        norenewalbefore    => $norenewalbefore,
149
        auto_renew         => $auto_renew,
150
        auto_renew         => $auto_renew,
150
        reservesallowed    => $reservesallowed,
151
        reservesallowed    => $reservesallowed,
152
        holdspickupwait    => $holdspickupwait,
151
        issuelength        => $issuelength,
153
        issuelength        => $issuelength,
152
        lengthunit         => $lengthunit,
154
        lengthunit         => $lengthunit,
153
        hardduedate        => $hardduedate,
155
        hardduedate        => $hardduedate,
(-)a/circ/waitingreserves.pl (-29 / +27 lines)
Lines 30-40 use C4::Members; Link Here
30
use C4::Biblio;
30
use C4::Biblio;
31
use C4::Items;
31
use C4::Items;
32
use Koha::DateUtils;
32
use Koha::DateUtils;
33
use Date::Calc qw(
34
  Today
35
  Add_Delta_Days
36
  Date_to_Days
37
);
38
use C4::Reserves;
33
use C4::Reserves;
39
use C4::Koha;
34
use C4::Koha;
40
35
Lines 85-115 my ($reservcount, $overcount); Link Here
85
my @getreserves = $all_branches ? GetReservesForBranch() : GetReservesForBranch($default);
80
my @getreserves = $all_branches ? GetReservesForBranch() : GetReservesForBranch($default);
86
# get reserves for the branch we are logged into, or for all branches
81
# get reserves for the branch we are logged into, or for all branches
87
82
88
my $today = Date_to_Days(&Today);
83
my $today = dt_from_string;
89
foreach my $num (@getreserves) {
84
foreach my $num (@getreserves) {
90
    next unless ($num->{'waitingdate'} && $num->{'waitingdate'} ne '0000-00-00');
85
    next unless ($num->{'waitingdate'} && $num->{'waitingdate'} ne '0000-00-00');
91
86
92
    my $itemnumber = $num->{'itemnumber'};
87
    my $itemnumber = $num->{'itemnumber'};
93
    my $gettitle     = GetBiblioFromItemNumber( $itemnumber );
88
    my $gettitle     = GetBiblioFromItemNumber( $itemnumber );
94
    my $borrowernum = $num->{'borrowernumber'};
89
    my $borrowernumber = $num->{'borrowernumber'};
95
    my $holdingbranch = $gettitle->{'holdingbranch'};
90
    my $holdingbranch = $gettitle->{'holdingbranch'};
96
    my $homebranch = $gettitle->{'homebranch'};
91
    my $homebranch = $gettitle->{'homebranch'};
97
92
98
    my %getreserv = (
93
    my %getreserv = (
99
        itemnumber => $itemnumber,
94
        itemnumber => $itemnumber,
100
        borrowernum => $borrowernum,
95
        borrowernum => $borrowernumber,
101
    );
96
    );
102
97
103
    # fix up item type for display
98
    # fix up item type for display
104
    $gettitle->{'itemtype'} = C4::Context->preference('item-level_itypes') ? $gettitle->{'itype'} : $gettitle->{'itemtype'};
99
    $gettitle->{'itemtype'} = C4::Context->preference('item-level_itypes') ? $gettitle->{'itype'} : $gettitle->{'itemtype'};
105
    my $getborrower = GetMember(borrowernumber => $num->{'borrowernumber'});
100
    my $getborrower = GetMember(borrowernumber => $num->{'borrowernumber'});
106
    my $itemtypeinfo = getitemtypeinfo( $gettitle->{'itemtype'} );  # using the fixed up itype/itemtype
101
    my $itemtypeinfo = getitemtypeinfo( $gettitle->{'itemtype'} );  # using the fixed up itype/itemtype
107
    $getreserv{'waitingdate'} = $num->{'waitingdate'};
102
108
    my ( $waiting_year, $waiting_month, $waiting_day ) = split (/-/, $num->{'waitingdate'});
103
    if ( $num->{waitingdate} ) {
109
    ( $waiting_year, $waiting_month, $waiting_day ) =
104
        my $lastpickupdate = dt_from_string($num->{lastpickupdate});
110
      Add_Delta_Days( $waiting_year, $waiting_month, $waiting_day,
105
        $getreserv{waitingdate} = $num->{waitingdate};
111
        C4::Context->preference('ReservesMaxPickUpDelay'));
106
        $getreserv{lastpickupdate} = $num->{lastpickupdate};
112
    my $calcDate = Date_to_Days( $waiting_year, $waiting_month, $waiting_day );
107
        if ( DateTime->compare( $today, $lastpickupdate ) == 1 ) {
108
            if ($cancelall) {
109
                my $res = cancel( $itemnumber, $borrowernumber, $holdingbranch, $homebranch, !$transfer_when_cancel_all );
110
                push @cancel_result, $res if $res;
111
                next;
112
            } else {
113
                push @overloop,   \%getreserv;
114
                $overcount++;
115
            }
116
        }else{
117
            push @reservloop, \%getreserv;
118
            $reservcount++;
119
        }
120
    }
113
121
114
    $getreserv{'itemtype'}       = $itemtypeinfo->{'description'};
122
    $getreserv{'itemtype'}       = $itemtypeinfo->{'description'};
115
    $getreserv{'title'}          = $gettitle->{'title'};
123
    $getreserv{'title'}          = $gettitle->{'title'};
Lines 129-153 foreach my $num (@getreserves) { Link Here
129
    $getreserv{'borrowerfirstname'} = $getborrower->{'firstname'};
137
    $getreserv{'borrowerfirstname'} = $getborrower->{'firstname'};
130
    $getreserv{'borrowerphone'}     = $getborrower->{'phone'};
138
    $getreserv{'borrowerphone'}     = $getborrower->{'phone'};
131
139
132
    my $borEmail = GetFirstValidEmailAddress( $borrowernum );
140
    my $borEmail = GetFirstValidEmailAddress( $borrowernumber );
133
141
134
    if ( $borEmail ) {
142
    if ( $borEmail ) {
135
        $getreserv{'borrowermail'}  = $borEmail;
143
        $getreserv{'borrowermail'}  = $borEmail;
136
    }
144
    }
137
138
    if ($today > $calcDate) {
139
        if ($cancelall) {
140
            my $res = cancel( $itemnumber, $borrowernum, $holdingbranch, $homebranch, !$transfer_when_cancel_all );
141
            push @cancel_result, $res if $res;
142
            next;
143
        } else {
144
            push @overloop,   \%getreserv;
145
            $overcount++;
146
        }
147
    }else{
148
        push @reservloop, \%getreserv;
149
        $reservcount++;
150
    }
151
    
145
    
152
}
146
}
153
147
Lines 157-164 $template->param( Link Here
157
    reservecount => $reservcount,
151
    reservecount => $reservcount,
158
    overloop    => \@overloop,
152
    overloop    => \@overloop,
159
    overcount   => $overcount,
153
    overcount   => $overcount,
154
<<<<<<< HEAD
160
    show_date   => output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }),
155
    show_date   => output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }),
161
    ReservesMaxPickUpDelay => C4::Context->preference('ReservesMaxPickUpDelay')
156
    ReservesMaxPickUpDelay => C4::Context->preference('ReservesMaxPickUpDelay')
157
=======
158
    show_date   => format_date(C4::Dates->today('iso')),
159
>>>>>>> Bug 8367 - How long is a hold waiting for pickup at a more granular level
162
);
160
);
163
161
164
if ($cancelall) {
162
if ($cancelall) {
(-)a/installer/data/mysql/kohastructure.sql (+3 lines)
Lines 1195-1200 CREATE TABLE `issuingrules` ( -- circulation and fine rules Link Here
1195
  `norenewalbefore` int(4) default NULL, -- no renewal allowed until X days or hours before due date. In the unit set in issuingrules.lengthunit
1195
  `norenewalbefore` int(4) default NULL, -- no renewal allowed until X days or hours before due date. In the unit set in issuingrules.lengthunit
1196
  `auto_renew` BOOLEAN default FALSE, -- automatic renewal
1196
  `auto_renew` BOOLEAN default FALSE, -- automatic renewal
1197
  `reservesallowed` smallint(6) NOT NULL default "0", -- how many holds are allowed
1197
  `reservesallowed` smallint(6) NOT NULL default "0", -- how many holds are allowed
1198
  `holdspickupwait` int(11)  default NULL, -- How many open library days a hold can wait in the pickup shelf until it becomes problematic
1198
  `branchcode` varchar(10) NOT NULL default '', -- the branch this rule is for (branches.branchcode)
1199
  `branchcode` varchar(10) NOT NULL default '', -- the branch this rule is for (branches.branchcode)
1199
  overduefinescap decimal(28,6) default NULL, -- the maximum amount of an overdue fine
1200
  overduefinescap decimal(28,6) default NULL, -- the maximum amount of an overdue fine
1200
  onshelfholds tinyint(1) NOT NULL default 0, -- allow holds for items that are on shelf
1201
  onshelfholds tinyint(1) NOT NULL default 0, -- allow holds for items that are on shelf
Lines 1668-1673 CREATE TABLE `old_reserves` ( -- this table holds all holds/reserves that have b Link Here
1668
  `lowestPriority` tinyint(1) NOT NULL, -- has this hold been pinned to the lowest priority in the holds queue (1 for yes, 0 for no)
1669
  `lowestPriority` tinyint(1) NOT NULL, -- has this hold been pinned to the lowest priority in the holds queue (1 for yes, 0 for no)
1669
  `suspend` BOOLEAN NOT NULL DEFAULT 0, -- in this hold suspended (1 for yes, 0 for no)
1670
  `suspend` BOOLEAN NOT NULL DEFAULT 0, -- in this hold suspended (1 for yes, 0 for no)
1670
  `suspend_until` DATETIME NULL DEFAULT NULL, -- the date this hold is suspended until (NULL for infinitely)
1671
  `suspend_until` DATETIME NULL DEFAULT NULL, -- the date this hold is suspended until (NULL for infinitely)
1672
  `lastpickupdate` date NULL DEFAULT NULL, -- the last day this hold is available for pickup, until it becomes problematic
1671
  PRIMARY KEY (`reserve_id`),
1673
  PRIMARY KEY (`reserve_id`),
1672
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
1674
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
1673
  KEY `old_reserves_biblionumber` (`biblionumber`),
1675
  KEY `old_reserves_biblionumber` (`biblionumber`),
Lines 1841-1846 CREATE TABLE `reserves` ( -- information related to holds/reserves in Koha Link Here
1841
  `lowestPriority` tinyint(1) NOT NULL,
1843
  `lowestPriority` tinyint(1) NOT NULL,
1842
  `suspend` BOOLEAN NOT NULL DEFAULT 0,
1844
  `suspend` BOOLEAN NOT NULL DEFAULT 0,
1843
  `suspend_until` DATETIME NULL DEFAULT NULL,
1845
  `suspend_until` DATETIME NULL DEFAULT NULL,
1846
  `lastpickupdate` date NULL DEFAULT NULL, -- the last day this hold is available for pickup, until it becomes problematic
1844
  PRIMARY KEY (`reserve_id`),
1847
  PRIMARY KEY (`reserve_id`),
1845
  KEY priorityfoundidx (priority,found),
1848
  KEY priorityfoundidx (priority,found),
1846
  KEY `borrowernumber` (`borrowernumber`),
1849
  KEY `borrowernumber` (`borrowernumber`),
(-)a/installer/data/mysql/sysprefs.sql (-2 lines)
Lines 127-133 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
127
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
127
('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'),
128
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
128
('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'),
129
('ExpireReservesMaxPickUpDelayCharge','0',NULL,'If ExpireReservesMaxPickUpDelay is enabled, and this field has a non-zero value, than a borrower whose waiting hold has expired will be charged this amount.','free'),
129
('ExpireReservesMaxPickUpDelayCharge','0',NULL,'If ExpireReservesMaxPickUpDelay is enabled, and this field has a non-zero value, than a borrower whose waiting hold has expired will be charged this amount.','free'),
130
('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'),
131
('ExtendedPatronAttributes','0',NULL,'Use extended patron IDs and attributes','YesNo'),
130
('ExtendedPatronAttributes','0',NULL,'Use extended patron IDs and attributes','YesNo'),
132
('FacetLabelTruncationLength','20',NULL,'Specify the facet max length in OPAC','Integer'),
131
('FacetLabelTruncationLength','20',NULL,'Specify the facet max length in OPAC','Integer'),
133
('FacetMaxCount','20',NULL,'Specify the max facet count for each category','Integer'),
132
('FacetMaxCount','20',NULL,'Specify the max facet count for each category','Integer'),
Lines 365-371 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
365
('ReportsLog','0',NULL,'If ON, log information about reports.','YesNo'),
364
('ReportsLog','0',NULL,'If ON, log information about reports.','YesNo'),
366
('RequestOnOpac','1',NULL,'If ON, globally enables patron holds on OPAC','YesNo'),
365
('RequestOnOpac','1',NULL,'If ON, globally enables patron holds on OPAC','YesNo'),
367
('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice'),
366
('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice'),
368
('ReservesMaxPickUpDelay','7','','Define the Maximum delay to pick up an item on hold','Integer'),
369
('ReservesNeedReturns','1','','If ON, a hold placed on an item available in this library must be checked-in, otherwise, a hold on a specific item, that is in the library & available is considered available','YesNo'),
367
('ReservesNeedReturns','1','','If ON, a hold placed on an item available in this library must be checked-in, otherwise, a hold on a specific item, that is in the library & available is considered available','YesNo'),
370
('RestrictedPageLocalIPs','',NULL,'Beginning of IP addresses considered as local (comma separated ex: "127.0.0,127.0.2")','Free'),
368
('RestrictedPageLocalIPs','',NULL,'Beginning of IP addresses considered as local (comma separated ex: "127.0.0,127.0.2")','Free'),
371
('RestrictedPageContent','',NULL,'HTML content of the restricted page','TextArea'),
369
('RestrictedPageContent','',NULL,'HTML content of the restricted page','TextArea'),
(-)a/installer/data/mysql/updatedatabase.pl (+31 lines)
Lines 37-42 use Getopt::Long; Link Here
37
use C4::Context;
37
use C4::Context;
38
use C4::Installer;
38
use C4::Installer;
39
use C4::Dates;
39
use C4::Dates;
40
use C4::Reserves;
41
use DateTime::Duration;
40
use Koha::Database;
42
use Koha::Database;
41
use Koha;
43
use Koha;
42
44
Lines 10868-10873 if ( CheckVersion($DBversion) ) { Link Here
10868
    SetVersion($DBversion);
10870
    SetVersion($DBversion);
10869
}
10871
}
10870
10872
10873
$DBversion = "3.21.00.XXX";
10874
if ( CheckVersion($DBversion) ) {
10875
    my $maxpickupdelay = C4::Context->preference('ReservesMaxPickUpDelay') || 0; #MaxPickupDelay
10876
    $dbh->do(q{ DELETE FROM systempreferences WHERE variable='ReservesMaxPickUpDelay'; });
10877
    $dbh->do(q{ DELETE FROM systempreferences WHERE variable='ExpireReservesOnHolidays'; });
10878
#        //DELETE FROM systempreferences WHERE variable='ExpireReservesMaxPickUpDelay'; #This syspref is not needed and would be better suited to be calculated from the holdspickupwait
10879
#        //ExpireReservesMaxPickUpDelayCharge #This could be added as a column to the issuing rules.
10880
    $dbh->do(q{ ALTER TABLE issuingrules ADD COLUMN holdspickupwait INT(11) NULL default NULL AFTER reservesallowed; });
10881
    $dbh->do(q{ ALTER TABLE reserves ADD COLUMN lastpickupdate DATE NULL default NULL AFTER suspend_until; });
10882
    $dbh->do(q{ ALTER TABLE old_reserves ADD COLUMN lastpickupdate DATE NULL default NULL AFTER suspend_until; });
10883
10884
    my $sth = $dbh->prepare(q{
10885
        UPDATE issuingrules SET holdspickupwait = ?
10886
    });
10887
    $sth->execute( $maxpickupdelay ) if $maxpickupdelay; #Don't want to accidentally nullify all!
10888
10889
    ##Populate the lastpickupdate-column from existing 'ReservesMaxPickUpDelay'
10890
    print "Populating the new lastpickupdate-column for all waiting holds. This might take a while.\n";
10891
    $sth = $dbh->prepare(q{ SELECT * FROM reserves WHERE found = 'W'; });
10892
    $sth->execute( );
10893
    my $dtdur = DateTime::Duration->new( days => 0 );
10894
    while ( my $res = $sth->fetchrow_hashref ) {
10895
        C4::Reserves::MoveWaitingdate( $res, $dtdur ); #We call MoveWaitingdate with a 0 duration to simply recalculate the lastpickupdate and store the new values to DB.
10896
    }
10897
10898
  print "Upgrade to $DBversion done (8367: Add colum issuingrules.holdspickupwait and reserves.lastpickupdate. Populates introduced columns from the expiring ReservesMaxPickUpDelay. Deletes the ReservesMaxPickUpDelay and ExpireReservesOnHolidays -sysprefs)\n";
10899
    SetVersion($DBversion);
10900
}
10901
10871
# DEVELOPER PROCESS, search for anything to execute in the db_update directory
10902
# DEVELOPER PROCESS, search for anything to execute in the db_update directory
10872
# SEE bug 13068
10903
# SEE bug 13068
10873
# if there is anything in the atomicupdate, read and execute it.
10904
# if there is anything in the atomicupdate, read and execute it.
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js (+3 lines)
Lines 17-22 $(document).ready(function() { Link Here
17
                        "mDataProp": "reservedate_formatted"
17
                        "mDataProp": "reservedate_formatted"
18
                    },
18
                    },
19
                    {
19
                    {
20
                        "mDataProp": "lastpickupdate_formatted"
21
                    },
22
                    {
20
                        "mDataProp": function ( oObj ) {
23
                        "mDataProp": function ( oObj ) {
21
                            title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber="
24
                            title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber="
22
                                  + oObj.biblionumber
25
                                  + oObj.biblionumber
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref (-12 / +1 lines)
Lines 456-471 Circulation: Link Here
456
                  PatronLibrary: "patron's home library"
456
                  PatronLibrary: "patron's home library"
457
            - to see if the patron can place a hold on the item.    
457
            - to see if the patron can place a hold on the item.    
458
        -
458
        -
459
            - Mark a hold as problematic if it has been waiting for more than
460
            - pref: ReservesMaxPickUpDelay
461
              class: integer
462
            - days.
463
        -
464
            - pref: ExpireReservesMaxPickUpDelay
459
            - pref: ExpireReservesMaxPickUpDelay
465
              choices:
460
              choices:
466
                  yes: Allow
461
                  yes: Allow
467
                  no: "Don't allow"
462
                  no: "Don't allow"
468
            - "holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay"
463
            - "holds to expire automatically if they have not been picked by within the time period specified in hold pickup delay defined in the issuing rules"
469
        -
464
        -
470
            - If using ExpireReservesMaxPickUpDelay, charge a borrower who allows his or her waiting hold to expire a fee of
465
            - If using ExpireReservesMaxPickUpDelay, charge a borrower who allows his or her waiting hold to expire a fee of
471
            - pref: ExpireReservesMaxPickUpDelayCharge
466
            - pref: ExpireReservesMaxPickUpDelayCharge
Lines 540-551 Circulation: Link Here
540
                  no: "Don't allow"
535
                  no: "Don't allow"
541
            - holds to be suspended from the OPAC.
536
            - holds to be suspended from the OPAC.
542
        -
537
        -
543
            - pref: ExpireReservesOnHolidays
544
              choices:
545
                  yes: Allow
546
                  no: "Don't allow"
547
            - expired holds to be canceled on days the library is closed.
548
        -
549
            - pref: decreaseLoanHighHolds
538
            - pref: decreaseLoanHighHolds
550
              choices:
539
              choices:
551
                  yes: Enable
540
                  yes: Enable
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt (+5 lines)
Lines 117-122 $(document).ready(function() { Link Here
117
            <li>default (all libraries), all patron types, same item type</li>
117
            <li>default (all libraries), all patron types, same item type</li>
118
            <li>default (all libraries), all patron types, all item types</li>
118
            <li>default (all libraries), all patron types, all item types</li>
119
        </ul>
119
        </ul>
120
        <p>To get more information about how these settings affect Koha, hover your cursor over a column header.</p>
120
        <p>To modify a rule, create a new one with the same patron type and item type.</p>
121
        <p>To modify a rule, create a new one with the same patron type and item type.</p>
121
    </div>
122
    </div>
122
    <div>
123
    <div>
Lines 153-158 for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde Link Here
153
                <th>Max. suspension duration (day)</th>
154
                <th>Max. suspension duration (day)</th>
154
                <th>Renewals allowed (count)</th>
155
                <th>Renewals allowed (count)</th>
155
                <th>Renewal period</th>
156
                <th>Renewal period</th>
157
                <th>Holds wait for pickup (day)</th>
156
                <th>No renewal before</th>
158
                <th>No renewal before</th>
157
                <th>Automatic renewal</th>
159
                <th>Automatic renewal</th>
158
                <th>Holds allowed (count)</th>
160
                <th>Holds allowed (count)</th>
Lines 211-216 for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde Link Here
211
                            <td>[% rule.maxsuspensiondays %]</td>
213
                            <td>[% rule.maxsuspensiondays %]</td>
212
							<td>[% rule.renewalsallowed %]</td>
214
							<td>[% rule.renewalsallowed %]</td>
213
                            <td>[% rule.renewalperiod %]</td>
215
                            <td>[% rule.renewalperiod %]</td>
216
                            <td>[% rule.holdspickupwait %]</td>
214
                            <td>[% rule.norenewalbefore %]</td>
217
                            <td>[% rule.norenewalbefore %]</td>
215
                            <td>
218
                            <td>
216
                                [% IF ( rule.auto_renew ) %]
219
                                [% IF ( rule.auto_renew ) %]
Lines 271-276 for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde Link Here
271
                    <td><input type="text" name="maxsuspensiondays" id="maxsuspensiondays" size="3" /> </td>
274
                    <td><input type="text" name="maxsuspensiondays" id="maxsuspensiondays" size="3" /> </td>
272
                    <td><input type="text" name="renewalsallowed" id="renewalsallowed" size="2" /></td>
275
                    <td><input type="text" name="renewalsallowed" id="renewalsallowed" size="2" /></td>
273
                    <td><input type="text" name="renewalperiod" id="renewalperiod" size="3" /></td>
276
                    <td><input type="text" name="renewalperiod" id="renewalperiod" size="3" /></td>
277
                    <td><input type="text" name="holdspickupwait" id="holdspickupwait" size="2" /></td>
274
                    <td><input type="text" name="norenewalbefore" id="norenewalbefore" size="3" /></td>
278
                    <td><input type="text" name="norenewalbefore" id="norenewalbefore" size="3" /></td>
275
                    <td>
279
                    <td>
276
                        <select name="auto_renew" id="auto_renew">
280
                        <select name="auto_renew" id="auto_renew">
Lines 315-320 for="tobranch"><strong>Clone these rules to:</strong></label> <input type="hidde Link Here
315
                      <th>Max. suspension duration (day)</th>
319
                      <th>Max. suspension duration (day)</th>
316
                      <th>Renewals allowed (count)</th>
320
                      <th>Renewals allowed (count)</th>
317
                      <th>Renewal period</th>
321
                      <th>Renewal period</th>
322
                      <th>Holds wait for pickup (day)</th>
318
                      <th>No renewal before</th>
323
                      <th>No renewal before</th>
319
                      <th>Automatic renewal</th>
324
                      <th>Automatic renewal</th>
320
                      <th>Holds allowed (count)</th>
325
                      <th>Holds allowed (count)</th>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt (+1 lines)
Lines 921-926 No patron matched <span class="ex">[% message %]</span> Link Here
921
            <thead>
921
            <thead>
922
                <tr>
922
                <tr>
923
                    <th>Hold date</th>
923
                    <th>Hold date</th>
924
                    <th>Last pickup date</th>
924
                    <th>Title</th>
925
                    <th>Title</th>
925
                    <th>Call number</th>
926
                    <th>Call number</th>
926
                    <th>Barcode</th>
927
                    <th>Barcode</th>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt (-4 / +4 lines)
Lines 78-84 Link Here
78
            [% IF ( reserveloop ) %]
78
            [% IF ( reserveloop ) %]
79
               <table id="holdst">
79
               <table id="holdst">
80
               <thead><tr>
80
               <thead><tr>
81
                    <th class="title-string">Available since</th>
81
                    <th class="title-string">Available since-until</th>
82
                    <th class="anti-the">Title</th>
82
                    <th class="anti-the">Title</th>
83
                    <th>Patron</th>
83
                    <th>Patron</th>
84
                    <th>Location</th>
84
                    <th>Location</th>
Lines 88-94 Link Here
88
               </tr></thead>
88
               </tr></thead>
89
               <tbody>[% FOREACH reserveloo IN reserveloop %]
89
               <tbody>[% FOREACH reserveloo IN reserveloop %]
90
                <tr>
90
                <tr>
91
                    <td><span title="[% reserveloo.waitingdate %]">[% reserveloo.waitingdate | $KohaDates %]</span></td>
91
                    <td><span title="[% reserveloo.waitingdate %]">[% reserveloo.waitingdate | $KohaDates %] - [% reserveloo.lastpickupdate | $KohaDates %]</span></td>
92
                    <td>[% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %]
92
                    <td>[% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %]
93
                        [% reserveloo.title |html %] [% FOREACH subtitl IN reserveloo.subtitle %] [% subtitl.subfield %][% END %]
93
                        [% reserveloo.title |html %] [% FOREACH subtitl IN reserveloo.subtitle %] [% subtitl.subfield %][% END %]
94
                        </a>
94
                        </a>
Lines 138-144 Link Here
138
138
139
               <table id="holdso">
139
               <table id="holdso">
140
               <thead><tr>
140
               <thead><tr>
141
                    <th class="title-string">Available since</th>
141
                    <th class="title-string">Available since-until</th>
142
                    <th class="anti-the">Title</th>
142
                    <th class="anti-the">Title</th>
143
                    <th>Patron</th>
143
                    <th>Patron</th>
144
                    <th>Location</th>
144
                    <th>Location</th>
Lines 148-154 Link Here
148
               </tr></thead>
148
               </tr></thead>
149
               <tbody>[% FOREACH overloo IN overloop %]
149
               <tbody>[% FOREACH overloo IN overloop %]
150
                    <tr>
150
                    <tr>
151
                        <td><p><span title="[% overloo.waitingdate %]">[% overloo.waitingdate | $KohaDates %]</span></p></td>
151
                        <td><p><span title="[% overloo.waitingdate %]">[% overloo.waitingdate | $KohaDates %] - [% overloo.lastpickupdate | $KohaDates %]</span></p></td>
152
                        <td>[% INCLUDE 'biblio-default-view.inc' biblionumber = overloo.biblionumber %][% overloo.title |html %]
152
                        <td>[% INCLUDE 'biblio-default-view.inc' biblionumber = overloo.biblionumber %][% overloo.title |html %]
153
                            [% FOREACH subtitl IN overloo.subtitle %] [% subtitl.subfield %][% END %]
153
                            [% FOREACH subtitl IN overloo.subtitle %] [% subtitl.subfield %][% END %]
154
                        </a>
154
                        </a>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt (+1 lines)
Lines 67-72 Link Here
67
    <li>You can also define the maximum number of days a patron will be suspended in the 'Max suspension duration' setting</li>
67
    <li>You can also define the maximum number of days a patron will be suspended in the 'Max suspension duration' setting</li>
68
    <li>Next decide if the patron can renew this item type and if so, enter how many times they can renew it in the 'Renewals Allowed' box</li>
68
    <li>Next decide if the patron can renew this item type and if so, enter how many times they can renew it in the 'Renewals Allowed' box</li>
69
    <li>If you're allowing renewals you can control how long the renewal loan period will be (in the units you have chosen) in the 'Renewal period' box</li>
69
    <li>If you're allowing renewals you can control how long the renewal loan period will be (in the units you have chosen) in the 'Renewal period' box</li>
70
    <li><i>Holds wait for pickup (day)</i> - After a hold is caught and put waiting for pickup, the hold will wait for this many days until it becomes problematic. Set it to 0 or less to disable the expiration of waiting holds. This value respects the Calendar holidays, skipping the last pickup date to the next available open library day.</li>
70
    <li>If you're allowing renewals you can control how soon before the due date patrons can renew their materials with the 'No renewals before' box.
71
    <li>If you're allowing renewals you can control how soon before the due date patrons can renew their materials with the 'No renewals before' box.
71
    <ul><li>Items can be renewed at any time if this value is left blank. Otherwise items can only be renewed if the item is before the number in units (days/hours) entered in this box.</li></ul></li>
72
    <ul><li>Items can be renewed at any time if this value is left blank. Otherwise items can only be renewed if the item is before the number in units (days/hours) entered in this box.</li></ul></li>
72
    <li>You can enable automatic renewals for certain items/patrons if you'd like. This will renew automatically following your circulation rules unless there is a hold on the item
73
    <li>You can enable automatic renewals for certain items/patrons if you'd like. This will renew automatically following your circulation rules unless there is a hold on the item
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt (-1 / +1 lines)
Lines 4-10 Link Here
4
4
5
<p>This report will show all of the holds that are waiting for patrons to pick them up.</p>
5
<p>This report will show all of the holds that are waiting for patrons to pick them up.</p>
6
6
7
<p>Items that have been on the hold shelf longer than you normally allow (based on the ReservesMaxPickUpDelay preference value) will appear on the 'Holds Over' tab, they will not automatically be cancelled unless you have set the cron job to do that for you, but you can cancel all holds using the button at the top of the list.</p>
7
<p>Items that have been on the hold shelf longer than you normally allow (based on the holds pickup delay defined in the issuing rules) will appear on the 'Holds Over' tab, they will not automatically be cancelled.</p>
8
8
9
<p><strong>See the full documentation for Holds Awaiting Pickup in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/circreports.html#holdspickup">manual</a> (online).</strong></p>
9
<p><strong>See the full documentation for Holds Awaiting Pickup in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/circreports.html#holdspickup">manual</a> (online).</strong></p>
10
10
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt (+1 lines)
Lines 480-485 function validate1(date) { Link Here
480
            <thead>
480
            <thead>
481
                <tr>
481
                <tr>
482
                    <th>Hold date</th>
482
                    <th>Hold date</th>
483
                    <th>Last pickup date</th>
483
                    <th>Title</th>
484
                    <th>Title</th>
484
                    <th>Call number</th>
485
                    <th>Call number</th>
485
                    <th>Barcode</th>
486
                    <th>Barcode</th>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (-1 / +1 lines)
Lines 566-572 Link Here
566
                                                [% IF ( RESERVE.wait ) %]
566
                                                [% IF ( RESERVE.wait ) %]
567
                                                    [% IF ( RESERVE.atdestination ) %]
567
                                                    [% IF ( RESERVE.atdestination ) %]
568
                                                        [% IF ( RESERVE.found ) %]
568
                                                        [% IF ( RESERVE.found ) %]
569
                                                            Item waiting at <b> [% RESERVE.wbrname %]</b>[% IF ( RESERVE.waitingdate ) %] since [% RESERVE.waitingdate | $KohaDates %][% END %]
569
                                                            Item waiting at <b> [% RESERVE.wbrname %]</b>[% IF ( RESERVE.waitingdate ) %] since [% RESERVE.waitingdate | $KohaDates %] until [% RESERVE.lastpickupdate | $KohaDates %][% END %]
570
                                                            <input type="hidden" name="pickup" value="[% RESERVE.wbrcd %]" />
570
                                                            <input type="hidden" name="pickup" value="[% RESERVE.wbrcd %]" />
571
                                                        [% ELSE %]
571
                                                        [% ELSE %]
572
                                                            Item waiting to be pulled from <b> [% RESERVE.wbrname %]</b>
572
                                                            Item waiting to be pulled from <b> [% RESERVE.wbrname %]</b>
(-)a/svc/holds (-1 / +6 lines)
Lines 47-53 my $branch = C4::Context->userenv->{'branch'}; Link Here
47
my $schema = Koha::Database->new()->schema();
47
my $schema = Koha::Database->new()->schema();
48
48
49
my @sort_columns =
49
my @sort_columns =
50
  qw/reservedate title itemcallnumber barcode expirationdate priority/;
50
  qw/reservedate lastpickupdate title itemcallnumber barcode expirationdate priority/;
51
51
52
my $borrowernumber    = $input->param('borrowernumber');
52
my $borrowernumber    = $input->param('borrowernumber');
53
my $offset            = $input->param('iDisplayStart');
53
my $offset            = $input->param('iDisplayStart');
Lines 81-86 while ( my $h = $holds_rs->next() ) { Link Here
81
        reserve_id     => $h->reserve_id(),
81
        reserve_id     => $h->reserve_id(),
82
        branchcode     => $h->branch()->branchname(),
82
        branchcode     => $h->branch()->branchname(),
83
        reservedate    => $h->reservedate(),
83
        reservedate    => $h->reservedate(),
84
        lastpickupdate => $h->lastpickupdate(),
84
        expirationdate => $h->expirationdate(),
85
        expirationdate => $h->expirationdate(),
85
        suspend        => $h->suspend(),
86
        suspend        => $h->suspend(),
86
        suspend_until  => $h->suspend_until(),
87
        suspend_until  => $h->suspend_until(),
Lines 97-102 while ( my $h = $holds_rs->next() ) { Link Here
97
            { dt => dt_from_string( $h->reservedate() ), dateonly => 1 }
98
            { dt => dt_from_string( $h->reservedate() ), dateonly => 1 }
98
          )
99
          )
99
        : q{},
100
        : q{},
101
        lastpickupdate_formatted => $h->lastpickupdate() ? output_pref(
102
            { dt => dt_from_string( $h->lastpickupdate() ), dateonly => 1 }
103
          )
104
        : q{},  
100
        suspend_until_formatted => $h->suspend_until() ? output_pref(
105
        suspend_until_formatted => $h->suspend_until() ? output_pref(
101
            { dt => dt_from_string( $h->suspend_until() ), dateonly => 1 }
106
            { dt => dt_from_string( $h->suspend_until() ), dateonly => 1 }
102
          )
107
          )
(-)a/t/db_dependent/Holds.t (-38 / +200 lines)
Lines 6-12 use t::lib::Mocks; Link Here
6
use C4::Context;
6
use C4::Context;
7
use C4::Branch;
7
use C4::Branch;
8
8
9
use Test::More tests => 51;
9
use Test::More tests => 59;
10
use MARC::Record;
10
use MARC::Record;
11
use C4::Biblio;
11
use C4::Biblio;
12
use C4::Items;
12
use C4::Items;
Lines 380-421 is(CanItemBeReserved($borrowernumbers[0], $itemnumber), Link Here
380
is(CanItemBeReserved($borrowernumbers[0], $itemnumber), 'OK',
380
is(CanItemBeReserved($borrowernumbers[0], $itemnumber), 'OK',
381
    "CanItemBeReserved should returns 'OK'");
381
    "CanItemBeReserved should returns 'OK'");
382
382
383
##Setting duration variables
384
my $now = DateTime->now();
385
my $minus4days = DateTime::Duration->new(days => -4);
386
my $minus1days = DateTime::Duration->new(days => -1);
387
my $plus1days = DateTime::Duration->new(days => 1);
388
my $plus4days = DateTime::Duration->new(days => 4);
389
##Setting some test prerequisites testing environment
390
C4::Context->set_preference( 'ExpireReservesMaxPickUpDelay', 1 );
391
setSimpleCircPolicy();
392
setCalendars();
393
#Running more tests
394
testGetLastPickupDate();
395
testMoveWaitingdate();
396
testCancelExpiredReserves();
397
C4::Context->set_preference( 'ExpireReservesMaxPickUpDelay', 0 );
398
399
## Environment should be the following
400
## Holidays: days from today; -2,-3,-4
401
sub testCancelExpiredReserves {
402
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day
403
404
    $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves WHERE found IS NULL', { Slice => {} });
405
    $reserve = $reserves->[0];
406
    #Catch this hold and make it Waiting for pickup today.
407
    C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} );
408
    $reserve = C4::Reserves::GetReserve( $reserve->{reserve_id} ); #UPDATE DB changes to local scope.
409
410
    CancelExpiredReserves();
411
    my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} );
412
    is( $count, 1, "Waiting reserve with lastpickupdate for ".$reserve->{lastpickupdate}." not canceled" );
413
414
    C4::Reserves::MoveWaitingdate( $reserve, DateTime::Duration->new(days => -4) );
415
    CancelExpiredReserves();
416
    $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} );
417
    is( $count, 0, "Waiting reserve with lastpickupdate for ".$reserve->{lastpickupdate}." totally canceled" );
418
419
    # Test expirationdate
420
    $reserve = $reserves->[1];
421
    $dbh->do("UPDATE reserves SET expirationdate = DATE_SUB( NOW(), INTERVAL 1 DAY ) WHERE reserve_id = ?", undef, $reserve->{reserve_id} );
422
    CancelExpiredReserves();
423
    $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} );
424
    is( $count, 0, "Reserve with manual expiration date canceled correctly" );
425
426
    #This test verifies that reserves with holdspickupwait disabled are not ćanceled!
427
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 0'); #Make holds never problematize
428
    $reserve = $reserves->[2];
429
    #Catch this hold and make it Waiting for pickup today.
430
    C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} );
431
    $reserve = C4::Reserves::GetReserve( $reserve->{reserve_id} ); #UPDATE DB changes to local scope.
432
    #Move the caught reserve 4 days to past and try to cancel it.
433
    C4::Reserves::MoveWaitingdate( $reserve, $minus4days );
434
    CancelExpiredReserves();
435
    $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} );
436
    is( $count, 1, "CancelExpiredReserves(): not canceling lastpickupdate-less hold." );
437
}
383
438
384
# Test CancelExpiredReserves
439
## Environment should be the following
385
C4::Context->set_preference('ExpireReservesMaxPickUpDelay', 1);
440
## Holidays: days from today; -2,-3,-4
386
C4::Context->set_preference('ReservesMaxPickUpDelay', 1);
441
sub testMoveWaitingdate {
387
442
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day
388
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
443
389
$year += 1900;
444
    $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves WHERE found IS NULL', { Slice => {} }); #Get reserves not waiting for pickup
390
$mon += 1;
445
    $reserve = $reserves->[0];
391
$reserves = $dbh->selectall_arrayref('SELECT * FROM reserves', { Slice => {} });
446
392
$reserve = $reserves->[0];
447
    C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} ); #Catch the reserve and put it to wait for pickup, now we get a waitingdate generated.
393
my $calendar = C4::Calendar->new(branchcode => $reserve->{branchcode});
448
394
$calendar->insert_single_holiday(
449
    C4::Reserves::MoveWaitingdate( $reserve, $minus1days );
395
    day         => $mday,
450
    $reserve = C4::Reserves::GetReserve( $reserve_id ); #UPDATE DB changes to local scope. Actually MoveWaitingdate already updates changes to DB, but just making sure it does.
396
    month       => $mon,
451
    is( ($reserve->{waitingdate} eq $now->clone()->add_duration($minus1days)->ymd() &&
397
    year        => $year,
452
         $reserve->{lastpickupdate} eq $now->ymd()),
398
    title       => 'Test',
453
         1, "MoveWaitingdate(): Moving to past");
399
    description => 'Test',
454
    C4::Reserves::MoveWaitingdate( $reserve, $plus1days );
400
);
455
401
$reserve_id = $reserve->{reserve_id};
456
    C4::Reserves::MoveWaitingdate( $reserve, $plus4days );
402
$dbh->do("UPDATE reserves SET waitingdate = DATE_SUB( NOW(), INTERVAL 5 DAY ), found = 'W', priority = 0 WHERE reserve_id = ?", undef, $reserve_id );
457
    $reserve = C4::Reserves::GetReserve( $reserve_id );
403
C4::Context->set_preference('ExpireReservesOnHolidays', 0);
458
    is( ($reserve->{waitingdate} eq $now->clone()->add_duration($plus4days)->ymd() &&
404
CancelExpiredReserves();
459
         $reserve->{lastpickupdate} eq $now->clone()->add_duration($plus4days)->add_duration($plus1days)->ymd()),
405
my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
460
         1, "MoveWaitingdate(): Moving to future");
406
is( $count, 1, "Waiting reserve beyond max pickup delay *not* canceled on holiday" );
461
    C4::Reserves::MoveWaitingdate( $reserve, $minus4days );
407
C4::Context->set_preference('ExpireReservesOnHolidays', 1);
462
}
408
CancelExpiredReserves();
463
409
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
464
## Environment should be the following
410
is( $count, 0, "Waiting reserve beyond max pickup delay canceled on holiday" );
465
## Holidays: days from today; -2,-3,-4
411
466
sub testGetLastPickupDate {
412
# Test expirationdate
467
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day
413
$reserve = $reserves->[1];
468
414
$reserve_id = $reserve->{reserve_id};
469
    my $now = DateTime->now();
415
$dbh->do("UPDATE reserves SET expirationdate = DATE_SUB( NOW(), INTERVAL 1 DAY ) WHERE reserve_id = ?", undef, $reserve_id );
470
    my $minus4days = DateTime::Duration->new(days => -4);
416
CancelExpiredReserves();
471
    my $minus1days = DateTime::Duration->new(days => -1);
417
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id );
472
    my $plus1days = DateTime::Duration->new(days => 1);
418
is( $count, 0, "Reserve with manual expiration date canceled correctly" );
473
    my $plus4days = DateTime::Duration->new(days => 4);
474
475
    $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves', { Slice => {} }); #Get reserves not waiting for pickup
476
    $reserve = $reserves->[0];
477
478
    $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd();
479
    my $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd();
480
    $reserve = C4::Reserves::GetReserve( $reserve_id ); #UPDATE DB changes to local scope
481
    is( $lastpickupdate, $now->clone()->add_duration($minus1days)->ymd(),
482
         "GetLastPickupDate(): Calendar finds the next open day for lastpickupdate.");
483
484
    $reserve->{waitingdate} = $now->clone()->add_duration($minus1days)->ymd();
485
    $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd();
486
    is( $lastpickupdate, $now->ymd(),
487
         "GetLastPickupDate(): Not using Calendar");
488
489
    $reserve->{waitingdate} = $now->clone()->add_duration($plus4days)->ymd();
490
    $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd();
491
    is( $lastpickupdate, $now->clone()->add_duration($plus4days)->add_duration($plus1days)->ymd(),
492
         "GetLastPickupDate(): Moving to future");
493
494
    #This test catches moving lastpickupdate for each holiday, instead of just moving the last date to an open library day
495
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 4'); #Make holds problematize after 4 days
496
    $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd();
497
    $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd();
498
    is( $lastpickupdate, $now->ymd(),
499
         "GetLastPickupDate(): Moving lastpickupdate over holidays, but not affected by them");
500
501
    #This test verifies that this feature is disabled and an undef is returned
502
    $dbh->do('UPDATE issuingrules SET holdspickupwait = 0'); #Make holds never problematize
503
    $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd();
504
    $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve );
505
    is( $reserve->{lastpickupdate}, undef,
506
         "GetLastPickupDate(): holdspickupwait disabled");
507
}
419
508
420
# Bug 12632
509
# Bug 12632
421
t::lib::Mocks::mock_preference( 'item-level_itypes',     1 );
510
t::lib::Mocks::mock_preference( 'item-level_itypes',     1 );
Lines 446-452 is( CanItemBeReserved( $borrowernumbers[0], $itemnumber ), Link Here
446
535
447
# Helper method to set up a Biblio.
536
# Helper method to set up a Biblio.
448
sub create_helper_biblio {
537
sub create_helper_biblio {
449
    my $itemtype = shift;
538
    my $itemtype = $_[0] ? $_[0] : 'BK';
450
    my $bib = MARC::Record->new();
539
    my $bib = MARC::Record->new();
451
    my $title = 'Silence in the library';
540
    my $title = 'Silence in the library';
452
    $bib->append_fields(
541
    $bib->append_fields(
Lines 456-458 sub create_helper_biblio { Link Here
456
    );
545
    );
457
    return ($bibnum, $title, $bibitemnum) = AddBiblio($bib, '');
546
    return ($bibnum, $title, $bibitemnum) = AddBiblio($bib, '');
458
}
547
}
459
- 
548
549
sub setSimpleCircPolicy {
550
    $dbh->do('DELETE FROM issuingrules');
551
    $dbh->do(
552
        q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed,
553
                                    maxissueqty, issuelength, lengthunit,
554
                                    renewalsallowed, renewalperiod,
555
                                    norenewalbefore, auto_renew,
556
                                    fine, chargeperiod, holdspickupwait)
557
          VALUES (?, ?, ?, ?,
558
                  ?, ?, ?,
559
                  ?, ?,
560
                  ?, ?,
561
                  ?, ?, ?
562
                 )
563
        },
564
        {},
565
        '*', '*', '*', 25,
566
        20, 14, 'days',
567
        1, 7,
568
        '', 0,
569
        .10, 1,1
570
    );
571
}
572
573
###Set C4::Calendar and Koha::Calendar holidays for
574
# today -2 days
575
# today -3 days
576
# today -4 days
577
#
578
## Koha::Calendar for caching purposes (supposedly) doesn't work from the DB in this script
579
## So we must set the cache for Koha::calnder as well as the DB modifications for C4::Calendar.
580
## When making date comparisons with Koha::Calendar, using DateTime::Set, DateTime-objects
581
## need to match by the nanosecond and time_zone.
582
sub setCalendars {
583
584
    ##Set the C4::Calendar
585
    my $now = DateTime->now(time_zone => C4::Context->tz())->truncate(to => 'day');
586
    my $c4calendar = C4::Calendar->new(branchcode => $reserve->{branchcode});
587
    $now->add_duration( DateTime::Duration->new(days => -2) );
588
    $c4calendar->insert_single_holiday(
589
        day         => $now->day(),
590
        month       => $now->month(),
591
        year        => $now->year(),
592
        title       => 'Test',
593
        description => 'Test',
594
    );
595
    $now->add_duration( DateTime::Duration->new(days => -1) );
596
    $c4calendar->insert_single_holiday(
597
        day         => $now->day(),
598
        month       => $now->month(),
599
        year        => $now->year(),
600
        title       => 'Test',
601
        description => 'Test',
602
    );
603
    $now->add_duration( DateTime::Duration->new(days => -1) );
604
    $c4calendar->insert_single_holiday(
605
        day         => $now->day(),
606
        month       => $now->month(),
607
        year        => $now->year(),
608
        title       => 'Test',
609
        description => 'Test',
610
    );
611
612
    #Set the Koha::Calendar
613
    my $kohaCalendar = Koha::Calendar->new(branchcode => $reserve->{branchcode});
614
    $now = DateTime->now(time_zone => C4::Context->tz())->truncate(to => 'day');
615
    $now->add_duration( DateTime::Duration->new(days => -2) );
616
    $kohaCalendar->add_holiday( $now );
617
    $now->add_duration( DateTime::Duration->new(days => -1) );
618
    $kohaCalendar->add_holiday( $now );
619
    $now->add_duration( DateTime::Duration->new(days => -1) );
620
    $kohaCalendar->add_holiday( $now );
621
}

Return to bug 8367