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 );
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
124 @EXPORT_OK = qw( MergeHolds );
129 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
135 $branch, $borrowernumber, $biblionumber,
136 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
137 $title, $checkitem, $found
140 GetReserveFee($borrowernumber, $biblionumber, $constraint,
142 my $dbh = C4::Context->dbh;
143 my $const = lc substr( $constraint, 0, 1 );
144 $resdate = format_date_in_iso( $resdate ) if ( $resdate );
145 $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
147 $expdate = format_date_in_iso( $expdate );
149 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
151 if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
152 # Make room in reserves for this before those of a later reserve date
153 $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
157 # If the reserv had the waiting status, we had the value of the resdate
158 if ( $found eq 'W' ) {
159 $waitingdate = $resdate;
163 # updates take place here
165 my $nextacctno = &getnextacctno( $borrowernumber );
167 INSERT INTO accountlines
168 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
170 (?,?,now(),?,?,'Res',?)
172 my $usth = $dbh->prepare($query);
173 $usth->execute( $borrowernumber, $nextacctno, $fee,
174 "Reserve Charge - $title", $fee );
180 (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
181 priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
186 my $sth = $dbh->prepare($query);
188 $borrowernumber, $biblionumber, $resdate, $branch,
189 $const, $priority, $notes, $checkitem,
190 $found, $waitingdate, $expdate
193 # Send e-mail to librarian if syspref is active
194 if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
195 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
196 my $biblio = GetBiblioData($biblionumber);
197 my $letter = C4::Letters::getletter( 'reserves', 'HOLDPLACED');
198 my $branchcode = $borrower->{branchcode};
199 my $branch_details = C4::Branch::GetBranchDetail($branchcode);
200 my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
202 my %keys = (%$borrower, %$biblio);
203 foreach my $key (keys %keys) {
204 my $replacefield = "<<$key>>";
205 $letter->{content} =~ s/$replacefield/$keys{$key}/g;
206 $letter->{title} =~ s/$replacefield/$keys{$key}/g;
209 C4::Letters::EnqueueLetter(
211 borrowernumber => $borrowernumber,
212 message_transport_type => 'email',
213 from_address => $admin_email_address,
214 to_address => $admin_email_address,
223 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
225 INSERT INTO reserveconstraints
226 (borrowernumber,biblionumber,reservedate,biblioitemnumber)
230 $sth = $dbh->prepare($query); # keep prepare outside the loop!
231 foreach (@$bibitems) {
232 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
235 return; # FIXME: why not have a useful return value?
238 =head2 GetReservesFromBiblionumber
240 ($count, $title_reserves) = &GetReserves($biblionumber);
242 This function gets the list of reservations for one C<$biblionumber>, returning a count
243 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
247 sub GetReservesFromBiblionumber {
248 my ($biblionumber) = shift or return (0, []);
249 my ($all_dates) = shift;
250 my $dbh = C4::Context->dbh;
252 # Find the desired items in the reserves
255 timestamp AS rtimestamp,
267 WHERE biblionumber = ? ";
268 unless ( $all_dates ) {
269 $query .= "AND reservedate <= CURRENT_DATE()";
271 $query .= "ORDER BY priority";
272 my $sth = $dbh->prepare($query);
273 $sth->execute($biblionumber);
276 while ( my $data = $sth->fetchrow_hashref ) {
278 # FIXME - What is this doing? How do constraints work?
279 if ($data->{constrainttype} eq 'o') {
281 SELECT biblioitemnumber
282 FROM reserveconstraints
283 WHERE biblionumber = ?
284 AND borrowernumber = ?
287 my $csth = $dbh->prepare($query);
288 $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
290 while ( my $bibitemnos = $csth->fetchrow_array ) {
291 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
293 my $count = scalar @bibitemno;
295 # if we have two or more different specific itemtypes
296 # reserved by same person on same day
299 $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense.
300 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
303 # Look up the book we just found.
304 $bdata = GetBiblioItemData( $bibitemno[0] );
306 # Add the results of this latest search to the current
308 # FIXME - An 'each' would probably be more efficient.
309 foreach my $key ( keys %$bdata ) {
310 $data->{$key} = $bdata->{$key};
313 push @results, $data;
315 return ( $#results + 1, \@results );
318 =head2 GetReservesFromItemnumber
320 ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
322 TODO :: Description here
326 sub GetReservesFromItemnumber {
327 my ( $itemnumber, $all_dates ) = @_;
328 my $dbh = C4::Context->dbh;
330 SELECT reservedate,borrowernumber,branchcode
334 unless ( $all_dates ) {
335 $query .= " AND reservedate <= CURRENT_DATE()";
337 my $sth_res = $dbh->prepare($query);
338 $sth_res->execute($itemnumber);
339 my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
340 return ( $reservedate, $borrowernumber, $branchcode );
343 =head2 GetReservesFromBorrowernumber
345 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
351 sub GetReservesFromBorrowernumber {
352 my ( $borrowernumber, $status ) = @_;
353 my $dbh = C4::Context->dbh;
356 $sth = $dbh->prepare("
359 WHERE borrowernumber=?
363 $sth->execute($borrowernumber,$status);
365 $sth = $dbh->prepare("
368 WHERE borrowernumber=?
371 $sth->execute($borrowernumber);
373 my $data = $sth->fetchall_arrayref({});
376 #-------------------------------------------------------------------------------------
377 =head2 CanBookBeReserved
379 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
383 sub CanBookBeReserved{
384 my ($borrowernumber, $biblionumber) = @_;
386 my @items = get_itemnumbers_of($biblionumber);
387 #get items linked via host records
388 my @hostitems = get_hostitemnumbers_of($biblionumber);
390 push (@items,@hostitems);
393 foreach my $item (@items){
394 return 1 if CanItemBeReserved($borrowernumber, $item);
399 =head2 CanItemBeReserved
401 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
403 This function return 1 if an item can be issued by this borrower.
407 sub CanItemBeReserved{
408 my ($borrowernumber, $itemnumber) = @_;
410 my $dbh = C4::Context->dbh;
411 my $allowedreserves = 0;
413 my $controlbranch = C4::Context->preference('ReservesControlBranch');
414 my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
416 # we retrieve borrowers and items informations #
417 my $item = GetItem($itemnumber);
418 my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);
420 # we retrieve user rights on this itemtype and branchcode
421 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
423 WHERE (categorycode in (?,'*') )
424 AND (itemtype IN (?,'*'))
425 AND (branchcode IN (?,'*'))
432 my $querycount ="SELECT
435 LEFT JOIN items USING (itemnumber)
436 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
437 LEFT JOIN borrowers USING (borrowernumber)
438 WHERE borrowernumber = ?
442 my $itemtype = $item->{$itype};
443 my $categorycode = $borrower->{categorycode};
445 my $branchfield = "reserves.branchcode";
447 if( $controlbranch eq "ItemHomeLibrary" ){
448 $branchfield = "items.homebranch";
449 $branchcode = $item->{homebranch};
450 }elsif( $controlbranch eq "PatronLibrary" ){
451 $branchfield = "borrowers.branchcode";
452 $branchcode = $borrower->{branchcode};
456 $sth->execute($categorycode, $itemtype, $branchcode);
457 if(my $rights = $sth->fetchrow_hashref()){
458 $itemtype = $rights->{itemtype};
459 $allowedreserves = $rights->{reservesallowed};
466 $querycount .= "AND $branchfield = ?";
468 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
469 my $sthcount = $dbh->prepare($querycount);
471 if($itemtype eq "*"){
472 $sthcount->execute($borrowernumber, $branchcode);
474 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
477 my $reservecount = "0";
478 if(my $rowcount = $sthcount->fetchrow_hashref()){
479 $reservecount = $rowcount->{count};
482 # we check if it's ok or not
483 if( $reservecount < $allowedreserves ){
489 #--------------------------------------------------------------------------------
490 =head2 GetReserveCount
492 $number = &GetReserveCount($borrowernumber);
494 this function returns the number of reservation for a borrower given on input arg.
498 sub GetReserveCount {
499 my ($borrowernumber) = @_;
501 my $dbh = C4::Context->dbh;
504 SELECT COUNT(*) AS counter
506 WHERE borrowernumber = ?
508 my $sth = $dbh->prepare($query);
509 $sth->execute($borrowernumber);
510 my $row = $sth->fetchrow_hashref;
511 return $row->{counter};
514 =head2 GetOtherReserves
516 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
518 Check queued list of this document and check if this document must be transfered
522 sub GetOtherReserves {
523 my ($itemnumber) = @_;
526 my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
527 if ($checkreserves) {
528 my $iteminfo = GetItem($itemnumber);
529 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
530 $messages->{'transfert'} = $checkreserves->{'branchcode'};
531 #minus priorities of others reservs
532 ModReserveMinusPriority(
534 $checkreserves->{'borrowernumber'},
535 $iteminfo->{'biblionumber'}
538 #launch the subroutine dotransfer
539 C4::Items::ModItemTransfer(
541 $iteminfo->{'holdingbranch'},
542 $checkreserves->{'branchcode'}
547 #step 2b : case of a reservation on the same branch, set the waiting status
549 $messages->{'waiting'} = 1;
550 ModReserveMinusPriority(
552 $checkreserves->{'borrowernumber'},
553 $iteminfo->{'biblionumber'}
555 ModReserveStatus($itemnumber,'W');
558 $nextreservinfo = $checkreserves->{'borrowernumber'};
561 return ( $messages, $nextreservinfo );
566 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
568 Calculate the fee for a reserve
573 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
576 my $dbh = C4::Context->dbh;
577 my $const = lc substr( $constraint, 0, 1 );
579 SELECT * FROM borrowers
580 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
581 WHERE borrowernumber = ?
583 my $sth = $dbh->prepare($query);
584 $sth->execute($borrowernumber);
585 my $data = $sth->fetchrow_hashref;
587 my $fee = $data->{'reservefee'};
588 my $cntitems = @- > $bibitems;
592 # check for items on issue
593 # first find biblioitem records
595 my $sth1 = $dbh->prepare(
596 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
597 WHERE (biblio.biblionumber = ?)"
599 $sth1->execute($biblionumber);
600 while ( my $data1 = $sth1->fetchrow_hashref ) {
601 if ( $const eq "a" ) {
602 push @biblioitems, $data1;
607 while ( $x < $cntitems ) {
608 if ( @$bibitems->{'biblioitemnumber'} ==
609 $data->{'biblioitemnumber'} )
615 if ( $const eq 'o' ) {
617 push @biblioitems, $data1;
622 push @biblioitems, $data1;
628 my $cntitemsfound = @biblioitems;
632 while ( $x < $cntitemsfound ) {
633 my $bitdata = $biblioitems[$x];
634 my $sth2 = $dbh->prepare(
636 WHERE biblioitemnumber = ?"
638 $sth2->execute( $bitdata->{'biblioitemnumber'} );
639 while ( my $itdata = $sth2->fetchrow_hashref ) {
640 my $sth3 = $dbh->prepare(
641 "SELECT * FROM issues
642 WHERE itemnumber = ?"
644 $sth3->execute( $itdata->{'itemnumber'} );
645 if ( my $isdata = $sth3->fetchrow_hashref ) {
653 if ( $allissued == 0 ) {
655 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
656 $rsth->execute($biblionumber);
657 if ( my $rdata = $rsth->fetchrow_hashref ) {
667 =head2 GetReservesToBranch
669 @transreserv = GetReservesToBranch( $frombranch );
671 Get reserve list for a given branch
675 sub GetReservesToBranch {
676 my ( $frombranch ) = @_;
677 my $dbh = C4::Context->dbh;
678 my $sth = $dbh->prepare(
679 "SELECT borrowernumber,reservedate,itemnumber,timestamp
684 $sth->execute( $frombranch );
687 while ( my $data = $sth->fetchrow_hashref ) {
688 $transreserv[$i] = $data;
691 return (@transreserv);
694 =head2 GetReservesForBranch
696 @transreserv = GetReservesForBranch($frombranch);
700 sub GetReservesForBranch {
701 my ($frombranch) = @_;
702 my $dbh = C4::Context->dbh;
703 my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
708 $query .= " AND branchcode=? ";
710 $query .= "ORDER BY waitingdate" ;
711 my $sth = $dbh->prepare($query);
713 $sth->execute($frombranch);
720 while ( my $data = $sth->fetchrow_hashref ) {
721 $transreserv[$i] = $data;
724 return (@transreserv);
727 sub GetReserveStatus {
728 my ($itemnumber) = @_;
730 my $dbh = C4::Context->dbh;
732 my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
734 $itemstatus->execute($itemnumber);
735 my ($found) = $itemstatus->fetchrow_array;
741 ($status, $reserve) = &CheckReserves($itemnumber);
742 ($status, $reserve) = &CheckReserves(undef, $barcode);
744 Find a book in the reserves.
746 C<$itemnumber> is the book's item number.
748 As I understand it, C<&CheckReserves> looks for the given item in the
749 reserves. If it is found, that's a match, and C<$status> is set to
752 Otherwise, it finds the most important item in the reserves with the
753 same biblio number as this book (I'm not clear on this) and returns it
754 with C<$status> set to C<Reserved>.
756 C<&CheckReserves> returns a two-element list:
758 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
760 C<$reserve> is the reserve item that matched. It is a
761 reference-to-hash whose keys are mostly the fields of the reserves
762 table in the Koha database.
766 our $_check_reserves;
769 my ( $item, $barcode ) = @_;
771 if ( defined $_check_reserves->{"$item $barcode"} ) {
772 warn "XXX _check_reserves $item $barcode\n";
773 return @{ $_check_reserves->{"$item $barcode"} };
775 $_check_reserves->{"$item $barcode"} = [ 0,0 ]; # default
777 my $dbh = C4::Context->dbh;
780 if (C4::Context->preference('item-level_itypes')){
782 SELECT items.biblionumber,
783 items.biblioitemnumber,
784 itemtypes.notforloan,
785 items.notforloan AS itemnotforloan,
788 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
789 LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype
794 SELECT items.biblionumber,
795 items.biblioitemnumber,
796 itemtypes.notforloan,
797 items.notforloan AS itemnotforloan,
800 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
801 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
806 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
807 $sth->execute($item);
810 $sth = $dbh->prepare("$select WHERE barcode = ?");
811 $sth->execute($barcode);
813 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
814 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
816 return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
818 # if item is not for loan it cannot be reserved either.....
819 # execpt where items.notforloan < 0 : This indicates the item is holdable.
820 return ( 0, 0 ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
822 # Find this item in the reserves
823 my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
825 # $priority and $highest are used to find the most important item
826 # in the list returned by &_Findgroupreserve. (The lower $priority,
827 # the more important the item.)
828 # $highest is the most important item we've seen so far.
830 if (scalar @reserves) {
831 my $priority = 10000000;
832 foreach my $res (@reserves) {
833 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
834 return ( "Waiting", $res ); # Found it
836 # See if this item is more important than what we've got so far
837 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
838 my $borrowerinfo=C4::Members::GetMember(borrowernumber => $res->{'borrowernumber'});
839 my $iteminfo=C4::Items::GetItem($itemnumber);
840 my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo);
841 my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
842 next if ($branchitemrule->{'holdallowed'} == 0);
843 next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
844 $priority = $res->{'priority'};
851 # If we get this far, then no exact match was found.
852 # We return the most important (i.e. next) reservation.
854 $highest->{'itemnumber'} = $item;
855 $_check_reserves->{"item $barcode"} = [ "Reserved", $highest ]; # XXX cache
856 return ( "Reserved", $highest );
863 =head2 CancelExpiredReserves
865 CancelExpiredReserves();
867 Cancels all reserves with an expiration date from before today.
871 sub CancelExpiredReserves {
873 my $dbh = C4::Context->dbh;
874 my $sth = $dbh->prepare( "
875 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
876 AND expirationdate IS NOT NULL
880 while ( my $res = $sth->fetchrow_hashref() ) {
881 CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
888 &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
892 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
893 cancel, but not both: if both are given, C<&CancelReserve> does
896 C<$borrowernumber> is the borrower number of the patron on whose
897 behalf the book was reserved.
899 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
900 priorities of the other people who are waiting on the book.
905 my ( $biblio, $item, $borr ) = @_;
906 my $dbh = C4::Context->dbh;
907 if ( $item and $borr ) {
908 # removing a waiting reserve record....
909 # update the database...
912 SET cancellationdate = now(),
916 AND borrowernumber = ?
918 my $sth = $dbh->prepare($query);
919 $sth->execute( $item, $borr );
922 INSERT INTO old_reserves
923 SELECT * FROM reserves
925 AND borrowernumber = ?
927 $sth = $dbh->prepare($query);
928 $sth->execute( $item, $borr );
932 AND borrowernumber = ?
934 $sth = $dbh->prepare($query);
935 $sth->execute( $item, $borr );
938 # removing a reserve record....
939 # get the prioritiy on this record....
942 SELECT priority FROM reserves
943 WHERE biblionumber = ?
944 AND borrowernumber = ?
945 AND cancellationdate IS NULL
946 AND itemnumber IS NULL
948 my $sth = $dbh->prepare($query);
949 $sth->execute( $biblio, $borr );
950 ($priority) = $sth->fetchrow_array;
954 SET cancellationdate = now(),
957 WHERE biblionumber = ?
958 AND borrowernumber = ?
961 # update the database, removing the record...
962 $sth = $dbh->prepare($query);
963 $sth->execute( $biblio, $borr );
967 INSERT INTO old_reserves
968 SELECT * FROM reserves
969 WHERE biblionumber = ?
970 AND borrowernumber = ?
972 $sth = $dbh->prepare($query);
973 $sth->execute( $biblio, $borr );
977 WHERE biblionumber = ?
978 AND borrowernumber = ?
980 $sth = $dbh->prepare($query);
981 $sth->execute( $biblio, $borr );
983 # now fix the priority on the others....
984 _FixPriority( $biblio, $borr );
990 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
992 Change a hold request's priority or cancel it.
994 C<$rank> specifies the effect of the change. If C<$rank>
995 is 'W' or 'n', nothing happens. This corresponds to leaving a
996 request alone when changing its priority in the holds queue
999 If C<$rank> is 'del', the hold request is cancelled.
1001 If C<$rank> is an integer greater than zero, the priority of
1002 the request is set to that value. Since priority != 0 means
1003 that the item is not waiting on the hold shelf, setting the
1004 priority to a non-zero value also sets the request's found
1005 status and waiting date to NULL.
1007 The optional C<$itemnumber> parameter is used only when
1008 C<$rank> is a non-zero integer; if supplied, the itemnumber
1009 of the hold request is set accordingly; if omitted, the itemnumber
1012 B<FIXME:> Note that the forgoing can have the effect of causing
1013 item-level hold requests to turn into title-level requests. This
1014 will be fixed once reserves has separate columns for requested
1015 itemnumber and supplying itemnumber.
1020 #subroutine to update a reserve
1021 my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
1022 return if $rank eq "W";
1023 return if $rank eq "n";
1024 my $dbh = C4::Context->dbh;
1025 if ( $rank eq "del" ) {
1028 SET cancellationdate=now()
1029 WHERE biblionumber = ?
1030 AND borrowernumber = ?
1032 my $sth = $dbh->prepare($query);
1033 $sth->execute( $biblio, $borrower );
1036 INSERT INTO old_reserves
1039 WHERE biblionumber = ?
1040 AND borrowernumber = ?
1042 $sth = $dbh->prepare($query);
1043 $sth->execute( $biblio, $borrower );
1045 DELETE FROM reserves
1046 WHERE biblionumber = ?
1047 AND borrowernumber = ?
1049 $sth = $dbh->prepare($query);
1050 $sth->execute( $biblio, $borrower );
1053 elsif ($rank =~ /^\d+/ and $rank > 0) {
1055 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1056 WHERE biblionumber = ?
1057 AND borrowernumber = ?
1059 my $sth = $dbh->prepare($query);
1060 $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1062 _FixPriority( $biblio, $borrower, $rank);
1066 =head2 ModReserveFill
1068 &ModReserveFill($reserve);
1070 Fill a reserve. If I understand this correctly, this means that the
1071 reserved book has been found and given to the patron who reserved it.
1073 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1074 whose keys are fields from the reserves table in the Koha database.
1078 sub ModReserveFill {
1080 my $dbh = C4::Context->dbh;
1081 # fill in a reserve record....
1082 my $biblionumber = $res->{'biblionumber'};
1083 my $borrowernumber = $res->{'borrowernumber'};
1084 my $resdate = $res->{'reservedate'};
1086 # get the priority on this record....
1088 my $query = "SELECT priority
1090 WHERE biblionumber = ?
1091 AND borrowernumber = ?
1092 AND reservedate = ?";
1093 my $sth = $dbh->prepare($query);
1094 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1095 ($priority) = $sth->fetchrow_array;
1098 # update the database...
1099 $query = "UPDATE reserves
1102 WHERE biblionumber = ?
1104 AND borrowernumber = ?
1106 $sth = $dbh->prepare($query);
1107 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1110 # move to old_reserves
1111 $query = "INSERT INTO old_reserves
1112 SELECT * FROM reserves
1113 WHERE biblionumber = ?
1115 AND borrowernumber = ?
1117 $sth = $dbh->prepare($query);
1118 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1119 $query = "DELETE FROM reserves
1120 WHERE biblionumber = ?
1122 AND borrowernumber = ?
1124 $sth = $dbh->prepare($query);
1125 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1127 # now fix the priority on the others (if the priority wasn't
1128 # already sorted!)....
1129 unless ( $priority == 0 ) {
1130 _FixPriority( $biblionumber, $borrowernumber );
1134 =head2 ModReserveStatus
1136 &ModReserveStatus($itemnumber, $newstatus);
1138 Update the reserve status for the active (priority=0) reserve.
1140 $itemnumber is the itemnumber the reserve is on
1142 $newstatus is the new status.
1146 sub ModReserveStatus {
1148 #first : check if we have a reservation for this item .
1149 my ($itemnumber, $newstatus) = @_;
1150 my $dbh = C4::Context->dbh;
1151 my $query = " UPDATE reserves
1152 SET found=?,waitingdate = now()
1157 my $sth_set = $dbh->prepare($query);
1158 $sth_set->execute( $newstatus, $itemnumber );
1160 if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1161 CartToShelf( $itemnumber );
1165 =head2 ModReserveAffect
1167 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1169 This function affect an item and a status for a given reserve
1170 The itemnumber parameter is used to find the biblionumber.
1171 with the biblionumber & the borrowernumber, we can affect the itemnumber
1172 to the correct reserve.
1174 if $transferToDo is not set, then the status is set to "Waiting" as well.
1175 otherwise, a transfer is on the way, and the end of the transfer will
1176 take care of the waiting status
1180 sub ModReserveAffect {
1181 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1182 my $dbh = C4::Context->dbh;
1184 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1185 # attached to $itemnumber
1186 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1187 $sth->execute($itemnumber);
1188 my ($biblionumber) = $sth->fetchrow;
1190 # get request - need to find out if item is already
1191 # waiting in order to not send duplicate hold filled notifications
1192 my $request = GetReserveInfo($borrowernumber, $biblionumber);
1193 my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1195 # If we affect a reserve that has to be transfered, don't set to Waiting
1197 if ($transferToDo) {
1203 WHERE borrowernumber = ?
1204 AND biblionumber = ?
1208 # affect the reserve to Waiting as well.
1215 WHERE borrowernumber = ?
1216 AND biblionumber = ?
1219 $sth = $dbh->prepare($query);
1220 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1221 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1223 if ( C4::Context->preference("ReturnToShelvingCart") ) {
1224 CartToShelf( $itemnumber );
1230 =head2 ModReserveCancelAll
1232 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1234 function to cancel reserv,check other reserves, and transfer document if it's necessary
1238 sub ModReserveCancelAll {
1241 my ( $itemnumber, $borrowernumber ) = @_;
1243 #step 1 : cancel the reservation
1244 my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1246 #step 2 launch the subroutine of the others reserves
1247 ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1249 return ( $messages, $nextreservinfo );
1252 =head2 ModReserveMinusPriority
1254 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1256 Reduce the values of queuded list
1260 sub ModReserveMinusPriority {
1261 my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1263 #first step update the value of the first person on reserv
1264 my $dbh = C4::Context->dbh;
1267 SET priority = 0 , itemnumber = ?
1268 WHERE borrowernumber=?
1271 my $sth_upd = $dbh->prepare($query);
1272 $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1273 # second step update all others reservs
1274 _FixPriority($biblionumber, $borrowernumber, '0');
1277 =head2 GetReserveInfo
1279 &GetReserveInfo($borrowernumber,$biblionumber);
1281 Get item and borrower details for a current hold.
1282 Current implementation this query should have a single result.
1286 sub GetReserveInfo {
1287 my ( $borrowernumber, $biblionumber ) = @_;
1288 my $dbh = C4::Context->dbh;
1292 reserves.borrowernumber,
1293 reserves.biblionumber,
1294 reserves.branchcode,
1295 reserves.waitingdate,
1311 items.holdingbranch,
1312 items.itemcallnumber,
1318 LEFT JOIN items USING(itemnumber)
1319 LEFT JOIN borrowers USING(borrowernumber)
1320 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1322 reserves.borrowernumber=?
1323 AND reserves.biblionumber=?";
1324 my $sth = $dbh->prepare($strsth);
1325 $sth->execute($borrowernumber,$biblionumber);
1327 my $data = $sth->fetchrow_hashref;
1332 =head2 IsAvailableForItemLevelRequest
1334 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1336 Checks whether a given item record is available for an
1337 item-level hold request. An item is available if
1339 * it is not lost AND
1340 * it is not damaged AND
1341 * it is not withdrawn AND
1342 * does not have a not for loan value > 0
1344 Whether or not the item is currently on loan is
1345 also checked - if the AllowOnShelfHolds system preference
1346 is ON, an item can be requested even if it is currently
1347 on loan to somebody else. If the system preference
1348 is OFF, an item that is currently checked out cannot
1349 be the target of an item-level hold request.
1351 Note that IsAvailableForItemLevelRequest() does not
1352 check if the staff operator is authorized to place
1353 a request on the item - in particular,
1354 this routine does not check IndependantBranches
1355 and canreservefromotherbranches.
1359 sub IsAvailableForItemLevelRequest {
1360 my $itemnumber = shift;
1362 my $item = GetItem($itemnumber);
1364 # must check the notforloan setting of the itemtype
1365 # FIXME - a lot of places in the code do this
1366 # or something similar - need to be
1368 my $dbh = C4::Context->dbh;
1369 my $notforloan_query;
1370 if (C4::Context->preference('item-level_itypes')) {
1371 $notforloan_query = "SELECT itemtypes.notforloan
1373 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1374 WHERE itemnumber = ?";
1376 $notforloan_query = "SELECT itemtypes.notforloan
1378 JOIN biblioitems USING (biblioitemnumber)
1379 JOIN itemtypes USING (itemtype)
1380 WHERE itemnumber = ?";
1382 my $sth = $dbh->prepare($notforloan_query);
1383 $sth->execute($itemnumber);
1384 my $notforloan_per_itemtype = 0;
1385 if (my ($notforloan) = $sth->fetchrow_array) {
1386 $notforloan_per_itemtype = 1 if $notforloan;
1389 my $available_per_item = 1;
1390 $available_per_item = 0 if $item->{itemlost} or
1391 ( $item->{notforloan} > 0 ) or
1392 ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1393 $item->{wthdrawn} or
1394 $notforloan_per_itemtype;
1397 if (C4::Context->preference('AllowOnShelfHolds')) {
1398 return $available_per_item;
1400 return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W"));
1404 =head2 AlterPriority
1406 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1408 This function changes a reserve's priority up, down, to the top, or to the bottom.
1409 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1414 my ( $where, $borrowernumber, $biblionumber ) = @_;
1416 my $dbh = C4::Context->dbh;
1418 ## Find this reserve
1419 my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1420 $sth->execute( $biblionumber, $borrowernumber );
1421 my $reserve = $sth->fetchrow_hashref();
1424 if ( $where eq 'up' || $where eq 'down' ) {
1426 my $priority = $reserve->{'priority'};
1427 $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1428 _FixPriority( $biblionumber, $borrowernumber, $priority )
1430 } elsif ( $where eq 'top' ) {
1432 _FixPriority( $biblionumber, $borrowernumber, '1' )
1434 } elsif ( $where eq 'bottom' ) {
1436 _FixPriority( $biblionumber, $borrowernumber, '999999' )
1441 =head2 ToggleLowestPriority
1443 ToggleLowestPriority( $borrowernumber, $biblionumber );
1445 This function sets the lowestPriority field to true if is false, and false if it is true.
1449 sub ToggleLowestPriority {
1450 my ( $borrowernumber, $biblionumber ) = @_;
1452 my $dbh = C4::Context->dbh;
1454 my $sth = $dbh->prepare(
1455 "UPDATE reserves SET lowestPriority = NOT lowestPriority
1456 WHERE biblionumber = ?
1457 AND borrowernumber = ?"
1465 _FixPriority( $biblionumber, $borrowernumber, '999999' );
1470 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1472 Only used internally (so don't export it)
1473 Changed how this functions works #
1474 Now just gets an array of reserves in the rank order and updates them with
1475 the array index (+1 as array starts from 0)
1476 and if $rank is supplied will splice item from the array and splice it back in again
1477 in new priority rank
1482 my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1483 my $dbh = C4::Context->dbh;
1484 if ( $rank eq "del" ) {
1485 CancelReserve( $biblio, undef, $borrowernumber );
1487 if ( $rank eq "W" || $rank eq "0" ) {
1489 # make sure priority for waiting or in-transit items is 0
1493 WHERE biblionumber = ?
1494 AND borrowernumber = ?
1495 AND found IN ('W', 'T')
1497 my $sth = $dbh->prepare($query);
1498 $sth->execute( $biblio, $borrowernumber );
1504 # FIXME adding a new security in returned elements for changing priority,
1505 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1506 # This is wrong a waiting reserve has W set
1507 # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1509 SELECT borrowernumber, reservedate, constrainttype
1511 WHERE biblionumber = ?
1512 AND ((found <> 'W' AND found <> 'T') or found is NULL)
1513 ORDER BY priority ASC
1515 my $sth = $dbh->prepare($query);
1516 $sth->execute($biblio);
1517 while ( my $line = $sth->fetchrow_hashref ) {
1518 push( @reservedates, $line );
1519 push( @priority, $line );
1522 # To find the matching index
1524 my $key = -1; # to allow for 0 to be a valid result
1525 for ( $i = 0 ; $i < @priority ; $i++ ) {
1526 if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1527 $key = $i; # save the index
1532 # if index exists in array then move it to new position
1533 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1534 my $new_rank = $rank -
1535 1; # $new_rank is what you want the new index to be in the array
1536 my $moving_item = splice( @priority, $key, 1 );
1537 splice( @priority, $new_rank, 0, $moving_item );
1540 # now fix the priority on those that are left....
1544 WHERE biblionumber = ?
1545 AND borrowernumber = ?
1549 $sth = $dbh->prepare($query);
1550 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1553 $priority[$j]->{'borrowernumber'},
1554 $priority[$j]->{'reservedate'}
1559 $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1562 unless ( $ignoreSetLowestRank ) {
1563 while ( my $res = $sth->fetchrow_hashref() ) {
1564 _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1569 =head2 _Findgroupreserve
1571 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1573 Looks for an item-specific match first, then for a title-level match, returning the
1574 first match found. If neither, then we look for a 3rd kind of match based on
1575 reserve constraints.
1577 TODO: add more explanation about reserve constraints
1579 C<&_Findgroupreserve> returns :
1580 C<@results> is an array of references-to-hash whose keys are mostly
1581 fields from the reserves table of the Koha database, plus
1582 C<biblioitemnumber>.
1586 sub _Findgroupreserve {
1587 my ( $bibitem, $biblio, $itemnumber ) = @_;
1588 my $dbh = C4::Context->dbh;
1590 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1591 # check for exact targetted match
1592 my $item_level_target_query = qq/
1593 SELECT reserves.biblionumber AS biblionumber,
1594 reserves.borrowernumber AS borrowernumber,
1595 reserves.reservedate AS reservedate,
1596 reserves.branchcode AS branchcode,
1597 reserves.cancellationdate AS cancellationdate,
1598 reserves.found AS found,
1599 reserves.reservenotes AS reservenotes,
1600 reserves.priority AS priority,
1601 reserves.timestamp AS timestamp,
1602 biblioitems.biblioitemnumber AS biblioitemnumber,
1603 reserves.itemnumber AS itemnumber
1605 JOIN biblioitems USING (biblionumber)
1606 JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1609 AND item_level_request = 1
1611 AND reservedate <= CURRENT_DATE()
1613 my $sth = $dbh->prepare($item_level_target_query);
1614 $sth->execute($itemnumber);
1616 if ( my $data = $sth->fetchrow_hashref ) {
1617 push( @results, $data );
1619 return @results if @results;
1621 # check for title-level targetted match
1622 my $title_level_target_query = qq/
1623 SELECT reserves.biblionumber AS biblionumber,
1624 reserves.borrowernumber AS borrowernumber,
1625 reserves.reservedate AS reservedate,
1626 reserves.branchcode AS branchcode,
1627 reserves.cancellationdate AS cancellationdate,
1628 reserves.found AS found,
1629 reserves.reservenotes AS reservenotes,
1630 reserves.priority AS priority,
1631 reserves.timestamp AS timestamp,
1632 biblioitems.biblioitemnumber AS biblioitemnumber,
1633 reserves.itemnumber AS itemnumber
1635 JOIN biblioitems USING (biblionumber)
1636 JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1639 AND item_level_request = 0
1640 AND hold_fill_targets.itemnumber = ?
1641 AND reservedate <= CURRENT_DATE()
1643 $sth = $dbh->prepare($title_level_target_query);
1644 $sth->execute($itemnumber);
1646 if ( my $data = $sth->fetchrow_hashref ) {
1647 push( @results, $data );
1649 return @results if @results;
1652 SELECT reserves.biblionumber AS biblionumber,
1653 reserves.borrowernumber AS borrowernumber,
1654 reserves.reservedate AS reservedate,
1655 reserves.waitingdate AS waitingdate,
1656 reserves.branchcode AS branchcode,
1657 reserves.cancellationdate AS cancellationdate,
1658 reserves.found AS found,
1659 reserves.reservenotes AS reservenotes,
1660 reserves.priority AS priority,
1661 reserves.timestamp AS timestamp,
1662 reserveconstraints.biblioitemnumber AS biblioitemnumber,
1663 reserves.itemnumber AS itemnumber
1665 LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1666 WHERE reserves.biblionumber = ?
1667 AND ( ( reserveconstraints.biblioitemnumber = ?
1668 AND reserves.borrowernumber = reserveconstraints.borrowernumber
1669 AND reserves.reservedate = reserveconstraints.reservedate )
1670 OR reserves.constrainttype='a' )
1671 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1672 AND reserves.reservedate <= CURRENT_DATE()
1674 $sth = $dbh->prepare($query);
1675 $sth->execute( $biblio, $bibitem, $itemnumber );
1677 while ( my $data = $sth->fetchrow_hashref ) {
1678 push( @results, $data );
1683 =head2 _koha_notify_reserve
1685 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1687 Sends a notification to the patron that their hold has been filled (through
1688 ModReserveAffect, _not_ ModReserveFill)
1692 sub _koha_notify_reserve {
1693 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1695 my $dbh = C4::Context->dbh;
1696 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1698 # Try to get the borrower's email address
1700 my $which_address = C4::Context->preference('AutoEmailPrimaryAddress');
1701 # If the system preference is set to 'first valid' (value == OFF), look up email address
1702 if ($which_address eq 'OFF') {
1703 $to_address = C4::Members::GetFirstValidEmailAddress( $borrowernumber );
1705 $to_address = $borrower->{$which_address};
1711 if ( $to_address || $borrower->{'smsalertnumber'} ) {
1712 $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
1714 return if ( !defined( $messagingprefs->{'letter_code'} ) );
1715 $letter_code = $messagingprefs->{'letter_code'};
1717 $letter_code = 'HOLD_PRINT';
1721 my $sth = $dbh->prepare("
1724 WHERE borrowernumber = ?
1725 AND biblionumber = ?
1727 $sth->execute( $borrowernumber, $biblionumber );
1728 my $reserve = $sth->fetchrow_hashref;
1729 my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1731 my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1733 my $letter = getletter( 'reserves', $letter_code );
1734 die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1736 C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1737 C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1738 C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1739 C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1741 if ( $reserve->{'itemnumber'} ) {
1742 C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1744 my $today = C4::Dates->new()->output();
1745 $letter->{'title'} =~ s/<<today>>/$today/g;
1746 $letter->{'content'} =~ s/<<today>>/$today/g;
1747 $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1749 if ( $print_mode ) {
1750 C4::Letters::EnqueueLetter( {
1752 borrowernumber => $borrowernumber,
1753 message_transport_type => 'print',
1759 if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1760 # aka, 'email' in ->{'transports'}
1761 C4::Letters::EnqueueLetter(
1762 { letter => $letter,
1763 borrowernumber => $borrowernumber,
1764 message_transport_type => 'email',
1765 from_address => $admin_email_address,
1770 if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1771 C4::Letters::EnqueueLetter(
1772 { letter => $letter,
1773 borrowernumber => $borrowernumber,
1774 message_transport_type => 'sms',
1780 =head2 _ShiftPriorityByDateAndPriority
1782 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1784 This increments the priority of all reserves after the one
1785 with either the lowest date after C<$reservedate>
1786 or the lowest priority after C<$priority>.
1788 It effectively makes room for a new reserve to be inserted with a certain
1789 priority, which is returned.
1791 This is most useful when the reservedate can be set by the user. It allows
1792 the new reserve to be placed before other reserves that have a later
1793 reservedate. Since priority also is set by the form in reserves/request.pl
1794 the sub accounts for that too.
1798 sub _ShiftPriorityByDateAndPriority {
1799 my ( $biblio, $resdate, $new_priority ) = @_;
1801 my $dbh = C4::Context->dbh;
1802 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1803 my $sth = $dbh->prepare( $query );
1804 $sth->execute( $biblio, $resdate, $new_priority );
1805 my $min_priority = $sth->fetchrow;
1806 # if no such matches are found, $new_priority remains as original value
1807 $new_priority = $min_priority if ( $min_priority );
1809 # Shift the priority up by one; works in conjunction with the next SQL statement
1810 $query = "UPDATE reserves
1811 SET priority = priority+1
1812 WHERE biblionumber = ?
1813 AND borrowernumber = ?
1816 my $sth_update = $dbh->prepare( $query );
1818 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1819 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1820 $sth = $dbh->prepare( $query );
1821 $sth->execute( $new_priority, $biblio );
1822 while ( my $row = $sth->fetchrow_hashref ) {
1823 $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1826 return $new_priority; # so the caller knows what priority they wind up receiving
1831 MergeHolds($dbh,$to_biblio, $from_biblio);
1833 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
1838 my ( $dbh, $to_biblio, $from_biblio ) = @_;
1839 my $sth = $dbh->prepare(
1840 "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
1842 $sth->execute($from_biblio);
1843 if ( my $data = $sth->fetchrow_hashref() ) {
1845 # holds exist on old record, if not we don't need to do anything
1846 $sth = $dbh->prepare(
1847 "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
1848 $sth->execute( $to_biblio, $from_biblio );
1851 # don't reorder those already waiting
1853 $sth = $dbh->prepare(
1854 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
1856 my $upd_sth = $dbh->prepare(
1857 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
1858 AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
1860 $sth->execute( $to_biblio, 'W', 'T' );
1862 while ( my $reserve = $sth->fetchrow_hashref() ) {
1864 $priority, $to_biblio,
1865 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
1866 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
1876 Koha Development Team <http://koha-community.org/>