From fa99c08b42be966556c1074bd0cf691f140a91f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joonas=20Kylm=C3=A4l=C3=A4?= Date: Fri, 18 Sep 2015 13:07:46 +0000 Subject: [PATCH] Bug 8367 - How long is a hold waiting for pickup at a more granular level You can now specify a pickup delay for an hold function of a patron category and/or a item type and/or a library. The lastpickupdate-column is set based on the given instruction and lands only on an open library day. Also cancel_expired_reserves.pl now cancels only on open library days after the last pickup date has expired. And waitingreserves.pl respects the new DB column. If ReservesMaxPickUpDelay is not set prior to the databaseupdate, the holdspickupwait-directive is never defined, thus disabling this feature until the holdspickupwait is set. If the issuingrules holdspickupwait is 0 or less, this feature is disabled. So each issuingrule can disable this feature for the material it matches, while being fully functional with other rules. This patch adds: - a new column in the issuing rules, including help topics. - a new column reserves.lastpickupdate (+old_reserves) It contains the waitingdate + the corresponding "holds pickup wait". Each time the waitingdate is modified, this value will be modified too. - a new field issuingrules.holdspickupwait - a new function C4::Reserves::MoveWaitingdate() to help in testing this feature. - a new function C4::Reserves::GetLastPickupDate() to calculate the proper date from Calendar. This patch removes: - the ReservesMaxPickUpDelay syspref. - the ExpireReservesOnHolidays syspref, because it became obsolete due to the changes in handling the last pickup date with Koha Calendar. - Also removes a code section from C4::Letters::_parseLetter() which worked blatantly disrespectfully towards Koha Calendar. - TODO figure out how to rename existing letter placeholders <> with <> Unit tests included. $$$$$$$$$$$$$ $ TEST PLAN $ $$$$$$$$$$$$$ 0. BEFORE UPDATING THE DATABASE! MAKE SURE THAT THE SYSPREF 'ReservesMaxPickUpDelay' is bigger than 0. Otherwise existing reserves won't get UPDATEd with the new lastpickupdate-values. ****** ****** *** *** *** TEST: BASIC WORKFLOW * ****** ****** 1. Check there is no regression with a normal reserve workflow. 2. Add one or more issuingrules. 3. Update the new column 'Holds pickup wait' in your issuing rules. 4. In the 3 templates of staff client, you can see the 'Last pickup date' for an hold (circ/circulation.pl, circ/waitingreserves.pl, members/moremember.pl) 5. In opac-user.pl the last pickup date is displayed in the status-column if available. 5. According to a library and an item type, the lastpickupdate value will be equal to the waiting date + the "holds pickup wait" defined moved to the next open library day. ****** ****** *** *** *** TEST: LETTER PLACEHOLDER (no code modifications here, just regression)* ****** ****** 1. Modify the RESERVESLIP->email Letter. Add "Last pickup date<>" somewhere. 2. Make a hold for a borrower. 3. Catch the hold and print the reserve slip. Verify that the lastpickupdate is actually the (current date + the 'Holds pickup wait' in your issuing rules) in days. ** ** ** ** ** ** *** *** *** TEST: RESPECTING CALENDAR HOLIDAYS * ** ** ** ** ** ** 1) Note your ReservesMaxPickUpDelay setting. 2) Create and trap a hold 3) View waitingreserves.pl, note the last pickup date, and the pickup location 4) Cancel this hold 5) Edit the calendar for the given location, and make the pickup date a single, unrepeated, holiday. Then make the next day a weekly repeating holiday. Then make the next two days an annual holiday repeatable every year. 6) Repeat step 2 7) View waitingreserves.pl, the last pickup date for this new hold should now be increased by four days. ** ** ** ** ** ** *** *** *** TEST: LETTER lastpickupdate PLACEHOLDER AND REGRESSION * ** ** ** ** ** ** 1. Modify the HOLD-letter, add <> and <> somewhere. 2. Catch a hold for a borrower. 3. See the members/notices.pl for the message queued and verify that the lastpickupdate-placeholder is set in your desired datetime format and that it is not on a holiday. Verify that the waitingdate is today. --- C4/Letters.pm | 15 -- C4/Reserves.pm | 212 +++++++++++++++--- admin/smart-rules.pl | 2 + circ/waitingreserves.pl | 56 +++-- installer/data/mysql/kohastructure.sql | 3 + installer/data/mysql/sysprefs.sql | 2 - installer/data/mysql/updatedatabase.pl | 31 +++ koha-tmpl/intranet-tmpl/prog/en/js/holds.js | 3 + .../en/modules/admin/preferences/circulation.pref | 13 +- .../prog/en/modules/admin/smart-rules.tt | 5 + .../prog/en/modules/circ/circulation.tt | 1 + .../prog/en/modules/circ/waitingreserves.tt | 8 +- .../prog/en/modules/help/admin/smart-rules.tt | 1 + .../prog/en/modules/help/circ/waitingreserves.tt | 2 +- .../prog/en/modules/members/moremember.tt | 1 + .../opac-tmpl/bootstrap/en/modules/opac-user.tt | 2 +- svc/holds | 7 +- t/db_dependent/Holds.t | 237 +++++++++++++++++---- 18 files changed, 471 insertions(+), 130 deletions(-) diff --git a/C4/Letters.pm b/C4/Letters.pm index 863da75..6dd6095 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -798,21 +798,6 @@ sub _parseletter_sth { sub _parseletter { my ( $letter, $table, $values ) = @_; - if ( $table eq 'reserves' && $values->{'waitingdate'} ) { - my @waitingdate = split /-/, $values->{'waitingdate'}; - - $values->{'expirationdate'} = ''; - if( C4::Context->preference('ExpireReservesMaxPickUpDelay') && - C4::Context->preference('ReservesMaxPickUpDelay') ) { - my $dt = dt_from_string(); - $dt->add( days => C4::Context->preference('ReservesMaxPickUpDelay') ); - $values->{'expirationdate'} = output_pref({ dt => $dt, dateonly => 1 }); - } - - $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 }); - - } - if ($letter->{content} && $letter->{content} =~ /<>/) { my $todaysdate = output_pref( DateTime->now() ); $letter->{content} =~ s/<>/$todaysdate/go; diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 5f0bf64..17b32f5 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -167,26 +167,32 @@ sub AddReserve { # Make room in reserves for this before those of a later reserve date $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority ); } - my $waitingdate; + my ($waitingdate, $lastpickupdate); + my $item = C4::Items::GetItem( $checkitem ); # If the reserv had the waiting status, we had the value of the resdate if ( $found eq 'W' ) { $waitingdate = $resdate; + + #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. + my $reserve = {borrowernumber => $borrowernumber, waitingdate => $waitingdate, branchcode => $branch}; + $lastpickupdate = GetLastPickupDate( $reserve, $item ); } # updates take place here my $query = qq{ INSERT INTO reserves (borrowernumber,biblionumber,reservedate,branchcode, - priority,reservenotes,itemnumber,found,waitingdate,expirationdate) + priority,reservenotes,itemnumber,found,waitingdate,expirationdate,lastpickupdate) VALUES (?,?,?,?,?, - ?,?,?,?,?) + ?,?,?,?,?,?) }; my $sth = $dbh->prepare($query); $sth->execute( $borrowernumber, $biblionumber, $resdate, $branch, $priority, - $notes, $checkitem, $found, $waitingdate, $expdate + $notes, $checkitem, $found, $waitingdate, $expdate, + $lastpickupdate ); my $reserve_id = $sth->{mysql_insertid}; # add a reserve fee if needed @@ -738,7 +744,7 @@ sub GetReservesForBranch { my $dbh = C4::Context->dbh; my $query = " - SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate + SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate, lastpickupdate FROM reserves WHERE priority='0' AND found='W' @@ -762,6 +768,105 @@ sub GetReservesForBranch { return (@transreserv); } +=head2 GetLastPickupDate + + my $lastpickupdate = GetLastPickupDate($reserve, $item); + my $lastpickupdate = GetLastPickupDate($reserve, $item, $borrower); + my $lastpickupdate = GetLastPickupDate(undef, $item); + +Gets the last pickup date from the issuingrules for the given reserves-row and sets the +$reserve->{lastpickupdate}.-value. +If the reserves-row is not passed, function tries to figure it out from the item-row. + +Calculating the last pickup date respects Calendar holidays and skips to the next open day. + +If the issuingrule's holdspickupwait is 0 or less, means that the lastpickupdate-feature +is disabled for this kind of material. + +@PARAM1 koha.reserves-row +@PARAM2 koha.items-row, If the reserve is not given, an item must be given to be + able to find a reservation +@PARAM3 koha.borrowers-row, OPTIONAL +RETURNS DateTime, depicting the last pickup date. + OR undef if there is an error or if the issuingrules disable this feature for this material. +=cut + +sub GetLastPickupDate { + my ($reserve, $item, $borrower) = @_; + + ##Verify parameters + if ( not defined $reserve and not defined $item ) { + warn "C4::Reserves::GetMaxPickupDate(), is called without a reserve and an item"; + return; + } + if ( defined $reserve and not defined $item ) { + $item = C4::Items::GetItem( $reserve->{itemnumber} ); + } + unless ( defined $reserve ) { + my $reserve = GetReservesFromItemnumber( $item->{itemnumber} ); + } + + my $date = $reserve->{waitingdate}; + unless ( $date ) { #It is possible that a reserve is just caught and it doesn't have a waitingdate yet. + $date = DateTime->now( time_zone => C4::Context->tz() ); #So default to NOW() + } + else { + $date = (ref $reserve->{waitingdate} eq 'DateTime') ? $reserve->{waitingdate} : dt_from_string($reserve->{waitingdate}); + } + $borrower = C4::Members::GetMember( 'borrowernumber' => $reserve->{borrowernumber} ) unless $borrower; + + ##Get churning the LastPickupDate + my $controlbranch = GetReservesControlBranch( $item, $borrower ); + + my $issuingrule = C4::Circulation::GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $controlbranch ); + + if ( defined($issuingrule) + && defined $issuingrule->{holdspickupwait} + && $issuingrule->{holdspickupwait} > 0 ) { #If holdspickupwait is <= 0, it means this feature is disabled for this type of material. + + $date->add( days => $issuingrule->{holdspickupwait} ); + + my $calendar = Koha::Calendar->new( branchcode => $reserve->{'branchcode'} ); + my $is_holiday = $calendar->is_holiday( $date ); + + while ( $is_holiday ) { + $date->add( days => 1 ); + $is_holiday = $calendar->is_holiday( $date ); + } + + $reserve->{lastpickupdate} = $date->ymd(); + return $date; + } + #Without explicitly setting the return value undef, the lastpickupdate-column is + # set as 0000-00-00 instead of NULL in DBD::MySQL. + #This causes this hold to always expire any lastpickupdate check, + # effectively canceling it as soon as cancel_expired_holds.pl is ran. + #This is exactly the opposite of disabling the autoexpire feature. + #So please let me explicitly return undef, because Perl cant always handle everything. + delete $reserve->{lastpickupdate}; + return undef; +} +=head2 GetReservesControlBranch + + $branchcode = &GetReservesControlBranch($borrower, $item) + +Returns the branchcode to consider to check hold rules against + +=cut + +sub GetReservesControlBranch { + my ( $borrower, $item ) = @_; + my $controlbranch = C4::Context->preference('ReservesControlBranch'); + my $hbr = C4::Context->preference('HomeOrHoldingBranch') || "homebranch"; + my $branchcode = "*"; + if ( $controlbranch eq "ItemHomeLibrary" ) { + $branchcode = $item->{$hbr}; + } elsif ( $controlbranch eq "PatronLibrary" ) { + $branchcode = $borrower->{branchcode}; + } + return $branchcode; +} + =head2 GetReserveStatus $reservestatus = GetReserveStatus($itemnumber); @@ -971,29 +1076,13 @@ sub CancelExpiredReserves { # Cancel reserves that have been waiting too long if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) { - my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay"); - my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays'); - my $today = dt_from_string(); - - my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0"; + my $query = "SELECT * FROM reserves WHERE NOW() > lastpickupdate AND found = 'W' AND priority = 0"; $sth = $dbh->prepare( $query ); - $sth->execute( $max_pickup_delay ); + $sth->execute(); while ( my $res = $sth->fetchrow_hashref ) { - my $do_cancel = 1; - unless ( $cancel_on_holidays ) { - my $calendar = Koha::Calendar->new( branchcode => $res->{'branchcode'} ); - my $is_holiday = $calendar->is_holiday( $today ); - - if ( $is_holiday ) { - $do_cancel = 0; - } - } - - if ( $do_cancel ) { CancelReserve({ reserve_id => $res->{'reserve_id'}, charge_cancel_fee => 1 }); - } } } @@ -1239,10 +1328,30 @@ sub ModReserveStatus { #first : check if we have a reservation for this item . my ($itemnumber, $newstatus) = @_; my $dbh = C4::Context->dbh; + + my $now = dt_from_string; + my $reserve = $dbh->selectrow_hashref(q{ + SELECT * + FROM reserves + WHERE itemnumber = ? + AND found IS NULL + AND priority = 0 + }, {}, $itemnumber); + return unless $reserve; + + my $lastpickupdate = GetLastPickupDate( $reserve ); - my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0"; + my $query = q{ + UPDATE reserves + SET found = ?, + waitingdate = ?, + lastpickupdate = ? + WHERE itemnumber = ? + AND found IS NULL + AND priority = 0 + }; my $sth_set = $dbh->prepare($query); - $sth_set->execute( $newstatus, $itemnumber ); + $sth_set->execute( $newstatus, $now, $lastpickupdate, $itemnumber ); if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) { CartToShelf( $itemnumber ); @@ -1295,21 +1404,27 @@ sub ModReserveAffect { WHERE borrowernumber = ? AND biblionumber = ? "; + $sth = $dbh->prepare($query); + $sth->execute( $itemnumber, $borrowernumber,$biblionumber); } else { # affect the reserve to Waiting as well. + my $item = C4::Items::GetItem( $itemnumber ); + my $lastpickupdate = GetLastPickupDate( $request, $item ); $query = " UPDATE reserves SET priority = 0, found = 'W', waitingdate = NOW(), + lastpickupdate = ?, itemnumber = ? WHERE borrowernumber = ? AND biblionumber = ? "; + $sth = $dbh->prepare($query); + $sth->execute( $lastpickupdate, $itemnumber, $borrowernumber,$biblionumber); } - $sth = $dbh->prepare($query); - $sth->execute( $itemnumber, $borrowernumber,$biblionumber); + _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf ); _FixPriority( { biblionumber => $biblionumber } ); if ( C4::Context->preference("ReturnToShelvingCart") ) { @@ -1385,6 +1500,7 @@ sub GetReserveInfo { reserves.biblionumber, reserves.branchcode, reserves.waitingdate, + reserves.lastpickupdate, notificationdate, reminderdate, priority, @@ -1821,6 +1937,7 @@ sub _Findgroupreserve { reserves.borrowernumber AS borrowernumber, reserves.reservedate AS reservedate, reserves.branchcode AS branchcode, + reserves.lastpickupdate AS lastpickupdate, reserves.cancellationdate AS cancellationdate, reserves.found AS found, reserves.reservenotes AS reservenotes, @@ -1855,6 +1972,7 @@ sub _Findgroupreserve { reserves.borrowernumber AS borrowernumber, reserves.reservedate AS reservedate, reserves.branchcode AS branchcode, + reserves.lastpickupdate AS lastpickupdate, reserves.cancellationdate AS cancellationdate, reserves.found AS found, reserves.reservenotes AS reservenotes, @@ -1889,6 +2007,7 @@ sub _Findgroupreserve { reserves.reservedate AS reservedate, reserves.waitingdate AS waitingdate, reserves.branchcode AS branchcode, + reserves.lastpickupdate AS lastpickupdate, reserves.cancellationdate AS cancellationdate, reserves.found AS found, reserves.reservenotes AS reservenotes, @@ -2145,6 +2264,42 @@ sub MoveReserve { } } +=head MoveWaitingdate + + #Move waitingdate two months and fifteen days forward. + my $dateDuration = DateTime::Duration->new( months => 2, days => 15 ); + $reserve = MoveWaitingdate( $reserve, $dateDuration); + + #Move waitingdate one year and eleven days backwards. + my $dateDuration = DateTime::Duration->new( years => -1, days => -11 ); + $reserve = MoveWaitingdate( $reserve, $dateDuration); + +Moves the waitingdate and updates the lastpickupdate to match. +If waitingdate is not defined, uses today. +Is intended to be used from automated tests, because under normal library +operations there should be NO REASON to move the waitingdate. + +@PARAM1 koha.reserves-row, with waitingdate set. +@PARAM2 DateTime::Duration, with the desired offset. +RETURNS koha.reserve-row, with keys waitingdate and lastpickupdate updated. +=cut +sub MoveWaitingdate { + my ($reserve, $dateDuration) = @_; + + my $dt = dt_from_string( $reserve->{waitingdate} ); + $dt->add_duration( $dateDuration ); + $reserve->{waitingdate} = $dt->ymd(); + + GetLastPickupDate( $reserve ); #Update the $reserve->{lastpickupdate} + + #UPDATE the DB part + my $dbh = C4::Context->dbh(); + my $sth = $dbh->prepare( "UPDATE reserves SET waitingdate=?, lastpickupdate=? WHERE reserve_id=?" ); + $sth->execute( $reserve->{waitingdate}, $reserve->{lastpickupdate}, $reserve->{reserve_id} ); + + return $reserve; +} + =head2 MergeHolds MergeHolds($dbh,$to_biblio, $from_biblio); @@ -2241,7 +2396,8 @@ sub RevertWaitingStatus { SET priority = 1, found = NULL, - waitingdate = NULL + waitingdate = NULL, + lastpickupdate = NULL, WHERE reserve_id = ? "; diff --git a/admin/smart-rules.pl b/admin/smart-rules.pl index 9ef6ef6..d1a1f1a 100755 --- a/admin/smart-rules.pl +++ b/admin/smart-rules.pl @@ -117,6 +117,7 @@ elsif ($op eq 'add') { $norenewalbefore = undef if $norenewalbefore eq '0' or $norenewalbefore =~ /^\s*$/; my $auto_renew = $input->param('auto_renew') eq 'yes' ? 1 : 0; my $reservesallowed = $input->param('reservesallowed'); + my $holdspickupwait = $input->param('holdspickupwait'); my $onshelfholds = $input->param('onshelfholds') || 0; $maxissueqty =~ s/\s//g; $maxissueqty = undef if $maxissueqty !~ /^\d+/; @@ -148,6 +149,7 @@ elsif ($op eq 'add') { norenewalbefore => $norenewalbefore, auto_renew => $auto_renew, reservesallowed => $reservesallowed, + holdspickupwait => $holdspickupwait, issuelength => $issuelength, lengthunit => $lengthunit, hardduedate => $hardduedate, diff --git a/circ/waitingreserves.pl b/circ/waitingreserves.pl index 577a926..6d75f04 100755 --- a/circ/waitingreserves.pl +++ b/circ/waitingreserves.pl @@ -30,11 +30,6 @@ use C4::Members; use C4::Biblio; use C4::Items; use Koha::DateUtils; -use Date::Calc qw( - Today - Add_Delta_Days - Date_to_Days -); use C4::Reserves; use C4::Koha; @@ -85,31 +80,44 @@ my ($reservcount, $overcount); my @getreserves = $all_branches ? GetReservesForBranch() : GetReservesForBranch($default); # get reserves for the branch we are logged into, or for all branches -my $today = Date_to_Days(&Today); +my $today = dt_from_string; foreach my $num (@getreserves) { next unless ($num->{'waitingdate'} && $num->{'waitingdate'} ne '0000-00-00'); my $itemnumber = $num->{'itemnumber'}; my $gettitle = GetBiblioFromItemNumber( $itemnumber ); - my $borrowernum = $num->{'borrowernumber'}; + my $borrowernumber = $num->{'borrowernumber'}; my $holdingbranch = $gettitle->{'holdingbranch'}; my $homebranch = $gettitle->{'homebranch'}; my %getreserv = ( itemnumber => $itemnumber, - borrowernum => $borrowernum, + borrowernum => $borrowernumber, ); # fix up item type for display $gettitle->{'itemtype'} = C4::Context->preference('item-level_itypes') ? $gettitle->{'itype'} : $gettitle->{'itemtype'}; my $getborrower = GetMember(borrowernumber => $num->{'borrowernumber'}); my $itemtypeinfo = getitemtypeinfo( $gettitle->{'itemtype'} ); # using the fixed up itype/itemtype - $getreserv{'waitingdate'} = $num->{'waitingdate'}; - my ( $waiting_year, $waiting_month, $waiting_day ) = split (/-/, $num->{'waitingdate'}); - ( $waiting_year, $waiting_month, $waiting_day ) = - Add_Delta_Days( $waiting_year, $waiting_month, $waiting_day, - C4::Context->preference('ReservesMaxPickUpDelay')); - my $calcDate = Date_to_Days( $waiting_year, $waiting_month, $waiting_day ); + + if ( $num->{waitingdate} ) { + my $lastpickupdate = dt_from_string($num->{lastpickupdate}); + $getreserv{waitingdate} = $num->{waitingdate}; + $getreserv{lastpickupdate} = $num->{lastpickupdate}; + if ( DateTime->compare( $today, $lastpickupdate ) == 1 ) { + if ($cancelall) { + my $res = cancel( $itemnumber, $borrowernumber, $holdingbranch, $homebranch, !$transfer_when_cancel_all ); + push @cancel_result, $res if $res; + next; + } else { + push @overloop, \%getreserv; + $overcount++; + } + }else{ + push @reservloop, \%getreserv; + $reservcount++; + } + } $getreserv{'itemtype'} = $itemtypeinfo->{'description'}; $getreserv{'title'} = $gettitle->{'title'}; @@ -129,25 +137,11 @@ foreach my $num (@getreserves) { $getreserv{'borrowerfirstname'} = $getborrower->{'firstname'}; $getreserv{'borrowerphone'} = $getborrower->{'phone'}; - my $borEmail = GetFirstValidEmailAddress( $borrowernum ); + my $borEmail = GetFirstValidEmailAddress( $borrowernumber ); if ( $borEmail ) { $getreserv{'borrowermail'} = $borEmail; } - - if ($today > $calcDate) { - if ($cancelall) { - my $res = cancel( $itemnumber, $borrowernum, $holdingbranch, $homebranch, !$transfer_when_cancel_all ); - push @cancel_result, $res if $res; - next; - } else { - push @overloop, \%getreserv; - $overcount++; - } - }else{ - push @reservloop, \%getreserv; - $reservcount++; - } } @@ -157,8 +151,12 @@ $template->param( reservecount => $reservcount, overloop => \@overloop, overcount => $overcount, +<<<<<<< HEAD show_date => output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }), ReservesMaxPickUpDelay => C4::Context->preference('ReservesMaxPickUpDelay') +======= + show_date => format_date(C4::Dates->today('iso')), +>>>>>>> Bug 8367 - How long is a hold waiting for pickup at a more granular level ); if ($cancelall) { diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index fd58258..514bd8a 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -1195,6 +1195,7 @@ CREATE TABLE `issuingrules` ( -- circulation and fine rules `norenewalbefore` int(4) default NULL, -- no renewal allowed until X days or hours before due date. In the unit set in issuingrules.lengthunit `auto_renew` BOOLEAN default FALSE, -- automatic renewal `reservesallowed` smallint(6) NOT NULL default "0", -- how many holds are allowed + `holdspickupwait` int(11) default NULL, -- How many open library days a hold can wait in the pickup shelf until it becomes problematic `branchcode` varchar(10) NOT NULL default '', -- the branch this rule is for (branches.branchcode) overduefinescap decimal(28,6) default NULL, -- the maximum amount of an overdue fine onshelfholds tinyint(1) NOT NULL default 0, -- allow holds for items that are on shelf @@ -1668,6 +1669,7 @@ CREATE TABLE `old_reserves` ( -- this table holds all holds/reserves that have b `lowestPriority` tinyint(1) NOT NULL, -- has this hold been pinned to the lowest priority in the holds queue (1 for yes, 0 for no) `suspend` BOOLEAN NOT NULL DEFAULT 0, -- in this hold suspended (1 for yes, 0 for no) `suspend_until` DATETIME NULL DEFAULT NULL, -- the date this hold is suspended until (NULL for infinitely) + `lastpickupdate` date NULL DEFAULT NULL, -- the last day this hold is available for pickup, until it becomes problematic PRIMARY KEY (`reserve_id`), KEY `old_reserves_borrowernumber` (`borrowernumber`), KEY `old_reserves_biblionumber` (`biblionumber`), @@ -1841,6 +1843,7 @@ CREATE TABLE `reserves` ( -- information related to holds/reserves in Koha `lowestPriority` tinyint(1) NOT NULL, `suspend` BOOLEAN NOT NULL DEFAULT 0, `suspend_until` DATETIME NULL DEFAULT NULL, + `lastpickupdate` date NULL DEFAULT NULL, -- the last day this hold is available for pickup, until it becomes problematic PRIMARY KEY (`reserve_id`), KEY priorityfoundidx (priority,found), KEY `borrowernumber` (`borrowernumber`), diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 88a1f5f..ad5a663 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -127,7 +127,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('expandedSearchOption','0',NULL,'If ON, set advanced search to be expanded by default','YesNo'), ('ExpireReservesMaxPickUpDelay','0','','Enabling this allows holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay','YesNo'), ('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'), -('ExpireReservesOnHolidays', '1', NULL, 'If false, reserves at a library will not be canceled on days the library is not open.', 'YesNo'), ('ExtendedPatronAttributes','0',NULL,'Use extended patron IDs and attributes','YesNo'), ('FacetLabelTruncationLength','20',NULL,'Specify the facet max length in OPAC','Integer'), ('FacetMaxCount','20',NULL,'Specify the max facet count for each category','Integer'), @@ -365,7 +364,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('ReportsLog','0',NULL,'If ON, log information about reports.','YesNo'), ('RequestOnOpac','1',NULL,'If ON, globally enables patron holds on OPAC','YesNo'), ('ReservesControlBranch','PatronLibrary','ItemHomeLibrary|PatronLibrary','Branch checked for members reservations rights','Choice'), -('ReservesMaxPickUpDelay','7','','Define the Maximum delay to pick up an item on hold','Integer'), ('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'), ('RestrictedPageLocalIPs','',NULL,'Beginning of IP addresses considered as local (comma separated ex: "127.0.0,127.0.2")','Free'), ('RestrictedPageContent','',NULL,'HTML content of the restricted page','TextArea'), diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 6d02ff1..1f8179b 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -37,6 +37,8 @@ use Getopt::Long; use C4::Context; use C4::Installer; use C4::Dates; +use C4::Reserves; +use DateTime::Duration; use Koha::Database; use Koha; @@ -10868,6 +10870,35 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } +$DBversion = "3.21.00.XXX"; +if ( CheckVersion($DBversion) ) { + my $maxpickupdelay = C4::Context->preference('ReservesMaxPickUpDelay') || 0; #MaxPickupDelay + $dbh->do(q{ DELETE FROM systempreferences WHERE variable='ReservesMaxPickUpDelay'; }); + $dbh->do(q{ DELETE FROM systempreferences WHERE variable='ExpireReservesOnHolidays'; }); +# //DELETE FROM systempreferences WHERE variable='ExpireReservesMaxPickUpDelay'; #This syspref is not needed and would be better suited to be calculated from the holdspickupwait +# //ExpireReservesMaxPickUpDelayCharge #This could be added as a column to the issuing rules. + $dbh->do(q{ ALTER TABLE issuingrules ADD COLUMN holdspickupwait INT(11) NULL default NULL AFTER reservesallowed; }); + $dbh->do(q{ ALTER TABLE reserves ADD COLUMN lastpickupdate DATE NULL default NULL AFTER suspend_until; }); + $dbh->do(q{ ALTER TABLE old_reserves ADD COLUMN lastpickupdate DATE NULL default NULL AFTER suspend_until; }); + + my $sth = $dbh->prepare(q{ + UPDATE issuingrules SET holdspickupwait = ? + }); + $sth->execute( $maxpickupdelay ) if $maxpickupdelay; #Don't want to accidentally nullify all! + + ##Populate the lastpickupdate-column from existing 'ReservesMaxPickUpDelay' + print "Populating the new lastpickupdate-column for all waiting holds. This might take a while.\n"; + $sth = $dbh->prepare(q{ SELECT * FROM reserves WHERE found = 'W'; }); + $sth->execute( ); + my $dtdur = DateTime::Duration->new( days => 0 ); + while ( my $res = $sth->fetchrow_hashref ) { + C4::Reserves::MoveWaitingdate( $res, $dtdur ); #We call MoveWaitingdate with a 0 duration to simply recalculate the lastpickupdate and store the new values to DB. + } + + 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"; + SetVersion($DBversion); +} + # DEVELOPER PROCESS, search for anything to execute in the db_update directory # SEE bug 13068 # if there is anything in the atomicupdate, read and execute it. diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js b/koha-tmpl/intranet-tmpl/prog/en/js/holds.js index 077b3b6..51c337d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js +++ b/koha-tmpl/intranet-tmpl/prog/en/js/holds.js @@ -17,6 +17,9 @@ $(document).ready(function() { "mDataProp": "reservedate_formatted" }, { + "mDataProp": "lastpickupdate_formatted" + }, + { "mDataProp": function ( oObj ) { title = "default (all libraries), all patron types, same item type
  • default (all libraries), all patron types, all item types
  • +

    To get more information about how these settings affect Koha, hover your cursor over a column header.

    To modify a rule, create a new one with the same patron type and item type.

    @@ -153,6 +154,7 @@ for="tobranch">Clone these rules to: Clone these rules to: Clone these rules to: + [% message %] Hold date + Last pickup date Title Call number Barcode diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt index 1d5faf4..1351769 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt @@ -78,7 +78,7 @@ [% IF ( reserveloop ) %] - + @@ -88,7 +88,7 @@ [% FOREACH reserveloo IN reserveloop %] - +
    Available sinceAvailable since-until Title Patron Location
    [% reserveloo.waitingdate | $KohaDates %][% reserveloo.waitingdate | $KohaDates %] - [% reserveloo.lastpickupdate | $KohaDates %] [% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %] [% reserveloo.title |html %] [% FOREACH subtitl IN reserveloo.subtitle %] [% subtitl.subfield %][% END %] @@ -138,7 +138,7 @@ - + @@ -148,7 +148,7 @@ [% FOREACH overloo IN overloop %] - + + diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt index b5bc8d2..1b9f6af 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -566,7 +566,7 @@ [% IF ( RESERVE.wait ) %] [% IF ( RESERVE.atdestination ) %] [% IF ( RESERVE.found ) %] - Item waiting at [% RESERVE.wbrname %][% IF ( RESERVE.waitingdate ) %] since [% RESERVE.waitingdate | $KohaDates %][% END %] + Item waiting at [% RESERVE.wbrname %][% IF ( RESERVE.waitingdate ) %] since [% RESERVE.waitingdate | $KohaDates %] until [% RESERVE.lastpickupdate | $KohaDates %][% END %] [% ELSE %] Item waiting to be pulled from [% RESERVE.wbrname %] diff --git a/svc/holds b/svc/holds index 10d9205..44dcc5a 100755 --- a/svc/holds +++ b/svc/holds @@ -47,7 +47,7 @@ my $branch = C4::Context->userenv->{'branch'}; my $schema = Koha::Database->new()->schema(); my @sort_columns = - qw/reservedate title itemcallnumber barcode expirationdate priority/; + qw/reservedate lastpickupdate title itemcallnumber barcode expirationdate priority/; my $borrowernumber = $input->param('borrowernumber'); my $offset = $input->param('iDisplayStart'); @@ -81,6 +81,7 @@ while ( my $h = $holds_rs->next() ) { reserve_id => $h->reserve_id(), branchcode => $h->branch()->branchname(), reservedate => $h->reservedate(), + lastpickupdate => $h->lastpickupdate(), expirationdate => $h->expirationdate(), suspend => $h->suspend(), suspend_until => $h->suspend_until(), @@ -97,6 +98,10 @@ while ( my $h = $holds_rs->next() ) { { dt => dt_from_string( $h->reservedate() ), dateonly => 1 } ) : q{}, + lastpickupdate_formatted => $h->lastpickupdate() ? output_pref( + { dt => dt_from_string( $h->lastpickupdate() ), dateonly => 1 } + ) + : q{}, suspend_until_formatted => $h->suspend_until() ? output_pref( { dt => dt_from_string( $h->suspend_until() ), dateonly => 1 } ) diff --git a/t/db_dependent/Holds.t b/t/db_dependent/Holds.t index 82d6f62..75fea76 100755 --- a/t/db_dependent/Holds.t +++ b/t/db_dependent/Holds.t @@ -6,7 +6,7 @@ use t::lib::Mocks; use C4::Context; use C4::Branch; -use Test::More tests => 51; +use Test::More tests => 59; use MARC::Record; use C4::Biblio; use C4::Items; @@ -380,42 +380,131 @@ is(CanItemBeReserved($borrowernumbers[0], $itemnumber), is(CanItemBeReserved($borrowernumbers[0], $itemnumber), 'OK', "CanItemBeReserved should returns 'OK'"); +##Setting duration variables +my $now = DateTime->now(); +my $minus4days = DateTime::Duration->new(days => -4); +my $minus1days = DateTime::Duration->new(days => -1); +my $plus1days = DateTime::Duration->new(days => 1); +my $plus4days = DateTime::Duration->new(days => 4); +##Setting some test prerequisites testing environment +C4::Context->set_preference( 'ExpireReservesMaxPickUpDelay', 1 ); +setSimpleCircPolicy(); +setCalendars(); +#Running more tests +testGetLastPickupDate(); +testMoveWaitingdate(); +testCancelExpiredReserves(); +C4::Context->set_preference( 'ExpireReservesMaxPickUpDelay', 0 ); + +## Environment should be the following +## Holidays: days from today; -2,-3,-4 +sub testCancelExpiredReserves { + $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day + + $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves WHERE found IS NULL', { Slice => {} }); + $reserve = $reserves->[0]; + #Catch this hold and make it Waiting for pickup today. + C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} ); + $reserve = C4::Reserves::GetReserve( $reserve->{reserve_id} ); #UPDATE DB changes to local scope. + + CancelExpiredReserves(); + my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} ); + is( $count, 1, "Waiting reserve with lastpickupdate for ".$reserve->{lastpickupdate}." not canceled" ); + + C4::Reserves::MoveWaitingdate( $reserve, DateTime::Duration->new(days => -4) ); + CancelExpiredReserves(); + $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} ); + is( $count, 0, "Waiting reserve with lastpickupdate for ".$reserve->{lastpickupdate}." totally canceled" ); + + # Test expirationdate + $reserve = $reserves->[1]; + $dbh->do("UPDATE reserves SET expirationdate = DATE_SUB( NOW(), INTERVAL 1 DAY ) WHERE reserve_id = ?", undef, $reserve->{reserve_id} ); + CancelExpiredReserves(); + $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} ); + is( $count, 0, "Reserve with manual expiration date canceled correctly" ); + + #This test verifies that reserves with holdspickupwait disabled are not ćanceled! + $dbh->do('UPDATE issuingrules SET holdspickupwait = 0'); #Make holds never problematize + $reserve = $reserves->[2]; + #Catch this hold and make it Waiting for pickup today. + C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} ); + $reserve = C4::Reserves::GetReserve( $reserve->{reserve_id} ); #UPDATE DB changes to local scope. + #Move the caught reserve 4 days to past and try to cancel it. + C4::Reserves::MoveWaitingdate( $reserve, $minus4days ); + CancelExpiredReserves(); + $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve->{reserve_id} ); + is( $count, 1, "CancelExpiredReserves(): not canceling lastpickupdate-less hold." ); +} -# Test CancelExpiredReserves -C4::Context->set_preference('ExpireReservesMaxPickUpDelay', 1); -C4::Context->set_preference('ReservesMaxPickUpDelay', 1); - -my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time); -$year += 1900; -$mon += 1; -$reserves = $dbh->selectall_arrayref('SELECT * FROM reserves', { Slice => {} }); -$reserve = $reserves->[0]; -my $calendar = C4::Calendar->new(branchcode => $reserve->{branchcode}); -$calendar->insert_single_holiday( - day => $mday, - month => $mon, - year => $year, - title => 'Test', - description => 'Test', -); -$reserve_id = $reserve->{reserve_id}; -$dbh->do("UPDATE reserves SET waitingdate = DATE_SUB( NOW(), INTERVAL 5 DAY ), found = 'W', priority = 0 WHERE reserve_id = ?", undef, $reserve_id ); -C4::Context->set_preference('ExpireReservesOnHolidays', 0); -CancelExpiredReserves(); -my $count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id ); -is( $count, 1, "Waiting reserve beyond max pickup delay *not* canceled on holiday" ); -C4::Context->set_preference('ExpireReservesOnHolidays', 1); -CancelExpiredReserves(); -$count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id ); -is( $count, 0, "Waiting reserve beyond max pickup delay canceled on holiday" ); - -# Test expirationdate -$reserve = $reserves->[1]; -$reserve_id = $reserve->{reserve_id}; -$dbh->do("UPDATE reserves SET expirationdate = DATE_SUB( NOW(), INTERVAL 1 DAY ) WHERE reserve_id = ?", undef, $reserve_id ); -CancelExpiredReserves(); -$count = $dbh->selectrow_array("SELECT COUNT(*) FROM reserves WHERE reserve_id = ?", undef, $reserve_id ); -is( $count, 0, "Reserve with manual expiration date canceled correctly" ); +## Environment should be the following +## Holidays: days from today; -2,-3,-4 +sub testMoveWaitingdate { + $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day + + $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves WHERE found IS NULL', { Slice => {} }); #Get reserves not waiting for pickup + $reserve = $reserves->[0]; + + C4::Reserves::ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber} ); #Catch the reserve and put it to wait for pickup, now we get a waitingdate generated. + + C4::Reserves::MoveWaitingdate( $reserve, $minus1days ); + $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. + is( ($reserve->{waitingdate} eq $now->clone()->add_duration($minus1days)->ymd() && + $reserve->{lastpickupdate} eq $now->ymd()), + 1, "MoveWaitingdate(): Moving to past"); + C4::Reserves::MoveWaitingdate( $reserve, $plus1days ); + + C4::Reserves::MoveWaitingdate( $reserve, $plus4days ); + $reserve = C4::Reserves::GetReserve( $reserve_id ); + is( ($reserve->{waitingdate} eq $now->clone()->add_duration($plus4days)->ymd() && + $reserve->{lastpickupdate} eq $now->clone()->add_duration($plus4days)->add_duration($plus1days)->ymd()), + 1, "MoveWaitingdate(): Moving to future"); + C4::Reserves::MoveWaitingdate( $reserve, $minus4days ); +} + +## Environment should be the following +## Holidays: days from today; -2,-3,-4 +sub testGetLastPickupDate { + $dbh->do('UPDATE issuingrules SET holdspickupwait = 1'); #Make holds problematize after 1 day + + my $now = DateTime->now(); + my $minus4days = DateTime::Duration->new(days => -4); + my $minus1days = DateTime::Duration->new(days => -1); + my $plus1days = DateTime::Duration->new(days => 1); + my $plus4days = DateTime::Duration->new(days => 4); + + $reserves = $dbh->selectall_arrayref('SELECT * FROM reserves', { Slice => {} }); #Get reserves not waiting for pickup + $reserve = $reserves->[0]; + + $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd(); + my $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd(); + $reserve = C4::Reserves::GetReserve( $reserve_id ); #UPDATE DB changes to local scope + is( $lastpickupdate, $now->clone()->add_duration($minus1days)->ymd(), + "GetLastPickupDate(): Calendar finds the next open day for lastpickupdate."); + + $reserve->{waitingdate} = $now->clone()->add_duration($minus1days)->ymd(); + $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd(); + is( $lastpickupdate, $now->ymd(), + "GetLastPickupDate(): Not using Calendar"); + + $reserve->{waitingdate} = $now->clone()->add_duration($plus4days)->ymd(); + $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd(); + is( $lastpickupdate, $now->clone()->add_duration($plus4days)->add_duration($plus1days)->ymd(), + "GetLastPickupDate(): Moving to future"); + + #This test catches moving lastpickupdate for each holiday, instead of just moving the last date to an open library day + $dbh->do('UPDATE issuingrules SET holdspickupwait = 4'); #Make holds problematize after 4 days + $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd(); + $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve )->ymd(); + is( $lastpickupdate, $now->ymd(), + "GetLastPickupDate(): Moving lastpickupdate over holidays, but not affected by them"); + + #This test verifies that this feature is disabled and an undef is returned + $dbh->do('UPDATE issuingrules SET holdspickupwait = 0'); #Make holds never problematize + $reserve->{waitingdate} = $now->clone()->add_duration($minus4days)->ymd(); + $lastpickupdate = C4::Reserves::GetLastPickupDate( $reserve ); + is( $reserve->{lastpickupdate}, undef, + "GetLastPickupDate(): holdspickupwait disabled"); +} # Bug 12632 t::lib::Mocks::mock_preference( 'item-level_itypes', 1 ); @@ -446,7 +535,7 @@ is( CanItemBeReserved( $borrowernumbers[0], $itemnumber ), # Helper method to set up a Biblio. sub create_helper_biblio { - my $itemtype = shift; + my $itemtype = $_[0] ? $_[0] : 'BK'; my $bib = MARC::Record->new(); my $title = 'Silence in the library'; $bib->append_fields( @@ -456,3 +545,77 @@ sub create_helper_biblio { ); return ($bibnum, $title, $bibitemnum) = AddBiblio($bib, ''); } + +sub setSimpleCircPolicy { + $dbh->do('DELETE FROM issuingrules'); + $dbh->do( + q{INSERT INTO issuingrules (categorycode, branchcode, itemtype, reservesallowed, + maxissueqty, issuelength, lengthunit, + renewalsallowed, renewalperiod, + norenewalbefore, auto_renew, + fine, chargeperiod, holdspickupwait) + VALUES (?, ?, ?, ?, + ?, ?, ?, + ?, ?, + ?, ?, + ?, ?, ? + ) + }, + {}, + '*', '*', '*', 25, + 20, 14, 'days', + 1, 7, + '', 0, + .10, 1,1 + ); +} + +###Set C4::Calendar and Koha::Calendar holidays for +# today -2 days +# today -3 days +# today -4 days +# +## Koha::Calendar for caching purposes (supposedly) doesn't work from the DB in this script +## So we must set the cache for Koha::calnder as well as the DB modifications for C4::Calendar. +## When making date comparisons with Koha::Calendar, using DateTime::Set, DateTime-objects +## need to match by the nanosecond and time_zone. +sub setCalendars { + + ##Set the C4::Calendar + my $now = DateTime->now(time_zone => C4::Context->tz())->truncate(to => 'day'); + my $c4calendar = C4::Calendar->new(branchcode => $reserve->{branchcode}); + $now->add_duration( DateTime::Duration->new(days => -2) ); + $c4calendar->insert_single_holiday( + day => $now->day(), + month => $now->month(), + year => $now->year(), + title => 'Test', + description => 'Test', + ); + $now->add_duration( DateTime::Duration->new(days => -1) ); + $c4calendar->insert_single_holiday( + day => $now->day(), + month => $now->month(), + year => $now->year(), + title => 'Test', + description => 'Test', + ); + $now->add_duration( DateTime::Duration->new(days => -1) ); + $c4calendar->insert_single_holiday( + day => $now->day(), + month => $now->month(), + year => $now->year(), + title => 'Test', + description => 'Test', + ); + + #Set the Koha::Calendar + my $kohaCalendar = Koha::Calendar->new(branchcode => $reserve->{branchcode}); + $now = DateTime->now(time_zone => C4::Context->tz())->truncate(to => 'day'); + $now->add_duration( DateTime::Duration->new(days => -2) ); + $kohaCalendar->add_holiday( $now ); + $now->add_duration( DateTime::Duration->new(days => -1) ); + $kohaCalendar->add_holiday( $now ); + $now->add_duration( DateTime::Duration->new(days => -1) ); + $kohaCalendar->add_holiday( $now ); +} \ No newline at end of file -- 1.9.1
    Available sinceAvailable since-until Title Patron Location

    [% overloo.waitingdate | $KohaDates %]

    [% overloo.waitingdate | $KohaDates %] - [% overloo.lastpickupdate | $KohaDates %]

    [% INCLUDE 'biblio-default-view.inc' biblionumber = overloo.biblionumber %][% overloo.title |html %] [% FOREACH subtitl IN overloo.subtitle %] [% subtitl.subfield %][% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt index 1663128..72a4b91 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt @@ -67,6 +67,7 @@
  • You can also define the maximum number of days a patron will be suspended in the 'Max suspension duration' setting
  • 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
  • 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
  • +
  • Holds wait for pickup (day) - 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.
  • 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.
    • 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.
  • 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 diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt index b174a96..c49fd2f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt @@ -4,7 +4,7 @@

    This report will show all of the holds that are waiting for patrons to pick them up.

    -

    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.

    +

    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.

    See the full documentation for Holds Awaiting Pickup in the manual (online).

    diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt index 47ac879..7572e36 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt @@ -480,6 +480,7 @@ function validate1(date) {
  • Hold dateLast pickup date Title Call number Barcode