3 # Copyright 2000-2002 Katipo Communications
4 # 2006 SAN Ouest Provence
5 # 2007-2010 BibLibre Paul POULAIN
8 # This file is part of Koha.
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with Koha; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #use warnings; FIXME - Bug 2505
33 # for _koha_notify_reserve
34 use C4
::Members
::Messaging
;
37 use C4
::Branch
qw( GetBranchDetail );
38 use C4
::Dates
qw( format_date_in_iso );
42 use List
::MoreUtils
qw( firstidx );
44 use vars
qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
48 C4::Reserves - Koha functions for dealing with reservation.
56 This modules provides somes functions to deal with reservations.
58 Reserves are stored in reserves table.
59 The following columns contains important values :
60 - priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
61 =0 : then the reserve is being dealed
62 - found : NULL : means the patron requested the 1st available, and we haven't choosen the item
63 T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
64 W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
65 F(inished) : the reserve has been completed, and is done
66 - itemnumber : empty : the reserve is still unaffected to an item
67 filled: the reserve is attached to an item
68 The complete workflow is :
69 ==== 1st use case ====
70 patron request a document, 1st available : P >0, F=NULL, I=NULL
71 a library having it run "transfertodo", and clic on the list
72 if there is no transfer to do, the reserve waiting
73 patron can pick it up P =0, F=W, I=filled
74 if there is a transfer to do, write in branchtransfer P =0, F=T, I=filled
75 The pickup library recieve the book, it check in P =0, F=W, I=filled
76 The patron borrow the book P =0, F=F, I=filled
78 ==== 2nd use case ====
79 patron requests a document, a given item,
80 If pickup is holding branch P =0, F=W, I=filled
81 If transfer needed, write in branchtransfer P =0, F=T, I=filled
82 The pickup library receive the book, it checks it in P =0, F=W, I=filled
83 The patron borrow the book P =0, F=F, I=filled
90 # set the version for version checking
91 $VERSION = 3.07.00.049;
97 &GetReservesFromItemnumber
98 &GetReservesFromBiblionumber
99 &GetReservesFromBorrowernumber
100 &GetReservesForBranch
114 &ModReserveMinusPriority
121 &CancelExpiredReserves
123 &AutoUnsuspendReserves
125 &IsAvailableForItemLevelRequest
128 &ToggleLowestPriority
134 @EXPORT_OK = qw( MergeHolds );
139 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
145 $branch, $borrowernumber, $biblionumber,
146 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
147 $title, $checkitem, $found
150 GetReserveFee
($borrowernumber, $biblionumber, $constraint,
152 my $dbh = C4
::Context
->dbh;
153 my $const = lc substr( $constraint, 0, 1 );
154 $resdate = format_date_in_iso
( $resdate ) if ( $resdate );
155 $resdate = C4
::Dates
->today( 'iso' ) unless ( $resdate );
157 $expdate = format_date_in_iso
( $expdate );
159 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
161 if ( C4
::Context
->preference( 'AllowHoldDateInFuture' ) ) {
162 # Make room in reserves for this before those of a later reserve date
163 $priority = _ShiftPriorityByDateAndPriority
( $biblionumber, $resdate, $priority );
167 # If the reserv had the waiting status, we had the value of the resdate
168 if ( $found eq 'W' ) {
169 $waitingdate = $resdate;
173 # updates take place here
175 my $nextacctno = &getnextacctno
( $borrowernumber );
177 INSERT INTO accountlines
178 (borrowernumber
,accountno
,date
,amount
,description
,accounttype
,amountoutstanding
)
180 (?
,?
,now
(),?
,?
,'Res',?
)
182 my $usth = $dbh->prepare($query);
183 $usth->execute( $borrowernumber, $nextacctno, $fee,
184 "Reserve Charge - $title", $fee );
190 (borrowernumber
,biblionumber
,reservedate
,branchcode
,constrainttype
,
191 priority
,reservenotes
,itemnumber
,found
,waitingdate
,expirationdate
)
196 my $sth = $dbh->prepare($query);
198 $borrowernumber, $biblionumber, $resdate, $branch,
199 $const, $priority, $notes, $checkitem,
200 $found, $waitingdate, $expdate
203 # Send e-mail to librarian if syspref is active
204 if(C4
::Context
->preference("emailLibrarianWhenHoldIsPlaced")){
205 my $borrower = C4
::Members
::GetMember
(borrowernumber
=> $borrowernumber);
206 my $branch_details = C4
::Branch
::GetBranchDetail
($borrower->{branchcode
});
207 if ( my $letter = C4
::Letters
::GetPreparedLetter
(
208 module
=> 'reserves',
209 letter_code
=> 'HOLDPLACED',
210 branchcode
=> $branch,
212 'branches' => $branch_details,
213 'borrowers' => $borrower,
214 'biblio' => $biblionumber,
218 my $admin_email_address =$branch_details->{'branchemail'} || C4
::Context
->preference('KohaAdminEmailAddress');
220 C4
::Letters
::EnqueueLetter
(
222 borrowernumber
=> $borrowernumber,
223 message_transport_type
=> 'email',
224 from_address
=> $admin_email_address,
225 to_address
=> $admin_email_address,
232 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
234 INSERT INTO reserveconstraints
235 (borrowernumber
,biblionumber
,reservedate
,biblioitemnumber
)
239 $sth = $dbh->prepare($query); # keep prepare outside the loop!
240 foreach (@
$bibitems) {
241 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
244 return; # FIXME: why not have a useful return value?
247 =head2 GetReservesFromBiblionumber
249 ($count, $title_reserves) = &GetReserves($biblionumber);
251 This function gets the list of reservations for one C<$biblionumber>, returning a count
252 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
256 sub GetReservesFromBiblionumber
{
257 my ($biblionumber) = shift or return (0, []);
258 my ($all_dates) = shift;
259 my $dbh = C4
::Context
->dbh;
261 # Find the desired items in the reserves
264 timestamp AS rtimestamp,
278 WHERE biblionumber = ? ";
279 unless ( $all_dates ) {
280 $query .= "AND reservedate <= CURRENT_DATE()";
282 $query .= "ORDER BY priority";
283 my $sth = $dbh->prepare($query);
284 $sth->execute($biblionumber);
287 while ( my $data = $sth->fetchrow_hashref ) {
289 # FIXME - What is this doing? How do constraints work?
290 if ($data->{constrainttype
} eq 'o') {
292 SELECT biblioitemnumber
293 FROM reserveconstraints
294 WHERE biblionumber = ?
295 AND borrowernumber = ?
298 my $csth = $dbh->prepare($query);
299 $csth->execute($data->{biblionumber
}, $data->{borrowernumber
}, $data->{reservedate
});
301 while ( my $bibitemnos = $csth->fetchrow_array ) {
302 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
304 my $count = scalar @bibitemno;
306 # if we have two or more different specific itemtypes
307 # reserved by same person on same day
310 $bdata = GetBiblioItemData
( $bibitemno[$i] ); # FIXME: This doesn't make sense.
311 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
314 # Look up the book we just found.
315 $bdata = GetBiblioItemData
( $bibitemno[0] );
317 # Add the results of this latest search to the current
319 # FIXME - An 'each' would probably be more efficient.
320 foreach my $key ( keys %$bdata ) {
321 $data->{$key} = $bdata->{$key};
324 push @results, $data;
326 return ( $#results + 1, \
@results );
329 =head2 GetReservesFromItemnumber
331 ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
333 TODO :: Description here
337 sub GetReservesFromItemnumber
{
338 my ( $itemnumber, $all_dates ) = @_;
339 my $dbh = C4
::Context
->dbh;
341 SELECT reservedate,borrowernumber,branchcode
345 unless ( $all_dates ) {
346 $query .= " AND reservedate <= CURRENT_DATE()";
348 my $sth_res = $dbh->prepare($query);
349 $sth_res->execute($itemnumber);
350 my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
351 return ( $reservedate, $borrowernumber, $branchcode );
354 =head2 GetReservesFromBorrowernumber
356 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
362 sub GetReservesFromBorrowernumber
{
363 my ( $borrowernumber, $status ) = @_;
364 my $dbh = C4
::Context
->dbh;
367 $sth = $dbh->prepare("
370 WHERE borrowernumber=?
374 $sth->execute($borrowernumber,$status);
376 $sth = $dbh->prepare("
379 WHERE borrowernumber=?
382 $sth->execute($borrowernumber);
384 my $data = $sth->fetchall_arrayref({});
387 #-------------------------------------------------------------------------------------
388 =head2 CanBookBeReserved
390 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
394 sub CanBookBeReserved
{
395 my ($borrowernumber, $biblionumber) = @_;
397 my $items = GetItemnumbersForBiblio
($biblionumber);
398 #get items linked via host records
399 my @hostitems = get_hostitemnumbers_of
($biblionumber);
401 push (@
$items,@hostitems);
404 foreach my $item (@
$items){
405 return 1 if CanItemBeReserved
($borrowernumber, $item);
410 =head2 CanItemBeReserved
412 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
414 This function return 1 if an item can be issued by this borrower.
418 sub CanItemBeReserved
{
419 my ($borrowernumber, $itemnumber) = @_;
421 my $dbh = C4
::Context
->dbh;
422 my $allowedreserves = 0;
424 my $controlbranch = C4
::Context
->preference('ReservesControlBranch');
425 my $itype = C4
::Context
->preference('item-level_itypes') ?
"itype" : "itemtype";
427 # we retrieve borrowers and items informations #
428 my $item = GetItem
($itemnumber);
429 my $borrower = C4
::Members
::GetMember
('borrowernumber'=>$borrowernumber);
431 # we retrieve user rights on this itemtype and branchcode
432 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
434 WHERE (categorycode in (?,'*') )
435 AND (itemtype IN (?,'*'))
436 AND (branchcode IN (?,'*'))
443 my $querycount ="SELECT
446 LEFT JOIN items USING (itemnumber)
447 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
448 LEFT JOIN borrowers USING (borrowernumber)
449 WHERE borrowernumber = ?
453 my $itemtype = $item->{$itype};
454 my $categorycode = $borrower->{categorycode
};
456 my $branchfield = "reserves.branchcode";
458 if( $controlbranch eq "ItemHomeLibrary" ){
459 $branchfield = "items.homebranch";
460 $branchcode = $item->{homebranch
};
461 }elsif( $controlbranch eq "PatronLibrary" ){
462 $branchfield = "borrowers.branchcode";
463 $branchcode = $borrower->{branchcode
};
467 $sth->execute($categorycode, $itemtype, $branchcode);
468 if(my $rights = $sth->fetchrow_hashref()){
469 $itemtype = $rights->{itemtype
};
470 $allowedreserves = $rights->{reservesallowed
};
477 $querycount .= "AND $branchfield = ?";
479 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
480 my $sthcount = $dbh->prepare($querycount);
482 if($itemtype eq "*"){
483 $sthcount->execute($borrowernumber, $branchcode);
485 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
488 my $reservecount = "0";
489 if(my $rowcount = $sthcount->fetchrow_hashref()){
490 $reservecount = $rowcount->{count
};
493 # we check if it's ok or not
494 if( $reservecount < $allowedreserves ){
500 #--------------------------------------------------------------------------------
501 =head2 GetReserveCount
503 $number = &GetReserveCount($borrowernumber);
505 this function returns the number of reservation for a borrower given on input arg.
509 sub GetReserveCount
{
510 my ($borrowernumber) = @_;
512 my $dbh = C4
::Context
->dbh;
515 SELECT COUNT(*) AS counter
517 WHERE borrowernumber = ?
519 my $sth = $dbh->prepare($query);
520 $sth->execute($borrowernumber);
521 my $row = $sth->fetchrow_hashref;
522 return $row->{counter
};
525 =head2 GetOtherReserves
527 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
529 Check queued list of this document and check if this document must be transfered
533 sub GetOtherReserves
{
534 my ($itemnumber) = @_;
537 my ( undef, $checkreserves, undef ) = CheckReserves
($itemnumber);
538 if ($checkreserves) {
539 my $iteminfo = GetItem
($itemnumber);
540 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
541 $messages->{'transfert'} = $checkreserves->{'branchcode'};
542 #minus priorities of others reservs
543 ModReserveMinusPriority
(
545 $checkreserves->{'borrowernumber'},
546 $iteminfo->{'biblionumber'}
549 #launch the subroutine dotransfer
550 C4
::Items
::ModItemTransfer
(
552 $iteminfo->{'holdingbranch'},
553 $checkreserves->{'branchcode'}
558 #step 2b : case of a reservation on the same branch, set the waiting status
560 $messages->{'waiting'} = 1;
561 ModReserveMinusPriority
(
563 $checkreserves->{'borrowernumber'},
564 $iteminfo->{'biblionumber'}
566 ModReserveStatus
($itemnumber,'W');
569 $nextreservinfo = $checkreserves->{'borrowernumber'};
572 return ( $messages, $nextreservinfo );
577 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
579 Calculate the fee for a reserve
584 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
587 my $dbh = C4
::Context
->dbh;
588 my $const = lc substr( $constraint, 0, 1 );
590 SELECT
* FROM borrowers
591 LEFT JOIN categories ON borrowers
.categorycode
= categories
.categorycode
592 WHERE borrowernumber
= ?
594 my $sth = $dbh->prepare($query);
595 $sth->execute($borrowernumber);
596 my $data = $sth->fetchrow_hashref;
598 my $fee = $data->{'reservefee'};
599 my $cntitems = @
- > $bibitems;
603 # check for items on issue
604 # first find biblioitem records
606 my $sth1 = $dbh->prepare(
607 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
608 WHERE (biblio.biblionumber = ?)"
610 $sth1->execute($biblionumber);
611 while ( my $data1 = $sth1->fetchrow_hashref ) {
612 if ( $const eq "a" ) {
613 push @biblioitems, $data1;
618 while ( $x < $cntitems ) {
619 if ( @
$bibitems->{'biblioitemnumber'} ==
620 $data->{'biblioitemnumber'} )
626 if ( $const eq 'o' ) {
628 push @biblioitems, $data1;
633 push @biblioitems, $data1;
639 my $cntitemsfound = @biblioitems;
643 while ( $x < $cntitemsfound ) {
644 my $bitdata = $biblioitems[$x];
645 my $sth2 = $dbh->prepare(
647 WHERE biblioitemnumber = ?"
649 $sth2->execute( $bitdata->{'biblioitemnumber'} );
650 while ( my $itdata = $sth2->fetchrow_hashref ) {
651 my $sth3 = $dbh->prepare(
652 "SELECT * FROM issues
653 WHERE itemnumber = ?"
655 $sth3->execute( $itdata->{'itemnumber'} );
656 if ( my $isdata = $sth3->fetchrow_hashref ) {
664 if ( $allissued == 0 ) {
666 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
667 $rsth->execute($biblionumber);
668 if ( my $rdata = $rsth->fetchrow_hashref ) {
678 =head2 GetReservesToBranch
680 @transreserv = GetReservesToBranch( $frombranch );
682 Get reserve list for a given branch
686 sub GetReservesToBranch
{
687 my ( $frombranch ) = @_;
688 my $dbh = C4
::Context
->dbh;
689 my $sth = $dbh->prepare(
690 "SELECT borrowernumber,reservedate,itemnumber,timestamp
695 $sth->execute( $frombranch );
698 while ( my $data = $sth->fetchrow_hashref ) {
699 $transreserv[$i] = $data;
702 return (@transreserv);
705 =head2 GetReservesForBranch
707 @transreserv = GetReservesForBranch($frombranch);
711 sub GetReservesForBranch
{
712 my ($frombranch) = @_;
713 my $dbh = C4
::Context
->dbh;
714 my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
719 $query .= " AND branchcode=? ";
721 $query .= "ORDER BY waitingdate" ;
722 my $sth = $dbh->prepare($query);
724 $sth->execute($frombranch);
731 while ( my $data = $sth->fetchrow_hashref ) {
732 $transreserv[$i] = $data;
735 return (@transreserv);
738 =head2 GetReserveStatus
740 $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
742 Take an itemnumber or a biblionumber and return the status of the reserve places on it.
743 If several reserves exist, the reserve with the lower priority is given.
747 sub GetReserveStatus
{
748 my ($itemnumber, $biblionumber) = @_;
750 my $dbh = C4
::Context
->dbh;
752 my ($sth, $found, $priority);
754 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
755 $sth->execute($itemnumber);
756 ($found, $priority) = $sth->fetchrow_array;
759 if ( $biblionumber and not defined $found and not defined $priority ) {
760 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
761 $sth->execute($biblionumber);
762 ($found, $priority) = $sth->fetchrow_array;
766 return 'Waiting' if $found eq 'W' and $priority == 0;
767 return 'Finished' if $found eq 'F';
768 return 'Reserved' if $priority > 0;
771 #empty string here will remove need for checking undef, or less log lines
776 ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber);
777 ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode);
779 Find a book in the reserves.
781 C<$itemnumber> is the book's item number.
783 As I understand it, C<&CheckReserves> looks for the given item in the
784 reserves. If it is found, that's a match, and C<$status> is set to
787 Otherwise, it finds the most important item in the reserves with the
788 same biblio number as this book (I'm not clear on this) and returns it
789 with C<$status> set to C<Reserved>.
791 C<&CheckReserves> returns a two-element list:
793 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
795 C<$reserve> is the reserve item that matched. It is a
796 reference-to-hash whose keys are mostly the fields of the reserves
797 table in the Koha database.
802 my ( $item, $barcode ) = @_;
803 my $dbh = C4
::Context
->dbh;
806 if (C4
::Context
->preference('item-level_itypes')){
808 SELECT items.biblionumber,
809 items.biblioitemnumber,
810 itemtypes.notforloan,
811 items.notforloan AS itemnotforloan,
814 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
815 LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype
820 SELECT items.biblionumber,
821 items.biblioitemnumber,
822 itemtypes.notforloan,
823 items.notforloan AS itemnotforloan,
826 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
827 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
832 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
833 $sth->execute($item);
836 $sth = $dbh->prepare("$select WHERE barcode = ?");
837 $sth->execute($barcode);
839 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
840 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
842 return ( '' ) unless $itemnumber; # bail if we got nothing.
844 # if item is not for loan it cannot be reserved either.....
845 # execpt where items.notforloan < 0 : This indicates the item is holdable.
846 return ( '' ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
848 # Find this item in the reserves
849 my @reserves = _Findgroupreserve
( $bibitem, $biblio, $itemnumber );
851 # $priority and $highest are used to find the most important item
852 # in the list returned by &_Findgroupreserve. (The lower $priority,
853 # the more important the item.)
854 # $highest is the most important item we've seen so far.
856 if (scalar @reserves) {
857 my $priority = 10000000;
858 foreach my $res (@reserves) {
859 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
860 return ( "Waiting", $res, \
@reserves ); # Found it
862 # See if this item is more important than what we've got so far
863 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
864 my $borrowerinfo=C4
::Members
::GetMember
(borrowernumber
=> $res->{'borrowernumber'});
865 my $iteminfo=C4
::Items
::GetItem
($itemnumber);
866 my $branch=C4
::Circulation
::_GetCircControlBranch
($iteminfo,$borrowerinfo);
867 my $branchitemrule = C4
::Circulation
::GetBranchItemRule
($branch,$iteminfo->{'itype'});
868 next if ($branchitemrule->{'holdallowed'} == 0);
869 next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
870 $priority = $res->{'priority'};
877 # If we get this far, then no exact match was found.
878 # We return the most important (i.e. next) reservation.
880 $highest->{'itemnumber'} = $item;
881 return ( "Reserved", $highest, \
@reserves );
887 =head2 CancelExpiredReserves
889 CancelExpiredReserves();
891 Cancels all reserves with an expiration date from before today.
895 sub CancelExpiredReserves
{
897 # Cancel reserves that have passed their expiration date.
898 my $dbh = C4
::Context
->dbh;
899 my $sth = $dbh->prepare( "
900 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
901 AND expirationdate IS NOT NULL
906 while ( my $res = $sth->fetchrow_hashref() ) {
907 CancelReserve
( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
910 # Cancel reserves that have been waiting too long
911 if ( C4
::Context
->preference("ExpireReservesMaxPickUpDelay") ) {
912 my $max_pickup_delay = C4
::Context
->preference("ReservesMaxPickUpDelay");
913 my $charge = C4
::Context
->preference("ExpireReservesMaxPickUpDelayCharge");
915 my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
916 $sth = $dbh->prepare( $query );
917 $sth->execute( $max_pickup_delay );
919 while (my $res = $sth->fetchrow_hashref ) {
921 manualinvoice
($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
924 CancelReserve
( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
930 =head2 AutoUnsuspendReserves
932 AutoUnsuspendReserves();
934 Unsuspends all suspended reserves with a suspend_until date from before today.
938 sub AutoUnsuspendReserves
{
940 my $dbh = C4
::Context
->dbh;
942 my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )";
943 my $sth = $dbh->prepare( $query );
950 &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
954 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
955 cancel, but not both: if both are given, C<&CancelReserve> uses
958 C<$borrowernumber> is the borrower number of the patron on whose
959 behalf the book was reserved.
961 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
962 priorities of the other people who are waiting on the book.
967 my ( $biblio, $item, $borr ) = @_;
968 my $dbh = C4
::Context
->dbh;
969 if ( $item and $borr ) {
970 # removing a waiting reserve record....
971 # update the database...
974 SET cancellationdate = now(),
978 AND borrowernumber = ?
980 my $sth = $dbh->prepare($query);
981 $sth->execute( $item, $borr );
984 INSERT INTO old_reserves
985 SELECT * FROM reserves
987 AND borrowernumber = ?
989 $sth = $dbh->prepare($query);
990 $sth->execute( $item, $borr );
994 AND borrowernumber = ?
996 $sth = $dbh->prepare($query);
997 $sth->execute( $item, $borr );
1000 # removing a reserve record....
1001 # get the prioritiy on this record....
1004 SELECT priority FROM reserves
1005 WHERE biblionumber
= ?
1006 AND borrowernumber
= ?
1007 AND cancellationdate IS NULL
1008 AND itemnumber IS NULL
1010 my $sth = $dbh->prepare($query);
1011 $sth->execute( $biblio, $borr );
1012 ($priority) = $sth->fetchrow_array;
1016 SET cancellationdate
= now
(),
1019 WHERE biblionumber
= ?
1020 AND borrowernumber
= ?
1023 # update the database, removing the record...
1024 $sth = $dbh->prepare($query);
1025 $sth->execute( $biblio, $borr );
1029 INSERT INTO old_reserves
1030 SELECT
* FROM reserves
1031 WHERE biblionumber
= ?
1032 AND borrowernumber
= ?
1034 $sth = $dbh->prepare($query);
1035 $sth->execute( $biblio, $borr );
1038 DELETE FROM reserves
1039 WHERE biblionumber
= ?
1040 AND borrowernumber
= ?
1042 $sth = $dbh->prepare($query);
1043 $sth->execute( $biblio, $borr );
1045 # now fix the priority on the others....
1046 _FixPriority
( $biblio, $borr );
1052 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
1054 Change a hold request's priority or cancel it.
1056 C<$rank> specifies the effect of the change. If C<$rank>
1057 is 'W' or 'n', nothing happens. This corresponds to leaving a
1058 request alone when changing its priority in the holds queue
1061 If C<$rank> is 'del', the hold request is cancelled.
1063 If C<$rank> is an integer greater than zero, the priority of
1064 the request is set to that value. Since priority != 0 means
1065 that the item is not waiting on the hold shelf, setting the
1066 priority to a non-zero value also sets the request's found
1067 status and waiting date to NULL.
1069 The optional C<$itemnumber> parameter is used only when
1070 C<$rank> is a non-zero integer; if supplied, the itemnumber
1071 of the hold request is set accordingly; if omitted, the itemnumber
1074 B<FIXME:> Note that the forgoing can have the effect of causing
1075 item-level hold requests to turn into title-level requests. This
1076 will be fixed once reserves has separate columns for requested
1077 itemnumber and supplying itemnumber.
1082 #subroutine to update a reserve
1083 my ( $rank, $biblio, $borrower, $branch , $itemnumber, $suspend_until) = @_;
1084 return if $rank eq "W";
1085 return if $rank eq "n";
1086 my $dbh = C4
::Context
->dbh;
1087 if ( $rank eq "del" ) {
1090 SET cancellationdate
=now
()
1091 WHERE biblionumber
= ?
1092 AND borrowernumber
= ?
1094 my $sth = $dbh->prepare($query);
1095 $sth->execute( $biblio, $borrower );
1098 INSERT INTO old_reserves
1101 WHERE biblionumber
= ?
1102 AND borrowernumber
= ?
1104 $sth = $dbh->prepare($query);
1105 $sth->execute( $biblio, $borrower );
1107 DELETE FROM reserves
1108 WHERE biblionumber
= ?
1109 AND borrowernumber
= ?
1111 $sth = $dbh->prepare($query);
1112 $sth->execute( $biblio, $borrower );
1115 elsif ($rank =~ /^\d+/ and $rank > 0) {
1117 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1118 WHERE biblionumber = ?
1119 AND borrowernumber = ?
1121 my $sth = $dbh->prepare($query);
1122 $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1125 if ( defined( $suspend_until ) ) {
1126 if ( $suspend_until ) {
1127 $suspend_until = C4
::Dates
->new( $suspend_until )->output("iso");
1128 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $suspend_until, $biblio, $borrower ) );
1130 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $biblio, $borrower ) );
1134 _FixPriority
( $biblio, $borrower, $rank);
1138 =head2 ModReserveFill
1140 &ModReserveFill($reserve);
1142 Fill a reserve. If I understand this correctly, this means that the
1143 reserved book has been found and given to the patron who reserved it.
1145 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1146 whose keys are fields from the reserves table in the Koha database.
1150 sub ModReserveFill
{
1152 my $dbh = C4
::Context
->dbh;
1153 # fill in a reserve record....
1154 my $biblionumber = $res->{'biblionumber'};
1155 my $borrowernumber = $res->{'borrowernumber'};
1156 my $resdate = $res->{'reservedate'};
1158 # get the priority on this record....
1160 my $query = "SELECT priority
1162 WHERE biblionumber = ?
1163 AND borrowernumber = ?
1164 AND reservedate = ?";
1165 my $sth = $dbh->prepare($query);
1166 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1167 ($priority) = $sth->fetchrow_array;
1170 # update the database...
1171 $query = "UPDATE reserves
1174 WHERE biblionumber = ?
1176 AND borrowernumber = ?
1178 $sth = $dbh->prepare($query);
1179 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1182 # move to old_reserves
1183 $query = "INSERT INTO old_reserves
1184 SELECT * FROM reserves
1185 WHERE biblionumber = ?
1187 AND borrowernumber = ?
1189 $sth = $dbh->prepare($query);
1190 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1191 $query = "DELETE FROM reserves
1192 WHERE biblionumber = ?
1194 AND borrowernumber = ?
1196 $sth = $dbh->prepare($query);
1197 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1199 # now fix the priority on the others (if the priority wasn't
1200 # already sorted!)....
1201 unless ( $priority == 0 ) {
1202 _FixPriority
( $biblionumber, $borrowernumber );
1206 =head2 ModReserveStatus
1208 &ModReserveStatus($itemnumber, $newstatus);
1210 Update the reserve status for the active (priority=0) reserve.
1212 $itemnumber is the itemnumber the reserve is on
1214 $newstatus is the new status.
1218 sub ModReserveStatus
{
1220 #first : check if we have a reservation for this item .
1221 my ($itemnumber, $newstatus) = @_;
1222 my $dbh = C4
::Context
->dbh;
1224 my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1225 my $sth_set = $dbh->prepare($query);
1226 $sth_set->execute( $newstatus, $itemnumber );
1228 if ( C4
::Context
->preference("ReturnToShelvingCart") && $newstatus ) {
1229 CartToShelf
( $itemnumber );
1233 =head2 ModReserveAffect
1235 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1237 This function affect an item and a status for a given reserve
1238 The itemnumber parameter is used to find the biblionumber.
1239 with the biblionumber & the borrowernumber, we can affect the itemnumber
1240 to the correct reserve.
1242 if $transferToDo is not set, then the status is set to "Waiting" as well.
1243 otherwise, a transfer is on the way, and the end of the transfer will
1244 take care of the waiting status
1248 sub ModReserveAffect
{
1249 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1250 my $dbh = C4
::Context
->dbh;
1252 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1253 # attached to $itemnumber
1254 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1255 $sth->execute($itemnumber);
1256 my ($biblionumber) = $sth->fetchrow;
1258 # get request - need to find out if item is already
1259 # waiting in order to not send duplicate hold filled notifications
1260 my $request = GetReserveInfo
($borrowernumber, $biblionumber);
1261 my $already_on_shelf = ($request && $request->{found
} eq 'W') ?
1 : 0;
1263 # If we affect a reserve that has to be transfered, don't set to Waiting
1265 if ($transferToDo) {
1271 WHERE borrowernumber = ?
1272 AND biblionumber = ?
1276 # affect the reserve to Waiting as well.
1281 waitingdate = NOW(),
1283 WHERE borrowernumber = ?
1284 AND biblionumber = ?
1287 $sth = $dbh->prepare($query);
1288 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1289 _koha_notify_reserve
( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1291 if ( C4
::Context
->preference("ReturnToShelvingCart") ) {
1292 CartToShelf
( $itemnumber );
1298 =head2 ModReserveCancelAll
1300 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1302 function to cancel reserv,check other reserves, and transfer document if it's necessary
1306 sub ModReserveCancelAll
{
1309 my ( $itemnumber, $borrowernumber ) = @_;
1311 #step 1 : cancel the reservation
1312 my $CancelReserve = CancelReserve
( undef, $itemnumber, $borrowernumber );
1314 #step 2 launch the subroutine of the others reserves
1315 ( $messages, $nextreservinfo ) = GetOtherReserves
($itemnumber);
1317 return ( $messages, $nextreservinfo );
1320 =head2 ModReserveMinusPriority
1322 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1324 Reduce the values of queuded list
1328 sub ModReserveMinusPriority
{
1329 my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1331 #first step update the value of the first person on reserv
1332 my $dbh = C4
::Context
->dbh;
1335 SET priority = 0 , itemnumber = ?
1336 WHERE borrowernumber=?
1339 my $sth_upd = $dbh->prepare($query);
1340 $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1341 # second step update all others reservs
1342 _FixPriority
($biblionumber, $borrowernumber, '0');
1345 =head2 GetReserveInfo
1347 &GetReserveInfo($borrowernumber,$biblionumber);
1349 Get item and borrower details for a current hold.
1350 Current implementation this query should have a single result.
1354 sub GetReserveInfo
{
1355 my ( $borrowernumber, $biblionumber ) = @_;
1356 my $dbh = C4
::Context
->dbh;
1360 reserves.borrowernumber,
1361 reserves.biblionumber,
1362 reserves.branchcode,
1363 reserves.waitingdate,
1379 items.holdingbranch,
1380 items.itemcallnumber,
1386 LEFT JOIN items USING(itemnumber)
1387 LEFT JOIN borrowers USING(borrowernumber)
1388 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1390 reserves.borrowernumber=?
1391 AND reserves.biblionumber=?";
1392 my $sth = $dbh->prepare($strsth);
1393 $sth->execute($borrowernumber,$biblionumber);
1395 my $data = $sth->fetchrow_hashref;
1400 =head2 IsAvailableForItemLevelRequest
1402 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1404 Checks whether a given item record is available for an
1405 item-level hold request. An item is available if
1407 * it is not lost AND
1408 * it is not damaged AND
1409 * it is not withdrawn AND
1410 * does not have a not for loan value > 0
1412 Whether or not the item is currently on loan is
1413 also checked - if the AllowOnShelfHolds system preference
1414 is ON, an item can be requested even if it is currently
1415 on loan to somebody else. If the system preference
1416 is OFF, an item that is currently checked out cannot
1417 be the target of an item-level hold request.
1419 Note that IsAvailableForItemLevelRequest() does not
1420 check if the staff operator is authorized to place
1421 a request on the item - in particular,
1422 this routine does not check IndependentBranches
1423 and canreservefromotherbranches.
1427 sub IsAvailableForItemLevelRequest
{
1428 my $itemnumber = shift;
1430 my $item = GetItem
($itemnumber);
1432 # must check the notforloan setting of the itemtype
1433 # FIXME - a lot of places in the code do this
1434 # or something similar - need to be
1436 my $dbh = C4
::Context
->dbh;
1437 my $notforloan_query;
1438 if (C4
::Context
->preference('item-level_itypes')) {
1439 $notforloan_query = "SELECT itemtypes.notforloan
1441 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1442 WHERE itemnumber = ?";
1444 $notforloan_query = "SELECT itemtypes.notforloan
1446 JOIN biblioitems USING (biblioitemnumber)
1447 JOIN itemtypes USING (itemtype)
1448 WHERE itemnumber = ?";
1450 my $sth = $dbh->prepare($notforloan_query);
1451 $sth->execute($itemnumber);
1452 my $notforloan_per_itemtype = 0;
1453 if (my ($notforloan) = $sth->fetchrow_array) {
1454 $notforloan_per_itemtype = 1 if $notforloan;
1457 my $available_per_item = 1;
1458 $available_per_item = 0 if $item->{itemlost
} or
1459 ( $item->{notforloan
} > 0 ) or
1460 ($item->{damaged
} and not C4
::Context
->preference('AllowHoldsOnDamagedItems')) or
1461 $item->{wthdrawn
} or
1462 $notforloan_per_itemtype;
1465 if (C4
::Context
->preference('AllowOnShelfHolds')) {
1466 return $available_per_item;
1468 return ($available_per_item and ($item->{onloan
} or GetReserveStatus
($itemnumber) eq "Waiting"));
1472 =head2 AlterPriority
1474 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1476 This function changes a reserve's priority up, down, to the top, or to the bottom.
1477 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1482 my ( $where, $borrowernumber, $biblionumber ) = @_;
1484 my $dbh = C4
::Context
->dbh;
1486 ## Find this reserve
1487 my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1488 $sth->execute( $biblionumber, $borrowernumber );
1489 my $reserve = $sth->fetchrow_hashref();
1492 if ( $where eq 'up' || $where eq 'down' ) {
1494 my $priority = $reserve->{'priority'};
1495 $priority = $where eq 'up' ?
$priority - 1 : $priority + 1;
1496 _FixPriority
( $biblionumber, $borrowernumber, $priority )
1498 } elsif ( $where eq 'top' ) {
1500 _FixPriority
( $biblionumber, $borrowernumber, '1' )
1502 } elsif ( $where eq 'bottom' ) {
1504 _FixPriority
( $biblionumber, $borrowernumber, '999999' )
1509 =head2 ToggleLowestPriority
1511 ToggleLowestPriority( $borrowernumber, $biblionumber );
1513 This function sets the lowestPriority field to true if is false, and false if it is true.
1517 sub ToggleLowestPriority
{
1518 my ( $borrowernumber, $biblionumber ) = @_;
1520 my $dbh = C4
::Context
->dbh;
1522 my $sth = $dbh->prepare(
1523 "UPDATE reserves SET lowestPriority = NOT lowestPriority
1524 WHERE biblionumber = ?
1525 AND borrowernumber = ?"
1533 _FixPriority
( $biblionumber, $borrowernumber, '999999' );
1536 =head2 ToggleSuspend
1538 ToggleSuspend( $borrowernumber, $biblionumber );
1540 This function sets the suspend field to true if is false, and false if it is true.
1541 If the reserve is currently suspended with a suspend_until date, that date will
1542 be cleared when it is unsuspended.
1547 my ( $borrowernumber, $biblionumber, $suspend_until ) = @_;
1549 $suspend_until = output_pref
( dt_from_string
( $suspend_until ), 'iso' ) if ( $suspend_until );
1551 my $do_until = ( $suspend_until ) ?
'?' : 'NULL';
1553 my $dbh = C4
::Context
->dbh;
1555 my $sth = $dbh->prepare(
1556 "UPDATE reserves SET suspend = NOT suspend,
1557 suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
1558 WHERE biblionumber = ?
1559 AND borrowernumber = ?
1563 push( @params, $suspend_until ) if ( $suspend_until );
1564 push( @params, $biblionumber );
1565 push( @params, $borrowernumber );
1567 $sth->execute( @params );
1574 borrowernumber => $borrowernumber,
1575 [ biblionumber => $biblionumber, ]
1576 [ suspend_until => $suspend_until, ]
1577 [ suspend => $suspend ]
1580 This function accepts a set of hash keys as its parameters.
1581 It requires either borrowernumber or biblionumber, or both.
1583 suspend_until is wholly optional.
1590 my $borrowernumber = $params{'borrowernumber'} || undef;
1591 my $biblionumber = $params{'biblionumber'} || undef;
1592 my $suspend_until = $params{'suspend_until'} || undef;
1593 my $suspend = defined( $params{'suspend'} ) ?
$params{'suspend'} : 1;
1595 $suspend_until = C4
::Dates
->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1597 return unless ( $borrowernumber || $biblionumber );
1599 my ( $query, $sth, $dbh, @query_params );
1601 $query = "UPDATE reserves SET suspend = ? ";
1602 push( @query_params, $suspend );
1604 $query .= ", suspend_until = NULL ";
1605 } elsif ( $suspend_until ) {
1606 $query .= ", suspend_until = ? ";
1607 push( @query_params, $suspend_until );
1609 $query .= " WHERE ";
1610 if ( $borrowernumber ) {
1611 $query .= " borrowernumber = ? ";
1612 push( @query_params, $borrowernumber );
1614 $query .= " AND " if ( $borrowernumber && $biblionumber );
1615 if ( $biblionumber ) {
1616 $query .= " biblionumber = ? ";
1617 push( @query_params, $biblionumber );
1619 $query .= " AND found IS NULL ";
1621 $dbh = C4
::Context
->dbh;
1622 $sth = $dbh->prepare( $query );
1623 $sth->execute( @query_params );
1630 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1632 Only used internally (so don't export it)
1633 Changed how this functions works #
1634 Now just gets an array of reserves in the rank order and updates them with
1635 the array index (+1 as array starts from 0)
1636 and if $rank is supplied will splice item from the array and splice it back in again
1637 in new priority rank
1642 my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1643 my $dbh = C4
::Context
->dbh;
1644 if ( $rank eq "del" ) {
1645 CancelReserve
( $biblio, undef, $borrowernumber );
1647 if ( $rank eq "W" || $rank eq "0" ) {
1649 # make sure priority for waiting or in-transit items is 0
1653 WHERE biblionumber
= ?
1654 AND borrowernumber
= ?
1655 AND found IN
('W', 'T')
1657 my $sth = $dbh->prepare($query);
1658 $sth->execute( $biblio, $borrowernumber );
1664 # FIXME adding a new security in returned elements for changing priority,
1665 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1666 # This is wrong a waiting reserve has W set
1667 # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1669 SELECT borrowernumber
, reservedate
, constrainttype
1671 WHERE biblionumber
= ?
1672 AND
((found
<> 'W' AND found
<> 'T') or found is NULL
)
1673 ORDER BY priority ASC
1675 my $sth = $dbh->prepare($query);
1676 $sth->execute($biblio);
1677 while ( my $line = $sth->fetchrow_hashref ) {
1678 push( @reservedates, $line );
1679 push( @priority, $line );
1682 # To find the matching index
1684 my $key = -1; # to allow for 0 to be a valid result
1685 for ( $i = 0 ; $i < @priority ; $i++ ) {
1686 if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1687 $key = $i; # save the index
1692 # if index exists in array then move it to new position
1693 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1694 my $new_rank = $rank -
1695 1; # $new_rank is what you want the new index to be in the array
1696 my $moving_item = splice( @priority, $key, 1 );
1697 splice( @priority, $new_rank, 0, $moving_item );
1700 # now fix the priority on those that are left....
1704 WHERE biblionumber = ?
1705 AND borrowernumber = ?
1709 $sth = $dbh->prepare($query);
1710 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1713 $priority[$j]->{'borrowernumber'},
1714 $priority[$j]->{'reservedate'}
1719 $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1722 unless ( $ignoreSetLowestRank ) {
1723 while ( my $res = $sth->fetchrow_hashref() ) {
1724 _FixPriority
( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1729 =head2 _Findgroupreserve
1731 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1733 Looks for an item-specific match first, then for a title-level match, returning the
1734 first match found. If neither, then we look for a 3rd kind of match based on
1735 reserve constraints.
1737 TODO: add more explanation about reserve constraints
1739 C<&_Findgroupreserve> returns :
1740 C<@results> is an array of references-to-hash whose keys are mostly
1741 fields from the reserves table of the Koha database, plus
1742 C<biblioitemnumber>.
1746 sub _Findgroupreserve
{
1747 my ( $bibitem, $biblio, $itemnumber ) = @_;
1748 my $dbh = C4
::Context
->dbh;
1750 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1751 # check for exact targetted match
1752 my $item_level_target_query = qq/
1753 SELECT reserves
.biblionumber AS biblionumber
,
1754 reserves
.borrowernumber AS borrowernumber
,
1755 reserves
.reservedate AS reservedate
,
1756 reserves
.branchcode AS branchcode
,
1757 reserves
.cancellationdate AS cancellationdate
,
1758 reserves
.found AS found
,
1759 reserves
.reservenotes AS reservenotes
,
1760 reserves
.priority AS priority
,
1761 reserves
.timestamp AS timestamp
,
1762 biblioitems
.biblioitemnumber AS biblioitemnumber
,
1763 reserves
.itemnumber AS itemnumber
1765 JOIN biblioitems USING
(biblionumber
)
1766 JOIN hold_fill_targets USING
(biblionumber
, borrowernumber
, itemnumber
)
1769 AND item_level_request
= 1
1771 AND reservedate
<= CURRENT_DATE
()
1774 my $sth = $dbh->prepare($item_level_target_query);
1775 $sth->execute($itemnumber);
1777 if ( my $data = $sth->fetchrow_hashref ) {
1778 push( @results, $data );
1780 return @results if @results;
1782 # check for title-level targetted match
1783 my $title_level_target_query = qq/
1784 SELECT reserves
.biblionumber AS biblionumber
,
1785 reserves
.borrowernumber AS borrowernumber
,
1786 reserves
.reservedate AS reservedate
,
1787 reserves
.branchcode AS branchcode
,
1788 reserves
.cancellationdate AS cancellationdate
,
1789 reserves
.found AS found
,
1790 reserves
.reservenotes AS reservenotes
,
1791 reserves
.priority AS priority
,
1792 reserves
.timestamp AS timestamp
,
1793 biblioitems
.biblioitemnumber AS biblioitemnumber
,
1794 reserves
.itemnumber AS itemnumber
1796 JOIN biblioitems USING
(biblionumber
)
1797 JOIN hold_fill_targets USING
(biblionumber
, borrowernumber
)
1800 AND item_level_request
= 0
1801 AND hold_fill_targets
.itemnumber
= ?
1802 AND reservedate
<= CURRENT_DATE
()
1805 $sth = $dbh->prepare($title_level_target_query);
1806 $sth->execute($itemnumber);
1808 if ( my $data = $sth->fetchrow_hashref ) {
1809 push( @results, $data );
1811 return @results if @results;
1814 SELECT reserves
.biblionumber AS biblionumber
,
1815 reserves
.borrowernumber AS borrowernumber
,
1816 reserves
.reservedate AS reservedate
,
1817 reserves
.waitingdate AS waitingdate
,
1818 reserves
.branchcode AS branchcode
,
1819 reserves
.cancellationdate AS cancellationdate
,
1820 reserves
.found AS found
,
1821 reserves
.reservenotes AS reservenotes
,
1822 reserves
.priority AS priority
,
1823 reserves
.timestamp AS timestamp
,
1824 reserveconstraints
.biblioitemnumber AS biblioitemnumber
,
1825 reserves
.itemnumber AS itemnumber
1827 LEFT JOIN reserveconstraints ON reserves
.biblionumber
= reserveconstraints
.biblionumber
1828 WHERE reserves
.biblionumber
= ?
1829 AND
( ( reserveconstraints
.biblioitemnumber
= ?
1830 AND reserves
.borrowernumber
= reserveconstraints
.borrowernumber
1831 AND reserves
.reservedate
= reserveconstraints
.reservedate
)
1832 OR reserves
.constrainttype
='a' )
1833 AND
(reserves
.itemnumber IS NULL OR reserves
.itemnumber
= ?
)
1834 AND reserves
.reservedate
<= CURRENT_DATE
()
1837 $sth = $dbh->prepare($query);
1838 $sth->execute( $biblio, $bibitem, $itemnumber );
1840 while ( my $data = $sth->fetchrow_hashref ) {
1841 push( @results, $data );
1846 =head2 _koha_notify_reserve
1848 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1850 Sends a notification to the patron that their hold has been filled (through
1851 ModReserveAffect, _not_ ModReserveFill)
1855 sub _koha_notify_reserve
{
1856 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1858 my $dbh = C4
::Context
->dbh;
1859 my $borrower = C4
::Members
::GetMember
(borrowernumber
=> $borrowernumber);
1861 # Try to get the borrower's email address
1862 my $to_address = C4
::Members
::GetNoticeEmailAddress
($borrowernumber);
1867 if ( $to_address || $borrower->{'smsalertnumber'} ) {
1868 $messagingprefs = C4
::Members
::Messaging
::GetMessagingPreferences
( { borrowernumber
=> $borrowernumber, message_name
=> 'Hold_Filled' } );
1873 my $sth = $dbh->prepare("
1876 WHERE borrowernumber = ?
1877 AND biblionumber = ?
1879 $sth->execute( $borrowernumber, $biblionumber );
1880 my $reserve = $sth->fetchrow_hashref;
1881 my $branch_details = GetBranchDetail
( $reserve->{'branchcode'} );
1883 my $admin_email_address = $branch_details->{'branchemail'} || C4
::Context
->preference('KohaAdminEmailAddress');
1885 my %letter_params = (
1886 module
=> 'reserves',
1887 branchcode
=> $reserve->{branchcode
},
1889 'branches' => $branch_details,
1890 'borrowers' => $borrower,
1891 'biblio' => $biblionumber,
1892 'reserves' => $reserve,
1893 'items', $reserve->{'itemnumber'},
1895 substitute
=> { today
=> C4
::Dates
->new()->output() },
1899 if ( $print_mode ) {
1900 $letter_params{ 'letter_code' } = 'HOLD_PRINT';
1901 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1903 C4
::Letters
::EnqueueLetter
( {
1905 borrowernumber
=> $borrowernumber,
1906 message_transport_type
=> 'print',
1912 if ( $to_address && defined $messagingprefs->{transports
}->{'email'} ) {
1913 $letter_params{ 'letter_code' } = $messagingprefs->{transports
}->{'email'};
1914 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1916 C4
::Letters
::EnqueueLetter
(
1917 { letter
=> $letter,
1918 borrowernumber
=> $borrowernumber,
1919 message_transport_type
=> 'email',
1920 from_address
=> $admin_email_address,
1925 if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports
}->{'sms'} ) {
1926 $letter_params{ 'letter_code' } = $messagingprefs->{transports
}->{'sms'};
1927 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1929 C4
::Letters
::EnqueueLetter
(
1930 { letter
=> $letter,
1931 borrowernumber
=> $borrowernumber,
1932 message_transport_type
=> 'sms',
1938 =head2 _ShiftPriorityByDateAndPriority
1940 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1942 This increments the priority of all reserves after the one
1943 with either the lowest date after C<$reservedate>
1944 or the lowest priority after C<$priority>.
1946 It effectively makes room for a new reserve to be inserted with a certain
1947 priority, which is returned.
1949 This is most useful when the reservedate can be set by the user. It allows
1950 the new reserve to be placed before other reserves that have a later
1951 reservedate. Since priority also is set by the form in reserves/request.pl
1952 the sub accounts for that too.
1956 sub _ShiftPriorityByDateAndPriority
{
1957 my ( $biblio, $resdate, $new_priority ) = @_;
1959 my $dbh = C4
::Context
->dbh;
1960 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1961 my $sth = $dbh->prepare( $query );
1962 $sth->execute( $biblio, $resdate, $new_priority );
1963 my $min_priority = $sth->fetchrow;
1964 # if no such matches are found, $new_priority remains as original value
1965 $new_priority = $min_priority if ( $min_priority );
1967 # Shift the priority up by one; works in conjunction with the next SQL statement
1968 $query = "UPDATE reserves
1969 SET priority = priority+1
1970 WHERE biblionumber = ?
1971 AND borrowernumber = ?
1974 my $sth_update = $dbh->prepare( $query );
1976 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1977 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1978 $sth = $dbh->prepare( $query );
1979 $sth->execute( $new_priority, $biblio );
1980 while ( my $row = $sth->fetchrow_hashref ) {
1981 $sth_update->execute( $biblio, $row->{borrowernumber
}, $row->{reservedate
} );
1984 return $new_priority; # so the caller knows what priority they wind up receiving
1989 MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
1991 Use when checking out an item to handle reserves
1992 If $cancelreserve boolean is set to true, it will remove existing reserve
1997 my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
1999 my ( $restype, $res, $all_reserves ) = CheckReserves
( $itemnumber );
2002 my $biblionumber = $res->{biblionumber
};
2003 my $biblioitemnumber = $res->{biblioitemnumber
};
2005 if ($res->{borrowernumber
} == $borrowernumber) {
2006 ModReserveFill
($res);
2010 # The item is reserved by someone else.
2011 # Find this item in the reserves
2014 foreach (@
$all_reserves) {
2015 $_->{'borrowernumber'} == $borrowernumber or next;
2016 $_->{'biblionumber'} == $biblionumber or next;
2023 # The item is reserved by the current patron
2024 ModReserveFill
($borr_res);
2027 if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2028 RevertWaitingStatus
({ itemnumber
=> $itemnumber });
2030 elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2031 CancelReserve
(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
2032 CancelReserve
($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
2039 MergeHolds($dbh,$to_biblio, $from_biblio);
2041 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2046 my ( $dbh, $to_biblio, $from_biblio ) = @_;
2047 my $sth = $dbh->prepare(
2048 "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
2050 $sth->execute($from_biblio);
2051 if ( my $data = $sth->fetchrow_hashref() ) {
2053 # holds exist on old record, if not we don't need to do anything
2054 $sth = $dbh->prepare(
2055 "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2056 $sth->execute( $to_biblio, $from_biblio );
2059 # don't reorder those already waiting
2061 $sth = $dbh->prepare(
2062 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2064 my $upd_sth = $dbh->prepare(
2065 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2066 AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2068 $sth->execute( $to_biblio, 'W', 'T' );
2070 while ( my $reserve = $sth->fetchrow_hashref() ) {
2072 $priority, $to_biblio,
2073 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2074 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2081 =head2 RevertWaitingStatus
2083 $success = RevertWaitingStatus({ itemnumber => $itemnumber });
2085 Reverts a 'waiting' hold back to a regular hold with a priority of 1.
2087 Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
2088 item level hold, even if it was only a bibliolevel hold to
2089 begin with. This is because we can no longer know if a hold
2090 was item-level or bib-level after a hold has been set to
2095 sub RevertWaitingStatus
{
2096 my ( $params ) = @_;
2097 my $itemnumber = $params->{'itemnumber'};
2099 return unless ( $itemnumber );
2101 my $dbh = C4
::Context
->dbh;
2103 ## Get the waiting reserve we want to revert
2105 SELECT * FROM reserves
2106 WHERE itemnumber = ?
2107 AND found IS NOT NULL
2109 my $sth = $dbh->prepare( $query );
2110 $sth->execute( $itemnumber );
2111 my $reserve = $sth->fetchrow_hashref();
2113 ## Increment the priority of all other non-waiting
2114 ## reserves for this bib record
2118 priority = priority + 1
2124 $sth = $dbh->prepare( $query );
2125 $sth->execute( $reserve->{'biblionumber'} );
2127 ## Fix up the currently waiting reserve
2137 $sth = $dbh->prepare( $query );
2138 return $sth->execute( $reserve->{'reserve_id'} );
2143 ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2145 Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2150 my ($branch, $borrowernumber, $biblionumber) = @_;
2152 # return unless ( C4::Context->boolean_preference('printreserveslips') );
2154 my $reserve = GetReserveInfo
($borrowernumber,$biblionumber )
2157 return C4
::Letters
::GetPreparedLetter
(
2158 module
=> 'circulation',
2159 letter_code
=> 'RESERVESLIP',
2160 branchcode
=> $branch,
2162 'reserves' => $reserve,
2163 'branches' => $reserve->{branchcode
},
2164 'borrowers' => $reserve->{borrowernumber
},
2165 'biblio' => $reserve->{biblionumber
},
2166 'items' => $reserve->{itemnumber
},
2173 Koha Development Team <http://koha-community.org/>