Bugzilla – Attachment 92425 Details for
Bug 8367
How long is a hold waiting for pickup at a more granular level
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 8367: How long is a hold waiting for pickup at a more granular level
Bug-8367-How-long-is-a-hold-waiting-for-pickup-at-.patch (text/plain), 77.18 KB, created by
Hayley Pelham
on 2019-08-22 00:45:48 UTC
(
hide
)
Description:
Bug 8367: How long is a hold waiting for pickup at a more granular level
Filename:
MIME Type:
Creator:
Hayley Pelham
Created:
2019-08-22 00:45:48 UTC
Size:
77.18 KB
patch
obsolete
>From 2d8a8a58a80d76cb47a1203b73a0ff08d452ed5b Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Joonas=20Kylm=C3=A4l=C3=A4?= <j.kylmala@gmail.com> >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 >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >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 <<expirationdate>> with <<lastpickupdate>> > >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 following 3 templates of staff client, you can see the 'Last pickup date' for a hold > -circ/circulation.pl, > Circulation>Check outs>Search for Patron>Holds tab > -circ/waitingreserves.pl > Circulation>Holds awaiting pickup > -members/moremember.pl > Patrons>Search for patron>Holds tab >5. In opac-user.pl the last pickup date is displayed in the Status column if available. >6. 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: RESPECTING CALENDAR HOLIDAYS * > ** ** ** ** ** ** >1) Create and trap a hold >2) View waitingreserves.pl (Circulation>Holds awaiting pickup), note the last pickup date, and the pickup > location >3) Cancel this hold >4) 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. >5) Repeat step 2 >6) 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_SLIP, add <<reserves.lastpickupdate>> and <<reserves.waitingdate>> > somewhere. >2. Catch a hold for a borrower and print the slip. Verify that the >lastpickupdate is actually (currentdate + the 'Holds pickup wait' in > your issuing rules) in days. >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. > >Originally Authored-by: Joonas Kylmälä <j.kylmala@gmail.com> >Taken up by: Hayley Mapley and Alex Buckley (Catalyst IT) >Sponsored-by: Brimbank District Council >--- > C4/Circulation.pm | 4 +- > C4/Letters.pm | 6 +- > C4/Reserves.pm | 201 +++++++++++++++++-- > Koha/Hold.pm | 32 ++- > Koha/IssuingRules.pm | 2 +- > admin/smart-rules.pl | 4 +- > circ/returns.pl | 1 + > circ/waitingreserves.pl | 79 ++++++-- > installer/data/mysql/atomicupdate/bug_8367.perl | 21 ++ > installer/data/mysql/kohastructure.sql | 3 + > installer/data/mysql/sysprefs.sql | 4 +- > installer/data/mysql/updatedatabase.pl | 4 + > .../prog/en/includes/waiting_holds.inc | 2 +- > koha-tmpl/intranet-tmpl/prog/en/js/holds.js | 148 ++++++++++++++ > .../en/modules/admin/preferences/circulation.pref | 9 +- > .../prog/en/modules/admin/smart-rules.tt | 4 + > .../prog/en/modules/circ/circulation.tt | 11 +- > .../prog/en/modules/help/admin/smart-rules.tt | 165 ++++++++++++++++ > .../prog/en/modules/help/circ/waitingreserves.tt | 11 ++ > .../prog/en/modules/members/moremember.tt | 1 + > koha-tmpl/intranet-tmpl/prog/js/holds.js | 3 + > .../bootstrap/en/includes/holds-table.inc | 5 +- > svc/holds | 7 +- > t/db_dependent/Holds.t | 219 +++++++++++++++++++++ > 24 files changed, 877 insertions(+), 69 deletions(-) > create mode 100644 installer/data/mysql/atomicupdate/bug_8367.perl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/js/holds.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt > >diff --git a/C4/Circulation.pm b/C4/Circulation.pm >index 9961d380778..44f94e7fec7 100644 >--- a/C4/Circulation.pm >+++ b/C4/Circulation.pm >@@ -2005,7 +2005,6 @@ sub AddReturn { > } > $validTransfert = 1; > } >- > # fix up the accounts..... > if ( $item->itemlost ) { > $messages->{'WasLost'} = 1; >@@ -2061,8 +2060,9 @@ sub AddReturn { > my $lookahead= C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds > ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->itemnumber, undef, $lookahead ) unless ( $item->withdrawn ); > if ($resfound) { >- $resrec->{'ResFound'} = $resfound; >+ $resrec->{'ResFound'} = $resfound; > $messages->{'ResFound'} = $resrec; >+ C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W'); > } > > # Record the fact that this book was returned. >diff --git a/C4/Letters.pm b/C4/Letters.pm >index bdb213a49e8..cb6c29ed636 100644 >--- a/C4/Letters.pm >+++ b/C4/Letters.pm >@@ -786,16 +786,12 @@ sub _parseletter { > > # Work on a local copy of $values_in (passed by reference) to avoid side effects > # in callers ( by changing / formatting values ) >- my $values = $values_in ? { %$values_in } : {}; >+ my $values = $values_in ? { %$values_in } : {}; > > if ( $table eq 'borrowers' && $values->{'dateexpiry'} ){ > $values->{'dateexpiry'} = output_pref({ dt => dt_from_string( $values->{'dateexpiry'} ), dateonly => 1 }); > } > >- if ( $table eq 'reserves' && $values->{'waitingdate'} ) { >- $values->{'waitingdate'} = output_pref({ dt => dt_from_string( $values->{'waitingdate'} ), dateonly => 1 }); >- } >- > if ($letter->{content} && $letter->{content} =~ /<<today>>/) { > my $todaysdate = output_pref( DateTime->now() ); > $letter->{content} =~ s/<<today>>/$todaysdate/go; >diff --git a/C4/Reserves.pm b/C4/Reserves.pm >index 2a77c3dc5ba..716ef622142 100644 >--- a/C4/Reserves.pm >+++ b/C4/Reserves.pm >@@ -120,7 +120,6 @@ BEGIN { > &CanItemBeReserved > &CanReserveBeCanceledFromOpac > &CancelExpiredReserves >- > &AutoUnsuspendReserves > > &IsAvailableForItemLevelRequest >@@ -185,17 +184,20 @@ sub AddReserve { > # Make room in reserves for this before those of a later reserve date > $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority ); > } >+ my ($waitingdate, $lastpickupdate); > >- my $waitingdate; >- >+ 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}; >+ my $patron = Koha::Patrons->find( {borrowernumber => $borrowernumber} ); >+ $lastpickupdate = GetLastPickupDate( $reserve, $item, $patron ); >+ } > # Don't add itemtype limit if specific item is selected > $itemtype = undef if $checkitem; >- > # updates take place here > my $hold = Koha::Hold->new( > { >@@ -211,10 +213,10 @@ sub AddReserve { > expirationdate => $expdate, > itemtype => $itemtype, > item_level_hold => $checkitem ? 1 : 0, >+ lastpickupdate => $lastpickupdate, > } > )->store(); > $hold->set_waiting() if $found eq 'W'; >- > logaction( 'HOLDS', 'CREATE', $hold->id, Dumper($hold->unblessed) ) > if C4::Context->preference('HoldsLog'); > >@@ -661,6 +663,104 @@ sub GetReserveStatus { > return ''; # empty string here will remove need for checking undef, or less log lines > } > >+ >+=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 $borrower) { >+ my $borrower = Koha::Patrons->find( $reserve->{borrowernumber} ); >+ $borrower = $borrower->unblessed; >+ } >+ >+ 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}); >+ } >+ ##Get churning the LastPickupDate >+ >+ # Get the controlbranch >+ my $controlbranch = GetReservesControlBranch( $item, $borrower ); >+ my $hbr = C4::Context->preference('HomeOrHoldingBranch') || "homebranch"; >+ my $branchcode = "*"; >+ if ( $controlbranch eq "ItemHomeLibrary" ) { >+ $branchcode = $item->{$hbr}; >+ } elsif ( $controlbranch eq "PatronLibrary" ) { >+ $branchcode = $borrower->branchcode; >+ } >+ warn 'getlastpickup'; >+ warn $branchcode; >+ warn $borrower->{'categorycode'}; >+ warn $item->{itype}; >+ my $issuingrule = Koha::IssuingRules->get_effective_issuing_rule({ >+ branchcode => $branchcode, >+ categorycode => $borrower->{'categorycode'}, >+ itemtype => $item->{itype}, >+ }); >+ >+ my $reservebranch; >+ if ($reserve->{'branchcode'}) { >+ $reservebranch = $reserve->{'branchcode'}; >+ } else { >+ $reservebranch = $reserve->branchcode; >+ } >+ 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 => $reservebranch ); >+ 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 CheckReserves > > ($status, $matched_reserve, $possible_reserves) = &CheckReserves($itemnumber); >@@ -1006,7 +1106,7 @@ sub ModReserveFill { > > =head2 ModReserveStatus > >- &ModReserveStatus($itemnumber, $newstatus); >+ &ModReserveStatus($itemnumber, $newstatus, $holdtype); > > Update the reserve status for the active (priority=0) reserve. > >@@ -1017,14 +1117,37 @@ $newstatus is the new status. > =cut > > sub ModReserveStatus { >- > #first : check if we have a reservation for this item . > my ($itemnumber, $newstatus) = @_; > my $dbh = C4::Context->dbh; > >- my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0"; >+ 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 $borrower = Koha::Patrons->find( $reserve->{borrowernumber} ); >+ $borrower = $borrower->unblessed; >+ my $item = C4::Items::GetItem( $reserve->{itemnumber} ); >+ >+ my $lastpickupdate = GetLastPickupDate( $reserve, $item, $borrower ); >+ 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 ); > > my $item = Koha::Items->find($itemnumber); > if ( ( $item->location eq 'CART' && $item->permanent_location ne 'CART' ) && $newstatus ) { >@@ -1075,6 +1198,20 @@ sub ModReserveAffect { > $hold->itemnumber($itemnumber); > $hold->set_waiting($transferToDo); > >+ my $item = C4::Items::GetItem( $itemnumber ); >+ my $borrower = Koha::Patrons->find( $borrowernumber ); >+ $borrower = $borrower->unblessed; >+ my $lastpickupdate = GetLastPickupDate( $hold, $item, $borrower ); >+ warn $lastpickupdate; >+ my $query = " >+ UPDATE reserves >+ SET lastpickupdate = ? >+ WHERE borrowernumber = ? >+ AND itemnumber = ? >+ "; >+ $sth = $dbh->prepare($query); >+ $sth->execute( $lastpickupdate, $borrowernumber,$itemnumber); >+ > _koha_notify_reserve( $hold->reserve_id ) > if ( !$transferToDo && !$already_on_shelf ); > >@@ -1530,6 +1667,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, >@@ -1565,6 +1703,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, >@@ -1600,6 +1739,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, >@@ -1820,6 +1960,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); >@@ -1918,7 +2094,8 @@ sub RevertWaitingStatus { > SET > priority = 1, > found = NULL, >- waitingdate = NULL >+ waitingdate = NULL, >+ lastpickupdate = NULL, > WHERE > reserve_id = ? > "; >@@ -1992,7 +2169,7 @@ sub ReserveSlip { > > return unless $hold; > my $reserve = $hold->unblessed; >- >+warn Dumper($reserve); > return C4::Letters::GetPreparedLetter ( > module => 'circulation', > letter_code => 'HOLD_SLIP', >diff --git a/Koha/Hold.pm b/Koha/Hold.pm >index 30188ae69e1..b509fbd4313 100644 >--- a/Koha/Hold.pm >+++ b/Koha/Hold.pm >@@ -33,6 +33,7 @@ use Koha::Items; > use Koha::Libraries; > use Koha::Old::Holds; > use Koha::Calendar; >+use Koha::IssuingRules; > > use Koha::Exceptions::Hold; > >@@ -181,10 +182,37 @@ sub set_waiting { > my $calendar = Koha::Calendar->new( branchcode => $self->branchcode ); > > my $expirationdate = $today->clone; >- $expirationdate->add(days => $max_pickup_delay); >+ >+ my $patron = Koha::Patrons->find( {borrowernumber => $self->borrowernumber} ); >+ my $item = Koha::Items->find( $self->itemnumber() ); >+ my $hold = Koha::Holds->find( $self->reserve_id ); >+# my $lastpickupdate = C4::Reserves->GetLastPickupDate( $hold, $item, $patron); >+ >+ # Get the controlbranch >+ my $controlbranch = C4::Reserves->GetReservesControlBranch( $item, $patron ); >+ my $hbr = C4::Context->preference('HomeOrHoldingBranch') || "homebranch"; >+ my $branchcode = "*"; >+ if ( $controlbranch eq "ItemHomeLibrary" ) { >+ $branchcode = $item->{$hbr}; >+ } elsif ( $controlbranch eq "PatronLibrary" ) { >+ $branchcode = $patron->branchcode; >+ } >+ >+ warn $branchcode; >+ warn $patron->categorycode; >+ warn $item->itype; >+ my $issuingrule = Koha::IssuingRules->get_effective_issuing_rule({ >+ branchcode => $branchcode, >+ categorycode => $patron->categorycode, >+ itemtype => $item->itype, >+ }); >+ >+ if ( defined $issuingrule->holdspickupwait && $issuingrule->holdspickupwait > 0) { >+ $expirationdate->add(days => $issuingrule->holdspickupwait); >+ } > > if ( C4::Context->preference("ExcludeHolidaysFromMaxPickUpDelay") ) { >- $expirationdate = $calendar->days_forward( dt_from_string(), $max_pickup_delay ); >+ $expirationdate = $calendar->days_forward( dt_from_string(), $issuingrule->{holdspickupwait} ); > } > > # If patron's requested expiration date is prior to the >diff --git a/Koha/IssuingRules.pm b/Koha/IssuingRules.pm >index 608b4edeedb..a9459d0543f 100644 >--- a/Koha/IssuingRules.pm >+++ b/Koha/IssuingRules.pm >@@ -22,7 +22,7 @@ use Modern::Perl; > > use Koha::Database; > use Koha::Caches; >- >+use Data::Dumper; > use Koha::IssuingRule; > > use base qw(Koha::Objects); >diff --git a/admin/smart-rules.pl b/admin/smart-rules.pl >index 403f425e54d..6bfb7af61c4 100755 >--- a/admin/smart-rules.pl >+++ b/admin/smart-rules.pl >@@ -240,10 +240,11 @@ elsif ($op eq 'add') { > $no_auto_renewal_after_hard_limit = eval { dt_from_string( $input->param('no_auto_renewal_after_hard_limit') ) } if ( $no_auto_renewal_after_hard_limit ); > $no_auto_renewal_after_hard_limit = output_pref( { dt => $no_auto_renewal_after_hard_limit, dateonly => 1, dateformat => 'iso' } ) if ( $no_auto_renewal_after_hard_limit ); > my $reservesallowed = $input->param('reservesallowed'); >- my $holds_per_record = $input->param('holds_per_record'); > my $holds_per_day = $input->param('holds_per_day'); > $holds_per_day =~ s/\s//g; > $holds_per_day = undef if $holds_per_day !~ /^\d+/; >+ my $holds_per_record = $input->param('holds_per_record'); >+ my $holdspickupwait = $input->param('holdspickupwait'); > my $onshelfholds = $input->param('onshelfholds') || 0; > $maxissueqty =~ s/\s//g; > $maxissueqty = '' if $maxissueqty !~ /^\d+/; >@@ -277,6 +278,7 @@ elsif ($op eq 'add') { > chargeperiod_charge_at => $chargeperiod_charge_at, > renewalsallowed => $renewalsallowed, > renewalperiod => $renewalperiod, >+ holdspickupwait => $holdspickupwait, > norenewalbefore => $norenewalbefore, > auto_renew => $auto_renew, > no_auto_renewal_after => $no_auto_renewal_after, >diff --git a/circ/returns.pl b/circ/returns.pl >index 657229a10e1..5f01c5959e4 100755 >--- a/circ/returns.pl >+++ b/circ/returns.pl >@@ -424,6 +424,7 @@ if ( $messages->{'ResFound'}) { > > my $diffBranchSend = !$branchCheck ? $reserve->{branchcode} : undef; > ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber}, $diffBranchSend, $reserve->{reserve_id} ); >+ ModReserveStatus($reserve->{itemnumber}, 'W'); > my ( $messages, $nextreservinfo ) = GetOtherReserves($reserve->{itemnumber}); > > my $patron = Koha::Patrons->find( $nextreservinfo ); >diff --git a/circ/waitingreserves.pl b/circ/waitingreserves.pl >index c78884f70e7..3e54bbef97d 100755 >--- a/circ/waitingreserves.pl >+++ b/circ/waitingreserves.pl >@@ -27,11 +27,11 @@ use C4::Circulation; > use C4::Members; > use C4::Biblio; > use C4::Items; >-use Date::Calc qw( >- Today >- Add_Delta_Days >- Date_to_Days >-); >+#use Date::Calc qw( >+# Today >+# Add_Delta_Days >+# Date_to_Days >+#); > use C4::Reserves; > use C4::Koha; > use Koha::DateUtils; >@@ -39,6 +39,7 @@ use Koha::BiblioFrameworks; > use Koha::Items; > use Koha::ItemTypes; > use Koha::Patrons; >+use Data::Dumper; > > my $input = new CGI; > >@@ -87,26 +88,64 @@ my $holds = Koha::Holds->waiting->search({ priority => 0, ( $all_branches ? () : > > # get reserves for the branch we are logged into, or for all branches > >-my $today = Date_to_Days(&Today); >+my $today = dt_from_string; >+my $max_pickup_delay = C4::Context->preference('ReservesMaxPickUpDelay'); > > while ( my $hold = $holds->next ) { > next unless ($hold->waitingdate && $hold->waitingdate ne '0000-00-00'); >+ my $item = $hold->item; >+ my $patron = $hold->borrower; >+ my $biblio = $item->biblio; >+ my $holdingbranch = $item->holdingbranch; >+ my $homebranch = $item->homebranch; >+ my %getreserv = ( >+ title => $biblio->title, >+ itemnumber => $item->itemnumber, >+ waitingdate => $hold->waitingdate, >+ reservedate => $hold->reservedate, >+ borrowernumber => $patron->borrowernumber, >+ biblionumber => $biblio->biblionumber, >+ barcode => $item->barcode, >+ homebranch => $homebranch, >+ holdingbranch => $item->holdingbranch, >+ itemcallnumber => $item->itemcallnumber, >+ enumchron => $item->enumchron, >+ copynumber => $item->copynumber, >+ borrowername => $patron->surname, # FIXME Let's send $patron to the template >+ borrowerfirstname => $patron->firstname, >+ borrowerphone => $patron->phone, >+ lastpickupdate => $hold->lastpickupdate, >+ ); >+ >+ my $itemtype = Koha::ItemTypes->find( $item->effective_itemtype ); >+ $getreserv{'itemtype'} = $itemtype->description; # FIXME Should not it be translated_description? >+ $getreserv{'subtitle'} = GetRecordValue( >+ 'subtitle', >+ GetMarcBiblio({ biblionumber => $biblio->biblionumber }), >+ $biblio->frameworkcode); >+ if ( $homebranch ne $holdingbranch ) { >+ $getreserv{'dotransfer'} = 1; >+ } > >- my ( $expire_year, $expire_month, $expire_day ) = split (/-/, $hold->expirationdate); >- my $calcDate = Date_to_Days( $expire_year, $expire_month, $expire_day ); >- >- if ($today > $calcDate) { >- if ($cancelall) { >- my $res = cancel( $hold->item->itemnumber, $hold->borrowernumber, $hold->item->holdingbranch, $hold->item->homebranch, !$transfer_when_cancel_all ); >- push @cancel_result, $res if $res; >- next; >- } else { >- push @over_loop, $hold; >+ $getreserv{patron} = $patron; >+ warn $getreserv{'waitingdate'}; >+ warn $getreserv{'lastpickupdate'}; >+ if ( $getreserv{'waitingdate'} ) { >+ my $lastpickupdate = dt_from_string($getreserv{'lastpickupdate'}); >+ if ( DateTime->compare( $today, $lastpickupdate ) == 1 ) { >+ if ($cancelall) { >+ my $res = cancel( $item->itemnumber, $patron->borrowernumber, $item->holdingbranch, $homebranch, !$transfer_when_cancel_all ); >+ push @cancel_result, $res if $res; >+ next; >+ } else { >+ push @overloop, \%getreserv; >+ $overcount++; >+ } >+ }else{ >+ push @reservloop, \%getreserv; >+ $reservcount++; > } >- }else{ >- push @reserve_loop, $hold; > } >- > } > > $template->param(cancel_result => \@cancel_result) if @cancel_result; >@@ -118,6 +157,7 @@ $template->param( > overcount => scalar @over_loop, > show_date => output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }), > tab => $tab, >+ show_date => $today, > ); > > # Checking if there is a Fast Cataloging Framework >@@ -139,7 +179,6 @@ sub cancel { > my $transfer = $fbr ne $tbr; # XXX && !$nextreservinfo; > > return if $transfer && $skip_transfers; >- > my ( $messages, $nextreservinfo ) = ModReserveCancelAll( $item, $borrowernumber ); > > # if the document is not in his homebranch location and there is not reservation after, we transfer it >diff --git a/installer/data/mysql/atomicupdate/bug_8367.perl b/installer/data/mysql/atomicupdate/bug_8367.perl >new file mode 100644 >index 00000000000..59cad22cb97 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug_8367.perl >@@ -0,0 +1,21 @@ >+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";>>>>>>> Bug 8367: Template fixes + fixes to C4/Reserves.pm and Koha/Hold.pm >diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql >index 5016b68c51d..4cc0ee46a7b 100644 >--- a/installer/data/mysql/kohastructure.sql >+++ b/installer/data/mysql/kohastructure.sql >@@ -832,6 +832,7 @@ CREATE TABLE `issuingrules` ( -- circulation and fine rules > `reservesallowed` smallint(6) NOT NULL default "0", -- how many holds are allowed > `holds_per_record` SMALLINT(6) NOT NULL DEFAULT 1, -- How many holds a patron can have on a given bib > `holds_per_day` SMALLINT(6) DEFAULT NULL, -- How many holds a patron can have on a day >+ `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 > cap_fine_to_replacement_price BOOLEAN NOT NULL DEFAULT '0', -- cap the fine based on item's replacement price >@@ -1818,6 +1819,7 @@ CREATE TABLE `reserves` ( -- information related to holds/reserves in Koha > `suspend_until` DATETIME NULL DEFAULT NULL, > `itemtype` VARCHAR(10) NULL DEFAULT NULL, -- If record level hold, the optional itemtype of the item the patron is requesting > `item_level_hold` BOOLEAN NOT NULL DEFAULT 0, -- Is the hpld placed at item level >+ `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`), >@@ -1858,6 +1860,7 @@ CREATE TABLE `old_reserves` ( -- this table holds all holds/reserves that have b > `suspend_until` DATETIME NULL DEFAULT NULL, -- the date this hold is suspended until (NULL for infinitely) > `itemtype` VARCHAR(10) NULL DEFAULT NULL, -- If record level hold, the optional itemtype of the item the patron is requesting > `item_level_hold` BOOLEAN NOT NULL DEFAULT 0, -- Is the hpld placed at item level >+ `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`), >diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql >index 82a8402a360..c2b6696ebf2 100644 >--- a/installer/data/mysql/sysprefs.sql >+++ b/installer/data/mysql/sysprefs.sql >@@ -171,11 +171,10 @@ 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'), > ('ExcludeHolidaysFromMaxPickUpDelay', '0', NULL, 'If ON, reserves max pickup delay takes into accountthe closed days.', 'YesNo'), > ('ExportCircHistory', 0, NULL, "Display the export circulation options", 'YesNo' ), > ('ExportRemoveFields','',NULL,'List of fields for non export in circulation.pl (separated by a space)','Free'), >-('ExtendedPatronAttributes','1',NULL,'Use extended patron IDs and attributes','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'), > ('FailedLoginAttempts','','','Number of login attempts before lockout the patron account','Integer'), >@@ -507,7 +506,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('RequestOnOpac','1',NULL,'If ON, globally enables patron holds on OPAC','YesNo'), > ('RequireStrongPassword','1','','Require a strong login password for staff and patrons','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'), > ('RESTBasicAuth','0',NULL,'If enabled, Basic authentication is enabled for the REST API.','YesNo'), > ('RESTdefaultPageSize','20','','Default page size for endpoints listing objects','Integer'), >diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl >index c879a0766cd..4b5415cf1b2 100755 >--- a/installer/data/mysql/updatedatabase.pl >+++ b/installer/data/mysql/updatedatabase.pl >@@ -37,6 +37,8 @@ use Getopt::Long; > # Koha modules > use C4::Context; > use C4::Installer; >+use C4::Reserves; >+use DateTime::Duration; > use Koha::Database; > use Koha; > use Koha::DateUtils; >@@ -16478,6 +16480,8 @@ if( CheckVersion( $DBversion ) ) { > print "Upgrade to $DBversion done (Bug 21403 - Add Indian Amazon Affiliate option to AmazonLocale setting)\n"; > } > >+# SEE bug 13068 >+# if there is anything in the atomicupdate, read and execute it. > > $DBversion = '18.06.00.036'; > if( CheckVersion( $DBversion ) ) { >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >index 716f1c7bdfe..de3f39e7f9e 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >@@ -19,7 +19,7 @@ > <tbody> > [% FOREACH reserveloo IN reserveloop %] > <tr> >- <td><span title="[% reserveloo.waitingdate | html %]">[% reserveloo.waitingdate | $KohaDates %]</span></td> >+ <td><span title="[% reserveloo.waitingdate | html %]">[% reserveloo.waitingdate | $KohaDates %] - [% reserveloo.lastpickupdate | $KohaDates %]</span></td> > <td><span title="[% reserveloo.reservedate | html %]">[% reserveloo.reservedate | $KohaDates %]</span></td> > <td>[% INCLUDE 'biblio-default-view.inc' biblionumber = reserveloo.biblionumber %] > [% INCLUDE 'biblio-title.inc' biblio=reserveloo.biblio %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/holds.js b/koha-tmpl/intranet-tmpl/prog/en/js/holds.js >new file mode 100644 >index 00000000000..51c337d470e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/js/holds.js >@@ -0,0 +1,148 @@ >+$(document).ready(function() { >+ var holdsTable; >+ >+ // Don't load holds table unless it is clicked on >+ $("#holds-tab").on( "click", function(){ load_holds_table() } ); >+ >+ // If the holds tab is preselected on load, we need to load the table >+ if ( $("#holds-tab").parent().hasClass('ui-state-active') ) { load_holds_table() } >+ >+ function load_holds_table() { >+ if ( ! holdsTable ) { >+ holdsTable = $("#holds-table").dataTable({ >+ "bAutoWidth": false, >+ "sDom": "rt", >+ "aoColumns": [ >+ { >+ "mDataProp": "reservedate_formatted" >+ }, >+ { >+ "mDataProp": "lastpickupdate_formatted" >+ }, >+ { >+ "mDataProp": function ( oObj ) { >+ title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" >+ + oObj.biblionumber >+ + "'>" >+ + oObj.title; >+ >+ $.each(oObj.subtitle, function( index, value ) { >+ title += " " + value.subfield; >+ }); >+ >+ title += "</a>"; >+ >+ if ( oObj.author ) { >+ title += " " + BY.replace( "_AUTHOR_", oObj.author ); >+ } >+ >+ if ( oObj.itemnotes ) { >+ var span_class = ""; >+ if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { >+ span_class = "circ-hlt"; >+ } >+ title += " - <span class='" + span_class + "'>" + oObj.itemnotes + "</span>" >+ } >+ >+ return title; >+ } >+ }, >+ { >+ "mDataProp": function( oObj ) { >+ return oObj.itemcallnumber || ""; >+ } >+ }, >+ { >+ "mDataProp": function( oObj ) { >+ var data = ""; >+ >+ if ( oObj.suspend == 1 ) { >+ data += "<p>" + HOLD_IS_SUSPENDED; >+ if ( oObj.suspend_until ) { >+ data += " " + UNTIL.format( oObj.suspend_until_formatted ); >+ } >+ data += "</p>"; >+ } >+ >+ if ( oObj.barcode ) { >+ data += "<em>"; >+ if ( oObj.found == "W" ) { >+ >+ if ( oObj.waiting_here ) { >+ data += ITEM_IS_WAITING_HERE; >+ } else { >+ data += ITEM_IS_WAITING; >+ data += " " + AT.format( oObj.waiting_at ); >+ } >+ >+ } else if ( oObj.transferred ) { >+ data += ITEM_IS_IN_TRANSIT.format( oObj.from_branch, oObj.date_sent ); >+ } else if ( oObj.not_transferred ) { >+ data += NOT_TRANSFERRED_YET.format( oObj.not_transferred_by ); >+ } >+ data += "</em>"; >+ >+ data += " <a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber=" >+ + oObj.biblionumber >+ + "&itemnumber=" >+ + oObj.itemnumber >+ + "#" >+ + oObj.itemnumber >+ + "'>" >+ + oObj.barcode >+ + "</a>"; >+ } >+ >+ return data; >+ } >+ }, >+ { >+ "mDataProp": function( oObj ) { >+ return oObj.branchcode || ""; >+ } >+ }, >+ { "mDataProp": "expirationdate_formatted" }, >+ { >+ "mDataProp": function( oObj ) { >+ if ( oObj.priority && parseInt( oObj.priority ) && parseInt( oObj.priority ) > 0 ) { >+ return oObj.priority; >+ } else { >+ return ""; >+ } >+ } >+ }, >+ { >+ "bSortable": false, >+ "mDataProp": function( oObj ) { >+ return "<select name='rank-request'>" >+ + "<option value='n'>" + NO + "</option>" >+ + "<option value='del'>" + YES + "</option>" >+ + "</select>" >+ + "<input type='hidden' name='biblionumber' value='" + oObj.biblionumber + "'>" >+ + "<input type='hidden' name='borrowernumber' value='" + borrowernumber + "'>" >+ + "<input type='hidden' name='reserve_id' value='" + oObj.reserve_id + "'>"; >+ } >+ } >+ ], >+ "bPaginate": false, >+ "bProcessing": true, >+ "bServerSide": false, >+ "sAjaxSource": '/cgi-bin/koha/svc/holds', >+ "fnServerData": function ( sSource, aoData, fnCallback ) { >+ aoData.push( { "name": "borrowernumber", "value": borrowernumber } ); >+ >+ $.getJSON( sSource, aoData, function (json) { >+ fnCallback(json) >+ } ); >+ }, >+ }); >+ >+ if ( $("#holds-table").length ) { >+ $("#holds-table_processing").position({ >+ of: $( "#holds-table" ), >+ collision: "none" >+ }); >+ } >+ } >+ } >+}); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >index 199809f1391..d52db6c1c2b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >@@ -629,18 +629,13 @@ Circulation: > choices: > ItemHomeLibrary: "item's home library" > PatronLibrary: "patron's home library" >- - to see if the patron can place a hold on the item. >- - >- - Mark a hold as problematic if it has been waiting for more than >- - pref: ReservesMaxPickUpDelay >- class: integer >- - days. >+ - to see if the patron can place a hold on the item. > - > - pref: ExpireReservesMaxPickUpDelay > choices: > yes: Allow > no: "Don't allow" >- - "holds to expire automatically if they have not been picked by within the time period specified in ReservesMaxPickUpDelay.<br><strong>NOTE:</strong> This system preference requires the <code>misc/cronjobs/holds/cancel_expired_holds.pl</code> cronjob. Ask your system administrator to schedule it." >+ - "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" > - > - If using ExpireReservesMaxPickUpDelay, charge a borrower who allows their waiting hold to expire a fee of > - pref: ExpireReservesMaxPickUpDelayCharge >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt >index dc09354f6e4..30d0ba9894b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt >@@ -97,6 +97,7 @@ > <th>Suspension charging interval</th> > <th>Renewals allowed (count)</th> > <th>Renewal period</th> >+ <th>Holds wait for pickup (day)</th> > <th>No renewal before</th> > <th>Automatic renewal</th> > <th>No automatic renewal after</th> >@@ -195,6 +196,7 @@ > <td>[% rule.suspension_chargeperiod | html %]</td> > <td>[% rule.renewalsallowed | html %]</td> > <td>[% rule.renewalperiod | html %]</td> >+ <td>[% rule.holdspickupwait | html %]</td> > <td>[% rule.norenewalbefore | html %]</td> > <td> > [% IF ( rule.auto_renew ) %] >@@ -307,6 +309,7 @@ > <td><input type="text" name="suspension_chargeperiod" id="suspension_chargeperiod" size="3" /> </td> > <td><input type="text" name="renewalsallowed" id="renewalsallowed" size="2" /></td> > <td><input type="text" name="renewalperiod" id="renewalperiod" size="3" /></td> >+ <td><input type="text" name="holdspickupwait" id="holdspickupwait" size="2" /></td> > <td><input type="text" name="norenewalbefore" id="norenewalbefore" size="3" /></td> > <td> > <select name="auto_renew" id="auto_renew"> >@@ -373,6 +376,7 @@ > <th>Suspension charging interval</th> > <th>Renewals allowed (count)</th> > <th>Renewal period</th> >+ <th>Holds wait for pickup (day)</th> > <th>No renewal before</th> > <th>Automatic renewal</th> > <th>No automatic renewal after</th> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >index eba096c688c..95a94cf5ba9 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >@@ -876,6 +876,7 @@ > <thead> > <tr> > <th>Hold date</th> >+ <th>Last pickup date</th> > <th>Title</th> > <th>Call number</th> > <th>Barcode</th> >@@ -949,16 +950,6 @@ > [% END %] > [% END %] > >- [% UNLESS ( borrowers ) %] >- [% IF borrowernumber and patron %] >- <div class="col-sm-2 col-sm-pull-10"> >- <aside> >- [% INCLUDE 'circ-menu.inc' %] >- </aside> >- </div> <!-- /.col-sm-2 col-sm-pull-10 --> >- [% END %] >- [% END %] >- > </div> <!-- /.row --> > </main> > >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 >new file mode 100644 >index 00000000000..1452e510a3a >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/admin/smart-rules.tt >@@ -0,0 +1,165 @@ >+[% INCLUDE 'help-top.inc' %] >+ >+<h1>Circulation and Fines Rules</h1> >+ >+<p>These rules define how your items are circulated, how/when fines are calculated and how holds are handled.</p> >+ >+<p>The rules are applied from most specific to less specific, using the first found in this order:</p> >+ >+<ul> >+ <li>same library, same patron type, same item type</li> >+ <li>same library, same patron type, all item type</li> >+ <li>same library, all patron types, same item type</li> >+ <li>same library, all patron types, all item types</li> >+ <li>all libraries, same patron type, same item type</li> >+ <li>all libraries, same patron type, all item types</li> >+ <li>all libraries, all patron types, same item type</li> >+ <li>all libraries, all patron types, all item types</li> >+</ul> >+ >+<p>The CircControl and HomeOrHoldingBranch also come in to play when figuring out which circulation rule to follow.</p> >+ >+<ul> >+ <li>If CircControl is set to "the library you are logged in at" circ rules will be selected based on the library you are logged in at</li> >+ <li>If CircControl is set to "the library the patron is from" circ rules will be selected based on the patron's library</li> >+ <li>If CircControl is set to "the library the item is from" circ rules will be selected based on the item's library where HomeOrHoldingBranch chooses if item's home library is used or holding library is used.</li> >+ <li>If IndependentBranches is set to 'Prevent' then the value of HomeOrHoldingBranch is used in figuring out if the item can be checked out. If the item's home library does not match the logged in library, the item cannot be checked out unless you are a superlibrarian.</li> >+</ul> >+ >+<p>If you are a single library system choose your branch name before creating rules (sometimes having only rules for the 'all libraries' option can cause issues with holds)</p> >+ >+<p style="color: #990000;">Important: At the very least you will need to set a default circulation rule. This rule should be set for all item types, all libraries and all patron categories. That will catch all instances that do not match a specific rule. When checking out if you do not have a rule for all libraries, all item types and all patron types then you may see patrons getting blocked from placing holds. You will also want a rule for your specific library set for all item types and all patron types to avoid this holds issue. Koha needs to know what rule to fall back on.</p> >+ >+<h4>Default Circulation Rules</h4> >+ >+<p>Using the issuing rules matrix you can define rules that depend on patron/item type combinations. To set your rules, choose a library from the pull down (or 'all libraries' if you want to apply these rules to all libraries)</p> >+ >+<p>From the matrix you can choose any combination of patron categories and item types to apply the rules to</p> >+ >+<ul> >+ <li>First choose which patron category you'd like the rule to be applied to. If you leave this to 'All' it will apply to all patron categories</li> >+ <li>Choose the 'Item Type' you would like this rule to apply to. If you leave this to 'All' it will apply to all item types</li> >+ <li>Limit the number of items a patron can have checked out at the same time by entering a number in the 'Current Checkouts Allowed' field</li> >+ <li>Define the period of time an item can be checked out to a patron by entering the number of units (days or hours) in the 'Loan Period' box.</li> >+ <li>Choose which unit of time, Days or Hours, that the loan period and fines will be calculate in</li> >+ <li>You can also define a hard due date for a specific patron category and item type. A hard due date ignores your usual circulation rules and makes it so that all items of the type defined are due on, before or after the date you specify.</li> >+ <li>'Fine Amount' should have the amount you would like to charge for overdue items >+<ul> >+ <li style="color: #990000;">Important: Enter only numbers and decimal points (no currency symbols).</li> >+</ul> >+</li> >+ <li>Enter the 'Fine Charging Interval' in the unit you set (ex. charge fines every 1 day, or every 2 hours)</li> >+ <li>The 'Fine Grace Period' is the period of time an item can be overdue before you start charging fines. >+<ul> >+ <li style="color: #990000;">Important: This can only be set for the Day unit, not in Hours</li> >+</ul> >+</li> >+ <li>The 'Overdue Fines Cap' is the maximum fine for this patron and item combination >+<ul> >+ <li style="color: #990000;">Important: If this field is left blank then Koha will not put a limit on the fines this item will accrue. A maximum fine amount can be set using the MaxFinesystem preference.</li> >+</ul> >+</li> >+ <li>If your library 'fines' patrons by suspending their account you can enter the number of days their fine should be suspended in the 'Suspension in Days' field >+<ul> >+ <li style="color: #990000;">Important: This can only be set for the Day unit, not in Hours</li> >+</ul> >+</li> >+ <li>You can also define the maximum number of days a patron will be suspended in the 'Max suspension duration' setting</li> >+ <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> >+ <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> >+ <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> >+ <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. >+ <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> >+ <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 >+ <ul> >+ <li style="color: #990000;">Important: You will need to enable the automatic renewal cron job for this to work.</li> >+ <li style="color: #990000;">Important: This feature needs to have the "no renewal before" column filled in or it will auto renew everyday after the due date.</li> >+ </ul> >+ </li> >+ <li>If the patron can place holds on this item type, enter the total numbers of items (of this type) that can be put on hold in the 'Holds Allowed' field</li> >+ <li>Next you can decide if this patron/item combo are allowed to place holds on items that are on the shelf (or available in the library) or not. If you choose 'no' then items can only be placed on hold if checked out</li> >+ <li>You can also decide if patrons are allowed to place item specific holds on the item type in question. The options are: >+ <ul> >+ <li>Allow: Will allow patrons the option to choose next available or item specific</li> >+ <li>Don't allow: Will only allow patrons to choose next available</li> >+ <li>Force: Will only allow patrons to choose an specific item</li> >+ </ul></li> >+ <li>Finally, if you charge a rental fee for the item type and want to give a specific patron type a discount on that fee, enter the percentage discount (without the % symbol) in the 'Rental Discount' field</li> >+</ul> >+ >+<p>When finished, click 'Add' to save your changes. To modify a rule, create a new one with the same patron type and item type. If you would like to delete your rule, simply click the 'Delete' link to the right of the rule.</p> >+ >+<p>To save time you can clone rules from one library to another by choosing the clone option above the rules matrix.</p> >+ >+<p>After choosing to clone you will be presented with a confirmation message.</p> >+ >+<h4>Default Checkouts and Hold Policy</h4> >+ >+<p>You can set a default maximum number of checkouts and hold policy that will be used if none is defined below for a particular item type or category.</p> >+ >+<p>From this menu you can set a default to apply to all item types and patrons in the library.</p> >+ >+<ul> >+ <li>In 'Total Current Checkouts Allowed' enter the total number of items patrons can have checked out at one time</li> >+ <li>Control where patrons can place holds from using the 'Hold Policy' menu >+<ul> >+ <li>From Any Library: Patrons from any library may put this item on hold. (default if none is defined)</li> >+ <li>From Home Library: Only patrons from the item's home library may put this book on hold.</li> >+ <li>No Holds Allowed: No patron may put this book on hold.</li> >+</ul> >+</li> >+ <li>Control where the item returns to once it is checked in >+<ul> >+ <li>Item returns home</li> >+ <li>Item returns to issuing library</li> >+ <li>Item floats >+<ul> >+ <li>When an item floats it stays where it was checked in and does not ever return 'home'</li> >+</ul> >+</li> >+</ul> >+</li> >+ <li>Once your policy is set, you can unset it by clicking the 'Unset' link to the right of the rule</li> >+</ul> >+ >+<h4>Checkouts Per Patron</h4> >+ >+<p>For this library, you can specify the maximum number of loans that a patron of a given category can make, regardless of the item type.</p> >+ >+<p>Tip: If the total amount loanable for a given patron category is left blank, no limit applies, except possibly for a limit you define for a specific item type.</p> >+ >+<h4>Item Hold Policies</h4> >+ >+<p>For this library, you can edit rules for given itemtypes, regardless of the patron's category. Currently, this means hold policies.</p> >+ >+<p>The various Hold Policies have the following effects:</p> >+ >+<ul> >+ <li>From Any Library: Patrons from any library may put this item on hold. (default if none is defined)</li> >+ <li>From Home Library: Only patrons from the item's home library may put this book on hold.</li> >+ <li>No Holds Allowed: No patron may put this book on hold.</li> >+</ul> >+ >+<p style="color: #990000;">Important: Note that if the system preference AllowHoldPolicyOverrideset to 'allow', these policies can be overridden by your circulation staff.</p> >+ >+<p style="color: #990000;">Important: These policies are based on the patron's home library, not the library that the reserving staff member is from.</p> >+ >+<p>The various Return Policies have the following effects:</p> >+ >+<ul> >+ <li>Item returns home: The item will prompt the librarian to transfer the item to its home library >+<ul> >+ <li style="color: #990000;">Important: If the AutomaticItemReturnpreference is set to automatically transfer the items home, then a prompt will not appear</li> >+</ul> >+</li> >+ <li>Item returns to issuing library: The item will prompt the librarian to transfer the item back to the library where it was checked out >+<ul> >+ <li style="color: #990000;">Important: If the AutomaticItemReturnpreference is set to automatically transfer the items home, then a prompt will not appear</li> >+</ul> >+</li> >+ <li>Item floats: The item will not be transferred from the library it was checked in at, instead it will remain there until transferred manually or checked in at another library</li> >+</ul> >+ >+<p><strong>See the full documentation for Circulation and Fine Rules in the <a href="http://manual.koha-community.org/[% helpVersion %]/en/patscirc.html#circfinerules">manual</a> (online).</strong></p> >+ >+[% INCLUDE 'help-bottom.inc' %] >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 >new file mode 100644 >index 00000000000..c49fd2f723a >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/circ/waitingreserves.tt >@@ -0,0 +1,11 @@ >+[% INCLUDE 'help-top.inc' %] >+ >+<h1>Holds awaiting pickup</h1> >+ >+<p>This report will show all of the holds that are waiting for patrons to pick them up.</p> >+ >+<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> >+ >+<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> >+ >+[% INCLUDE 'help-bottom.inc' %] >\ No newline at end of file >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 86d26a586f3..227c690914a 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >@@ -750,6 +750,7 @@ > <thead> > <tr> > <th>Hold date</th> >+ <th>Last pickup date</th> > <th>Title</th> > <th>Call number</th> > <th>Barcode</th> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/holds.js b/koha-tmpl/intranet-tmpl/prog/js/holds.js >index 3c0d77dc48b..64bc49d2c12 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/holds.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/holds.js >@@ -18,6 +18,9 @@ $(document).ready(function() { > "data": { _: "reservedate_formatted", "sort": "reservedate" } > }, > { >+ "mDataProp": "lastpickupdate_formatted" >+ }, >+ { > "mDataProp": function ( oObj ) { > title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" > + oObj.biblionumber >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc >index 3c5ea159386..4f2ba9ede5b 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc >@@ -107,10 +107,7 @@ > [% IF ( HOLD.found ) %] > Item waiting at <b> [% HOLD.branch.branchname | html %]</b> > [% IF ( HOLD.waitingdate ) %] >- since [% HOLD.waitingdate | $KohaDates %] >- [% IF HOLD.expirationdate %] >- until [% HOLD.expirationdate | $KohaDates %] >- [% END %] >+ since [% HOLD.waitingdate | $KohaDates %] until [% RESERVE.lastpickupdate | $KohaDates %] > [% END %] > <input type="hidden" name="pickup" value="[% HOLD.branchcode | html %]" /> > [% ELSE %] >diff --git a/svc/holds b/svc/holds >index c406c0e4a44..4c904b7cdb8 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'); >@@ -99,6 +99,7 @@ while ( my $h = $holds_rs->next() ) { > branchcode => $h->branch()->branchname(), > branches => $libraries, > reservedate => $h->reservedate(), >+ lastpickupdate => $h->lastpickupdate(), > expirationdate => $h->expirationdate(), > suspend => $h->suspend(), > suspend_until => $h->suspend_until(), >@@ -112,6 +113,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 f7919b3827a..42378a6577d 100755 >--- a/t/db_dependent/Holds.t >+++ b/t/db_dependent/Holds.t >@@ -399,9 +399,141 @@ is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, > "CanItemBeReserved should use item home library rule when ReservesControlBranch set to 'ItemsHomeLibrary'"); > > ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem( >+<<<<<<< HEAD > { homebranch => $branch_1, holdingbranch => $branch_1, itype => 'CAN' } , $biblio->biblionumber); > is(CanItemBeReserved($borrowernumbers[0], $itemnumber)->{status}, 'OK', > "CanItemBeReserved should return 'OK'"); >+======= >+ { homebranch => 'CPL', holdingbranch => 'CPL', itype => 'CAN' } , $bibnum); >+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." ); >+} >+ >+## 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 8367: How long is a hold waiting for pickup at a more granular level > > # Bug 12632 > t::lib::Mocks::mock_preference( 'item-level_itypes', 1 ); >@@ -687,3 +819,90 @@ subtest 'CanItemBeReserved / holds_per_day tests' => sub { > > $schema->storage->txn_rollback; > }; >+ >+# Helper method to set up a Biblio. >+sub create_helper_biblio { >+ my $itemtype = $_[0] ? $_[0] : 'BK'; >+ my $bib = MARC::Record->new(); >+ my $title = 'Silence in the library'; >+ $bib->append_fields( >+ MARC::Field->new('100', ' ', ' ', a => 'Moffat, Steven'), >+ MARC::Field->new('245', ' ', ' ', a => $title), >+ MARC::Field->new('942', ' ', ' ', c => $itemtype), >+ ); >+ 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 ); >+} >-- >2.11.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 8367
:
12130
|
13139
|
13602
|
13603
|
13604
|
13624
|
15334
|
15335
|
15369
|
15498
|
15613
|
15658
|
15659
|
19093
|
19094
|
19095
|
19096
|
19104
|
19105
|
19107
|
19108
|
19116
|
19180
|
19181
|
19182
|
19360
|
19361
|
19362
|
20300
|
20301
|
20302
|
20303
|
20304
|
32786
|
32826
|
32884
|
32885
|
32916
|
32930
|
42706
|
42754
|
79790
|
79791
|
79798
|
79799
|
79800
|
79853
|
79854
|
79855
|
79859
|
92412
|
92413
|
92414
|
92415
|
92416
|
92417
|
92418
|
92419
|
92420
|
92421
|
92422
|
92423
|
92424
|
92425
|
92426
|
92427
|
92428
|
92958
|
92959
|
92960
|
92961
|
103425
|
113211
|
117284
|
118017
|
118021
|
124568
|
125087
|
131299
|
131300
|
131301
|
131302
|
131646
|
131647
|
131718
|
131719
|
131720
|
131722
|
131723
|
131724
|
133089
|
133090
|
133091
|
133092
|
133093
|
133094
|
142897
|
142898
|
142899
|
142900
|
142901
|
142902
|
142903
|
144097
|
144098
|
144099
|
144100
|
144101
|
144102
|
144103
|
144146
|
145644
|
145657
|
147798
|
147799
|
147800
|
147801
|
147802
|
147803
|
147804
|
147805
|
147806
|
147807
|
148536
|
148537
|
148538
|
148539
|
148540
|
148541
|
148542
|
148543
|
148544
|
148545
|
150437
|
150438
|
150439
|
153235
|
153237
|
153238
|
153239
|
153240
|
153241
|
153242
|
153243
|
153244
|
153245
|
153246
|
157138
|
157139
|
157140
|
157141
|
157142
|
157143
|
157144
|
157145
|
157146
|
157147
|
157148
|
157149
|
157150
|
157298
|
157343
|
157344
|
157345
|
157346
|
157347
|
157348
|
157349
|
157350
|
157351
|
157352
|
157354
|
157355
|
157356
|
157697
|
157698
|
157699
|
157700
|
157701
|
157702
|
157703
|
157704
|
157705
|
157706
|
157707
|
157708
|
157709
|
157715
|
158023
|
158024
|
158025
|
158026
|
158027
|
158028
|
158029
|
158030
|
158031
|
158032
|
158033
|
158034
|
158035
|
158254
|
158255