3 # Copyright 2000-2002 Katipo Communications
4 # 2006 SAN Ouest Provence
5 # 2007-2010 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 $branchcode = $borrower->{branchcode};
198 my $branch_details = C4::Branch::GetBranchDetail($branchcode);
199 my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
201 my %keys = (%$borrower, %$biblio);
202 foreach my $key (keys %keys) {
203 my $replacefield = "<<$key>>";
204 $letter->{content} =~ s/$replacefield/$keys{$key}/g;
205 $letter->{title} =~ s/$replacefield/$keys{$key}/g;
208 C4::Letters::EnqueueLetter(
210 borrowernumber => $borrowernumber,
211 message_transport_type => 'email',
212 from_address => $admin_email_address,
213 to_address => $admin_email_address,
222 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
224 INSERT INTO reserveconstraints
225 (borrowernumber,biblionumber,reservedate,biblioitemnumber)
229 $sth = $dbh->prepare($query); # keep prepare outside the loop!
230 foreach (@$bibitems) {
231 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
234 return; # FIXME: why not have a useful return value?
237 =head2 GetReservesFromBiblionumber
239 ($count, $title_reserves) = &GetReserves($biblionumber);
241 This function gets the list of reservations for one C<$biblionumber>, returning a count
242 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
246 sub GetReservesFromBiblionumber {
247 my ($biblionumber) = shift or return (0, []);
248 my ($all_dates) = shift;
249 my $dbh = C4::Context->dbh;
251 # Find the desired items in the reserves
254 timestamp AS rtimestamp,
266 WHERE biblionumber = ? ";
267 unless ( $all_dates ) {
268 $query .= "AND reservedate <= CURRENT_DATE()";
270 $query .= "ORDER BY priority";
271 my $sth = $dbh->prepare($query);
272 $sth->execute($biblionumber);
275 while ( my $data = $sth->fetchrow_hashref ) {
277 # FIXME - What is this doing? How do constraints work?
278 if ($data->{constrainttype} eq 'o') {
280 SELECT biblioitemnumber
281 FROM reserveconstraints
282 WHERE biblionumber = ?
283 AND borrowernumber = ?
286 my $csth = $dbh->prepare($query);
287 $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
289 while ( my $bibitemnos = $csth->fetchrow_array ) {
290 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
292 my $count = scalar @bibitemno;
294 # if we have two or more different specific itemtypes
295 # reserved by same person on same day
298 $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense.
299 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
302 # Look up the book we just found.
303 $bdata = GetBiblioItemData( $bibitemno[0] );
305 # Add the results of this latest search to the current
307 # FIXME - An 'each' would probably be more efficient.
308 foreach my $key ( keys %$bdata ) {
309 $data->{$key} = $bdata->{$key};
312 push @results, $data;
314 return ( $#results + 1, \@results );
317 =head2 GetReservesFromItemnumber
319 ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
321 TODO :: Description here
325 sub GetReservesFromItemnumber {
326 my ( $itemnumber, $all_dates ) = @_;
327 my $dbh = C4::Context->dbh;
329 SELECT reservedate,borrowernumber,branchcode
333 unless ( $all_dates ) {
334 $query .= " AND reservedate <= CURRENT_DATE()";
336 my $sth_res = $dbh->prepare($query);
337 $sth_res->execute($itemnumber);
338 my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
339 return ( $reservedate, $borrowernumber, $branchcode );
342 =head2 GetReservesFromBorrowernumber
344 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
350 sub GetReservesFromBorrowernumber {
351 my ( $borrowernumber, $status ) = @_;
352 my $dbh = C4::Context->dbh;
355 $sth = $dbh->prepare("
358 WHERE borrowernumber=?
362 $sth->execute($borrowernumber,$status);
364 $sth = $dbh->prepare("
367 WHERE borrowernumber=?
370 $sth->execute($borrowernumber);
372 my $data = $sth->fetchall_arrayref({});
375 #-------------------------------------------------------------------------------------
376 =head2 CanBookBeReserved
378 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
382 sub CanBookBeReserved{
383 my ($borrowernumber, $biblionumber) = @_;
385 my @items = GetItemsInfo($biblionumber);
386 foreach my $item (@items){
387 return 1 if CanItemBeReserved($borrowernumber, $item->{itemnumber});
392 =head2 CanItemBeReserved
394 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
396 This function return 1 if an item can be issued by this borrower.
400 sub CanItemBeReserved{
401 my ($borrowernumber, $itemnumber) = @_;
403 my $dbh = C4::Context->dbh;
404 my $allowedreserves = 0;
406 my $controlbranch = C4::Context->preference('ReservesControlBranch');
407 my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
409 # we retrieve borrowers and items informations #
410 my $item = GetItem($itemnumber);
411 my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);
413 # we retrieve user rights on this itemtype and branchcode
414 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
416 WHERE (categorycode in (?,'*') )
417 AND (itemtype IN (?,'*'))
418 AND (branchcode IN (?,'*'))
425 my $querycount ="SELECT
428 LEFT JOIN items USING (itemnumber)
429 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
430 LEFT JOIN borrowers USING (borrowernumber)
431 WHERE borrowernumber = ?
435 my $itemtype = $item->{$itype};
436 my $categorycode = $borrower->{categorycode};
438 my $branchfield = "reserves.branchcode";
440 if( $controlbranch eq "ItemHomeLibrary" ){
441 $branchfield = "items.homebranch";
442 $branchcode = $item->{homebranch};
443 }elsif( $controlbranch eq "PatronLibrary" ){
444 $branchfield = "borrowers.branchcode";
445 $branchcode = $borrower->{branchcode};
449 $sth->execute($categorycode, $itemtype, $branchcode);
450 if(my $rights = $sth->fetchrow_hashref()){
451 $itemtype = $rights->{itemtype};
452 $allowedreserves = $rights->{reservesallowed};
459 $querycount .= "AND $branchfield = ?";
461 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
462 my $sthcount = $dbh->prepare($querycount);
464 if($itemtype eq "*"){
465 $sthcount->execute($borrowernumber, $branchcode);
467 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
470 my $reservecount = "0";
471 if(my $rowcount = $sthcount->fetchrow_hashref()){
472 $reservecount = $rowcount->{count};
475 # we check if it's ok or not
476 if( $reservecount < $allowedreserves ){
482 #--------------------------------------------------------------------------------
483 =head2 GetReserveCount
485 $number = &GetReserveCount($borrowernumber);
487 this function returns the number of reservation for a borrower given on input arg.
491 sub GetReserveCount {
492 my ($borrowernumber) = @_;
494 my $dbh = C4::Context->dbh;
497 SELECT COUNT(*) AS counter
499 WHERE borrowernumber = ?
501 my $sth = $dbh->prepare($query);
502 $sth->execute($borrowernumber);
503 my $row = $sth->fetchrow_hashref;
504 return $row->{counter};
507 =head2 GetOtherReserves
509 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
511 Check queued list of this document and check if this document must be transfered
515 sub GetOtherReserves {
516 my ($itemnumber) = @_;
519 my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
520 if ($checkreserves) {
521 my $iteminfo = GetItem($itemnumber);
522 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
523 $messages->{'transfert'} = $checkreserves->{'branchcode'};
524 #minus priorities of others reservs
525 ModReserveMinusPriority(
527 $checkreserves->{'borrowernumber'},
528 $iteminfo->{'biblionumber'}
531 #launch the subroutine dotransfer
532 C4::Items::ModItemTransfer(
534 $iteminfo->{'holdingbranch'},
535 $checkreserves->{'branchcode'}
540 #step 2b : case of a reservation on the same branch, set the waiting status
542 $messages->{'waiting'} = 1;
543 ModReserveMinusPriority(
545 $checkreserves->{'borrowernumber'},
546 $iteminfo->{'biblionumber'}
548 ModReserveStatus($itemnumber,'W');
551 $nextreservinfo = $checkreserves->{'borrowernumber'};
554 return ( $messages, $nextreservinfo );
559 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
561 Calculate the fee for a reserve
566 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
569 my $dbh = C4::Context->dbh;
570 my $const = lc substr( $constraint, 0, 1 );
572 SELECT * FROM borrowers
573 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
574 WHERE borrowernumber = ?
576 my $sth = $dbh->prepare($query);
577 $sth->execute($borrowernumber);
578 my $data = $sth->fetchrow_hashref;
580 my $fee = $data->{'reservefee'};
581 my $cntitems = @- > $bibitems;
585 # check for items on issue
586 # first find biblioitem records
588 my $sth1 = $dbh->prepare(
589 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
590 WHERE (biblio.biblionumber = ?)"
592 $sth1->execute($biblionumber);
593 while ( my $data1 = $sth1->fetchrow_hashref ) {
594 if ( $const eq "a" ) {
595 push @biblioitems, $data1;
600 while ( $x < $cntitems ) {
601 if ( @$bibitems->{'biblioitemnumber'} ==
602 $data->{'biblioitemnumber'} )
608 if ( $const eq 'o' ) {
610 push @biblioitems, $data1;
615 push @biblioitems, $data1;
621 my $cntitemsfound = @biblioitems;
625 while ( $x < $cntitemsfound ) {
626 my $bitdata = $biblioitems[$x];
627 my $sth2 = $dbh->prepare(
629 WHERE biblioitemnumber = ?"
631 $sth2->execute( $bitdata->{'biblioitemnumber'} );
632 while ( my $itdata = $sth2->fetchrow_hashref ) {
633 my $sth3 = $dbh->prepare(
634 "SELECT * FROM issues
635 WHERE itemnumber = ?"
637 $sth3->execute( $itdata->{'itemnumber'} );
638 if ( my $isdata = $sth3->fetchrow_hashref ) {
646 if ( $allissued == 0 ) {
648 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
649 $rsth->execute($biblionumber);
650 if ( my $rdata = $rsth->fetchrow_hashref ) {
660 =head2 GetReservesToBranch
662 @transreserv = GetReservesToBranch( $frombranch );
664 Get reserve list for a given branch
668 sub GetReservesToBranch {
669 my ( $frombranch ) = @_;
670 my $dbh = C4::Context->dbh;
671 my $sth = $dbh->prepare(
672 "SELECT borrowernumber,reservedate,itemnumber,timestamp
677 $sth->execute( $frombranch );
680 while ( my $data = $sth->fetchrow_hashref ) {
681 $transreserv[$i] = $data;
684 return (@transreserv);
687 =head2 GetReservesForBranch
689 @transreserv = GetReservesForBranch($frombranch);
693 sub GetReservesForBranch {
694 my ($frombranch) = @_;
695 my $dbh = C4::Context->dbh;
696 my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
701 $query .= " AND branchcode=? ";
703 $query .= "ORDER BY waitingdate" ;
704 my $sth = $dbh->prepare($query);
706 $sth->execute($frombranch);
713 while ( my $data = $sth->fetchrow_hashref ) {
714 $transreserv[$i] = $data;
717 return (@transreserv);
720 sub GetReserveStatus {
721 my ($itemnumber) = @_;
723 my $dbh = C4::Context->dbh;
725 my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
727 $itemstatus->execute($itemnumber);
728 my ($found) = $itemstatus->fetchrow_array;
734 ($status, $reserve) = &CheckReserves($itemnumber);
735 ($status, $reserve) = &CheckReserves(undef, $barcode);
737 Find a book in the reserves.
739 C<$itemnumber> is the book's item number.
741 As I understand it, C<&CheckReserves> looks for the given item in the
742 reserves. If it is found, that's a match, and C<$status> is set to
745 Otherwise, it finds the most important item in the reserves with the
746 same biblio number as this book (I'm not clear on this) and returns it
747 with C<$status> set to C<Reserved>.
749 C<&CheckReserves> returns a two-element list:
751 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
753 C<$reserve> is the reserve item that matched. It is a
754 reference-to-hash whose keys are mostly the fields of the reserves
755 table in the Koha database.
760 my ( $item, $barcode ) = @_;
761 my $dbh = C4::Context->dbh;
764 if (C4::Context->preference('item-level_itypes')){
766 SELECT items.biblionumber,
767 items.biblioitemnumber,
768 itemtypes.notforloan,
769 items.notforloan AS itemnotforloan,
772 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
773 LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype
778 SELECT items.biblionumber,
779 items.biblioitemnumber,
780 itemtypes.notforloan,
781 items.notforloan AS itemnotforloan,
784 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
785 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
790 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
791 $sth->execute($item);
794 $sth = $dbh->prepare("$select WHERE barcode = ?");
795 $sth->execute($barcode);
797 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
798 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
800 return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
802 # if item is not for loan it cannot be reserved either.....
803 # execpt where items.notforloan < 0 : This indicates the item is holdable.
804 return ( 0, 0 ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
806 # Find this item in the reserves
807 my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
809 # $priority and $highest are used to find the most important item
810 # in the list returned by &_Findgroupreserve. (The lower $priority,
811 # the more important the item.)
812 # $highest is the most important item we've seen so far.
814 if (scalar @reserves) {
815 my $priority = 10000000;
816 foreach my $res (@reserves) {
817 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
818 return ( "Waiting", $res ); # Found it
820 # See if this item is more important than what we've got so far
821 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
822 my $borrowerinfo=C4::Members::GetMemberDetails($res->{'borrowernumber'});
823 my $iteminfo=C4::Items::GetItem($itemnumber);
824 my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo);
825 my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
826 next if ($branchitemrule->{'holdallowed'} == 0);
827 next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
828 $priority = $res->{'priority'};
835 # If we get this far, then no exact match was found.
836 # We return the most important (i.e. next) reservation.
838 $highest->{'itemnumber'} = $item;
839 return ( "Reserved", $highest );
846 =head2 CancelExpiredReserves
848 CancelExpiredReserves();
850 Cancels all reserves with an expiration date from before today.
854 sub CancelExpiredReserves {
856 my $dbh = C4::Context->dbh;
857 my $sth = $dbh->prepare( "
858 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
859 AND expirationdate IS NOT NULL
863 while ( my $res = $sth->fetchrow_hashref() ) {
864 CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
871 &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
875 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
876 cancel, but not both: if both are given, C<&CancelReserve> does
879 C<$borrowernumber> is the borrower number of the patron on whose
880 behalf the book was reserved.
882 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
883 priorities of the other people who are waiting on the book.
888 my ( $biblio, $item, $borr ) = @_;
889 my $dbh = C4::Context->dbh;
890 if ( $item and $borr ) {
891 # removing a waiting reserve record....
892 # update the database...
895 SET cancellationdate = now(),
899 AND borrowernumber = ?
901 my $sth = $dbh->prepare($query);
902 $sth->execute( $item, $borr );
905 INSERT INTO old_reserves
906 SELECT * FROM reserves
908 AND borrowernumber = ?
910 $sth = $dbh->prepare($query);
911 $sth->execute( $item, $borr );
915 AND borrowernumber = ?
917 $sth = $dbh->prepare($query);
918 $sth->execute( $item, $borr );
921 # removing a reserve record....
922 # get the prioritiy on this record....
925 SELECT priority FROM reserves
926 WHERE biblionumber = ?
927 AND borrowernumber = ?
928 AND cancellationdate IS NULL
929 AND itemnumber IS NULL
931 my $sth = $dbh->prepare($query);
932 $sth->execute( $biblio, $borr );
933 ($priority) = $sth->fetchrow_array;
937 SET cancellationdate = now(),
940 WHERE biblionumber = ?
941 AND borrowernumber = ?
944 # update the database, removing the record...
945 $sth = $dbh->prepare($query);
946 $sth->execute( $biblio, $borr );
950 INSERT INTO old_reserves
951 SELECT * FROM reserves
952 WHERE biblionumber = ?
953 AND borrowernumber = ?
955 $sth = $dbh->prepare($query);
956 $sth->execute( $biblio, $borr );
960 WHERE biblionumber = ?
961 AND borrowernumber = ?
963 $sth = $dbh->prepare($query);
964 $sth->execute( $biblio, $borr );
966 # now fix the priority on the others....
967 _FixPriority( $biblio, $borr );
973 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
975 Change a hold request's priority or cancel it.
977 C<$rank> specifies the effect of the change. If C<$rank>
978 is 'W' or 'n', nothing happens. This corresponds to leaving a
979 request alone when changing its priority in the holds queue
982 If C<$rank> is 'del', the hold request is cancelled.
984 If C<$rank> is an integer greater than zero, the priority of
985 the request is set to that value. Since priority != 0 means
986 that the item is not waiting on the hold shelf, setting the
987 priority to a non-zero value also sets the request's found
988 status and waiting date to NULL.
990 The optional C<$itemnumber> parameter is used only when
991 C<$rank> is a non-zero integer; if supplied, the itemnumber
992 of the hold request is set accordingly; if omitted, the itemnumber
995 B<FIXME:> Note that the forgoing can have the effect of causing
996 item-level hold requests to turn into title-level requests. This
997 will be fixed once reserves has separate columns for requested
998 itemnumber and supplying itemnumber.
1003 #subroutine to update a reserve
1004 my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
1005 return if $rank eq "W";
1006 return if $rank eq "n";
1007 my $dbh = C4::Context->dbh;
1008 if ( $rank eq "del" ) {
1011 SET cancellationdate=now()
1012 WHERE biblionumber = ?
1013 AND borrowernumber = ?
1015 my $sth = $dbh->prepare($query);
1016 $sth->execute( $biblio, $borrower );
1019 INSERT INTO old_reserves
1022 WHERE biblionumber = ?
1023 AND borrowernumber = ?
1025 $sth = $dbh->prepare($query);
1026 $sth->execute( $biblio, $borrower );
1028 DELETE FROM reserves
1029 WHERE biblionumber = ?
1030 AND borrowernumber = ?
1032 $sth = $dbh->prepare($query);
1033 $sth->execute( $biblio, $borrower );
1036 elsif ($rank =~ /^\d+/ and $rank > 0) {
1038 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1039 WHERE biblionumber = ?
1040 AND borrowernumber = ?
1042 my $sth = $dbh->prepare($query);
1043 $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1045 _FixPriority( $biblio, $borrower, $rank);
1049 =head2 ModReserveFill
1051 &ModReserveFill($reserve);
1053 Fill a reserve. If I understand this correctly, this means that the
1054 reserved book has been found and given to the patron who reserved it.
1056 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1057 whose keys are fields from the reserves table in the Koha database.
1061 sub ModReserveFill {
1063 my $dbh = C4::Context->dbh;
1064 # fill in a reserve record....
1065 my $biblionumber = $res->{'biblionumber'};
1066 my $borrowernumber = $res->{'borrowernumber'};
1067 my $resdate = $res->{'reservedate'};
1069 # get the priority on this record....
1071 my $query = "SELECT priority
1073 WHERE biblionumber = ?
1074 AND borrowernumber = ?
1075 AND reservedate = ?";
1076 my $sth = $dbh->prepare($query);
1077 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1078 ($priority) = $sth->fetchrow_array;
1081 # update the database...
1082 $query = "UPDATE reserves
1085 WHERE biblionumber = ?
1087 AND borrowernumber = ?
1089 $sth = $dbh->prepare($query);
1090 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1093 # move to old_reserves
1094 $query = "INSERT INTO old_reserves
1095 SELECT * FROM reserves
1096 WHERE biblionumber = ?
1098 AND borrowernumber = ?
1100 $sth = $dbh->prepare($query);
1101 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1102 $query = "DELETE FROM reserves
1103 WHERE biblionumber = ?
1105 AND borrowernumber = ?
1107 $sth = $dbh->prepare($query);
1108 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1110 # now fix the priority on the others (if the priority wasn't
1111 # already sorted!)....
1112 unless ( $priority == 0 ) {
1113 _FixPriority( $biblionumber, $borrowernumber );
1117 =head2 ModReserveStatus
1119 &ModReserveStatus($itemnumber, $newstatus);
1121 Update the reserve status for the active (priority=0) reserve.
1123 $itemnumber is the itemnumber the reserve is on
1125 $newstatus is the new status.
1129 sub ModReserveStatus {
1131 #first : check if we have a reservation for this item .
1132 my ($itemnumber, $newstatus) = @_;
1133 my $dbh = C4::Context->dbh;
1134 my $query = " UPDATE reserves
1135 SET found=?,waitingdate = now()
1140 my $sth_set = $dbh->prepare($query);
1141 $sth_set->execute( $newstatus, $itemnumber );
1143 if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1144 CartToShelf( $itemnumber );
1148 =head2 ModReserveAffect
1150 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1152 This function affect an item and a status for a given reserve
1153 The itemnumber parameter is used to find the biblionumber.
1154 with the biblionumber & the borrowernumber, we can affect the itemnumber
1155 to the correct reserve.
1157 if $transferToDo is not set, then the status is set to "Waiting" as well.
1158 otherwise, a transfer is on the way, and the end of the transfer will
1159 take care of the waiting status
1163 sub ModReserveAffect {
1164 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1165 my $dbh = C4::Context->dbh;
1167 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1168 # attached to $itemnumber
1169 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1170 $sth->execute($itemnumber);
1171 my ($biblionumber) = $sth->fetchrow;
1173 # get request - need to find out if item is already
1174 # waiting in order to not send duplicate hold filled notifications
1175 my $request = GetReserveInfo($borrowernumber, $biblionumber);
1176 my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1178 # If we affect a reserve that has to be transfered, don't set to Waiting
1180 if ($transferToDo) {
1186 WHERE borrowernumber = ?
1187 AND biblionumber = ?
1191 # affect the reserve to Waiting as well.
1198 WHERE borrowernumber = ?
1199 AND biblionumber = ?
1202 $sth = $dbh->prepare($query);
1203 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1204 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1206 if ( C4::Context->preference("ReturnToShelvingCart") ) {
1207 CartToShelf( $itemnumber );
1213 =head2 ModReserveCancelAll
1215 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1217 function to cancel reserv,check other reserves, and transfer document if it's necessary
1221 sub ModReserveCancelAll {
1224 my ( $itemnumber, $borrowernumber ) = @_;
1226 #step 1 : cancel the reservation
1227 my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1229 #step 2 launch the subroutine of the others reserves
1230 ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1232 return ( $messages, $nextreservinfo );
1235 =head2 ModReserveMinusPriority
1237 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1239 Reduce the values of queuded list
1243 sub ModReserveMinusPriority {
1244 my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1246 #first step update the value of the first person on reserv
1247 my $dbh = C4::Context->dbh;
1250 SET priority = 0 , itemnumber = ?
1251 WHERE borrowernumber=?
1254 my $sth_upd = $dbh->prepare($query);
1255 $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1256 # second step update all others reservs
1257 _FixPriority($biblionumber, $borrowernumber, '0');
1260 =head2 GetReserveInfo
1262 &GetReserveInfo($borrowernumber,$biblionumber);
1264 Get item and borrower details for a current hold.
1265 Current implementation this query should have a single result.
1269 sub GetReserveInfo {
1270 my ( $borrowernumber, $biblionumber ) = @_;
1271 my $dbh = C4::Context->dbh;
1275 reserves.borrowernumber,
1276 reserves.biblionumber,
1277 reserves.branchcode,
1278 reserves.waitingdate,
1294 items.holdingbranch,
1295 items.itemcallnumber,
1301 LEFT JOIN items USING(itemnumber)
1302 LEFT JOIN borrowers USING(borrowernumber)
1303 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1305 reserves.borrowernumber=?
1306 AND reserves.biblionumber=?";
1307 my $sth = $dbh->prepare($strsth);
1308 $sth->execute($borrowernumber,$biblionumber);
1310 my $data = $sth->fetchrow_hashref;
1315 =head2 IsAvailableForItemLevelRequest
1317 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1319 Checks whether a given item record is available for an
1320 item-level hold request. An item is available if
1322 * it is not lost AND
1323 * it is not damaged AND
1324 * it is not withdrawn AND
1325 * does not have a not for loan value > 0
1327 Whether or not the item is currently on loan is
1328 also checked - if the AllowOnShelfHolds system preference
1329 is ON, an item can be requested even if it is currently
1330 on loan to somebody else. If the system preference
1331 is OFF, an item that is currently checked out cannot
1332 be the target of an item-level hold request.
1334 Note that IsAvailableForItemLevelRequest() does not
1335 check if the staff operator is authorized to place
1336 a request on the item - in particular,
1337 this routine does not check IndependantBranches
1338 and canreservefromotherbranches.
1342 sub IsAvailableForItemLevelRequest {
1343 my $itemnumber = shift;
1345 my $item = GetItem($itemnumber);
1347 # must check the notforloan setting of the itemtype
1348 # FIXME - a lot of places in the code do this
1349 # or something similar - need to be
1351 my $dbh = C4::Context->dbh;
1352 my $notforloan_query;
1353 if (C4::Context->preference('item-level_itypes')) {
1354 $notforloan_query = "SELECT itemtypes.notforloan
1356 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1357 WHERE itemnumber = ?";
1359 $notforloan_query = "SELECT itemtypes.notforloan
1361 JOIN biblioitems USING (biblioitemnumber)
1362 JOIN itemtypes USING (itemtype)
1363 WHERE itemnumber = ?";
1365 my $sth = $dbh->prepare($notforloan_query);
1366 $sth->execute($itemnumber);
1367 my $notforloan_per_itemtype = 0;
1368 if (my ($notforloan) = $sth->fetchrow_array) {
1369 $notforloan_per_itemtype = 1 if $notforloan;
1372 my $available_per_item = 1;
1373 $available_per_item = 0 if $item->{itemlost} or
1374 ( $item->{notforloan} > 0 ) or
1375 ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1376 $item->{wthdrawn} or
1377 $notforloan_per_itemtype;
1380 if (C4::Context->preference('AllowOnShelfHolds')) {
1381 return $available_per_item;
1383 return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W"));
1387 =head2 AlterPriority
1389 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1391 This function changes a reserve's priority up, down, to the top, or to the bottom.
1392 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1397 my ( $where, $borrowernumber, $biblionumber ) = @_;
1399 my $dbh = C4::Context->dbh;
1401 ## Find this reserve
1402 my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1403 $sth->execute( $biblionumber, $borrowernumber );
1404 my $reserve = $sth->fetchrow_hashref();
1407 if ( $where eq 'up' || $where eq 'down' ) {
1409 my $priority = $reserve->{'priority'};
1410 $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1411 _FixPriority( $biblionumber, $borrowernumber, $priority )
1413 } elsif ( $where eq 'top' ) {
1415 _FixPriority( $biblionumber, $borrowernumber, '1' )
1417 } elsif ( $where eq 'bottom' ) {
1419 _FixPriority( $biblionumber, $borrowernumber, '999999' )
1424 =head2 ToggleLowestPriority
1426 ToggleLowestPriority( $borrowernumber, $biblionumber );
1428 This function sets the lowestPriority field to true if is false, and false if it is true.
1432 sub ToggleLowestPriority {
1433 my ( $borrowernumber, $biblionumber ) = @_;
1435 my $dbh = C4::Context->dbh;
1437 my $sth = $dbh->prepare(
1438 "UPDATE reserves SET lowestPriority = NOT lowestPriority
1439 WHERE biblionumber = ?
1440 AND borrowernumber = ?"
1448 _FixPriority( $biblionumber, $borrowernumber, '999999' );
1453 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1455 Only used internally (so don't export it)
1456 Changed how this functions works #
1457 Now just gets an array of reserves in the rank order and updates them with
1458 the array index (+1 as array starts from 0)
1459 and if $rank is supplied will splice item from the array and splice it back in again
1460 in new priority rank
1465 my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1466 my $dbh = C4::Context->dbh;
1467 if ( $rank eq "del" ) {
1468 CancelReserve( $biblio, undef, $borrowernumber );
1470 if ( $rank eq "W" || $rank eq "0" ) {
1472 # make sure priority for waiting or in-transit items is 0
1476 WHERE biblionumber = ?
1477 AND borrowernumber = ?
1478 AND found IN ('W', 'T')
1480 my $sth = $dbh->prepare($query);
1481 $sth->execute( $biblio, $borrowernumber );
1487 # FIXME adding a new security in returned elements for changing priority,
1488 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1489 # This is wrong a waiting reserve has W set
1490 # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1492 SELECT borrowernumber, reservedate, constrainttype
1494 WHERE biblionumber = ?
1495 AND ((found <> 'W' AND found <> 'T') or found is NULL)
1496 ORDER BY priority ASC
1498 my $sth = $dbh->prepare($query);
1499 $sth->execute($biblio);
1500 while ( my $line = $sth->fetchrow_hashref ) {
1501 push( @reservedates, $line );
1502 push( @priority, $line );
1505 # To find the matching index
1507 my $key = -1; # to allow for 0 to be a valid result
1508 for ( $i = 0 ; $i < @priority ; $i++ ) {
1509 if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1510 $key = $i; # save the index
1515 # if index exists in array then move it to new position
1516 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1517 my $new_rank = $rank -
1518 1; # $new_rank is what you want the new index to be in the array
1519 my $moving_item = splice( @priority, $key, 1 );
1520 splice( @priority, $new_rank, 0, $moving_item );
1523 # now fix the priority on those that are left....
1527 WHERE biblionumber = ?
1528 AND borrowernumber = ?
1532 $sth = $dbh->prepare($query);
1533 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1536 $priority[$j]->{'borrowernumber'},
1537 $priority[$j]->{'reservedate'}
1542 $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1545 unless ( $ignoreSetLowestRank ) {
1546 while ( my $res = $sth->fetchrow_hashref() ) {
1547 _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1552 =head2 _Findgroupreserve
1554 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1556 Looks for an item-specific match first, then for a title-level match, returning the
1557 first match found. If neither, then we look for a 3rd kind of match based on
1558 reserve constraints.
1560 TODO: add more explanation about reserve constraints
1562 C<&_Findgroupreserve> returns :
1563 C<@results> is an array of references-to-hash whose keys are mostly
1564 fields from the reserves table of the Koha database, plus
1565 C<biblioitemnumber>.
1569 sub _Findgroupreserve {
1570 my ( $bibitem, $biblio, $itemnumber ) = @_;
1571 my $dbh = C4::Context->dbh;
1573 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1574 # check for exact targetted match
1575 my $item_level_target_query = qq/
1576 SELECT reserves.biblionumber AS biblionumber,
1577 reserves.borrowernumber AS borrowernumber,
1578 reserves.reservedate AS reservedate,
1579 reserves.branchcode AS branchcode,
1580 reserves.cancellationdate AS cancellationdate,
1581 reserves.found AS found,
1582 reserves.reservenotes AS reservenotes,
1583 reserves.priority AS priority,
1584 reserves.timestamp AS timestamp,
1585 biblioitems.biblioitemnumber AS biblioitemnumber,
1586 reserves.itemnumber AS itemnumber
1588 JOIN biblioitems USING (biblionumber)
1589 JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1592 AND item_level_request = 1
1594 AND reservedate <= CURRENT_DATE()
1596 my $sth = $dbh->prepare($item_level_target_query);
1597 $sth->execute($itemnumber);
1599 if ( my $data = $sth->fetchrow_hashref ) {
1600 push( @results, $data );
1602 return @results if @results;
1604 # check for title-level targetted match
1605 my $title_level_target_query = qq/
1606 SELECT reserves.biblionumber AS biblionumber,
1607 reserves.borrowernumber AS borrowernumber,
1608 reserves.reservedate AS reservedate,
1609 reserves.branchcode AS branchcode,
1610 reserves.cancellationdate AS cancellationdate,
1611 reserves.found AS found,
1612 reserves.reservenotes AS reservenotes,
1613 reserves.priority AS priority,
1614 reserves.timestamp AS timestamp,
1615 biblioitems.biblioitemnumber AS biblioitemnumber,
1616 reserves.itemnumber AS itemnumber
1618 JOIN biblioitems USING (biblionumber)
1619 JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1622 AND item_level_request = 0
1623 AND hold_fill_targets.itemnumber = ?
1624 AND reservedate <= CURRENT_DATE()
1626 $sth = $dbh->prepare($title_level_target_query);
1627 $sth->execute($itemnumber);
1629 if ( my $data = $sth->fetchrow_hashref ) {
1630 push( @results, $data );
1632 return @results if @results;
1635 SELECT reserves.biblionumber AS biblionumber,
1636 reserves.borrowernumber AS borrowernumber,
1637 reserves.reservedate AS reservedate,
1638 reserves.waitingdate AS waitingdate,
1639 reserves.branchcode AS branchcode,
1640 reserves.cancellationdate AS cancellationdate,
1641 reserves.found AS found,
1642 reserves.reservenotes AS reservenotes,
1643 reserves.priority AS priority,
1644 reserves.timestamp AS timestamp,
1645 reserveconstraints.biblioitemnumber AS biblioitemnumber,
1646 reserves.itemnumber AS itemnumber
1648 LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1649 WHERE reserves.biblionumber = ?
1650 AND ( ( reserveconstraints.biblioitemnumber = ?
1651 AND reserves.borrowernumber = reserveconstraints.borrowernumber
1652 AND reserves.reservedate = reserveconstraints.reservedate )
1653 OR reserves.constrainttype='a' )
1654 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1655 AND reserves.reservedate <= CURRENT_DATE()
1657 $sth = $dbh->prepare($query);
1658 $sth->execute( $biblio, $bibitem, $itemnumber );
1660 while ( my $data = $sth->fetchrow_hashref ) {
1661 push( @results, $data );
1666 =head2 _koha_notify_reserve
1668 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1670 Sends a notification to the patron that their hold has been filled (through
1671 ModReserveAffect, _not_ ModReserveFill)
1675 sub _koha_notify_reserve {
1676 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1678 my $dbh = C4::Context->dbh;
1679 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1681 # Try to get the borrower's email address
1683 my $which_address = C4::Context->preference('AutoEmailPrimaryAddress');
1684 # If the system preference is set to 'first valid' (value == OFF), look up email address
1685 if ($which_address eq 'OFF') {
1686 $to_address = C4::Members::GetFirstValidEmailAddress( $borrowernumber );
1688 $to_address = $borrower->{$which_address};
1694 if ( $to_address || $borrower->{'smsalertnumber'} ) {
1695 $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
1697 return if ( !defined( $messagingprefs->{'letter_code'} ) );
1698 $letter_code = $messagingprefs->{'letter_code'};
1700 $letter_code = 'HOLD_PRINT';
1704 my $sth = $dbh->prepare("
1707 WHERE borrowernumber = ?
1708 AND biblionumber = ?
1710 $sth->execute( $borrowernumber, $biblionumber );
1711 my $reserve = $sth->fetchrow_hashref;
1712 my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1714 my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1716 my $letter = getletter( 'reserves', $letter_code );
1717 die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1719 C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1720 C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1721 C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1722 C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1724 if ( $reserve->{'itemnumber'} ) {
1725 C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1727 my $today = C4::Dates->new()->output();
1728 $letter->{'title'} =~ s/<<today>>/$today/g;
1729 $letter->{'content'} =~ s/<<today>>/$today/g;
1730 $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1732 if ( $print_mode ) {
1733 C4::Letters::EnqueueLetter( {
1735 borrowernumber => $borrowernumber,
1736 message_transport_type => 'print',
1742 if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1743 # aka, 'email' in ->{'transports'}
1744 C4::Letters::EnqueueLetter(
1745 { letter => $letter,
1746 borrowernumber => $borrowernumber,
1747 message_transport_type => 'email',
1748 from_address => $admin_email_address,
1753 if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1754 C4::Letters::EnqueueLetter(
1755 { letter => $letter,
1756 borrowernumber => $borrowernumber,
1757 message_transport_type => 'sms',
1763 =head2 _ShiftPriorityByDateAndPriority
1765 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1767 This increments the priority of all reserves after the one
1768 with either the lowest date after C<$reservedate>
1769 or the lowest priority after C<$priority>.
1771 It effectively makes room for a new reserve to be inserted with a certain
1772 priority, which is returned.
1774 This is most useful when the reservedate can be set by the user. It allows
1775 the new reserve to be placed before other reserves that have a later
1776 reservedate. Since priority also is set by the form in reserves/request.pl
1777 the sub accounts for that too.
1781 sub _ShiftPriorityByDateAndPriority {
1782 my ( $biblio, $resdate, $new_priority ) = @_;
1784 my $dbh = C4::Context->dbh;
1785 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1786 my $sth = $dbh->prepare( $query );
1787 $sth->execute( $biblio, $resdate, $new_priority );
1788 my $min_priority = $sth->fetchrow;
1789 # if no such matches are found, $new_priority remains as original value
1790 $new_priority = $min_priority if ( $min_priority );
1792 # Shift the priority up by one; works in conjunction with the next SQL statement
1793 $query = "UPDATE reserves
1794 SET priority = priority+1
1795 WHERE biblionumber = ?
1796 AND borrowernumber = ?
1799 my $sth_update = $dbh->prepare( $query );
1801 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1802 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1803 $sth = $dbh->prepare( $query );
1804 $sth->execute( $new_priority, $biblio );
1805 while ( my $row = $sth->fetchrow_hashref ) {
1806 $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1809 return $new_priority; # so the caller knows what priority they wind up receiving
1814 Koha Development Team <http://koha-community.org/>