3 # Copyright 2000-2002 Katipo Communications
4 # 2006 SAN Ouest Provence
5 # 2007 BibLibre Paul POULAIN
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #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 );
39 use List::MoreUtils qw( firstidx );
41 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
45 C4::Reserves - Koha functions for dealing with reservation.
53 This modules provides somes functions to deal with reservations.
55 Reserves are stored in reserves table.
56 The following columns contains important values :
57 - priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
58 =0 : then the reserve is being dealed
59 - found : NULL : means the patron requested the 1st available, and we haven't choosen the item
60 T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
61 W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
62 F(inished) : the reserve has been completed, and is done
63 - itemnumber : empty : the reserve is still unaffected to an item
64 filled: the reserve is attached to an item
65 The complete workflow is :
66 ==== 1st use case ====
67 patron request a document, 1st available : P >0, F=NULL, I=NULL
68 a library having it run "transfertodo", and clic on the list
69 if there is no transfer to do, the reserve waiting
70 patron can pick it up P =0, F=W, I=filled
71 if there is a transfer to do, write in branchtransfer P =0, F=T, I=filled
72 The pickup library recieve the book, it check in P =0, F=W, I=filled
73 The patron borrow the book P =0, F=F, I=filled
75 ==== 2nd use case ====
76 patron requests a document, a given item,
77 If pickup is holding branch P =0, F=W, I=filled
78 If transfer needed, write in branchtransfer P =0, F=T, I=filled
79 The pickup library receive the book, it checks it in P =0, F=W, I=filled
80 The patron borrow the book P =0, F=F, I=filled
87 # set the version for version checking
94 &GetReservesFromItemnumber
95 &GetReservesFromBiblionumber
96 &GetReservesFromBorrowernumber
111 &ModReserveMinusPriority
117 &CancelExpiredReserves
119 &IsAvailableForItemLevelRequest
122 &ToggleLowestPriority
128 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
134 $branch, $borrowernumber, $biblionumber,
135 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
136 $title, $checkitem, $found
139 GetReserveFee($borrowernumber, $biblionumber, $constraint,
141 my $dbh = C4::Context->dbh;
142 my $const = lc substr( $constraint, 0, 1 );
143 $resdate = format_date_in_iso( $resdate ) if ( $resdate );
144 $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
146 $expdate = format_date_in_iso( $expdate );
148 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
150 if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
151 # Make room in reserves for this before those of a later reserve date
152 $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
156 # If the reserv had the waiting status, we had the value of the resdate
157 if ( $found eq 'W' ) {
158 $waitingdate = $resdate;
162 # updates take place here
164 my $nextacctno = &getnextacctno( $borrowernumber );
166 INSERT INTO accountlines
167 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
169 (?,?,now(),?,?,'Res',?)
171 my $usth = $dbh->prepare($query);
172 $usth->execute( $borrowernumber, $nextacctno, $fee,
173 "Reserve Charge - $title", $fee );
179 (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
180 priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
185 my $sth = $dbh->prepare($query);
187 $borrowernumber, $biblionumber, $resdate, $branch,
188 $const, $priority, $notes, $checkitem,
189 $found, $waitingdate, $expdate
192 # Send e-mail to librarian if syspref is active
193 if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
194 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
195 my $biblio = GetBiblioData($biblionumber);
196 my $letter = C4::Letters::getletter( 'reserves', 'HOLDPLACED');
197 my $admin_email_address = C4::Context->preference('KohaAdminEmailAddress');
199 my %keys = (%$borrower, %$biblio);
200 foreach my $key (keys %keys) {
201 my $replacefield = "<<$key>>";
202 $letter->{content} =~ s/$replacefield/$keys{$key}/g;
203 $letter->{title} =~ s/$replacefield/$keys{$key}/g;
206 C4::Letters::EnqueueLetter(
208 borrowernumber => $borrowernumber,
209 message_transport_type => 'email',
210 from_address => $admin_email_address,
211 to_address => $admin_email_address,
220 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
222 INSERT INTO reserveconstraints
223 (borrowernumber,biblionumber,reservedate,biblioitemnumber)
227 $sth = $dbh->prepare($query); # keep prepare outside the loop!
228 foreach (@$bibitems) {
229 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
232 return; # FIXME: why not have a useful return value?
235 =head2 GetReservesFromBiblionumber
237 ($count, $title_reserves) = &GetReserves($biblionumber);
239 This function gets the list of reservations for one C<$biblionumber>, returning a count
240 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
244 sub GetReservesFromBiblionumber {
245 my ($biblionumber) = shift or return (0, []);
246 my ($all_dates) = shift;
247 my $dbh = C4::Context->dbh;
249 # Find the desired items in the reserves
252 timestamp AS rtimestamp,
264 WHERE biblionumber = ? ";
265 unless ( $all_dates ) {
266 $query .= "AND reservedate <= CURRENT_DATE()";
268 $query .= "ORDER BY priority";
269 my $sth = $dbh->prepare($query);
270 $sth->execute($biblionumber);
273 while ( my $data = $sth->fetchrow_hashref ) {
275 # FIXME - What is this doing? How do constraints work?
276 if ($data->{constrainttype} eq 'o') {
278 SELECT biblioitemnumber
279 FROM reserveconstraints
280 WHERE biblionumber = ?
281 AND borrowernumber = ?
284 my $csth = $dbh->prepare($query);
285 $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
287 while ( my $bibitemnos = $csth->fetchrow_array ) {
288 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
290 my $count = scalar @bibitemno;
292 # if we have two or more different specific itemtypes
293 # reserved by same person on same day
296 $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense.
297 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
300 # Look up the book we just found.
301 $bdata = GetBiblioItemData( $bibitemno[0] );
303 # Add the results of this latest search to the current
305 # FIXME - An 'each' would probably be more efficient.
306 foreach my $key ( keys %$bdata ) {
307 $data->{$key} = $bdata->{$key};
310 push @results, $data;
312 return ( $#results + 1, \@results );
315 =head2 GetReservesFromItemnumber
317 ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
319 TODO :: Description here
323 sub GetReservesFromItemnumber {
324 my ( $itemnumber, $all_dates ) = @_;
325 my $dbh = C4::Context->dbh;
327 SELECT reservedate,borrowernumber,branchcode
331 unless ( $all_dates ) {
332 $query .= " AND reservedate <= CURRENT_DATE()";
334 my $sth_res = $dbh->prepare($query);
335 $sth_res->execute($itemnumber);
336 my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
337 return ( $reservedate, $borrowernumber, $branchcode );
340 =head2 GetReservesFromBorrowernumber
342 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
348 sub GetReservesFromBorrowernumber {
349 my ( $borrowernumber, $status ) = @_;
350 my $dbh = C4::Context->dbh;
353 $sth = $dbh->prepare("
356 WHERE borrowernumber=?
360 $sth->execute($borrowernumber,$status);
362 $sth = $dbh->prepare("
365 WHERE borrowernumber=?
368 $sth->execute($borrowernumber);
370 my $data = $sth->fetchall_arrayref({});
373 #-------------------------------------------------------------------------------------
374 =head2 CanBookBeReserved
376 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
380 sub CanBookBeReserved{
381 my ($borrowernumber, $biblionumber) = @_;
383 my @items = GetItemsInfo($biblionumber);
384 foreach my $item (@items){
385 return 1 if CanItemBeReserved($borrowernumber, $item->{itemnumber});
390 =head2 CanItemBeReserved
392 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
394 This function return 1 if an item can be issued by this borrower.
398 sub CanItemBeReserved{
399 my ($borrowernumber, $itemnumber) = @_;
401 my $dbh = C4::Context->dbh;
402 my $allowedreserves = 0;
404 my $controlbranch = C4::Context->preference('ReservesControlBranch');
405 my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
407 # we retrieve borrowers and items informations #
408 my $item = GetItem($itemnumber);
409 my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);
411 # we retrieve user rights on this itemtype and branchcode
412 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
414 WHERE (categorycode in (?,'*') )
415 AND (itemtype IN (?,'*'))
416 AND (branchcode IN (?,'*'))
423 my $querycount ="SELECT
426 LEFT JOIN items USING (itemnumber)
427 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
428 LEFT JOIN borrowers USING (borrowernumber)
429 WHERE borrowernumber = ?
433 my $itemtype = $item->{$itype};
434 my $categorycode = $borrower->{categorycode};
436 my $branchfield = "reserves.branchcode";
438 if( $controlbranch eq "ItemHomeLibrary" ){
439 $branchfield = "items.homebranch";
440 $branchcode = $item->{homebranch};
441 }elsif( $controlbranch eq "PatronLibrary" ){
442 $branchfield = "borrowers.branchcode";
443 $branchcode = $borrower->{branchcode};
447 $sth->execute($categorycode, $itemtype, $branchcode);
448 if(my $rights = $sth->fetchrow_hashref()){
449 $itemtype = $rights->{itemtype};
450 $allowedreserves = $rights->{reservesallowed};
457 $querycount .= "AND $branchfield = ?";
459 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
460 my $sthcount = $dbh->prepare($querycount);
462 if($itemtype eq "*"){
463 $sthcount->execute($borrowernumber, $branchcode);
465 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
468 my $reservecount = "0";
469 if(my $rowcount = $sthcount->fetchrow_hashref()){
470 $reservecount = $rowcount->{count};
473 # we check if it's ok or not
474 if( $reservecount < $allowedreserves ){
480 #--------------------------------------------------------------------------------
481 =head2 GetReserveCount
483 $number = &GetReserveCount($borrowernumber);
485 this function returns the number of reservation for a borrower given on input arg.
489 sub GetReserveCount {
490 my ($borrowernumber) = @_;
492 my $dbh = C4::Context->dbh;
495 SELECT COUNT(*) AS counter
497 WHERE borrowernumber = ?
499 my $sth = $dbh->prepare($query);
500 $sth->execute($borrowernumber);
501 my $row = $sth->fetchrow_hashref;
502 return $row->{counter};
505 =head2 GetOtherReserves
507 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
509 Check queued list of this document and check if this document must be transfered
513 sub GetOtherReserves {
514 my ($itemnumber) = @_;
517 my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
518 if ($checkreserves) {
519 my $iteminfo = GetItem($itemnumber);
520 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
521 $messages->{'transfert'} = $checkreserves->{'branchcode'};
522 #minus priorities of others reservs
523 ModReserveMinusPriority(
525 $checkreserves->{'borrowernumber'},
526 $iteminfo->{'biblionumber'}
529 #launch the subroutine dotransfer
530 C4::Items::ModItemTransfer(
532 $iteminfo->{'holdingbranch'},
533 $checkreserves->{'branchcode'}
538 #step 2b : case of a reservation on the same branch, set the waiting status
540 $messages->{'waiting'} = 1;
541 ModReserveMinusPriority(
543 $checkreserves->{'borrowernumber'},
544 $iteminfo->{'biblionumber'}
546 ModReserveStatus($itemnumber,'W');
549 $nextreservinfo = $checkreserves->{'borrowernumber'};
552 return ( $messages, $nextreservinfo );
557 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
559 Calculate the fee for a reserve
564 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
567 my $dbh = C4::Context->dbh;
568 my $const = lc substr( $constraint, 0, 1 );
570 SELECT * FROM borrowers
571 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
572 WHERE borrowernumber = ?
574 my $sth = $dbh->prepare($query);
575 $sth->execute($borrowernumber);
576 my $data = $sth->fetchrow_hashref;
578 my $fee = $data->{'reservefee'};
579 my $cntitems = @- > $bibitems;
583 # check for items on issue
584 # first find biblioitem records
586 my $sth1 = $dbh->prepare(
587 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
588 WHERE (biblio.biblionumber = ?)"
590 $sth1->execute($biblionumber);
591 while ( my $data1 = $sth1->fetchrow_hashref ) {
592 if ( $const eq "a" ) {
593 push @biblioitems, $data1;
598 while ( $x < $cntitems ) {
599 if ( @$bibitems->{'biblioitemnumber'} ==
600 $data->{'biblioitemnumber'} )
606 if ( $const eq 'o' ) {
608 push @biblioitems, $data1;
613 push @biblioitems, $data1;
619 my $cntitemsfound = @biblioitems;
623 while ( $x < $cntitemsfound ) {
624 my $bitdata = $biblioitems[$x];
625 my $sth2 = $dbh->prepare(
627 WHERE biblioitemnumber = ?"
629 $sth2->execute( $bitdata->{'biblioitemnumber'} );
630 while ( my $itdata = $sth2->fetchrow_hashref ) {
631 my $sth3 = $dbh->prepare(
632 "SELECT * FROM issues
633 WHERE itemnumber = ?"
635 $sth3->execute( $itdata->{'itemnumber'} );
636 if ( my $isdata = $sth3->fetchrow_hashref ) {
644 if ( $allissued == 0 ) {
646 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
647 $rsth->execute($biblionumber);
648 if ( my $rdata = $rsth->fetchrow_hashref ) {
658 =head2 GetReservesToBranch
660 @transreserv = GetReservesToBranch( $frombranch );
662 Get reserve list for a given branch
666 sub GetReservesToBranch {
667 my ( $frombranch ) = @_;
668 my $dbh = C4::Context->dbh;
669 my $sth = $dbh->prepare(
670 "SELECT borrowernumber,reservedate,itemnumber,timestamp
675 $sth->execute( $frombranch );
678 while ( my $data = $sth->fetchrow_hashref ) {
679 $transreserv[$i] = $data;
682 return (@transreserv);
685 =head2 GetReservesForBranch
687 @transreserv = GetReservesForBranch($frombranch);
691 sub GetReservesForBranch {
692 my ($frombranch) = @_;
693 my $dbh = C4::Context->dbh;
694 my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
699 $query .= " AND branchcode=? ";
701 $query .= "ORDER BY waitingdate" ;
702 my $sth = $dbh->prepare($query);
704 $sth->execute($frombranch);
711 while ( my $data = $sth->fetchrow_hashref ) {
712 $transreserv[$i] = $data;
715 return (@transreserv);
718 sub GetReserveStatus {
719 my ($itemnumber) = @_;
721 my $dbh = C4::Context->dbh;
723 my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
725 $itemstatus->execute($itemnumber);
726 my ($found) = $itemstatus->fetchrow_array;
732 ($status, $reserve) = &CheckReserves($itemnumber);
733 ($status, $reserve) = &CheckReserves(undef, $barcode);
735 Find a book in the reserves.
737 C<$itemnumber> is the book's item number.
739 As I understand it, C<&CheckReserves> looks for the given item in the
740 reserves. If it is found, that's a match, and C<$status> is set to
743 Otherwise, it finds the most important item in the reserves with the
744 same biblio number as this book (I'm not clear on this) and returns it
745 with C<$status> set to C<Reserved>.
747 C<&CheckReserves> returns a two-element list:
749 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
751 C<$reserve> is the reserve item that matched. It is a
752 reference-to-hash whose keys are mostly the fields of the reserves
753 table in the Koha database.
758 my ( $item, $barcode ) = @_;
759 my $dbh = C4::Context->dbh;
762 SELECT items.biblionumber,
763 items.biblioitemnumber,
764 itemtypes.notforloan,
765 items.notforloan AS itemnotforloan,
768 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
769 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
773 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
774 $sth->execute($item);
777 $sth = $dbh->prepare("$select WHERE barcode = ?");
778 $sth->execute($barcode);
780 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
781 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
783 return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
785 # if item is not for loan it cannot be reserved either.....
786 # execpt where items.notforloan < 0 : This indicates the item is holdable.
787 return ( 0, 0 ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
789 # Find this item in the reserves
790 my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
792 # $priority and $highest are used to find the most important item
793 # in the list returned by &_Findgroupreserve. (The lower $priority,
794 # the more important the item.)
795 # $highest is the most important item we've seen so far.
797 if (scalar @reserves) {
798 my $priority = 10000000;
799 foreach my $res (@reserves) {
800 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
801 return ( "Waiting", $res ); # Found it
803 # See if this item is more important than what we've got so far
804 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
805 $priority = $res->{'priority'};
812 # If we get this far, then no exact match was found.
813 # We return the most important (i.e. next) reservation.
815 $highest->{'itemnumber'} = $item;
816 return ( "Reserved", $highest );
823 =head2 CancelExpiredReserves
825 CancelExpiredReserves();
827 Cancels all reserves with an expiration date from before today.
831 sub CancelExpiredReserves {
833 my $dbh = C4::Context->dbh;
834 my $sth = $dbh->prepare( "
835 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
836 AND expirationdate IS NOT NULL
840 while ( my $res = $sth->fetchrow_hashref() ) {
841 CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
848 &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
852 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
853 cancel, but not both: if both are given, C<&CancelReserve> does
856 C<$borrowernumber> is the borrower number of the patron on whose
857 behalf the book was reserved.
859 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
860 priorities of the other people who are waiting on the book.
865 my ( $biblio, $item, $borr ) = @_;
866 my $dbh = C4::Context->dbh;
867 if ( $item and $borr ) {
868 # removing a waiting reserve record....
869 # update the database...
872 SET cancellationdate = now(),
876 AND borrowernumber = ?
878 my $sth = $dbh->prepare($query);
879 $sth->execute( $item, $borr );
882 INSERT INTO old_reserves
883 SELECT * FROM reserves
885 AND borrowernumber = ?
887 $sth = $dbh->prepare($query);
888 $sth->execute( $item, $borr );
892 AND borrowernumber = ?
894 $sth = $dbh->prepare($query);
895 $sth->execute( $item, $borr );
898 # removing a reserve record....
899 # get the prioritiy on this record....
902 SELECT priority FROM reserves
903 WHERE biblionumber = ?
904 AND borrowernumber = ?
905 AND cancellationdate IS NULL
906 AND itemnumber IS NULL
908 my $sth = $dbh->prepare($query);
909 $sth->execute( $biblio, $borr );
910 ($priority) = $sth->fetchrow_array;
914 SET cancellationdate = now(),
917 WHERE biblionumber = ?
918 AND borrowernumber = ?
921 # update the database, removing the record...
922 $sth = $dbh->prepare($query);
923 $sth->execute( $biblio, $borr );
927 INSERT INTO old_reserves
928 SELECT * FROM reserves
929 WHERE biblionumber = ?
930 AND borrowernumber = ?
932 $sth = $dbh->prepare($query);
933 $sth->execute( $biblio, $borr );
937 WHERE biblionumber = ?
938 AND borrowernumber = ?
940 $sth = $dbh->prepare($query);
941 $sth->execute( $biblio, $borr );
943 # now fix the priority on the others....
944 _FixPriority( $biblio, $borr );
950 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
952 Change a hold request's priority or cancel it.
954 C<$rank> specifies the effect of the change. If C<$rank>
955 is 'W' or 'n', nothing happens. This corresponds to leaving a
956 request alone when changing its priority in the holds queue
959 If C<$rank> is 'del', the hold request is cancelled.
961 If C<$rank> is an integer greater than zero, the priority of
962 the request is set to that value. Since priority != 0 means
963 that the item is not waiting on the hold shelf, setting the
964 priority to a non-zero value also sets the request's found
965 status and waiting date to NULL.
967 The optional C<$itemnumber> parameter is used only when
968 C<$rank> is a non-zero integer; if supplied, the itemnumber
969 of the hold request is set accordingly; if omitted, the itemnumber
972 B<FIXME:> Note that the forgoing can have the effect of causing
973 item-level hold requests to turn into title-level requests. This
974 will be fixed once reserves has separate columns for requested
975 itemnumber and supplying itemnumber.
980 #subroutine to update a reserve
981 my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
982 return if $rank eq "W";
983 return if $rank eq "n";
984 my $dbh = C4::Context->dbh;
985 if ( $rank eq "del" ) {
988 SET cancellationdate=now()
989 WHERE biblionumber = ?
990 AND borrowernumber = ?
992 my $sth = $dbh->prepare($query);
993 $sth->execute( $biblio, $borrower );
996 INSERT INTO old_reserves
999 WHERE biblionumber = ?
1000 AND borrowernumber = ?
1002 $sth = $dbh->prepare($query);
1003 $sth->execute( $biblio, $borrower );
1005 DELETE FROM reserves
1006 WHERE biblionumber = ?
1007 AND borrowernumber = ?
1009 $sth = $dbh->prepare($query);
1010 $sth->execute( $biblio, $borrower );
1013 elsif ($rank =~ /^\d+/ and $rank > 0) {
1015 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1016 WHERE biblionumber = ?
1017 AND borrowernumber = ?
1019 my $sth = $dbh->prepare($query);
1020 $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1022 _FixPriority( $biblio, $borrower, $rank);
1026 =head2 ModReserveFill
1028 &ModReserveFill($reserve);
1030 Fill a reserve. If I understand this correctly, this means that the
1031 reserved book has been found and given to the patron who reserved it.
1033 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1034 whose keys are fields from the reserves table in the Koha database.
1038 sub ModReserveFill {
1040 my $dbh = C4::Context->dbh;
1041 # fill in a reserve record....
1042 my $biblionumber = $res->{'biblionumber'};
1043 my $borrowernumber = $res->{'borrowernumber'};
1044 my $resdate = $res->{'reservedate'};
1046 # get the priority on this record....
1048 my $query = "SELECT priority
1050 WHERE biblionumber = ?
1051 AND borrowernumber = ?
1052 AND reservedate = ?";
1053 my $sth = $dbh->prepare($query);
1054 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1055 ($priority) = $sth->fetchrow_array;
1058 # update the database...
1059 $query = "UPDATE reserves
1062 WHERE biblionumber = ?
1064 AND borrowernumber = ?
1066 $sth = $dbh->prepare($query);
1067 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1070 # move to old_reserves
1071 $query = "INSERT INTO old_reserves
1072 SELECT * FROM reserves
1073 WHERE biblionumber = ?
1075 AND borrowernumber = ?
1077 $sth = $dbh->prepare($query);
1078 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1079 $query = "DELETE FROM reserves
1080 WHERE biblionumber = ?
1082 AND borrowernumber = ?
1084 $sth = $dbh->prepare($query);
1085 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1087 # now fix the priority on the others (if the priority wasn't
1088 # already sorted!)....
1089 unless ( $priority == 0 ) {
1090 _FixPriority( $biblionumber, $borrowernumber );
1094 =head2 ModReserveStatus
1096 &ModReserveStatus($itemnumber, $newstatus);
1098 Update the reserve status for the active (priority=0) reserve.
1100 $itemnumber is the itemnumber the reserve is on
1102 $newstatus is the new status.
1106 sub ModReserveStatus {
1108 #first : check if we have a reservation for this item .
1109 my ($itemnumber, $newstatus) = @_;
1110 my $dbh = C4::Context->dbh;
1111 my $query = " UPDATE reserves
1112 SET found=?,waitingdate = now()
1117 my $sth_set = $dbh->prepare($query);
1118 $sth_set->execute( $newstatus, $itemnumber );
1120 if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1121 CartToShelf( $itemnumber );
1125 =head2 ModReserveAffect
1127 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1129 This function affect an item and a status for a given reserve
1130 The itemnumber parameter is used to find the biblionumber.
1131 with the biblionumber & the borrowernumber, we can affect the itemnumber
1132 to the correct reserve.
1134 if $transferToDo is not set, then the status is set to "Waiting" as well.
1135 otherwise, a transfer is on the way, and the end of the transfer will
1136 take care of the waiting status
1140 sub ModReserveAffect {
1141 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1142 my $dbh = C4::Context->dbh;
1144 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1145 # attached to $itemnumber
1146 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1147 $sth->execute($itemnumber);
1148 my ($biblionumber) = $sth->fetchrow;
1150 # get request - need to find out if item is already
1151 # waiting in order to not send duplicate hold filled notifications
1152 my $request = GetReserveInfo($borrowernumber, $biblionumber);
1153 my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1155 # If we affect a reserve that has to be transfered, don't set to Waiting
1157 if ($transferToDo) {
1163 WHERE borrowernumber = ?
1164 AND biblionumber = ?
1168 # affect the reserve to Waiting as well.
1175 WHERE borrowernumber = ?
1176 AND biblionumber = ?
1179 $sth = $dbh->prepare($query);
1180 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1181 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1183 if ( C4::Context->preference("ReturnToShelvingCart") ) {
1184 CartToShelf( $itemnumber );
1190 =head2 ModReserveCancelAll
1192 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1194 function to cancel reserv,check other reserves, and transfer document if it's necessary
1198 sub ModReserveCancelAll {
1201 my ( $itemnumber, $borrowernumber ) = @_;
1203 #step 1 : cancel the reservation
1204 my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1206 #step 2 launch the subroutine of the others reserves
1207 ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1209 return ( $messages, $nextreservinfo );
1212 =head2 ModReserveMinusPriority
1214 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1216 Reduce the values of queuded list
1220 sub ModReserveMinusPriority {
1221 my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1223 #first step update the value of the first person on reserv
1224 my $dbh = C4::Context->dbh;
1227 SET priority = 0 , itemnumber = ?
1228 WHERE borrowernumber=?
1231 my $sth_upd = $dbh->prepare($query);
1232 $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1233 # second step update all others reservs
1234 _FixPriority($biblionumber, $borrowernumber, '0');
1237 =head2 GetReserveInfo
1239 &GetReserveInfo($borrowernumber,$biblionumber);
1241 Get item and borrower details for a current hold.
1242 Current implementation this query should have a single result.
1246 sub GetReserveInfo {
1247 my ( $borrowernumber, $biblionumber ) = @_;
1248 my $dbh = C4::Context->dbh;
1252 reserves.borrowernumber,
1253 reserves.biblionumber,
1254 reserves.branchcode,
1255 reserves.waitingdate,
1271 items.holdingbranch,
1272 items.itemcallnumber,
1278 LEFT JOIN items USING(itemnumber)
1279 LEFT JOIN borrowers USING(borrowernumber)
1280 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1282 reserves.borrowernumber=?
1283 AND reserves.biblionumber=?";
1284 my $sth = $dbh->prepare($strsth);
1285 $sth->execute($borrowernumber,$biblionumber);
1287 my $data = $sth->fetchrow_hashref;
1292 =head2 IsAvailableForItemLevelRequest
1294 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1296 Checks whether a given item record is available for an
1297 item-level hold request. An item is available if
1299 * it is not lost AND
1300 * it is not damaged AND
1301 * it is not withdrawn AND
1302 * does not have a not for loan value > 0
1304 Whether or not the item is currently on loan is
1305 also checked - if the AllowOnShelfHolds system preference
1306 is ON, an item can be requested even if it is currently
1307 on loan to somebody else. If the system preference
1308 is OFF, an item that is currently checked out cannot
1309 be the target of an item-level hold request.
1311 Note that IsAvailableForItemLevelRequest() does not
1312 check if the staff operator is authorized to place
1313 a request on the item - in particular,
1314 this routine does not check IndependantBranches
1315 and canreservefromotherbranches.
1319 sub IsAvailableForItemLevelRequest {
1320 my $itemnumber = shift;
1322 my $item = GetItem($itemnumber);
1324 # must check the notforloan setting of the itemtype
1325 # FIXME - a lot of places in the code do this
1326 # or something similar - need to be
1328 my $dbh = C4::Context->dbh;
1329 my $notforloan_query;
1330 if (C4::Context->preference('item-level_itypes')) {
1331 $notforloan_query = "SELECT itemtypes.notforloan
1333 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1334 WHERE itemnumber = ?";
1336 $notforloan_query = "SELECT itemtypes.notforloan
1338 JOIN biblioitems USING (biblioitemnumber)
1339 JOIN itemtypes USING (itemtype)
1340 WHERE itemnumber = ?";
1342 my $sth = $dbh->prepare($notforloan_query);
1343 $sth->execute($itemnumber);
1344 my $notforloan_per_itemtype = 0;
1345 if (my ($notforloan) = $sth->fetchrow_array) {
1346 $notforloan_per_itemtype = 1 if $notforloan;
1349 my $available_per_item = 1;
1350 $available_per_item = 0 if $item->{itemlost} or
1351 ( $item->{notforloan} > 0 ) or
1352 ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1353 $item->{wthdrawn} or
1354 $notforloan_per_itemtype;
1357 if (C4::Context->preference('AllowOnShelfHolds')) {
1358 return $available_per_item;
1360 return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W"));
1364 =head2 AlterPriority
1366 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1368 This function changes a reserve's priority up, down, to the top, or to the bottom.
1369 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1374 my ( $where, $borrowernumber, $biblionumber ) = @_;
1376 my $dbh = C4::Context->dbh;
1378 ## Find this reserve
1379 my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1380 $sth->execute( $biblionumber, $borrowernumber );
1381 my $reserve = $sth->fetchrow_hashref();
1384 if ( $where eq 'up' || $where eq 'down' ) {
1386 my $priority = $reserve->{'priority'};
1387 $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1388 _FixPriority( $biblionumber, $borrowernumber, $priority )
1390 } elsif ( $where eq 'top' ) {
1392 _FixPriority( $biblionumber, $borrowernumber, '1' )
1394 } elsif ( $where eq 'bottom' ) {
1396 _FixPriority( $biblionumber, $borrowernumber, '999999' )
1401 =head2 ToggleLowestPriority
1403 ToggleLowestPriority( $borrowernumber, $biblionumber );
1405 This function sets the lowestPriority field to true if is false, and false if it is true.
1409 sub ToggleLowestPriority {
1410 my ( $borrowernumber, $biblionumber ) = @_;
1412 my $dbh = C4::Context->dbh;
1414 my $sth = $dbh->prepare(
1415 "UPDATE reserves SET lowestPriority = NOT lowestPriority
1416 WHERE biblionumber = ?
1417 AND borrowernumber = ?"
1425 _FixPriority( $biblionumber, $borrowernumber, '999999' );
1430 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1432 Only used internally (so don't export it)
1433 Changed how this functions works #
1434 Now just gets an array of reserves in the rank order and updates them with
1435 the array index (+1 as array starts from 0)
1436 and if $rank is supplied will splice item from the array and splice it back in again
1437 in new priority rank
1442 my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1443 my $dbh = C4::Context->dbh;
1444 if ( $rank eq "del" ) {
1445 CancelReserve( $biblio, undef, $borrowernumber );
1447 if ( $rank eq "W" || $rank eq "0" ) {
1449 # make sure priority for waiting or in-transit items is 0
1453 WHERE biblionumber = ?
1454 AND borrowernumber = ?
1455 AND found IN ('W', 'T')
1457 my $sth = $dbh->prepare($query);
1458 $sth->execute( $biblio, $borrowernumber );
1464 # FIXME adding a new security in returned elements for changing priority,
1465 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1466 # This is wrong a waiting reserve has W set
1467 # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1469 SELECT borrowernumber, reservedate, constrainttype
1471 WHERE biblionumber = ?
1472 AND ((found <> 'W' AND found <> 'T') or found is NULL)
1473 ORDER BY priority ASC
1475 my $sth = $dbh->prepare($query);
1476 $sth->execute($biblio);
1477 while ( my $line = $sth->fetchrow_hashref ) {
1478 push( @reservedates, $line );
1479 push( @priority, $line );
1482 # To find the matching index
1484 my $key = -1; # to allow for 0 to be a valid result
1485 for ( $i = 0 ; $i < @priority ; $i++ ) {
1486 if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1487 $key = $i; # save the index
1492 # if index exists in array then move it to new position
1493 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1494 my $new_rank = $rank -
1495 1; # $new_rank is what you want the new index to be in the array
1496 my $moving_item = splice( @priority, $key, 1 );
1497 splice( @priority, $new_rank, 0, $moving_item );
1500 # now fix the priority on those that are left....
1504 WHERE biblionumber = ?
1505 AND borrowernumber = ?
1509 $sth = $dbh->prepare($query);
1510 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1513 $priority[$j]->{'borrowernumber'},
1514 $priority[$j]->{'reservedate'}
1519 $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1522 unless ( $ignoreSetLowestRank ) {
1523 while ( my $res = $sth->fetchrow_hashref() ) {
1524 _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1529 =head2 _Findgroupreserve
1531 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1533 Looks for an item-specific match first, then for a title-level match, returning the
1534 first match found. If neither, then we look for a 3rd kind of match based on
1535 reserve constraints.
1537 TODO: add more explanation about reserve constraints
1539 C<&_Findgroupreserve> returns :
1540 C<@results> is an array of references-to-hash whose keys are mostly
1541 fields from the reserves table of the Koha database, plus
1542 C<biblioitemnumber>.
1546 sub _Findgroupreserve {
1547 my ( $bibitem, $biblio, $itemnumber ) = @_;
1548 my $dbh = C4::Context->dbh;
1550 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1551 # check for exact targetted match
1552 my $item_level_target_query = qq/
1553 SELECT reserves.biblionumber AS biblionumber,
1554 reserves.borrowernumber AS borrowernumber,
1555 reserves.reservedate AS reservedate,
1556 reserves.branchcode AS branchcode,
1557 reserves.cancellationdate AS cancellationdate,
1558 reserves.found AS found,
1559 reserves.reservenotes AS reservenotes,
1560 reserves.priority AS priority,
1561 reserves.timestamp AS timestamp,
1562 biblioitems.biblioitemnumber AS biblioitemnumber,
1563 reserves.itemnumber AS itemnumber
1565 JOIN biblioitems USING (biblionumber)
1566 JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1569 AND item_level_request = 1
1571 AND reservedate <= CURRENT_DATE()
1573 my $sth = $dbh->prepare($item_level_target_query);
1574 $sth->execute($itemnumber);
1576 if ( my $data = $sth->fetchrow_hashref ) {
1577 push( @results, $data );
1579 return @results if @results;
1581 # check for title-level targetted match
1582 my $title_level_target_query = qq/
1583 SELECT reserves.biblionumber AS biblionumber,
1584 reserves.borrowernumber AS borrowernumber,
1585 reserves.reservedate AS reservedate,
1586 reserves.branchcode AS branchcode,
1587 reserves.cancellationdate AS cancellationdate,
1588 reserves.found AS found,
1589 reserves.reservenotes AS reservenotes,
1590 reserves.priority AS priority,
1591 reserves.timestamp AS timestamp,
1592 biblioitems.biblioitemnumber AS biblioitemnumber,
1593 reserves.itemnumber AS itemnumber
1595 JOIN biblioitems USING (biblionumber)
1596 JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1599 AND item_level_request = 0
1600 AND hold_fill_targets.itemnumber = ?
1601 AND reservedate <= CURRENT_DATE()
1603 $sth = $dbh->prepare($title_level_target_query);
1604 $sth->execute($itemnumber);
1606 if ( my $data = $sth->fetchrow_hashref ) {
1607 push( @results, $data );
1609 return @results if @results;
1612 SELECT reserves.biblionumber AS biblionumber,
1613 reserves.borrowernumber AS borrowernumber,
1614 reserves.reservedate AS reservedate,
1615 reserves.waitingdate AS waitingdate,
1616 reserves.branchcode AS branchcode,
1617 reserves.cancellationdate AS cancellationdate,
1618 reserves.found AS found,
1619 reserves.reservenotes AS reservenotes,
1620 reserves.priority AS priority,
1621 reserves.timestamp AS timestamp,
1622 reserveconstraints.biblioitemnumber AS biblioitemnumber,
1623 reserves.itemnumber AS itemnumber
1625 LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1626 WHERE reserves.biblionumber = ?
1627 AND ( ( reserveconstraints.biblioitemnumber = ?
1628 AND reserves.borrowernumber = reserveconstraints.borrowernumber
1629 AND reserves.reservedate = reserveconstraints.reservedate )
1630 OR reserves.constrainttype='a' )
1631 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1632 AND reserves.reservedate <= CURRENT_DATE()
1634 $sth = $dbh->prepare($query);
1635 $sth->execute( $biblio, $bibitem, $itemnumber );
1637 while ( my $data = $sth->fetchrow_hashref ) {
1638 push( @results, $data );
1643 =head2 _koha_notify_reserve
1645 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1647 Sends a notification to the patron that their hold has been filled (through
1648 ModReserveAffect, _not_ ModReserveFill)
1652 sub _koha_notify_reserve {
1653 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1655 my $dbh = C4::Context->dbh;
1656 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1660 if ( $borrower->{'email'} || $borrower->{'smsalertnumber'} ) {
1661 $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } );
1663 return if ( !defined( $messagingprefs->{'letter_code'} ) );
1664 $letter_code = $messagingprefs->{'letter_code'};
1666 $letter_code = 'HOLD_PRINT';
1670 my $sth = $dbh->prepare("
1673 WHERE borrowernumber = ?
1674 AND biblionumber = ?
1676 $sth->execute( $borrowernumber, $biblionumber );
1677 my $reserve = $sth->fetchrow_hashref;
1678 my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1680 my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1682 my $letter = getletter( 'reserves', $letter_code );
1683 die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1685 C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1686 C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1687 C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1688 C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1690 if ( $reserve->{'itemnumber'} ) {
1691 C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1693 my $today = C4::Dates->new()->output();
1694 $letter->{'title'} =~ s/<<today>>/$today/g;
1695 $letter->{'content'} =~ s/<<today>>/$today/g;
1696 $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1698 if ( $print_mode ) {
1699 C4::Letters::EnqueueLetter( {
1701 borrowernumber => $borrowernumber,
1702 message_transport_type => 'print',
1708 if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1709 # aka, 'email' in ->{'transports'}
1710 C4::Letters::EnqueueLetter(
1711 { letter => $letter,
1712 borrowernumber => $borrowernumber,
1713 message_transport_type => 'email',
1714 from_address => $admin_email_address,
1719 if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1720 C4::Letters::EnqueueLetter(
1721 { letter => $letter,
1722 borrowernumber => $borrowernumber,
1723 message_transport_type => 'sms',
1729 =head2 _ShiftPriorityByDateAndPriority
1731 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1733 This increments the priority of all reserves after the one
1734 with either the lowest date after C<$reservedate>
1735 or the lowest priority after C<$priority>.
1737 It effectively makes room for a new reserve to be inserted with a certain
1738 priority, which is returned.
1740 This is most useful when the reservedate can be set by the user. It allows
1741 the new reserve to be placed before other reserves that have a later
1742 reservedate. Since priority also is set by the form in reserves/request.pl
1743 the sub accounts for that too.
1747 sub _ShiftPriorityByDateAndPriority {
1748 my ( $biblio, $resdate, $new_priority ) = @_;
1750 my $dbh = C4::Context->dbh;
1751 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1752 my $sth = $dbh->prepare( $query );
1753 $sth->execute( $biblio, $resdate, $new_priority );
1754 my $min_priority = $sth->fetchrow;
1755 # if no such matches are found, $new_priority remains as original value
1756 $new_priority = $min_priority if ( $min_priority );
1758 # Shift the priority up by one; works in conjunction with the next SQL statement
1759 $query = "UPDATE reserves
1760 SET priority = priority+1
1761 WHERE biblionumber = ?
1762 AND borrowernumber = ?
1765 my $sth_update = $dbh->prepare( $query );
1767 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1768 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1769 $sth = $dbh->prepare( $query );
1770 $sth->execute( $new_priority, $biblio );
1771 while ( my $row = $sth->fetchrow_hashref ) {
1772 $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1775 return $new_priority; # so the caller knows what priority they wind up receiving
1780 Koha Development Team <http://koha-community.org/>