3 # Copyright 2000-2002 Katipo Communications
4 # 2006 SAN Ouest Provence
5 # 2007-2010 BibLibre Paul POULAIN
8 # This file is part of Koha.
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with Koha; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #use warnings; FIXME - Bug 2505
33 # for _koha_notify_reserve
34 use C4
::Members
::Messaging
;
37 use C4
::Branch
qw( GetBranchDetail );
38 use C4
::Dates
qw( format_date_in_iso );
42 use List
::MoreUtils
qw( firstidx );
44 use vars
qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
48 C4::Reserves - Koha functions for dealing with reservation.
56 This modules provides somes functions to deal with reservations.
58 Reserves are stored in reserves table.
59 The following columns contains important values :
60 - priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
61 =0 : then the reserve is being dealed
62 - found : NULL : means the patron requested the 1st available, and we haven't choosen the item
63 T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
64 W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
65 F(inished) : the reserve has been completed, and is done
66 - itemnumber : empty : the reserve is still unaffected to an item
67 filled: the reserve is attached to an item
68 The complete workflow is :
69 ==== 1st use case ====
70 patron request a document, 1st available : P >0, F=NULL, I=NULL
71 a library having it run "transfertodo", and clic on the list
72 if there is no transfer to do, the reserve waiting
73 patron can pick it up P =0, F=W, I=filled
74 if there is a transfer to do, write in branchtransfer P =0, F=T, I=filled
75 The pickup library recieve the book, it check in P =0, F=W, I=filled
76 The patron borrow the book P =0, F=F, I=filled
78 ==== 2nd use case ====
79 patron requests a document, a given item,
80 If pickup is holding branch P =0, F=W, I=filled
81 If transfer needed, write in branchtransfer P =0, F=T, I=filled
82 The pickup library receive the book, it checks it in P =0, F=W, I=filled
83 The patron borrow the book P =0, F=F, I=filled
90 # set the version for version checking
91 $VERSION = 3.07.00.049;
98 &GetReservesFromItemnumber
99 &GetReservesFromBiblionumber
100 &GetReservesFromBorrowernumber
101 &GetReservesForBranch
115 &ModReserveMinusPriority
122 &CancelExpiredReserves
124 &AutoUnsuspendReserves
126 &IsAvailableForItemLevelRequest
129 &ToggleLowestPriority
135 @EXPORT_OK = qw( MergeHolds );
140 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
146 $branch, $borrowernumber, $biblionumber,
147 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
148 $title, $checkitem, $found
151 GetReserveFee
($borrowernumber, $biblionumber, $constraint,
153 my $dbh = C4
::Context
->dbh;
154 my $const = lc substr( $constraint, 0, 1 );
155 $resdate = format_date_in_iso
( $resdate ) if ( $resdate );
156 $resdate = C4
::Dates
->today( 'iso' ) unless ( $resdate );
158 $expdate = format_date_in_iso
( $expdate );
160 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
162 if ( C4
::Context
->preference( 'AllowHoldDateInFuture' ) ) {
163 # Make room in reserves for this before those of a later reserve date
164 $priority = _ShiftPriorityByDateAndPriority
( $biblionumber, $resdate, $priority );
168 # If the reserv had the waiting status, we had the value of the resdate
169 if ( $found eq 'W' ) {
170 $waitingdate = $resdate;
174 # updates take place here
176 my $nextacctno = &getnextacctno
( $borrowernumber );
178 INSERT INTO accountlines
179 (borrowernumber
,accountno
,date
,amount
,description
,accounttype
,amountoutstanding
)
181 (?
,?
,now
(),?
,?
,'Res',?
)
183 my $usth = $dbh->prepare($query);
184 $usth->execute( $borrowernumber, $nextacctno, $fee,
185 "Reserve Charge - $title", $fee );
191 (borrowernumber
,biblionumber
,reservedate
,branchcode
,constrainttype
,
192 priority
,reservenotes
,itemnumber
,found
,waitingdate
,expirationdate
)
197 my $sth = $dbh->prepare($query);
199 $borrowernumber, $biblionumber, $resdate, $branch,
200 $const, $priority, $notes, $checkitem,
201 $found, $waitingdate, $expdate
204 # Send e-mail to librarian if syspref is active
205 if(C4
::Context
->preference("emailLibrarianWhenHoldIsPlaced")){
206 my $borrower = C4
::Members
::GetMember
(borrowernumber
=> $borrowernumber);
207 my $branch_details = C4
::Branch
::GetBranchDetail
($borrower->{branchcode
});
208 if ( my $letter = C4
::Letters
::GetPreparedLetter
(
209 module
=> 'reserves',
210 letter_code
=> 'HOLDPLACED',
211 branchcode
=> $branch,
213 'branches' => $branch_details,
214 'borrowers' => $borrower,
215 'biblio' => $biblionumber,
216 'items' => $checkitem,
220 my $admin_email_address =$branch_details->{'branchemail'} || C4
::Context
->preference('KohaAdminEmailAddress');
222 C4
::Letters
::EnqueueLetter
(
224 borrowernumber
=> $borrowernumber,
225 message_transport_type
=> 'email',
226 from_address
=> $admin_email_address,
227 to_address
=> $admin_email_address,
234 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
236 INSERT INTO reserveconstraints
237 (borrowernumber
,biblionumber
,reservedate
,biblioitemnumber
)
241 $sth = $dbh->prepare($query); # keep prepare outside the loop!
242 foreach (@
$bibitems) {
243 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
246 return; # FIXME: why not have a useful return value?
251 $res = GetReserve( $reserve_id );
256 my ($reserve_id) = @_;
258 my $dbh = C4
::Context
->dbh;
259 my $query = "SELECT * FROM reserves WHERE reserve_id = ?";
260 my $sth = $dbh->prepare( $query );
261 $sth->execute( $reserve_id );
262 my $res = $sth->fetchrow_hashref();
266 =head2 GetReservesFromBiblionumber
268 ($count, $title_reserves) = GetReservesFromBiblionumber($biblionumber);
270 This function gets the list of reservations for one C<$biblionumber>, returning a count
271 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
275 sub GetReservesFromBiblionumber
{
276 my ($biblionumber) = shift or return (0, []);
277 my ($all_dates) = shift;
278 my $dbh = C4
::Context
->dbh;
280 # Find the desired items in the reserves
284 timestamp AS rtimestamp,
298 WHERE biblionumber = ? ";
299 unless ( $all_dates ) {
300 $query .= "AND reservedate <= CURRENT_DATE()";
302 $query .= "ORDER BY priority";
303 my $sth = $dbh->prepare($query);
304 $sth->execute($biblionumber);
307 while ( my $data = $sth->fetchrow_hashref ) {
309 # FIXME - What is this doing? How do constraints work?
310 if ($data->{constrainttype
} eq 'o') {
312 SELECT biblioitemnumber
313 FROM reserveconstraints
314 WHERE biblionumber = ?
315 AND borrowernumber = ?
318 my $csth = $dbh->prepare($query);
319 $csth->execute($data->{biblionumber
}, $data->{borrowernumber
}, $data->{reservedate
});
321 while ( my $bibitemnos = $csth->fetchrow_array ) {
322 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
324 my $count = scalar @bibitemno;
326 # if we have two or more different specific itemtypes
327 # reserved by same person on same day
330 $bdata = GetBiblioItemData
( $bibitemno[$i] ); # FIXME: This doesn't make sense.
331 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
334 # Look up the book we just found.
335 $bdata = GetBiblioItemData
( $bibitemno[0] );
337 # Add the results of this latest search to the current
339 # FIXME - An 'each' would probably be more efficient.
340 foreach my $key ( keys %$bdata ) {
341 $data->{$key} = $bdata->{$key};
344 push @results, $data;
346 return ( $#results + 1, \
@results );
349 =head2 GetReservesFromItemnumber
351 ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = GetReservesFromItemnumber($itemnumber);
353 TODO :: Description here
357 sub GetReservesFromItemnumber
{
358 my ( $itemnumber, $all_dates ) = @_;
359 my $dbh = C4
::Context
->dbh;
361 SELECT reservedate,borrowernumber,branchcode,reserve_id
365 unless ( $all_dates ) {
366 $query .= " AND reservedate <= CURRENT_DATE()";
368 my $sth_res = $dbh->prepare($query);
369 $sth_res->execute($itemnumber);
370 my ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = $sth_res->fetchrow_array;
371 return ( $reservedate, $borrowernumber, $branchcode, $reserve_id );
374 =head2 GetReservesFromBorrowernumber
376 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
382 sub GetReservesFromBorrowernumber
{
383 my ( $borrowernumber, $status ) = @_;
384 my $dbh = C4
::Context
->dbh;
387 $sth = $dbh->prepare("
390 WHERE borrowernumber=?
394 $sth->execute($borrowernumber,$status);
396 $sth = $dbh->prepare("
399 WHERE borrowernumber=?
402 $sth->execute($borrowernumber);
404 my $data = $sth->fetchall_arrayref({});
407 #-------------------------------------------------------------------------------------
408 =head2 CanBookBeReserved
410 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
414 sub CanBookBeReserved
{
415 my ($borrowernumber, $biblionumber) = @_;
417 my $items = GetItemnumbersForBiblio
($biblionumber);
418 #get items linked via host records
419 my @hostitems = get_hostitemnumbers_of
($biblionumber);
421 push (@
$items,@hostitems);
424 foreach my $item (@
$items){
425 return 1 if CanItemBeReserved
($borrowernumber, $item);
430 =head2 CanItemBeReserved
432 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
434 This function return 1 if an item can be issued by this borrower.
438 sub CanItemBeReserved
{
439 my ($borrowernumber, $itemnumber) = @_;
441 my $dbh = C4
::Context
->dbh;
442 my $allowedreserves = 0;
444 my $controlbranch = C4
::Context
->preference('ReservesControlBranch');
445 my $itype = C4
::Context
->preference('item-level_itypes') ?
"itype" : "itemtype";
447 # we retrieve borrowers and items informations #
448 my $item = GetItem
($itemnumber);
449 my $borrower = C4
::Members
::GetMember
('borrowernumber'=>$borrowernumber);
451 # we retrieve user rights on this itemtype and branchcode
452 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
454 WHERE (categorycode in (?,'*') )
455 AND (itemtype IN (?,'*'))
456 AND (branchcode IN (?,'*'))
463 my $querycount ="SELECT
466 LEFT JOIN items USING (itemnumber)
467 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
468 LEFT JOIN borrowers USING (borrowernumber)
469 WHERE borrowernumber = ?
473 my $itemtype = $item->{$itype};
474 my $categorycode = $borrower->{categorycode
};
476 my $branchfield = "reserves.branchcode";
478 if( $controlbranch eq "ItemHomeLibrary" ){
479 $branchfield = "items.homebranch";
480 $branchcode = $item->{homebranch
};
481 }elsif( $controlbranch eq "PatronLibrary" ){
482 $branchfield = "borrowers.branchcode";
483 $branchcode = $borrower->{branchcode
};
487 $sth->execute($categorycode, $itemtype, $branchcode);
488 if(my $rights = $sth->fetchrow_hashref()){
489 $itemtype = $rights->{itemtype
};
490 $allowedreserves = $rights->{reservesallowed
};
497 $querycount .= "AND $branchfield = ?";
499 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
500 my $sthcount = $dbh->prepare($querycount);
502 if($itemtype eq "*"){
503 $sthcount->execute($borrowernumber, $branchcode);
505 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
508 my $reservecount = "0";
509 if(my $rowcount = $sthcount->fetchrow_hashref()){
510 $reservecount = $rowcount->{count
};
513 # we check if it's ok or not
514 if( $reservecount >= $allowedreserves ){
518 # If reservecount is ok, we check item branch if IndependentBranches is ON
519 # and canreservefromotherbranches is OFF
520 if ( C4
::Context
->preference('IndependentBranches')
521 and !C4
::Context
->preference('canreservefromotherbranches') )
523 my $itembranch = $item->{homebranch
};
524 if ($itembranch ne $borrower->{branchcode
}) {
531 #--------------------------------------------------------------------------------
532 =head2 GetReserveCount
534 $number = &GetReserveCount($borrowernumber);
536 this function returns the number of reservation for a borrower given on input arg.
540 sub GetReserveCount
{
541 my ($borrowernumber) = @_;
543 my $dbh = C4
::Context
->dbh;
546 SELECT COUNT(*) AS counter
548 WHERE borrowernumber = ?
550 my $sth = $dbh->prepare($query);
551 $sth->execute($borrowernumber);
552 my $row = $sth->fetchrow_hashref;
553 return $row->{counter
};
556 =head2 GetOtherReserves
558 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
560 Check queued list of this document and check if this document must be transfered
564 sub GetOtherReserves
{
565 my ($itemnumber) = @_;
568 my ( undef, $checkreserves, undef ) = CheckReserves
($itemnumber);
569 if ($checkreserves) {
570 my $iteminfo = GetItem
($itemnumber);
571 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
572 $messages->{'transfert'} = $checkreserves->{'branchcode'};
573 #minus priorities of others reservs
574 ModReserveMinusPriority
(
576 $checkreserves->{'reserve_id'},
579 #launch the subroutine dotransfer
580 C4
::Items
::ModItemTransfer
(
582 $iteminfo->{'holdingbranch'},
583 $checkreserves->{'branchcode'}
588 #step 2b : case of a reservation on the same branch, set the waiting status
590 $messages->{'waiting'} = 1;
591 ModReserveMinusPriority
(
593 $checkreserves->{'reserve_id'},
595 ModReserveStatus
($itemnumber,'W');
598 $nextreservinfo = $checkreserves->{'borrowernumber'};
601 return ( $messages, $nextreservinfo );
606 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
608 Calculate the fee for a reserve
613 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
616 my $dbh = C4
::Context
->dbh;
617 my $const = lc substr( $constraint, 0, 1 );
619 SELECT
* FROM borrowers
620 LEFT JOIN categories ON borrowers
.categorycode
= categories
.categorycode
621 WHERE borrowernumber
= ?
623 my $sth = $dbh->prepare($query);
624 $sth->execute($borrowernumber);
625 my $data = $sth->fetchrow_hashref;
627 my $fee = $data->{'reservefee'};
628 my $cntitems = @
- > $bibitems;
632 # check for items on issue
633 # first find biblioitem records
635 my $sth1 = $dbh->prepare(
636 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
637 WHERE (biblio.biblionumber = ?)"
639 $sth1->execute($biblionumber);
640 while ( my $data1 = $sth1->fetchrow_hashref ) {
641 if ( $const eq "a" ) {
642 push @biblioitems, $data1;
647 while ( $x < $cntitems ) {
648 if ( @
$bibitems->{'biblioitemnumber'} ==
649 $data->{'biblioitemnumber'} )
655 if ( $const eq 'o' ) {
657 push @biblioitems, $data1;
662 push @biblioitems, $data1;
668 my $cntitemsfound = @biblioitems;
672 while ( $x < $cntitemsfound ) {
673 my $bitdata = $biblioitems[$x];
674 my $sth2 = $dbh->prepare(
676 WHERE biblioitemnumber = ?"
678 $sth2->execute( $bitdata->{'biblioitemnumber'} );
679 while ( my $itdata = $sth2->fetchrow_hashref ) {
680 my $sth3 = $dbh->prepare(
681 "SELECT * FROM issues
682 WHERE itemnumber = ?"
684 $sth3->execute( $itdata->{'itemnumber'} );
685 if ( my $isdata = $sth3->fetchrow_hashref ) {
693 if ( $allissued == 0 ) {
695 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
696 $rsth->execute($biblionumber);
697 if ( my $rdata = $rsth->fetchrow_hashref ) {
707 =head2 GetReservesToBranch
709 @transreserv = GetReservesToBranch( $frombranch );
711 Get reserve list for a given branch
715 sub GetReservesToBranch
{
716 my ( $frombranch ) = @_;
717 my $dbh = C4
::Context
->dbh;
718 my $sth = $dbh->prepare(
719 "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
724 $sth->execute( $frombranch );
727 while ( my $data = $sth->fetchrow_hashref ) {
728 $transreserv[$i] = $data;
731 return (@transreserv);
734 =head2 GetReservesForBranch
736 @transreserv = GetReservesForBranch($frombranch);
740 sub GetReservesForBranch
{
741 my ($frombranch) = @_;
742 my $dbh = C4
::Context
->dbh;
745 SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
750 $query .= " AND branchcode=? " if ( $frombranch );
751 $query .= "ORDER BY waitingdate" ;
753 my $sth = $dbh->prepare($query);
755 $sth->execute($frombranch);
762 while ( my $data = $sth->fetchrow_hashref ) {
763 $transreserv[$i] = $data;
766 return (@transreserv);
769 =head2 GetReserveStatus
771 $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
773 Take an itemnumber or a biblionumber and return the status of the reserve places on it.
774 If several reserves exist, the reserve with the lower priority is given.
778 ## FIXME: I don't think this does what it thinks it does.
779 ## It only ever checks the first reserve result, even though
780 ## multiple reserves for that bib can have the itemnumber set
781 ## the sub is only used once in the codebase.
782 sub GetReserveStatus
{
783 my ($itemnumber, $biblionumber) = @_;
785 my $dbh = C4
::Context
->dbh;
787 my ($sth, $found, $priority);
789 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
790 $sth->execute($itemnumber);
791 ($found, $priority) = $sth->fetchrow_array;
794 if ( $biblionumber and not defined $found and not defined $priority ) {
795 $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
796 $sth->execute($biblionumber);
797 ($found, $priority) = $sth->fetchrow_array;
801 return 'Waiting' if $found eq 'W' and $priority == 0;
802 return 'Finished' if $found eq 'F';
803 return 'Reserved' if $priority > 0;
806 #empty string here will remove need for checking undef, or less log lines
811 ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber);
812 ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode);
814 Find a book in the reserves.
816 C<$itemnumber> is the book's item number.
818 As I understand it, C<&CheckReserves> looks for the given item in the
819 reserves. If it is found, that's a match, and C<$status> is set to
822 Otherwise, it finds the most important item in the reserves with the
823 same biblio number as this book (I'm not clear on this) and returns it
824 with C<$status> set to C<Reserved>.
826 C<&CheckReserves> returns a two-element list:
828 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
830 C<$reserve> is the reserve item that matched. It is a
831 reference-to-hash whose keys are mostly the fields of the reserves
832 table in the Koha database.
837 my ( $item, $barcode ) = @_;
838 my $dbh = C4
::Context
->dbh;
841 if (C4
::Context
->preference('item-level_itypes')){
843 SELECT items.biblionumber,
844 items.biblioitemnumber,
845 itemtypes.notforloan,
846 items.notforloan AS itemnotforloan,
849 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
850 LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype
855 SELECT items.biblionumber,
856 items.biblioitemnumber,
857 itemtypes.notforloan,
858 items.notforloan AS itemnotforloan,
861 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
862 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
867 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
868 $sth->execute($item);
871 $sth = $dbh->prepare("$select WHERE barcode = ?");
872 $sth->execute($barcode);
874 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
875 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
877 return ( '' ) unless $itemnumber; # bail if we got nothing.
879 # if item is not for loan it cannot be reserved either.....
880 # execpt where items.notforloan < 0 : This indicates the item is holdable.
881 return ( '' ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
883 # Find this item in the reserves
884 my @reserves = _Findgroupreserve
( $bibitem, $biblio, $itemnumber );
886 # $priority and $highest are used to find the most important item
887 # in the list returned by &_Findgroupreserve. (The lower $priority,
888 # the more important the item.)
889 # $highest is the most important item we've seen so far.
891 if (scalar @reserves) {
892 my $priority = 10000000;
893 foreach my $res (@reserves) {
894 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
895 return ( "Waiting", $res, \
@reserves ); # Found it
897 # See if this item is more important than what we've got so far
898 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
899 my $borrowerinfo=C4
::Members
::GetMember
(borrowernumber
=> $res->{'borrowernumber'});
900 my $iteminfo=C4
::Items
::GetItem
($itemnumber);
901 my $branch=C4
::Circulation
::_GetCircControlBranch
($iteminfo,$borrowerinfo);
902 my $branchitemrule = C4
::Circulation
::GetBranchItemRule
($branch,$iteminfo->{'itype'});
903 next if ($branchitemrule->{'holdallowed'} == 0);
904 next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
905 $priority = $res->{'priority'};
912 # If we get this far, then no exact match was found.
913 # We return the most important (i.e. next) reservation.
915 $highest->{'itemnumber'} = $item;
916 return ( "Reserved", $highest, \
@reserves );
922 =head2 CancelExpiredReserves
924 CancelExpiredReserves();
926 Cancels all reserves with an expiration date from before today.
930 sub CancelExpiredReserves
{
932 # Cancel reserves that have passed their expiration date.
933 my $dbh = C4
::Context
->dbh;
934 my $sth = $dbh->prepare( "
935 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
936 AND expirationdate IS NOT NULL
941 while ( my $res = $sth->fetchrow_hashref() ) {
942 CancelReserve
({ reserve_id
=> $res->{'reserve_id'} });
945 # Cancel reserves that have been waiting too long
946 if ( C4
::Context
->preference("ExpireReservesMaxPickUpDelay") ) {
947 my $max_pickup_delay = C4
::Context
->preference("ReservesMaxPickUpDelay");
948 my $charge = C4
::Context
->preference("ExpireReservesMaxPickUpDelayCharge");
950 my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
951 $sth = $dbh->prepare( $query );
952 $sth->execute( $max_pickup_delay );
954 while (my $res = $sth->fetchrow_hashref ) {
956 manualinvoice
($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
959 CancelReserve
({ reserve_id
=> $res->{'reserve_id'} });
965 =head2 AutoUnsuspendReserves
967 AutoUnsuspendReserves();
969 Unsuspends all suspended reserves with a suspend_until date from before today.
973 sub AutoUnsuspendReserves
{
975 my $dbh = C4
::Context
->dbh;
977 my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )";
978 my $sth = $dbh->prepare( $query );
985 CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber ] });
994 my $reserve_id = $params->{'reserve_id'};
995 $reserve_id = GetReserveId
( $params ) unless ( $reserve_id );
997 return unless ( $reserve_id );
999 my $dbh = C4
::Context
->dbh;
1003 SET cancellationdate = now(),
1006 WHERE reserve_id = ?
1008 my $sth = $dbh->prepare($query);
1009 $sth->execute( $reserve_id );
1013 INSERT INTO old_reserves
1014 SELECT * FROM reserves
1015 WHERE reserve_id = ?
1017 $sth = $dbh->prepare($query);
1018 $sth->execute( $reserve_id );
1021 DELETE FROM reserves
1022 WHERE reserve_id = ?
1024 $sth = $dbh->prepare($query);
1025 $sth->execute( $reserve_id );
1027 # now fix the priority on the others....
1028 _FixPriority
( $reserve_id );
1033 ModReserve({ rank => $rank,
1034 reserve_id => $reserve_id,
1035 branchcode => $branchcode
1036 [, itemnumber => $itemnumber ]
1037 [, biblionumber => $biblionumber, $borrowernumber => $borrowernumber ]
1040 Change a hold request's priority or cancel it.
1042 C<$rank> specifies the effect of the change. If C<$rank>
1043 is 'W' or 'n', nothing happens. This corresponds to leaving a
1044 request alone when changing its priority in the holds queue
1047 If C<$rank> is 'del', the hold request is cancelled.
1049 If C<$rank> is an integer greater than zero, the priority of
1050 the request is set to that value. Since priority != 0 means
1051 that the item is not waiting on the hold shelf, setting the
1052 priority to a non-zero value also sets the request's found
1053 status and waiting date to NULL.
1055 The optional C<$itemnumber> parameter is used only when
1056 C<$rank> is a non-zero integer; if supplied, the itemnumber
1057 of the hold request is set accordingly; if omitted, the itemnumber
1060 B<FIXME:> Note that the forgoing can have the effect of causing
1061 item-level hold requests to turn into title-level requests. This
1062 will be fixed once reserves has separate columns for requested
1063 itemnumber and supplying itemnumber.
1068 my ( $params ) = @_;
1070 my $rank = $params->{'rank'};
1071 my $reserve_id = $params->{'reserve_id'};
1072 my $branchcode = $params->{'branchcode'};
1073 my $itemnumber = $params->{'itemnumber'};
1074 my $suspend_until = $params->{'suspend_until'};
1075 my $borrowernumber = $params->{'borrowernumber'};
1076 my $biblionumber = $params->{'biblionumber'};
1078 return if $rank eq "W";
1079 return if $rank eq "n";
1081 return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
1082 $reserve_id = GetReserveId
({ biblionumber
=> $biblionumber, borrowernumber
=> $borrowernumber, itemnumber
=> $itemnumber }) unless ( $reserve_id );
1084 my $dbh = C4
::Context
->dbh;
1085 if ( $rank eq "del" ) {
1088 SET cancellationdate=now()
1089 WHERE reserve_id = ?
1091 my $sth = $dbh->prepare($query);
1092 $sth->execute( $reserve_id );
1095 INSERT INTO old_reserves
1098 WHERE reserve_id = ?
1100 $sth = $dbh->prepare($query);
1101 $sth->execute( $reserve_id );
1103 DELETE FROM reserves
1104 WHERE reserve_id = ?
1106 $sth = $dbh->prepare($query);
1107 $sth->execute( $reserve_id );
1110 elsif ($rank =~ /^\d+/ and $rank > 0) {
1112 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1113 WHERE reserve_id = ?
1115 my $sth = $dbh->prepare($query);
1116 $sth->execute( $rank, $branchcode, $itemnumber, $reserve_id );
1119 if ( defined( $suspend_until ) ) {
1120 if ( $suspend_until ) {
1121 $suspend_until = C4
::Dates
->new( $suspend_until )->output("iso");
1122 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE reserve_id = ?", undef, ( $suspend_until, $reserve_id ) );
1124 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE reserve_id = ?", undef, ( $reserve_id ) );
1128 _FixPriority
( $reserve_id, $rank );
1132 =head2 ModReserveFill
1134 &ModReserveFill($reserve);
1136 Fill a reserve. If I understand this correctly, this means that the
1137 reserved book has been found and given to the patron who reserved it.
1139 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1140 whose keys are fields from the reserves table in the Koha database.
1144 sub ModReserveFill
{
1146 my $dbh = C4
::Context
->dbh;
1147 # fill in a reserve record....
1148 my $reserve_id = $res->{'reserve_id'};
1149 my $biblionumber = $res->{'biblionumber'};
1150 my $borrowernumber = $res->{'borrowernumber'};
1151 my $resdate = $res->{'reservedate'};
1153 # get the priority on this record....
1155 my $query = "SELECT priority
1157 WHERE biblionumber = ?
1158 AND borrowernumber = ?
1159 AND reservedate = ?";
1160 my $sth = $dbh->prepare($query);
1161 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1162 ($priority) = $sth->fetchrow_array;
1165 # update the database...
1166 $query = "UPDATE reserves
1169 WHERE biblionumber = ?
1171 AND borrowernumber = ?
1173 $sth = $dbh->prepare($query);
1174 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1177 # move to old_reserves
1178 $query = "INSERT INTO old_reserves
1179 SELECT * FROM reserves
1180 WHERE biblionumber = ?
1182 AND borrowernumber = ?
1184 $sth = $dbh->prepare($query);
1185 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1186 $query = "DELETE FROM reserves
1187 WHERE biblionumber = ?
1189 AND borrowernumber = ?
1191 $sth = $dbh->prepare($query);
1192 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1194 # now fix the priority on the others (if the priority wasn't
1195 # already sorted!)....
1196 unless ( $priority == 0 ) {
1197 _FixPriority
( $reserve_id );
1201 =head2 ModReserveStatus
1203 &ModReserveStatus($itemnumber, $newstatus);
1205 Update the reserve status for the active (priority=0) reserve.
1207 $itemnumber is the itemnumber the reserve is on
1209 $newstatus is the new status.
1213 sub ModReserveStatus
{
1215 #first : check if we have a reservation for this item .
1216 my ($itemnumber, $newstatus) = @_;
1217 my $dbh = C4
::Context
->dbh;
1219 my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1220 my $sth_set = $dbh->prepare($query);
1221 $sth_set->execute( $newstatus, $itemnumber );
1223 if ( C4
::Context
->preference("ReturnToShelvingCart") && $newstatus ) {
1224 CartToShelf
( $itemnumber );
1228 =head2 ModReserveAffect
1230 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1232 This function affect an item and a status for a given reserve
1233 The itemnumber parameter is used to find the biblionumber.
1234 with the biblionumber & the borrowernumber, we can affect the itemnumber
1235 to the correct reserve.
1237 if $transferToDo is not set, then the status is set to "Waiting" as well.
1238 otherwise, a transfer is on the way, and the end of the transfer will
1239 take care of the waiting status
1243 sub ModReserveAffect
{
1244 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1245 my $dbh = C4
::Context
->dbh;
1247 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1248 # attached to $itemnumber
1249 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1250 $sth->execute($itemnumber);
1251 my ($biblionumber) = $sth->fetchrow;
1253 # get request - need to find out if item is already
1254 # waiting in order to not send duplicate hold filled notifications
1255 my $request = GetReserveInfo
($borrowernumber, $biblionumber);
1256 my $already_on_shelf = ($request && $request->{found
} eq 'W') ?
1 : 0;
1258 # If we affect a reserve that has to be transfered, don't set to Waiting
1260 if ($transferToDo) {
1266 WHERE borrowernumber = ?
1267 AND biblionumber = ?
1271 # affect the reserve to Waiting as well.
1276 waitingdate = NOW(),
1278 WHERE borrowernumber = ?
1279 AND biblionumber = ?
1282 $sth = $dbh->prepare($query);
1283 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1284 _koha_notify_reserve
( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1286 if ( C4
::Context
->preference("ReturnToShelvingCart") ) {
1287 CartToShelf
( $itemnumber );
1293 =head2 ModReserveCancelAll
1295 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1297 function to cancel reserv,check other reserves, and transfer document if it's necessary
1301 sub ModReserveCancelAll
{
1304 my ( $itemnumber, $borrowernumber ) = @_;
1306 #step 1 : cancel the reservation
1307 my $CancelReserve = CancelReserve
({ itemnumber
=> $itemnumber, borrowernumber
=> $borrowernumber });
1309 #step 2 launch the subroutine of the others reserves
1310 ( $messages, $nextreservinfo ) = GetOtherReserves
($itemnumber);
1312 return ( $messages, $nextreservinfo );
1315 =head2 ModReserveMinusPriority
1317 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1319 Reduce the values of queued list
1323 sub ModReserveMinusPriority
{
1324 my ( $itemnumber, $reserve_id ) = @_;
1326 #first step update the value of the first person on reserv
1327 my $dbh = C4
::Context
->dbh;
1330 SET priority = 0 , itemnumber = ?
1331 WHERE reserve_id = ?
1333 my $sth_upd = $dbh->prepare($query);
1334 $sth_upd->execute( $itemnumber, $reserve_id );
1335 # second step update all others reservs
1336 _FixPriority
( $reserve_id, '0');
1339 =head2 GetReserveInfo
1341 &GetReserveInfo($reserve_id);
1343 Get item and borrower details for a current hold.
1344 Current implementation this query should have a single result.
1348 sub GetReserveInfo
{
1349 my ( $reserve_id ) = @_;
1350 my $dbh = C4
::Context
->dbh;
1355 reserves.borrowernumber,
1356 reserves.biblionumber,
1357 reserves.branchcode,
1358 reserves.waitingdate,
1374 items.holdingbranch,
1375 items.itemcallnumber,
1381 LEFT JOIN items USING(itemnumber)
1382 LEFT JOIN borrowers USING(borrowernumber)
1383 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1384 WHERE reserves.reserve_id = ?";
1385 my $sth = $dbh->prepare($strsth);
1386 $sth->execute($reserve_id);
1388 my $data = $sth->fetchrow_hashref;
1392 =head2 IsAvailableForItemLevelRequest
1394 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1396 Checks whether a given item record is available for an
1397 item-level hold request. An item is available if
1399 * it is not lost AND
1400 * it is not damaged AND
1401 * it is not withdrawn AND
1402 * does not have a not for loan value > 0
1404 Whether or not the item is currently on loan is
1405 also checked - if the AllowOnShelfHolds system preference
1406 is ON, an item can be requested even if it is currently
1407 on loan to somebody else. If the system preference
1408 is OFF, an item that is currently checked out cannot
1409 be the target of an item-level hold request.
1411 Note that IsAvailableForItemLevelRequest() does not
1412 check if the staff operator is authorized to place
1413 a request on the item - in particular,
1414 this routine does not check IndependentBranches
1415 and canreservefromotherbranches.
1419 sub IsAvailableForItemLevelRequest
{
1420 my $itemnumber = shift;
1422 my $item = GetItem
($itemnumber);
1424 # must check the notforloan setting of the itemtype
1425 # FIXME - a lot of places in the code do this
1426 # or something similar - need to be
1428 my $dbh = C4
::Context
->dbh;
1429 my $notforloan_query;
1430 if (C4
::Context
->preference('item-level_itypes')) {
1431 $notforloan_query = "SELECT itemtypes.notforloan
1433 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1434 WHERE itemnumber = ?";
1436 $notforloan_query = "SELECT itemtypes.notforloan
1438 JOIN biblioitems USING (biblioitemnumber)
1439 JOIN itemtypes USING (itemtype)
1440 WHERE itemnumber = ?";
1442 my $sth = $dbh->prepare($notforloan_query);
1443 $sth->execute($itemnumber);
1444 my $notforloan_per_itemtype = 0;
1445 if (my ($notforloan) = $sth->fetchrow_array) {
1446 $notforloan_per_itemtype = 1 if $notforloan;
1449 my $available_per_item = 1;
1450 $available_per_item = 0 if $item->{itemlost
} or
1451 ( $item->{notforloan
} > 0 ) or
1452 ($item->{damaged
} and not C4
::Context
->preference('AllowHoldsOnDamagedItems')) or
1453 $item->{wthdrawn
} or
1454 $notforloan_per_itemtype;
1457 if (C4
::Context
->preference('AllowOnShelfHolds')) {
1458 return $available_per_item;
1460 return ($available_per_item and ($item->{onloan
} or GetReserveStatus
($itemnumber) eq "Waiting"));
1464 =head2 AlterPriority
1466 AlterPriority( $where, $reserve_id );
1468 This function changes a reserve's priority up, down, to the top, or to the bottom.
1469 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1474 my ( $where, $reserve_id ) = @_;
1476 my $dbh = C4
::Context
->dbh;
1478 my $reserve = GetReserve
( $reserve_id );
1480 if ( $reserve->{cancellationdate
} ) {
1481 warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (".$reserve->{cancellationdate
}.')';
1485 if ( $where eq 'up' || $where eq 'down' ) {
1487 my $priority = $reserve->{'priority'};
1488 $priority = $where eq 'up' ?
$priority - 1 : $priority + 1;
1489 _FixPriority
( $reserve_id, $priority )
1491 } elsif ( $where eq 'top' ) {
1493 _FixPriority
( $reserve_id, '1' )
1495 } elsif ( $where eq 'bottom' ) {
1497 _FixPriority
( $reserve_id, '999999' )
1502 =head2 ToggleLowestPriority
1504 ToggleLowestPriority( $borrowernumber, $biblionumber );
1506 This function sets the lowestPriority field to true if is false, and false if it is true.
1510 sub ToggleLowestPriority
{
1511 my ( $reserve_id ) = @_;
1513 my $dbh = C4
::Context
->dbh;
1515 my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1516 $sth->execute( $reserve_id );
1519 _FixPriority
( $reserve_id, '999999' );
1522 =head2 ToggleSuspend
1524 ToggleSuspend( $reserve_id );
1526 This function sets the suspend field to true if is false, and false if it is true.
1527 If the reserve is currently suspended with a suspend_until date, that date will
1528 be cleared when it is unsuspended.
1533 my ( $reserve_id, $suspend_until ) = @_;
1535 $suspend_until = output_pref
( dt_from_string
( $suspend_until ), 'iso' ) if ( $suspend_until );
1537 my $do_until = ( $suspend_until ) ?
'?' : 'NULL';
1539 my $dbh = C4
::Context
->dbh;
1541 my $sth = $dbh->prepare(
1542 "UPDATE reserves SET suspend = NOT suspend,
1543 suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
1544 WHERE reserve_id = ?
1548 push( @params, $suspend_until ) if ( $suspend_until );
1549 push( @params, $reserve_id );
1551 $sth->execute( @params );
1558 borrowernumber => $borrowernumber,
1559 [ biblionumber => $biblionumber, ]
1560 [ suspend_until => $suspend_until, ]
1561 [ suspend => $suspend ]
1564 This function accepts a set of hash keys as its parameters.
1565 It requires either borrowernumber or biblionumber, or both.
1567 suspend_until is wholly optional.
1574 my $borrowernumber = $params{'borrowernumber'} || undef;
1575 my $biblionumber = $params{'biblionumber'} || undef;
1576 my $suspend_until = $params{'suspend_until'} || undef;
1577 my $suspend = defined( $params{'suspend'} ) ?
$params{'suspend'} : 1;
1579 $suspend_until = C4
::Dates
->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1581 return unless ( $borrowernumber || $biblionumber );
1583 my ( $query, $sth, $dbh, @query_params );
1585 $query = "UPDATE reserves SET suspend = ? ";
1586 push( @query_params, $suspend );
1588 $query .= ", suspend_until = NULL ";
1589 } elsif ( $suspend_until ) {
1590 $query .= ", suspend_until = ? ";
1591 push( @query_params, $suspend_until );
1593 $query .= " WHERE ";
1594 if ( $borrowernumber ) {
1595 $query .= " borrowernumber = ? ";
1596 push( @query_params, $borrowernumber );
1598 $query .= " AND " if ( $borrowernumber && $biblionumber );
1599 if ( $biblionumber ) {
1600 $query .= " biblionumber = ? ";
1601 push( @query_params, $biblionumber );
1603 $query .= " AND found IS NULL ";
1605 $dbh = C4
::Context
->dbh;
1606 $sth = $dbh->prepare( $query );
1607 $sth->execute( @query_params );
1614 &_FixPriority( $reserve_id, $rank, $ignoreSetLowestRank);
1616 Only used internally (so don't export it)
1617 Changed how this functions works #
1618 Now just gets an array of reserves in the rank order and updates them with
1619 the array index (+1 as array starts from 0)
1620 and if $rank is supplied will splice item from the array and splice it back in again
1621 in new priority rank
1626 my ( $reserve_id, $rank, $ignoreSetLowestRank ) = @_;
1627 my $dbh = C4
::Context
->dbh;
1629 my $res = GetReserve
( $reserve_id );
1631 if ( $rank eq "del" ) {
1632 CancelReserve
({ reserve_id
=> $reserve_id });
1634 elsif ( $rank eq "W" || $rank eq "0" ) {
1636 # make sure priority for waiting or in-transit items is 0
1640 WHERE reserve_id = ?
1641 AND found IN ('W', 'T')
1643 my $sth = $dbh->prepare($query);
1644 $sth->execute( $reserve_id );
1650 SELECT reserve_id, borrowernumber, reservedate, constrainttype
1652 WHERE biblionumber = ?
1653 AND ((found <> 'W' AND found <> 'T') OR found IS NULL)
1654 ORDER BY priority ASC
1656 my $sth = $dbh->prepare($query);
1657 $sth->execute( $res->{'biblionumber'} );
1658 while ( my $line = $sth->fetchrow_hashref ) {
1659 push( @priority, $line );
1662 # To find the matching index
1664 my $key = -1; # to allow for 0 to be a valid result
1665 for ( $i = 0 ; $i < @priority ; $i++ ) {
1666 if ( $reserve_id == $priority[$i]->{'reserve_id'} ) {
1667 $key = $i; # save the index
1672 # if index exists in array then move it to new position
1673 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1674 my $new_rank = $rank -
1675 1; # $new_rank is what you want the new index to be in the array
1676 my $moving_item = splice( @priority, $key, 1 );
1677 splice( @priority, $new_rank, 0, $moving_item );
1680 # now fix the priority on those that are left....
1684 WHERE reserve_id = ?
1686 $sth = $dbh->prepare($query);
1687 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1690 $priority[$j]->{'reserve_id'}
1695 $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1698 unless ( $ignoreSetLowestRank ) {
1699 while ( my $res = $sth->fetchrow_hashref() ) {
1700 _FixPriority
( $res->{'reserve_id'}, '999999', 1 );
1705 =head2 _Findgroupreserve
1707 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1709 Looks for an item-specific match first, then for a title-level match, returning the
1710 first match found. If neither, then we look for a 3rd kind of match based on
1711 reserve constraints.
1713 TODO: add more explanation about reserve constraints
1715 C<&_Findgroupreserve> returns :
1716 C<@results> is an array of references-to-hash whose keys are mostly
1717 fields from the reserves table of the Koha database, plus
1718 C<biblioitemnumber>.
1722 sub _Findgroupreserve
{
1723 my ( $bibitem, $biblio, $itemnumber ) = @_;
1724 my $dbh = C4
::Context
->dbh;
1726 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1727 # check for exact targetted match
1728 my $item_level_target_query = qq/
1729 SELECT reserves
.biblionumber AS biblionumber
,
1730 reserves
.borrowernumber AS borrowernumber
,
1731 reserves
.reservedate AS reservedate
,
1732 reserves
.branchcode AS branchcode
,
1733 reserves
.cancellationdate AS cancellationdate
,
1734 reserves
.found AS found
,
1735 reserves
.reservenotes AS reservenotes
,
1736 reserves
.priority AS priority
,
1737 reserves
.timestamp AS timestamp
,
1738 biblioitems
.biblioitemnumber AS biblioitemnumber
,
1739 reserves
.itemnumber AS itemnumber
,
1740 reserves
.reserve_id AS reserve_id
1742 JOIN biblioitems USING
(biblionumber
)
1743 JOIN hold_fill_targets USING
(biblionumber
, borrowernumber
, itemnumber
)
1746 AND item_level_request
= 1
1748 AND reservedate
<= CURRENT_DATE
()
1751 my $sth = $dbh->prepare($item_level_target_query);
1752 $sth->execute($itemnumber);
1754 if ( my $data = $sth->fetchrow_hashref ) {
1755 push( @results, $data );
1757 return @results if @results;
1759 # check for title-level targetted match
1760 my $title_level_target_query = qq/
1761 SELECT reserves
.biblionumber AS biblionumber
,
1762 reserves
.borrowernumber AS borrowernumber
,
1763 reserves
.reservedate AS reservedate
,
1764 reserves
.branchcode AS branchcode
,
1765 reserves
.cancellationdate AS cancellationdate
,
1766 reserves
.found AS found
,
1767 reserves
.reservenotes AS reservenotes
,
1768 reserves
.priority AS priority
,
1769 reserves
.timestamp AS timestamp
,
1770 biblioitems
.biblioitemnumber AS biblioitemnumber
,
1771 reserves
.itemnumber AS itemnumber
1773 JOIN biblioitems USING
(biblionumber
)
1774 JOIN hold_fill_targets USING
(biblionumber
, borrowernumber
)
1777 AND item_level_request
= 0
1778 AND hold_fill_targets
.itemnumber
= ?
1779 AND reservedate
<= CURRENT_DATE
()
1782 $sth = $dbh->prepare($title_level_target_query);
1783 $sth->execute($itemnumber);
1785 if ( my $data = $sth->fetchrow_hashref ) {
1786 push( @results, $data );
1788 return @results if @results;
1791 SELECT reserves
.biblionumber AS biblionumber
,
1792 reserves
.borrowernumber AS borrowernumber
,
1793 reserves
.reservedate AS reservedate
,
1794 reserves
.waitingdate AS waitingdate
,
1795 reserves
.branchcode AS branchcode
,
1796 reserves
.cancellationdate AS cancellationdate
,
1797 reserves
.found AS found
,
1798 reserves
.reservenotes AS reservenotes
,
1799 reserves
.priority AS priority
,
1800 reserves
.timestamp AS timestamp
,
1801 reserveconstraints
.biblioitemnumber AS biblioitemnumber
,
1802 reserves
.itemnumber AS itemnumber
1804 LEFT JOIN reserveconstraints ON reserves
.biblionumber
= reserveconstraints
.biblionumber
1805 WHERE reserves
.biblionumber
= ?
1806 AND
( ( reserveconstraints
.biblioitemnumber
= ?
1807 AND reserves
.borrowernumber
= reserveconstraints
.borrowernumber
1808 AND reserves
.reservedate
= reserveconstraints
.reservedate
)
1809 OR reserves
.constrainttype
='a' )
1810 AND
(reserves
.itemnumber IS NULL OR reserves
.itemnumber
= ?
)
1811 AND reserves
.reservedate
<= CURRENT_DATE
()
1814 $sth = $dbh->prepare($query);
1815 $sth->execute( $biblio, $bibitem, $itemnumber );
1817 while ( my $data = $sth->fetchrow_hashref ) {
1818 push( @results, $data );
1823 =head2 _koha_notify_reserve
1825 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1827 Sends a notification to the patron that their hold has been filled (through
1828 ModReserveAffect, _not_ ModReserveFill)
1832 sub _koha_notify_reserve
{
1833 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1835 my $dbh = C4
::Context
->dbh;
1836 my $borrower = C4
::Members
::GetMember
(borrowernumber
=> $borrowernumber);
1838 # Try to get the borrower's email address
1839 my $to_address = C4
::Members
::GetNoticeEmailAddress
($borrowernumber);
1844 if ( $to_address || $borrower->{'smsalertnumber'} ) {
1845 $messagingprefs = C4
::Members
::Messaging
::GetMessagingPreferences
( { borrowernumber
=> $borrowernumber, message_name
=> 'Hold_Filled' } );
1850 my $sth = $dbh->prepare("
1853 WHERE borrowernumber = ?
1854 AND biblionumber = ?
1856 $sth->execute( $borrowernumber, $biblionumber );
1857 my $reserve = $sth->fetchrow_hashref;
1858 my $branch_details = GetBranchDetail
( $reserve->{'branchcode'} );
1860 my $admin_email_address = $branch_details->{'branchemail'} || C4
::Context
->preference('KohaAdminEmailAddress');
1862 my %letter_params = (
1863 module
=> 'reserves',
1864 branchcode
=> $reserve->{branchcode
},
1866 'branches' => $branch_details,
1867 'borrowers' => $borrower,
1868 'biblio' => $biblionumber,
1869 'reserves' => $reserve,
1870 'items', $reserve->{'itemnumber'},
1872 substitute
=> { today
=> C4
::Dates
->new()->output() },
1876 if ( $print_mode ) {
1877 $letter_params{ 'letter_code' } = 'HOLD_PRINT';
1878 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1880 C4
::Letters
::EnqueueLetter
( {
1882 borrowernumber
=> $borrowernumber,
1883 message_transport_type
=> 'print',
1889 if ( $to_address && defined $messagingprefs->{transports
}->{'email'} ) {
1890 $letter_params{ 'letter_code' } = $messagingprefs->{transports
}->{'email'};
1891 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1893 C4
::Letters
::EnqueueLetter
(
1894 { letter
=> $letter,
1895 borrowernumber
=> $borrowernumber,
1896 message_transport_type
=> 'email',
1897 from_address
=> $admin_email_address,
1902 if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports
}->{'sms'} ) {
1903 $letter_params{ 'letter_code' } = $messagingprefs->{transports
}->{'sms'};
1904 my $letter = C4
::Letters
::GetPreparedLetter
( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1906 C4
::Letters
::EnqueueLetter
(
1907 { letter
=> $letter,
1908 borrowernumber
=> $borrowernumber,
1909 message_transport_type
=> 'sms',
1915 =head2 _ShiftPriorityByDateAndPriority
1917 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1919 This increments the priority of all reserves after the one
1920 with either the lowest date after C<$reservedate>
1921 or the lowest priority after C<$priority>.
1923 It effectively makes room for a new reserve to be inserted with a certain
1924 priority, which is returned.
1926 This is most useful when the reservedate can be set by the user. It allows
1927 the new reserve to be placed before other reserves that have a later
1928 reservedate. Since priority also is set by the form in reserves/request.pl
1929 the sub accounts for that too.
1933 sub _ShiftPriorityByDateAndPriority
{
1934 my ( $biblio, $resdate, $new_priority ) = @_;
1936 my $dbh = C4
::Context
->dbh;
1937 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1938 my $sth = $dbh->prepare( $query );
1939 $sth->execute( $biblio, $resdate, $new_priority );
1940 my $min_priority = $sth->fetchrow;
1941 # if no such matches are found, $new_priority remains as original value
1942 $new_priority = $min_priority if ( $min_priority );
1944 # Shift the priority up by one; works in conjunction with the next SQL statement
1945 $query = "UPDATE reserves
1946 SET priority = priority+1
1947 WHERE biblionumber = ?
1948 AND borrowernumber = ?
1951 my $sth_update = $dbh->prepare( $query );
1953 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1954 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1955 $sth = $dbh->prepare( $query );
1956 $sth->execute( $new_priority, $biblio );
1957 while ( my $row = $sth->fetchrow_hashref ) {
1958 $sth_update->execute( $biblio, $row->{borrowernumber
}, $row->{reservedate
} );
1961 return $new_priority; # so the caller knows what priority they wind up receiving
1966 MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
1968 Use when checking out an item to handle reserves
1969 If $cancelreserve boolean is set to true, it will remove existing reserve
1974 my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
1976 my ( $restype, $res, $all_reserves ) = CheckReserves
( $itemnumber );
1979 my $biblionumber = $res->{biblionumber
};
1980 my $biblioitemnumber = $res->{biblioitemnumber
};
1982 if ($res->{borrowernumber
} == $borrowernumber) {
1983 ModReserveFill
($res);
1987 # The item is reserved by someone else.
1988 # Find this item in the reserves
1991 foreach (@
$all_reserves) {
1992 $_->{'borrowernumber'} == $borrowernumber or next;
1993 $_->{'biblionumber'} == $biblionumber or next;
2000 # The item is reserved by the current patron
2001 ModReserveFill
($borr_res);
2004 if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2005 RevertWaitingStatus
({ itemnumber
=> $itemnumber });
2007 elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2009 biblionumber
=> $res->{'biblionumber'},
2010 itemnumber
=> $res->{'itemnumber'},
2011 borrowernumber
=> $res->{'borrowernumber'}
2019 MergeHolds($dbh,$to_biblio, $from_biblio);
2021 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2026 my ( $dbh, $to_biblio, $from_biblio ) = @_;
2027 my $sth = $dbh->prepare(
2028 "SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?"
2030 $sth->execute($from_biblio);
2031 if ( my $data = $sth->fetchrow_hashref() ) {
2033 # holds exist on old record, if not we don't need to do anything
2034 $sth = $dbh->prepare(
2035 "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2036 $sth->execute( $to_biblio, $from_biblio );
2039 # don't reorder those already waiting
2041 $sth = $dbh->prepare(
2042 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2044 my $upd_sth = $dbh->prepare(
2045 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2046 AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2048 $sth->execute( $to_biblio, 'W', 'T' );
2050 while ( my $reserve = $sth->fetchrow_hashref() ) {
2052 $priority, $to_biblio,
2053 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2054 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2061 =head2 RevertWaitingStatus
2063 $success = RevertWaitingStatus({ itemnumber => $itemnumber });
2065 Reverts a 'waiting' hold back to a regular hold with a priority of 1.
2067 Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
2068 item level hold, even if it was only a bibliolevel hold to
2069 begin with. This is because we can no longer know if a hold
2070 was item-level or bib-level after a hold has been set to
2075 sub RevertWaitingStatus
{
2076 my ( $params ) = @_;
2077 my $itemnumber = $params->{'itemnumber'};
2079 return unless ( $itemnumber );
2081 my $dbh = C4
::Context
->dbh;
2083 ## Get the waiting reserve we want to revert
2085 SELECT * FROM reserves
2086 WHERE itemnumber = ?
2087 AND found IS NOT NULL
2089 my $sth = $dbh->prepare( $query );
2090 $sth->execute( $itemnumber );
2091 my $reserve = $sth->fetchrow_hashref();
2093 ## Increment the priority of all other non-waiting
2094 ## reserves for this bib record
2098 priority = priority + 1
2104 $sth = $dbh->prepare( $query );
2105 $sth->execute( $reserve->{'biblionumber'} );
2107 ## Fix up the currently waiting reserve
2117 $sth = $dbh->prepare( $query );
2118 return $sth->execute( $reserve->{'reserve_id'} );
2123 $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber [, itemnumber => $itemnumber ] });
2125 Returnes the first reserve id that matches the given criteria
2130 my ( $params ) = @_;
2132 return unless ( ( $params->{'biblionumber'} || $params->{'itemnumber'} ) && $params->{'borrowernumber'} );
2134 my $dbh = C4
::Context
->dbh();
2136 my $sql = "SELECT reserve_id FROM reserves WHERE ";
2140 foreach my $key ( keys %$params ) {
2141 if ( defined( $params->{$key} ) ) {
2142 push( @limits, "$key = ?" );
2143 push( @params, $params->{$key} );
2147 $sql .= join( " AND ", @limits );
2149 my $sth = $dbh->prepare( $sql );
2150 $sth->execute( @params );
2151 my $row = $sth->fetchrow_hashref();
2153 return $row->{'reserve_id'};
2158 ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2160 Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2165 my ($branch, $borrowernumber, $biblionumber) = @_;
2167 # return unless ( C4::Context->boolean_preference('printreserveslips') );
2169 my $reserve = GetReserveInfo
($borrowernumber,$biblionumber )
2172 return C4
::Letters
::GetPreparedLetter
(
2173 module
=> 'circulation',
2174 letter_code
=> 'RESERVESLIP',
2175 branchcode
=> $branch,
2177 'reserves' => $reserve,
2178 'branches' => $reserve->{branchcode
},
2179 'borrowers' => $reserve->{borrowernumber
},
2180 'biblio' => $reserve->{biblionumber
},
2181 'items' => $reserve->{itemnumber
},
2188 Koha Development Team <http://koha-community.org/>