Minor markup correction to send Cart form
[koha.git] / C4 / Reserves.pm
blob9b7014ef14b2769567542c229f29287ecd93052f
1 package C4::Reserves;
3 # Copyright 2000-2002 Katipo Communications
4 # 2006 SAN Ouest Provence
5 # 2007 BibLibre Paul POULAIN
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 use strict;
24 #use warnings; FIXME - Bug 2505
25 use C4::Context;
26 use C4::Biblio;
27 use C4::Members;
28 use C4::Items;
29 use C4::Search;
30 use C4::Circulation;
31 use C4::Accounts;
33 # for _koha_notify_reserve
34 use C4::Members::Messaging;
35 use C4::Members qw();
36 use C4::Letters;
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);
43 =head1 NAME
45 C4::Reserves - Koha functions for dealing with reservation.
47 =head1 SYNOPSIS
49 use C4::Reserves;
51 =head1 DESCRIPTION
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
82 =head1 FUNCTIONS
84 =cut
86 BEGIN {
87 # set the version for version checking
88 $VERSION = 3.01;
89 require Exporter;
90 @ISA = qw(Exporter);
91 @EXPORT = qw(
92 &AddReserve
94 &GetReservesFromItemnumber
95 &GetReservesFromBiblionumber
96 &GetReservesFromBorrowernumber
97 &GetReservesForBranch
98 &GetReservesToBranch
99 &GetReserveCount
100 &GetReserveFee
101 &GetReserveInfo
102 &GetReserveStatus
104 &GetOtherReserves
106 &ModReserveFill
107 &ModReserveAffect
108 &ModReserve
109 &ModReserveStatus
110 &ModReserveCancelAll
111 &ModReserveMinusPriority
113 &CheckReserves
114 &CanBookBeReserved
115 &CanItemBeReserved
116 &CancelReserve
117 &CancelExpiredReserves
119 &IsAvailableForItemLevelRequest
121 &AlterPriority
122 &ToggleLowestPriority
126 =head2 AddReserve
128 AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
130 =cut
132 sub AddReserve {
133 my (
134 $branch, $borrowernumber, $biblionumber,
135 $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
136 $title, $checkitem, $found
137 ) = @_;
138 my $fee =
139 GetReserveFee($borrowernumber, $biblionumber, $constraint,
140 $bibitems );
141 my $dbh = C4::Context->dbh;
142 my $const = lc substr( $constraint, 0, 1 );
143 $resdate = format_date_in_iso( $resdate ) if ( $resdate );
144 $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
145 if ($expdate) {
146 $expdate = format_date_in_iso( $expdate );
147 } else {
148 undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
150 if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
151 # Make room in reserves for this before those of a later reserve date
152 $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
154 my $waitingdate;
156 # If the reserv had the waiting status, we had the value of the resdate
157 if ( $found eq 'W' ) {
158 $waitingdate = $resdate;
161 #eval {
162 # updates take place here
163 if ( $fee > 0 ) {
164 my $nextacctno = &getnextacctno( $borrowernumber );
165 my $query = qq/
166 INSERT INTO accountlines
167 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
168 VALUES
169 (?,?,now(),?,?,'Res',?)
171 my $usth = $dbh->prepare($query);
172 $usth->execute( $borrowernumber, $nextacctno, $fee,
173 "Reserve Charge - $title", $fee );
176 #if ($const eq 'a'){
177 my $query = qq/
178 INSERT INTO reserves
179 (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
180 priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
181 VALUES
182 (?,?,?,?,?,
183 ?,?,?,?,?,?)
185 my $sth = $dbh->prepare($query);
186 $sth->execute(
187 $borrowernumber, $biblionumber, $resdate, $branch,
188 $const, $priority, $notes, $checkitem,
189 $found, $waitingdate, $expdate
192 # Send e-mail to librarian if syspref is active
193 if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
194 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
195 my $biblio = GetBiblioData($biblionumber);
196 my $letter = C4::Letters::getletter( 'reserves', 'HOLDPLACED');
197 my $admin_email_address = C4::Context->preference('KohaAdminEmailAddress');
199 my %keys = (%$borrower, %$biblio);
200 foreach my $key (keys %keys) {
201 my $replacefield = "<<$key>>";
202 $letter->{content} =~ s/$replacefield/$keys{$key}/g;
203 $letter->{title} =~ s/$replacefield/$keys{$key}/g;
206 C4::Letters::EnqueueLetter(
207 { letter => $letter,
208 borrowernumber => $borrowernumber,
209 message_transport_type => 'email',
210 from_address => $admin_email_address,
211 to_address => $admin_email_address,
220 ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value?
221 $query = qq/
222 INSERT INTO reserveconstraints
223 (borrowernumber,biblionumber,reservedate,biblioitemnumber)
224 VALUES
225 (?,?,?,?)
227 $sth = $dbh->prepare($query); # keep prepare outside the loop!
228 foreach (@$bibitems) {
229 $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
232 return; # FIXME: why not have a useful return value?
235 =head2 GetReservesFromBiblionumber
237 ($count, $title_reserves) = &GetReserves($biblionumber);
239 This function gets the list of reservations for one C<$biblionumber>, returning a count
240 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
242 =cut
244 sub GetReservesFromBiblionumber {
245 my ($biblionumber) = shift or return (0, []);
246 my ($all_dates) = shift;
247 my $dbh = C4::Context->dbh;
249 # Find the desired items in the reserves
250 my $query = "
251 SELECT branchcode,
252 timestamp AS rtimestamp,
253 priority,
254 biblionumber,
255 borrowernumber,
256 reservedate,
257 constrainttype,
258 found,
259 itemnumber,
260 reservenotes,
261 expirationdate,
262 lowestPriority
263 FROM reserves
264 WHERE biblionumber = ? ";
265 unless ( $all_dates ) {
266 $query .= "AND reservedate <= CURRENT_DATE()";
268 $query .= "ORDER BY priority";
269 my $sth = $dbh->prepare($query);
270 $sth->execute($biblionumber);
271 my @results;
272 my $i = 0;
273 while ( my $data = $sth->fetchrow_hashref ) {
275 # FIXME - What is this doing? How do constraints work?
276 if ($data->{constrainttype} eq 'o') {
277 $query = '
278 SELECT biblioitemnumber
279 FROM reserveconstraints
280 WHERE biblionumber = ?
281 AND borrowernumber = ?
282 AND reservedate = ?
284 my $csth = $dbh->prepare($query);
285 $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
286 my @bibitemno;
287 while ( my $bibitemnos = $csth->fetchrow_array ) {
288 push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref
290 my $count = scalar @bibitemno;
292 # if we have two or more different specific itemtypes
293 # reserved by same person on same day
294 my $bdata;
295 if ( $count > 1 ) {
296 $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense.
297 $i++; # $i can increase each pass, but the next @bibitemno might be smaller?
299 else {
300 # Look up the book we just found.
301 $bdata = GetBiblioItemData( $bibitemno[0] );
303 # Add the results of this latest search to the current
304 # results.
305 # FIXME - An 'each' would probably be more efficient.
306 foreach my $key ( keys %$bdata ) {
307 $data->{$key} = $bdata->{$key};
310 push @results, $data;
312 return ( $#results + 1, \@results );
315 =head2 GetReservesFromItemnumber
317 ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
319 TODO :: Description here
321 =cut
323 sub GetReservesFromItemnumber {
324 my ( $itemnumber, $all_dates ) = @_;
325 my $dbh = C4::Context->dbh;
326 my $query = "
327 SELECT reservedate,borrowernumber,branchcode
328 FROM reserves
329 WHERE itemnumber=?
331 unless ( $all_dates ) {
332 $query .= " AND reservedate <= CURRENT_DATE()";
334 my $sth_res = $dbh->prepare($query);
335 $sth_res->execute($itemnumber);
336 my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
337 return ( $reservedate, $borrowernumber, $branchcode );
340 =head2 GetReservesFromBorrowernumber
342 $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
344 TODO :: Descritpion
346 =cut
348 sub GetReservesFromBorrowernumber {
349 my ( $borrowernumber, $status ) = @_;
350 my $dbh = C4::Context->dbh;
351 my $sth;
352 if ($status) {
353 $sth = $dbh->prepare("
354 SELECT *
355 FROM reserves
356 WHERE borrowernumber=?
357 AND found =?
358 ORDER BY reservedate
360 $sth->execute($borrowernumber,$status);
361 } else {
362 $sth = $dbh->prepare("
363 SELECT *
364 FROM reserves
365 WHERE borrowernumber=?
366 ORDER BY reservedate
368 $sth->execute($borrowernumber);
370 my $data = $sth->fetchall_arrayref({});
371 return @$data;
373 #-------------------------------------------------------------------------------------
374 =head2 CanBookBeReserved
376 $error = &CanBookBeReserved($borrowernumber, $biblionumber)
378 =cut
380 sub CanBookBeReserved{
381 my ($borrowernumber, $biblionumber) = @_;
383 my $dbh = C4::Context->dbh;
384 my $biblio = GetBiblioData($biblionumber);
385 my $borrower = C4::Members::GetMember(borrowernumber=>$borrowernumber);
386 my $controlbranch = C4::Context->preference('ReservesControlBranch');
387 my $itype = C4::Context->preference('item-level_itypes');
388 my $reservesrights= 0;
389 my $reservescount = 0;
391 # we retrieve the user rights
392 my @args;
393 my $rightsquery = "SELECT categorycode, itemtype, branchcode, reservesallowed
394 FROM issuingrules
395 WHERE categorycode IN (?, '*')";
396 push @args,$borrower->{categorycode};
398 if($controlbranch eq "ItemHomeLibrary"){
399 $rightsquery .= " AND branchcode = '*'";
400 }elsif($controlbranch eq "PatronLibrary"){
401 $rightsquery .= " AND branchcode IN (?,'*')";
402 push @args, $borrower->{branchcode};
405 if(not $itype){
406 $rightsquery .= " AND itemtype IN (?,'*')";
407 push @args, $biblio->{itemtype};
408 }else{
409 $rightsquery .= " AND itemtype = '*'";
412 $rightsquery .= " ORDER BY categorycode DESC, itemtype DESC, branchcode DESC";
413 my $sthrights = $dbh->prepare($rightsquery);
414 $sthrights->execute(@args);
416 if(my $row = $sthrights->fetchrow_hashref()){
417 $reservesrights = $row->{reservesallowed};
420 @args = ();
421 # we count how many reserves the borrower have
422 my $countquery = "SELECT count(*) as count
423 FROM reserves
424 LEFT JOIN items USING (itemnumber)
425 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
426 LEFT JOIN borrowers USING (borrowernumber)
427 WHERE borrowernumber = ?
429 push @args, $borrowernumber;
431 if(not $itype){
432 $countquery .= "AND itemtype = ?";
433 push @args, $biblio->{itemtype};
436 if($controlbranch eq "PatronLibrary"){
437 $countquery .= " AND borrowers.branchcode = ? ";
438 push @args, $borrower->{branchcode};
441 my $sthcount = $dbh->prepare($countquery);
442 $sthcount->execute(@args);
444 if(my $row = $sthcount->fetchrow_hashref()){
445 $reservescount = $row->{count};
447 if($reservescount < $reservesrights){
448 return 1;
449 }else{
450 return 0;
455 =head2 CanItemBeReserved
457 $error = &CanItemBeReserved($borrowernumber, $itemnumber)
459 This function return 1 if an item can be issued by this borrower.
461 =cut
463 sub CanItemBeReserved{
464 my ($borrowernumber, $itemnumber) = @_;
466 my $dbh = C4::Context->dbh;
467 my $allowedreserves = 0;
469 my $controlbranch = C4::Context->preference('ReservesControlBranch');
470 my $itype = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
472 # we retrieve borrowers and items informations #
473 my $item = GetItem($itemnumber);
474 my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);
476 # we retrieve user rights on this itemtype and branchcode
477 my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed
478 FROM issuingrules
479 WHERE (categorycode in (?,'*') )
480 AND (itemtype IN (?,'*'))
481 AND (branchcode IN (?,'*'))
482 ORDER BY
483 categorycode DESC,
484 itemtype DESC,
485 branchcode DESC;"
488 my $querycount ="SELECT
489 count(*) as count
490 FROM reserves
491 LEFT JOIN items USING (itemnumber)
492 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
493 LEFT JOIN borrowers USING (borrowernumber)
494 WHERE borrowernumber = ?
498 my $itemtype = $item->{$itype};
499 my $categorycode = $borrower->{categorycode};
500 my $branchcode = "";
501 my $branchfield = "reserves.branchcode";
503 if( $controlbranch eq "ItemHomeLibrary" ){
504 $branchfield = "items.homebranch";
505 $branchcode = $item->{homebranch};
506 }elsif( $controlbranch eq "PatronLibrary" ){
507 $branchfield = "borrowers.branchcode";
508 $branchcode = $borrower->{branchcode};
511 # we retrieve rights
512 $sth->execute($categorycode, $itemtype, $branchcode);
513 if(my $rights = $sth->fetchrow_hashref()){
514 $itemtype = $rights->{itemtype};
515 $allowedreserves = $rights->{reservesallowed};
516 }else{
517 $itemtype = '*';
520 # we retrieve count
522 $querycount .= "AND $branchfield = ?";
524 $querycount .= " AND $itype = ?" if ($itemtype ne "*");
525 my $sthcount = $dbh->prepare($querycount);
527 if($itemtype eq "*"){
528 $sthcount->execute($borrowernumber, $branchcode);
529 }else{
530 $sthcount->execute($borrowernumber, $branchcode, $itemtype);
533 my $reservecount = "0";
534 if(my $rowcount = $sthcount->fetchrow_hashref()){
535 $reservecount = $rowcount->{count};
538 # we check if it's ok or not
539 if( $reservecount < $allowedreserves ){
540 return 1;
541 }else{
542 return 0;
545 #--------------------------------------------------------------------------------
546 =head2 GetReserveCount
548 $number = &GetReserveCount($borrowernumber);
550 this function returns the number of reservation for a borrower given on input arg.
552 =cut
554 sub GetReserveCount {
555 my ($borrowernumber) = @_;
557 my $dbh = C4::Context->dbh;
559 my $query = '
560 SELECT COUNT(*) AS counter
561 FROM reserves
562 WHERE borrowernumber = ?
564 my $sth = $dbh->prepare($query);
565 $sth->execute($borrowernumber);
566 my $row = $sth->fetchrow_hashref;
567 return $row->{counter};
570 =head2 GetOtherReserves
572 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
574 Check queued list of this document and check if this document must be transfered
576 =cut
578 sub GetOtherReserves {
579 my ($itemnumber) = @_;
580 my $messages;
581 my $nextreservinfo;
582 my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
583 if ($checkreserves) {
584 my $iteminfo = GetItem($itemnumber);
585 if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
586 $messages->{'transfert'} = $checkreserves->{'branchcode'};
587 #minus priorities of others reservs
588 ModReserveMinusPriority(
589 $itemnumber,
590 $checkreserves->{'borrowernumber'},
591 $iteminfo->{'biblionumber'}
594 #launch the subroutine dotransfer
595 C4::Items::ModItemTransfer(
596 $itemnumber,
597 $iteminfo->{'holdingbranch'},
598 $checkreserves->{'branchcode'}
603 #step 2b : case of a reservation on the same branch, set the waiting status
604 else {
605 $messages->{'waiting'} = 1;
606 ModReserveMinusPriority(
607 $itemnumber,
608 $checkreserves->{'borrowernumber'},
609 $iteminfo->{'biblionumber'}
611 ModReserveStatus($itemnumber,'W');
614 $nextreservinfo = $checkreserves->{'borrowernumber'};
617 return ( $messages, $nextreservinfo );
620 =head2 GetReserveFee
622 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
624 Calculate the fee for a reserve
626 =cut
628 sub GetReserveFee {
629 my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
631 #check for issues;
632 my $dbh = C4::Context->dbh;
633 my $const = lc substr( $constraint, 0, 1 );
634 my $query = qq/
635 SELECT * FROM borrowers
636 LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
637 WHERE borrowernumber = ?
639 my $sth = $dbh->prepare($query);
640 $sth->execute($borrowernumber);
641 my $data = $sth->fetchrow_hashref;
642 $sth->finish();
643 my $fee = $data->{'reservefee'};
644 my $cntitems = @- > $bibitems;
646 if ( $fee > 0 ) {
648 # check for items on issue
649 # first find biblioitem records
650 my @biblioitems;
651 my $sth1 = $dbh->prepare(
652 "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
653 WHERE (biblio.biblionumber = ?)"
655 $sth1->execute($biblionumber);
656 while ( my $data1 = $sth1->fetchrow_hashref ) {
657 if ( $const eq "a" ) {
658 push @biblioitems, $data1;
660 else {
661 my $found = 0;
662 my $x = 0;
663 while ( $x < $cntitems ) {
664 if ( @$bibitems->{'biblioitemnumber'} ==
665 $data->{'biblioitemnumber'} )
667 $found = 1;
669 $x++;
671 if ( $const eq 'o' ) {
672 if ( $found == 1 ) {
673 push @biblioitems, $data1;
676 else {
677 if ( $found == 0 ) {
678 push @biblioitems, $data1;
683 $sth1->finish;
684 my $cntitemsfound = @biblioitems;
685 my $issues = 0;
686 my $x = 0;
687 my $allissued = 1;
688 while ( $x < $cntitemsfound ) {
689 my $bitdata = $biblioitems[$x];
690 my $sth2 = $dbh->prepare(
691 "SELECT * FROM items
692 WHERE biblioitemnumber = ?"
694 $sth2->execute( $bitdata->{'biblioitemnumber'} );
695 while ( my $itdata = $sth2->fetchrow_hashref ) {
696 my $sth3 = $dbh->prepare(
697 "SELECT * FROM issues
698 WHERE itemnumber = ?"
700 $sth3->execute( $itdata->{'itemnumber'} );
701 if ( my $isdata = $sth3->fetchrow_hashref ) {
703 else {
704 $allissued = 0;
707 $x++;
709 if ( $allissued == 0 ) {
710 my $rsth =
711 $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
712 $rsth->execute($biblionumber);
713 if ( my $rdata = $rsth->fetchrow_hashref ) {
715 else {
716 $fee = 0;
720 return $fee;
723 =head2 GetReservesToBranch
725 @transreserv = GetReservesToBranch( $frombranch );
727 Get reserve list for a given branch
729 =cut
731 sub GetReservesToBranch {
732 my ( $frombranch ) = @_;
733 my $dbh = C4::Context->dbh;
734 my $sth = $dbh->prepare(
735 "SELECT borrowernumber,reservedate,itemnumber,timestamp
736 FROM reserves
737 WHERE priority='0'
738 AND branchcode=?"
740 $sth->execute( $frombranch );
741 my @transreserv;
742 my $i = 0;
743 while ( my $data = $sth->fetchrow_hashref ) {
744 $transreserv[$i] = $data;
745 $i++;
747 return (@transreserv);
750 =head2 GetReservesForBranch
752 @transreserv = GetReservesForBranch($frombranch);
754 =cut
756 sub GetReservesForBranch {
757 my ($frombranch) = @_;
758 my $dbh = C4::Context->dbh;
759 my $query = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
760 FROM reserves
761 WHERE priority='0'
762 AND found='W' ";
763 if ($frombranch){
764 $query .= " AND branchcode=? ";
766 $query .= "ORDER BY waitingdate" ;
767 my $sth = $dbh->prepare($query);
768 if ($frombranch){
769 $sth->execute($frombranch);
771 else {
772 $sth->execute();
774 my @transreserv;
775 my $i = 0;
776 while ( my $data = $sth->fetchrow_hashref ) {
777 $transreserv[$i] = $data;
778 $i++;
780 return (@transreserv);
783 sub GetReserveStatus {
784 my ($itemnumber) = @_;
786 my $dbh = C4::Context->dbh;
788 my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
790 $itemstatus->execute($itemnumber);
791 my ($found) = $itemstatus->fetchrow_array;
792 return $found;
795 =head2 CheckReserves
797 ($status, $reserve) = &CheckReserves($itemnumber);
798 ($status, $reserve) = &CheckReserves(undef, $barcode);
800 Find a book in the reserves.
802 C<$itemnumber> is the book's item number.
804 As I understand it, C<&CheckReserves> looks for the given item in the
805 reserves. If it is found, that's a match, and C<$status> is set to
806 C<Waiting>.
808 Otherwise, it finds the most important item in the reserves with the
809 same biblio number as this book (I'm not clear on this) and returns it
810 with C<$status> set to C<Reserved>.
812 C<&CheckReserves> returns a two-element list:
814 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
816 C<$reserve> is the reserve item that matched. It is a
817 reference-to-hash whose keys are mostly the fields of the reserves
818 table in the Koha database.
820 =cut
822 sub CheckReserves {
823 my ( $item, $barcode ) = @_;
824 my $dbh = C4::Context->dbh;
825 my $sth;
826 my $select = "
827 SELECT items.biblionumber,
828 items.biblioitemnumber,
829 itemtypes.notforloan,
830 items.notforloan AS itemnotforloan,
831 items.itemnumber
832 FROM items
833 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
834 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
837 if ($item) {
838 $sth = $dbh->prepare("$select WHERE itemnumber = ?");
839 $sth->execute($item);
841 else {
842 $sth = $dbh->prepare("$select WHERE barcode = ?");
843 $sth->execute($barcode);
845 # note: we get the itemnumber because we might have started w/ just the barcode. Now we know for sure we have it.
846 my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
848 return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
850 # if item is not for loan it cannot be reserved either.....
851 # execpt where items.notforloan < 0 : This indicates the item is holdable.
852 return ( 0, 0 ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
854 # Find this item in the reserves
855 my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
857 # $priority and $highest are used to find the most important item
858 # in the list returned by &_Findgroupreserve. (The lower $priority,
859 # the more important the item.)
860 # $highest is the most important item we've seen so far.
861 my $highest;
862 if (scalar @reserves) {
863 my $priority = 10000000;
864 foreach my $res (@reserves) {
865 if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
866 return ( "Waiting", $res ); # Found it
867 } else {
868 # See if this item is more important than what we've got so far
869 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
870 $priority = $res->{'priority'};
871 $highest = $res;
877 # If we get this far, then no exact match was found.
878 # We return the most important (i.e. next) reservation.
879 if ($highest) {
880 $highest->{'itemnumber'} = $item;
881 return ( "Reserved", $highest );
883 else {
884 return ( 0, 0 );
888 =head2 CancelExpiredReserves
890 CancelExpiredReserves();
892 Cancels all reserves with an expiration date from before today.
894 =cut
896 sub CancelExpiredReserves {
898 my $dbh = C4::Context->dbh;
899 my $sth = $dbh->prepare( "
900 SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() )
901 AND expirationdate IS NOT NULL
902 " );
903 $sth->execute();
905 while ( my $res = $sth->fetchrow_hashref() ) {
906 CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
911 =head2 CancelReserve
913 &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
915 Cancels a reserve.
917 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
918 cancel, but not both: if both are given, C<&CancelReserve> does
919 nothing.
921 C<$borrowernumber> is the borrower number of the patron on whose
922 behalf the book was reserved.
924 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
925 priorities of the other people who are waiting on the book.
927 =cut
929 sub CancelReserve {
930 my ( $biblio, $item, $borr ) = @_;
931 my $dbh = C4::Context->dbh;
932 if ( $item and $borr ) {
933 # removing a waiting reserve record....
934 # update the database...
935 my $query = "
936 UPDATE reserves
937 SET cancellationdate = now(),
938 found = Null,
939 priority = 0
940 WHERE itemnumber = ?
941 AND borrowernumber = ?
943 my $sth = $dbh->prepare($query);
944 $sth->execute( $item, $borr );
945 $sth->finish;
946 $query = "
947 INSERT INTO old_reserves
948 SELECT * FROM reserves
949 WHERE itemnumber = ?
950 AND borrowernumber = ?
952 $sth = $dbh->prepare($query);
953 $sth->execute( $item, $borr );
954 $query = "
955 DELETE FROM reserves
956 WHERE itemnumber = ?
957 AND borrowernumber = ?
959 $sth = $dbh->prepare($query);
960 $sth->execute( $item, $borr );
962 else {
963 # removing a reserve record....
964 # get the prioritiy on this record....
965 my $priority;
966 my $query = qq/
967 SELECT priority FROM reserves
968 WHERE biblionumber = ?
969 AND borrowernumber = ?
970 AND cancellationdate IS NULL
971 AND itemnumber IS NULL
973 my $sth = $dbh->prepare($query);
974 $sth->execute( $biblio, $borr );
975 ($priority) = $sth->fetchrow_array;
976 $sth->finish;
977 $query = qq/
978 UPDATE reserves
979 SET cancellationdate = now(),
980 found = Null,
981 priority = 0
982 WHERE biblionumber = ?
983 AND borrowernumber = ?
986 # update the database, removing the record...
987 $sth = $dbh->prepare($query);
988 $sth->execute( $biblio, $borr );
989 $sth->finish;
991 $query = qq/
992 INSERT INTO old_reserves
993 SELECT * FROM reserves
994 WHERE biblionumber = ?
995 AND borrowernumber = ?
997 $sth = $dbh->prepare($query);
998 $sth->execute( $biblio, $borr );
1000 $query = qq/
1001 DELETE FROM reserves
1002 WHERE biblionumber = ?
1003 AND borrowernumber = ?
1005 $sth = $dbh->prepare($query);
1006 $sth->execute( $biblio, $borr );
1008 # now fix the priority on the others....
1009 _FixPriority( $biblio, $borr );
1013 =head2 ModReserve
1015 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
1017 Change a hold request's priority or cancel it.
1019 C<$rank> specifies the effect of the change. If C<$rank>
1020 is 'W' or 'n', nothing happens. This corresponds to leaving a
1021 request alone when changing its priority in the holds queue
1022 for a bib.
1024 If C<$rank> is 'del', the hold request is cancelled.
1026 If C<$rank> is an integer greater than zero, the priority of
1027 the request is set to that value. Since priority != 0 means
1028 that the item is not waiting on the hold shelf, setting the
1029 priority to a non-zero value also sets the request's found
1030 status and waiting date to NULL.
1032 The optional C<$itemnumber> parameter is used only when
1033 C<$rank> is a non-zero integer; if supplied, the itemnumber
1034 of the hold request is set accordingly; if omitted, the itemnumber
1035 is cleared.
1037 B<FIXME:> Note that the forgoing can have the effect of causing
1038 item-level hold requests to turn into title-level requests. This
1039 will be fixed once reserves has separate columns for requested
1040 itemnumber and supplying itemnumber.
1042 =cut
1044 sub ModReserve {
1045 #subroutine to update a reserve
1046 my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
1047 return if $rank eq "W";
1048 return if $rank eq "n";
1049 my $dbh = C4::Context->dbh;
1050 if ( $rank eq "del" ) {
1051 my $query = qq/
1052 UPDATE reserves
1053 SET cancellationdate=now()
1054 WHERE biblionumber = ?
1055 AND borrowernumber = ?
1057 my $sth = $dbh->prepare($query);
1058 $sth->execute( $biblio, $borrower );
1059 $sth->finish;
1060 $query = qq/
1061 INSERT INTO old_reserves
1062 SELECT *
1063 FROM reserves
1064 WHERE biblionumber = ?
1065 AND borrowernumber = ?
1067 $sth = $dbh->prepare($query);
1068 $sth->execute( $biblio, $borrower );
1069 $query = qq/
1070 DELETE FROM reserves
1071 WHERE biblionumber = ?
1072 AND borrowernumber = ?
1074 $sth = $dbh->prepare($query);
1075 $sth->execute( $biblio, $borrower );
1078 elsif ($rank =~ /^\d+/ and $rank > 0) {
1079 my $query = qq/
1080 UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1081 WHERE biblionumber = ?
1082 AND borrowernumber = ?
1084 my $sth = $dbh->prepare($query);
1085 $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1086 $sth->finish;
1087 _FixPriority( $biblio, $borrower, $rank);
1091 =head2 ModReserveFill
1093 &ModReserveFill($reserve);
1095 Fill a reserve. If I understand this correctly, this means that the
1096 reserved book has been found and given to the patron who reserved it.
1098 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1099 whose keys are fields from the reserves table in the Koha database.
1101 =cut
1103 sub ModReserveFill {
1104 my ($res) = @_;
1105 my $dbh = C4::Context->dbh;
1106 # fill in a reserve record....
1107 my $biblionumber = $res->{'biblionumber'};
1108 my $borrowernumber = $res->{'borrowernumber'};
1109 my $resdate = $res->{'reservedate'};
1111 # get the priority on this record....
1112 my $priority;
1113 my $query = "SELECT priority
1114 FROM reserves
1115 WHERE biblionumber = ?
1116 AND borrowernumber = ?
1117 AND reservedate = ?";
1118 my $sth = $dbh->prepare($query);
1119 $sth->execute( $biblionumber, $borrowernumber, $resdate );
1120 ($priority) = $sth->fetchrow_array;
1121 $sth->finish;
1123 # update the database...
1124 $query = "UPDATE reserves
1125 SET found = 'F',
1126 priority = 0
1127 WHERE biblionumber = ?
1128 AND reservedate = ?
1129 AND borrowernumber = ?
1131 $sth = $dbh->prepare($query);
1132 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1133 $sth->finish;
1135 # move to old_reserves
1136 $query = "INSERT INTO old_reserves
1137 SELECT * FROM reserves
1138 WHERE biblionumber = ?
1139 AND reservedate = ?
1140 AND borrowernumber = ?
1142 $sth = $dbh->prepare($query);
1143 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1144 $query = "DELETE FROM reserves
1145 WHERE biblionumber = ?
1146 AND reservedate = ?
1147 AND borrowernumber = ?
1149 $sth = $dbh->prepare($query);
1150 $sth->execute( $biblionumber, $resdate, $borrowernumber );
1152 # now fix the priority on the others (if the priority wasn't
1153 # already sorted!)....
1154 unless ( $priority == 0 ) {
1155 _FixPriority( $biblionumber, $borrowernumber );
1159 =head2 ModReserveStatus
1161 &ModReserveStatus($itemnumber, $newstatus);
1163 Update the reserve status for the active (priority=0) reserve.
1165 $itemnumber is the itemnumber the reserve is on
1167 $newstatus is the new status.
1169 =cut
1171 sub ModReserveStatus {
1173 #first : check if we have a reservation for this item .
1174 my ($itemnumber, $newstatus) = @_;
1175 my $dbh = C4::Context->dbh;
1176 my $query = " UPDATE reserves
1177 SET found=?,waitingdate = now()
1178 WHERE itemnumber=?
1179 AND found IS NULL
1180 AND priority = 0
1182 my $sth_set = $dbh->prepare($query);
1183 $sth_set->execute( $newstatus, $itemnumber );
1185 if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1186 CartToShelf( $itemnumber );
1190 =head2 ModReserveAffect
1192 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1194 This function affect an item and a status for a given reserve
1195 The itemnumber parameter is used to find the biblionumber.
1196 with the biblionumber & the borrowernumber, we can affect the itemnumber
1197 to the correct reserve.
1199 if $transferToDo is not set, then the status is set to "Waiting" as well.
1200 otherwise, a transfer is on the way, and the end of the transfer will
1201 take care of the waiting status
1203 =cut
1205 sub ModReserveAffect {
1206 my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1207 my $dbh = C4::Context->dbh;
1209 # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1210 # attached to $itemnumber
1211 my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1212 $sth->execute($itemnumber);
1213 my ($biblionumber) = $sth->fetchrow;
1215 # get request - need to find out if item is already
1216 # waiting in order to not send duplicate hold filled notifications
1217 my $request = GetReserveInfo($borrowernumber, $biblionumber);
1218 my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1220 # If we affect a reserve that has to be transfered, don't set to Waiting
1221 my $query;
1222 if ($transferToDo) {
1223 $query = "
1224 UPDATE reserves
1225 SET priority = 0,
1226 itemnumber = ?,
1227 found = 'T'
1228 WHERE borrowernumber = ?
1229 AND biblionumber = ?
1232 else {
1233 # affect the reserve to Waiting as well.
1234 $query = "
1235 UPDATE reserves
1236 SET priority = 0,
1237 found = 'W',
1238 waitingdate=now(),
1239 itemnumber = ?
1240 WHERE borrowernumber = ?
1241 AND biblionumber = ?
1244 $sth = $dbh->prepare($query);
1245 $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1246 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1248 if ( C4::Context->preference("ReturnToShelvingCart") ) {
1249 CartToShelf( $itemnumber );
1252 return;
1255 =head2 ModReserveCancelAll
1257 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1259 function to cancel reserv,check other reserves, and transfer document if it's necessary
1261 =cut
1263 sub ModReserveCancelAll {
1264 my $messages;
1265 my $nextreservinfo;
1266 my ( $itemnumber, $borrowernumber ) = @_;
1268 #step 1 : cancel the reservation
1269 my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1271 #step 2 launch the subroutine of the others reserves
1272 ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1274 return ( $messages, $nextreservinfo );
1277 =head2 ModReserveMinusPriority
1279 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1281 Reduce the values of queuded list
1283 =cut
1285 sub ModReserveMinusPriority {
1286 my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1288 #first step update the value of the first person on reserv
1289 my $dbh = C4::Context->dbh;
1290 my $query = "
1291 UPDATE reserves
1292 SET priority = 0 , itemnumber = ?
1293 WHERE borrowernumber=?
1294 AND biblionumber=?
1296 my $sth_upd = $dbh->prepare($query);
1297 $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1298 # second step update all others reservs
1299 _FixPriority($biblionumber, $borrowernumber, '0');
1302 =head2 GetReserveInfo
1304 &GetReserveInfo($borrowernumber,$biblionumber);
1306 Get item and borrower details for a current hold.
1307 Current implementation this query should have a single result.
1309 =cut
1311 sub GetReserveInfo {
1312 my ( $borrowernumber, $biblionumber ) = @_;
1313 my $dbh = C4::Context->dbh;
1314 my $strsth="SELECT
1315 reservedate,
1316 reservenotes,
1317 reserves.borrowernumber,
1318 reserves.biblionumber,
1319 reserves.branchcode,
1320 reserves.waitingdate,
1321 notificationdate,
1322 reminderdate,
1323 priority,
1324 found,
1325 firstname,
1326 surname,
1327 phone,
1328 email,
1329 address,
1330 address2,
1331 cardnumber,
1332 city,
1333 zipcode,
1334 biblio.title,
1335 biblio.author,
1336 items.holdingbranch,
1337 items.itemcallnumber,
1338 items.itemnumber,
1339 items.location,
1340 barcode,
1341 notes
1342 FROM reserves
1343 LEFT JOIN items USING(itemnumber)
1344 LEFT JOIN borrowers USING(borrowernumber)
1345 LEFT JOIN biblio ON (reserves.biblionumber=biblio.biblionumber)
1346 WHERE
1347 reserves.borrowernumber=?
1348 AND reserves.biblionumber=?";
1349 my $sth = $dbh->prepare($strsth);
1350 $sth->execute($borrowernumber,$biblionumber);
1352 my $data = $sth->fetchrow_hashref;
1353 return $data;
1357 =head2 IsAvailableForItemLevelRequest
1359 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1361 Checks whether a given item record is available for an
1362 item-level hold request. An item is available if
1364 * it is not lost AND
1365 * it is not damaged AND
1366 * it is not withdrawn AND
1367 * does not have a not for loan value > 0
1369 Whether or not the item is currently on loan is
1370 also checked - if the AllowOnShelfHolds system preference
1371 is ON, an item can be requested even if it is currently
1372 on loan to somebody else. If the system preference
1373 is OFF, an item that is currently checked out cannot
1374 be the target of an item-level hold request.
1376 Note that IsAvailableForItemLevelRequest() does not
1377 check if the staff operator is authorized to place
1378 a request on the item - in particular,
1379 this routine does not check IndependantBranches
1380 and canreservefromotherbranches.
1382 =cut
1384 sub IsAvailableForItemLevelRequest {
1385 my $itemnumber = shift;
1387 my $item = GetItem($itemnumber);
1389 # must check the notforloan setting of the itemtype
1390 # FIXME - a lot of places in the code do this
1391 # or something similar - need to be
1392 # consolidated
1393 my $dbh = C4::Context->dbh;
1394 my $notforloan_query;
1395 if (C4::Context->preference('item-level_itypes')) {
1396 $notforloan_query = "SELECT itemtypes.notforloan
1397 FROM items
1398 JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1399 WHERE itemnumber = ?";
1400 } else {
1401 $notforloan_query = "SELECT itemtypes.notforloan
1402 FROM items
1403 JOIN biblioitems USING (biblioitemnumber)
1404 JOIN itemtypes USING (itemtype)
1405 WHERE itemnumber = ?";
1407 my $sth = $dbh->prepare($notforloan_query);
1408 $sth->execute($itemnumber);
1409 my $notforloan_per_itemtype = 0;
1410 if (my ($notforloan) = $sth->fetchrow_array) {
1411 $notforloan_per_itemtype = 1 if $notforloan;
1414 my $available_per_item = 1;
1415 $available_per_item = 0 if $item->{itemlost} or
1416 ( $item->{notforloan} > 0 ) or
1417 ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1418 $item->{wthdrawn} or
1419 $notforloan_per_itemtype;
1422 if (C4::Context->preference('AllowOnShelfHolds')) {
1423 return $available_per_item;
1424 } else {
1425 return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W"));
1429 =head2 AlterPriority
1431 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1433 This function changes a reserve's priority up, down, to the top, or to the bottom.
1434 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1436 =cut
1438 sub AlterPriority {
1439 my ( $where, $borrowernumber, $biblionumber ) = @_;
1441 my $dbh = C4::Context->dbh;
1443 ## Find this reserve
1444 my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1445 $sth->execute( $biblionumber, $borrowernumber );
1446 my $reserve = $sth->fetchrow_hashref();
1447 $sth->finish();
1449 if ( $where eq 'up' || $where eq 'down' ) {
1451 my $priority = $reserve->{'priority'};
1452 $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1453 _FixPriority( $biblionumber, $borrowernumber, $priority )
1455 } elsif ( $where eq 'top' ) {
1457 _FixPriority( $biblionumber, $borrowernumber, '1' )
1459 } elsif ( $where eq 'bottom' ) {
1461 _FixPriority( $biblionumber, $borrowernumber, '999999' )
1466 =head2 ToggleLowestPriority
1468 ToggleLowestPriority( $borrowernumber, $biblionumber );
1470 This function sets the lowestPriority field to true if is false, and false if it is true.
1472 =cut
1474 sub ToggleLowestPriority {
1475 my ( $borrowernumber, $biblionumber ) = @_;
1477 my $dbh = C4::Context->dbh;
1479 my $sth = $dbh->prepare(
1480 "UPDATE reserves SET lowestPriority = NOT lowestPriority
1481 WHERE biblionumber = ?
1482 AND borrowernumber = ?"
1484 $sth->execute(
1485 $biblionumber,
1486 $borrowernumber,
1488 $sth->finish;
1490 _FixPriority( $biblionumber, $borrowernumber, '999999' );
1493 =head2 _FixPriority
1495 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1497 Only used internally (so don't export it)
1498 Changed how this functions works #
1499 Now just gets an array of reserves in the rank order and updates them with
1500 the array index (+1 as array starts from 0)
1501 and if $rank is supplied will splice item from the array and splice it back in again
1502 in new priority rank
1504 =cut
1506 sub _FixPriority {
1507 my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1508 my $dbh = C4::Context->dbh;
1509 if ( $rank eq "del" ) {
1510 CancelReserve( $biblio, undef, $borrowernumber );
1512 if ( $rank eq "W" || $rank eq "0" ) {
1514 # make sure priority for waiting or in-transit items is 0
1515 my $query = qq/
1516 UPDATE reserves
1517 SET priority = 0
1518 WHERE biblionumber = ?
1519 AND borrowernumber = ?
1520 AND found IN ('W', 'T')
1522 my $sth = $dbh->prepare($query);
1523 $sth->execute( $biblio, $borrowernumber );
1525 my @priority;
1526 my @reservedates;
1528 # get whats left
1529 # FIXME adding a new security in returned elements for changing priority,
1530 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1531 # This is wrong a waiting reserve has W set
1532 # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1533 my $query = qq/
1534 SELECT borrowernumber, reservedate, constrainttype
1535 FROM reserves
1536 WHERE biblionumber = ?
1537 AND ((found <> 'W' AND found <> 'T') or found is NULL)
1538 ORDER BY priority ASC
1540 my $sth = $dbh->prepare($query);
1541 $sth->execute($biblio);
1542 while ( my $line = $sth->fetchrow_hashref ) {
1543 push( @reservedates, $line );
1544 push( @priority, $line );
1547 # To find the matching index
1548 my $i;
1549 my $key = -1; # to allow for 0 to be a valid result
1550 for ( $i = 0 ; $i < @priority ; $i++ ) {
1551 if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1552 $key = $i; # save the index
1553 last;
1557 # if index exists in array then move it to new position
1558 if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1559 my $new_rank = $rank -
1560 1; # $new_rank is what you want the new index to be in the array
1561 my $moving_item = splice( @priority, $key, 1 );
1562 splice( @priority, $new_rank, 0, $moving_item );
1565 # now fix the priority on those that are left....
1566 $query = "
1567 UPDATE reserves
1568 SET priority = ?
1569 WHERE biblionumber = ?
1570 AND borrowernumber = ?
1571 AND reservedate = ?
1572 AND found IS NULL
1574 $sth = $dbh->prepare($query);
1575 for ( my $j = 0 ; $j < @priority ; $j++ ) {
1576 $sth->execute(
1577 $j + 1, $biblio,
1578 $priority[$j]->{'borrowernumber'},
1579 $priority[$j]->{'reservedate'}
1581 $sth->finish;
1584 $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1585 $sth->execute();
1587 unless ( $ignoreSetLowestRank ) {
1588 while ( my $res = $sth->fetchrow_hashref() ) {
1589 _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1594 =head2 _Findgroupreserve
1596 @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1598 Looks for an item-specific match first, then for a title-level match, returning the
1599 first match found. If neither, then we look for a 3rd kind of match based on
1600 reserve constraints.
1602 TODO: add more explanation about reserve constraints
1604 C<&_Findgroupreserve> returns :
1605 C<@results> is an array of references-to-hash whose keys are mostly
1606 fields from the reserves table of the Koha database, plus
1607 C<biblioitemnumber>.
1609 =cut
1611 sub _Findgroupreserve {
1612 my ( $bibitem, $biblio, $itemnumber ) = @_;
1613 my $dbh = C4::Context->dbh;
1615 # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1616 # check for exact targetted match
1617 my $item_level_target_query = qq/
1618 SELECT reserves.biblionumber AS biblionumber,
1619 reserves.borrowernumber AS borrowernumber,
1620 reserves.reservedate AS reservedate,
1621 reserves.branchcode AS branchcode,
1622 reserves.cancellationdate AS cancellationdate,
1623 reserves.found AS found,
1624 reserves.reservenotes AS reservenotes,
1625 reserves.priority AS priority,
1626 reserves.timestamp AS timestamp,
1627 biblioitems.biblioitemnumber AS biblioitemnumber,
1628 reserves.itemnumber AS itemnumber
1629 FROM reserves
1630 JOIN biblioitems USING (biblionumber)
1631 JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1632 WHERE found IS NULL
1633 AND priority > 0
1634 AND item_level_request = 1
1635 AND itemnumber = ?
1636 AND reservedate <= CURRENT_DATE()
1638 my $sth = $dbh->prepare($item_level_target_query);
1639 $sth->execute($itemnumber);
1640 my @results;
1641 if ( my $data = $sth->fetchrow_hashref ) {
1642 push( @results, $data );
1644 return @results if @results;
1646 # check for title-level targetted match
1647 my $title_level_target_query = qq/
1648 SELECT reserves.biblionumber AS biblionumber,
1649 reserves.borrowernumber AS borrowernumber,
1650 reserves.reservedate AS reservedate,
1651 reserves.branchcode AS branchcode,
1652 reserves.cancellationdate AS cancellationdate,
1653 reserves.found AS found,
1654 reserves.reservenotes AS reservenotes,
1655 reserves.priority AS priority,
1656 reserves.timestamp AS timestamp,
1657 biblioitems.biblioitemnumber AS biblioitemnumber,
1658 reserves.itemnumber AS itemnumber
1659 FROM reserves
1660 JOIN biblioitems USING (biblionumber)
1661 JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1662 WHERE found IS NULL
1663 AND priority > 0
1664 AND item_level_request = 0
1665 AND hold_fill_targets.itemnumber = ?
1666 AND reservedate <= CURRENT_DATE()
1668 $sth = $dbh->prepare($title_level_target_query);
1669 $sth->execute($itemnumber);
1670 @results = ();
1671 if ( my $data = $sth->fetchrow_hashref ) {
1672 push( @results, $data );
1674 return @results if @results;
1676 my $query = qq/
1677 SELECT reserves.biblionumber AS biblionumber,
1678 reserves.borrowernumber AS borrowernumber,
1679 reserves.reservedate AS reservedate,
1680 reserves.branchcode AS branchcode,
1681 reserves.cancellationdate AS cancellationdate,
1682 reserves.found AS found,
1683 reserves.reservenotes AS reservenotes,
1684 reserves.priority AS priority,
1685 reserves.timestamp AS timestamp,
1686 reserveconstraints.biblioitemnumber AS biblioitemnumber,
1687 reserves.itemnumber AS itemnumber
1688 FROM reserves
1689 LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1690 WHERE reserves.biblionumber = ?
1691 AND ( ( reserveconstraints.biblioitemnumber = ?
1692 AND reserves.borrowernumber = reserveconstraints.borrowernumber
1693 AND reserves.reservedate = reserveconstraints.reservedate )
1694 OR reserves.constrainttype='a' )
1695 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1696 AND reserves.reservedate <= CURRENT_DATE()
1698 $sth = $dbh->prepare($query);
1699 $sth->execute( $biblio, $bibitem, $itemnumber );
1700 @results = ();
1701 while ( my $data = $sth->fetchrow_hashref ) {
1702 push( @results, $data );
1704 return @results;
1707 =head2 _koha_notify_reserve
1709 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1711 Sends a notification to the patron that their hold has been filled (through
1712 ModReserveAffect, _not_ ModReserveFill)
1714 =cut
1716 sub _koha_notify_reserve {
1717 my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1719 my $dbh = C4::Context->dbh;
1720 my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1721 my $letter_code;
1722 my $print_mode = 0;
1723 my $messagingprefs;
1724 if ( $borrower->{'email'} || $borrower->{'smsalertnumber'} ) {
1725 $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } );
1727 return if ( !defined( $messagingprefs->{'letter_code'} ) );
1728 $letter_code = $messagingprefs->{'letter_code'};
1729 } else {
1730 $letter_code = 'HOLD_PRINT';
1731 $print_mode = 1;
1734 my $sth = $dbh->prepare("
1735 SELECT *
1736 FROM reserves
1737 WHERE borrowernumber = ?
1738 AND biblionumber = ?
1740 $sth->execute( $borrowernumber, $biblionumber );
1741 my $reserve = $sth->fetchrow_hashref;
1742 my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1744 my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1746 my $letter = getletter( 'reserves', $letter_code );
1747 die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1749 C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1750 C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1751 C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1752 C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1754 if ( $reserve->{'itemnumber'} ) {
1755 C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1757 my $today = C4::Dates->new()->output();
1758 $letter->{'title'} =~ s/<<today>>/$today/g;
1759 $letter->{'content'} =~ s/<<today>>/$today/g;
1760 $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1762 if ( $print_mode ) {
1763 C4::Letters::EnqueueLetter( {
1764 letter => $letter,
1765 borrowernumber => $borrowernumber,
1766 message_transport_type => 'print',
1767 } );
1769 return;
1772 if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1773 # aka, 'email' in ->{'transports'}
1774 C4::Letters::EnqueueLetter(
1775 { letter => $letter,
1776 borrowernumber => $borrowernumber,
1777 message_transport_type => 'email',
1778 from_address => $admin_email_address,
1783 if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1784 C4::Letters::EnqueueLetter(
1785 { letter => $letter,
1786 borrowernumber => $borrowernumber,
1787 message_transport_type => 'sms',
1793 =head2 _ShiftPriorityByDateAndPriority
1795 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1797 This increments the priority of all reserves after the one
1798 with either the lowest date after C<$reservedate>
1799 or the lowest priority after C<$priority>.
1801 It effectively makes room for a new reserve to be inserted with a certain
1802 priority, which is returned.
1804 This is most useful when the reservedate can be set by the user. It allows
1805 the new reserve to be placed before other reserves that have a later
1806 reservedate. Since priority also is set by the form in reserves/request.pl
1807 the sub accounts for that too.
1809 =cut
1811 sub _ShiftPriorityByDateAndPriority {
1812 my ( $biblio, $resdate, $new_priority ) = @_;
1814 my $dbh = C4::Context->dbh;
1815 my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1816 my $sth = $dbh->prepare( $query );
1817 $sth->execute( $biblio, $resdate, $new_priority );
1818 my $min_priority = $sth->fetchrow;
1819 # if no such matches are found, $new_priority remains as original value
1820 $new_priority = $min_priority if ( $min_priority );
1822 # Shift the priority up by one; works in conjunction with the next SQL statement
1823 $query = "UPDATE reserves
1824 SET priority = priority+1
1825 WHERE biblionumber = ?
1826 AND borrowernumber = ?
1827 AND reservedate = ?
1828 AND found IS NULL";
1829 my $sth_update = $dbh->prepare( $query );
1831 # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1832 $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1833 $sth = $dbh->prepare( $query );
1834 $sth->execute( $new_priority, $biblio );
1835 while ( my $row = $sth->fetchrow_hashref ) {
1836 $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1839 return $new_priority; # so the caller knows what priority they wind up receiving
1842 =head1 AUTHOR
1844 Koha Development Team <http://koha-community.org/>
1846 =cut