bug 4865: added dependency for using memcached for sessions
[koha.git] / C4 / Reserves.pm
1 package C4::Reserves;
2
3 # Copyright 2000-2002 Katipo Communications
4 #           2006 SAN Ouest Provence
5 #           2007-2010 BibLibre Paul POULAIN
6 #
7 # This file is part of Koha.
8 #
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.
13 #
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.
17 #
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.
21
22
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;
32
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 );
40
41 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
42
43 =head1 NAME
44
45 C4::Reserves - Koha functions for dealing with reservation.
46
47 =head1 SYNOPSIS
48
49   use C4::Reserves;
50
51 =head1 DESCRIPTION
52
53 This modules provides somes functions to deal with reservations.
54
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
74   
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
81
82 =head1 FUNCTIONS
83
84 =cut
85
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
93   
94         &GetReservesFromItemnumber
95         &GetReservesFromBiblionumber
96         &GetReservesFromBorrowernumber
97         &GetReservesForBranch
98         &GetReservesToBranch
99         &GetReserveCount
100         &GetReserveFee
101                 &GetReserveInfo
102         &GetReserveStatus
103         
104         &GetOtherReserves
105         
106         &ModReserveFill
107         &ModReserveAffect
108         &ModReserve
109         &ModReserveStatus
110         &ModReserveCancelAll
111         &ModReserveMinusPriority
112         
113         &CheckReserves
114         &CanBookBeReserved
115         &CanItemBeReserved
116         &CancelReserve
117         &CancelExpiredReserves
118
119         &IsAvailableForItemLevelRequest
120         
121         &AlterPriority
122         &ToggleLowestPriority
123     );
124 }    
125
126 =head2 AddReserve
127
128     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
129
130 =cut
131
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'
149     }
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 );
153     }
154     my $waitingdate;
155
156     # If the reserv had the waiting status, we had the value of the resdate
157     if ( $found eq 'W' ) {
158         $waitingdate = $resdate;
159     }
160
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',?)
170     /;
171         my $usth = $dbh->prepare($query);
172         $usth->execute( $borrowernumber, $nextacctno, $fee,
173             "Reserve Charge - $title", $fee );
174     }
175
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              ?,?,?,?,?,?)
184     /;
185     my $sth = $dbh->prepare($query);
186     $sth->execute(
187         $borrowernumber, $biblionumber, $resdate, $branch,
188         $const,          $priority,     $notes,   $checkitem,
189         $found,          $waitingdate,  $expdate
190     );
191
192     # Send e-mail to librarian if syspref is active
193     if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
194         my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
195         my $biblio   = GetBiblioData($biblionumber);
196         my $letter = C4::Letters::getletter( 'reserves', 'HOLDPLACED');
197         my $branchcode = $borrower->{branchcode};
198         my $branch_details = C4::Branch::GetBranchDetail($branchcode);
199         my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
200
201         my %keys = (%$borrower, %$biblio);
202         foreach my $key (keys %keys) {
203             my $replacefield = "<<$key>>";
204             $letter->{content} =~ s/$replacefield/$keys{$key}/g;
205             $letter->{title} =~ s/$replacefield/$keys{$key}/g;
206         }
207         
208         C4::Letters::EnqueueLetter(
209                             {   letter                 => $letter,
210                                 borrowernumber         => $borrowernumber,
211                                 message_transport_type => 'email',
212                                 from_address           => $admin_email_address,
213                                 to_address           => $admin_email_address,
214                             }
215                         );
216         
217
218     }
219
220
221     #}
222     ($const eq "o" || $const eq "e") or return;   # FIXME: why not have a useful return value?
223     $query = qq/
224         INSERT INTO reserveconstraints
225             (borrowernumber,biblionumber,reservedate,biblioitemnumber)
226         VALUES
227             (?,?,?,?)
228     /;
229     $sth = $dbh->prepare($query);    # keep prepare outside the loop!
230     foreach (@$bibitems) {
231         $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
232     }
233         
234     return;     # FIXME: why not have a useful return value?
235 }
236
237 =head2 GetReservesFromBiblionumber
238
239   ($count, $title_reserves) = &GetReserves($biblionumber);
240
241 This function gets the list of reservations for one C<$biblionumber>, returning a count
242 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
243
244 =cut
245
246 sub GetReservesFromBiblionumber {
247     my ($biblionumber) = shift or return (0, []);
248     my ($all_dates) = shift;
249     my $dbh   = C4::Context->dbh;
250
251     # Find the desired items in the reserves
252     my $query = "
253         SELECT  branchcode,
254                 timestamp AS rtimestamp,
255                 priority,
256                 biblionumber,
257                 borrowernumber,
258                 reservedate,
259                 constrainttype,
260                 found,
261                 itemnumber,
262                 reservenotes,
263                 expirationdate,
264                 lowestPriority
265         FROM     reserves
266         WHERE biblionumber = ? ";
267     unless ( $all_dates ) {
268         $query .= "AND reservedate <= CURRENT_DATE()";
269     }
270     $query .= "ORDER BY priority";
271     my $sth = $dbh->prepare($query);
272     $sth->execute($biblionumber);
273     my @results;
274     my $i = 0;
275     while ( my $data = $sth->fetchrow_hashref ) {
276
277         # FIXME - What is this doing? How do constraints work?
278         if ($data->{constrainttype} eq 'o') {
279             $query = '
280                 SELECT biblioitemnumber
281                 FROM  reserveconstraints
282                 WHERE  biblionumber   = ?
283                 AND   borrowernumber = ?
284                 AND   reservedate    = ?
285             ';
286             my $csth = $dbh->prepare($query);
287             $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
288             my @bibitemno;
289             while ( my $bibitemnos = $csth->fetchrow_array ) {
290                 push( @bibitemno, $bibitemnos );    # FIXME: inefficient: use fetchall_arrayref
291             }
292             my $count = scalar @bibitemno;
293     
294             # if we have two or more different specific itemtypes
295             # reserved by same person on same day
296             my $bdata;
297             if ( $count > 1 ) {
298                 $bdata = GetBiblioItemData( $bibitemno[$i] );   # FIXME: This doesn't make sense.
299                 $i++; #  $i can increase each pass, but the next @bibitemno might be smaller?
300             }
301             else {
302                 # Look up the book we just found.
303                 $bdata = GetBiblioItemData( $bibitemno[0] );
304             }
305             # Add the results of this latest search to the current
306             # results.
307             # FIXME - An 'each' would probably be more efficient.
308             foreach my $key ( keys %$bdata ) {
309                 $data->{$key} = $bdata->{$key};
310             }
311         }
312         push @results, $data;
313     }
314     return ( $#results + 1, \@results );
315 }
316
317 =head2 GetReservesFromItemnumber
318
319  ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
320
321 TODO :: Description here
322
323 =cut
324
325 sub GetReservesFromItemnumber {
326     my ( $itemnumber, $all_dates ) = @_;
327     my $dbh   = C4::Context->dbh;
328     my $query = "
329     SELECT reservedate,borrowernumber,branchcode
330     FROM   reserves
331     WHERE  itemnumber=?
332     ";
333     unless ( $all_dates ) {
334         $query .= " AND reservedate <= CURRENT_DATE()";
335     }
336     my $sth_res = $dbh->prepare($query);
337     $sth_res->execute($itemnumber);
338     my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
339     return ( $reservedate, $borrowernumber, $branchcode );
340 }
341
342 =head2 GetReservesFromBorrowernumber
343
344     $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
345
346 TODO :: Descritpion
347
348 =cut
349
350 sub GetReservesFromBorrowernumber {
351     my ( $borrowernumber, $status ) = @_;
352     my $dbh   = C4::Context->dbh;
353     my $sth;
354     if ($status) {
355         $sth = $dbh->prepare("
356             SELECT *
357             FROM   reserves
358             WHERE  borrowernumber=?
359                 AND found =?
360             ORDER BY reservedate
361         ");
362         $sth->execute($borrowernumber,$status);
363     } else {
364         $sth = $dbh->prepare("
365             SELECT *
366             FROM   reserves
367             WHERE  borrowernumber=?
368             ORDER BY reservedate
369         ");
370         $sth->execute($borrowernumber);
371     }
372     my $data = $sth->fetchall_arrayref({});
373     return @$data;
374 }
375 #-------------------------------------------------------------------------------------
376 =head2 CanBookBeReserved
377
378   $error = &CanBookBeReserved($borrowernumber, $biblionumber)
379
380 =cut
381
382 sub CanBookBeReserved{
383     my ($borrowernumber, $biblionumber) = @_;
384
385     my @items = GetItemsInfo($biblionumber);
386     foreach my $item (@items){
387         return 1 if CanItemBeReserved($borrowernumber, $item->{itemnumber});
388     }
389     return 0;
390 }
391
392 =head2 CanItemBeReserved
393
394   $error = &CanItemBeReserved($borrowernumber, $itemnumber)
395
396 This function return 1 if an item can be issued by this borrower.
397
398 =cut
399
400 sub CanItemBeReserved{
401     my ($borrowernumber, $itemnumber) = @_;
402     
403     my $dbh             = C4::Context->dbh;
404     my $allowedreserves = 0;
405             
406     my $controlbranch = C4::Context->preference('ReservesControlBranch');
407     my $itype         = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
408
409     # we retrieve borrowers and items informations #
410     my $item     = GetItem($itemnumber);
411     my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);     
412     
413     # we retrieve user rights on this itemtype and branchcode
414     my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed 
415                              FROM issuingrules 
416                              WHERE (categorycode in (?,'*') ) 
417                              AND (itemtype IN (?,'*')) 
418                              AND (branchcode IN (?,'*')) 
419                              ORDER BY 
420                                categorycode DESC, 
421                                itemtype     DESC, 
422                                branchcode   DESC;"
423                            );
424                            
425     my $querycount ="SELECT 
426                             count(*) as count
427                             FROM reserves
428                                 LEFT JOIN items USING (itemnumber)
429                                 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
430                                 LEFT JOIN borrowers USING (borrowernumber)
431                             WHERE borrowernumber = ?
432                                 ";
433     
434     
435     my $itemtype     = $item->{$itype};
436     my $categorycode = $borrower->{categorycode};
437     my $branchcode   = "";
438     my $branchfield  = "reserves.branchcode";
439     
440     if( $controlbranch eq "ItemHomeLibrary" ){
441         $branchfield = "items.homebranch";
442         $branchcode = $item->{homebranch};
443     }elsif( $controlbranch eq "PatronLibrary" ){
444         $branchfield = "borrowers.branchcode";
445         $branchcode = $borrower->{branchcode};
446     }
447     
448     # we retrieve rights 
449     $sth->execute($categorycode, $itemtype, $branchcode);
450     if(my $rights = $sth->fetchrow_hashref()){
451         $itemtype        = $rights->{itemtype};
452         $allowedreserves = $rights->{reservesallowed}; 
453     }else{
454         $itemtype = '*';
455     }
456     
457     # we retrieve count
458     
459     $querycount .= "AND $branchfield = ?";
460     
461     $querycount .= " AND $itype = ?" if ($itemtype ne "*");
462     my $sthcount = $dbh->prepare($querycount);
463     
464     if($itemtype eq "*"){
465         $sthcount->execute($borrowernumber, $branchcode);
466     }else{
467         $sthcount->execute($borrowernumber, $branchcode, $itemtype);
468     }
469     
470     my $reservecount = "0";
471     if(my $rowcount = $sthcount->fetchrow_hashref()){
472         $reservecount = $rowcount->{count};
473     }
474     
475     # we check if it's ok or not
476     if( $reservecount < $allowedreserves ){
477         return 1;
478     }else{
479         return 0;
480     }
481 }
482 #--------------------------------------------------------------------------------
483 =head2 GetReserveCount
484
485   $number = &GetReserveCount($borrowernumber);
486
487 this function returns the number of reservation for a borrower given on input arg.
488
489 =cut
490
491 sub GetReserveCount {
492     my ($borrowernumber) = @_;
493
494     my $dbh = C4::Context->dbh;
495
496     my $query = '
497         SELECT COUNT(*) AS counter
498         FROM reserves
499           WHERE borrowernumber = ?
500     ';
501     my $sth = $dbh->prepare($query);
502     $sth->execute($borrowernumber);
503     my $row = $sth->fetchrow_hashref;
504     return $row->{counter};
505 }
506
507 =head2 GetOtherReserves
508
509   ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
510
511 Check queued list of this document and check if this document must be  transfered
512
513 =cut
514
515 sub GetOtherReserves {
516     my ($itemnumber) = @_;
517     my $messages;
518     my $nextreservinfo;
519     my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
520     if ($checkreserves) {
521         my $iteminfo = GetItem($itemnumber);
522         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
523             $messages->{'transfert'} = $checkreserves->{'branchcode'};
524             #minus priorities of others reservs
525             ModReserveMinusPriority(
526                 $itemnumber,
527                 $checkreserves->{'borrowernumber'},
528                 $iteminfo->{'biblionumber'}
529             );
530
531             #launch the subroutine dotransfer
532             C4::Items::ModItemTransfer(
533                 $itemnumber,
534                 $iteminfo->{'holdingbranch'},
535                 $checkreserves->{'branchcode'}
536               ),
537               ;
538         }
539
540      #step 2b : case of a reservation on the same branch, set the waiting status
541         else {
542             $messages->{'waiting'} = 1;
543             ModReserveMinusPriority(
544                 $itemnumber,
545                 $checkreserves->{'borrowernumber'},
546                 $iteminfo->{'biblionumber'}
547             );
548             ModReserveStatus($itemnumber,'W');
549         }
550
551         $nextreservinfo = $checkreserves->{'borrowernumber'};
552     }
553
554     return ( $messages, $nextreservinfo );
555 }
556
557 =head2 GetReserveFee
558
559   $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
560
561 Calculate the fee for a reserve
562
563 =cut
564
565 sub GetReserveFee {
566     my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
567
568     #check for issues;
569     my $dbh   = C4::Context->dbh;
570     my $const = lc substr( $constraint, 0, 1 );
571     my $query = qq/
572       SELECT * FROM borrowers
573     LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
574     WHERE borrowernumber = ?
575     /;
576     my $sth = $dbh->prepare($query);
577     $sth->execute($borrowernumber);
578     my $data = $sth->fetchrow_hashref;
579     $sth->finish();
580     my $fee      = $data->{'reservefee'};
581     my $cntitems = @- > $bibitems;
582
583     if ( $fee > 0 ) {
584
585         # check for items on issue
586         # first find biblioitem records
587         my @biblioitems;
588         my $sth1 = $dbh->prepare(
589             "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
590                    WHERE (biblio.biblionumber = ?)"
591         );
592         $sth1->execute($biblionumber);
593         while ( my $data1 = $sth1->fetchrow_hashref ) {
594             if ( $const eq "a" ) {
595                 push @biblioitems, $data1;
596             }
597             else {
598                 my $found = 0;
599                 my $x     = 0;
600                 while ( $x < $cntitems ) {
601                     if ( @$bibitems->{'biblioitemnumber'} ==
602                         $data->{'biblioitemnumber'} )
603                     {
604                         $found = 1;
605                     }
606                     $x++;
607                 }
608                 if ( $const eq 'o' ) {
609                     if ( $found == 1 ) {
610                         push @biblioitems, $data1;
611                     }
612                 }
613                 else {
614                     if ( $found == 0 ) {
615                         push @biblioitems, $data1;
616                     }
617                 }
618             }
619         }
620         $sth1->finish;
621         my $cntitemsfound = @biblioitems;
622         my $issues        = 0;
623         my $x             = 0;
624         my $allissued     = 1;
625         while ( $x < $cntitemsfound ) {
626             my $bitdata = $biblioitems[$x];
627             my $sth2    = $dbh->prepare(
628                 "SELECT * FROM items
629                      WHERE biblioitemnumber = ?"
630             );
631             $sth2->execute( $bitdata->{'biblioitemnumber'} );
632             while ( my $itdata = $sth2->fetchrow_hashref ) {
633                 my $sth3 = $dbh->prepare(
634                     "SELECT * FROM issues
635                        WHERE itemnumber = ?"
636                 );
637                 $sth3->execute( $itdata->{'itemnumber'} );
638                 if ( my $isdata = $sth3->fetchrow_hashref ) {
639                 }
640                 else {
641                     $allissued = 0;
642                 }
643             }
644             $x++;
645         }
646         if ( $allissued == 0 ) {
647             my $rsth =
648               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
649             $rsth->execute($biblionumber);
650             if ( my $rdata = $rsth->fetchrow_hashref ) {
651             }
652             else {
653                 $fee = 0;
654             }
655         }
656     }
657     return $fee;
658 }
659
660 =head2 GetReservesToBranch
661
662   @transreserv = GetReservesToBranch( $frombranch );
663
664 Get reserve list for a given branch
665
666 =cut
667
668 sub GetReservesToBranch {
669     my ( $frombranch ) = @_;
670     my $dbh = C4::Context->dbh;
671     my $sth = $dbh->prepare(
672         "SELECT borrowernumber,reservedate,itemnumber,timestamp
673          FROM reserves 
674          WHERE priority='0' 
675            AND branchcode=?"
676     );
677     $sth->execute( $frombranch );
678     my @transreserv;
679     my $i = 0;
680     while ( my $data = $sth->fetchrow_hashref ) {
681         $transreserv[$i] = $data;
682         $i++;
683     }
684     return (@transreserv);
685 }
686
687 =head2 GetReservesForBranch
688
689   @transreserv = GetReservesForBranch($frombranch);
690
691 =cut
692
693 sub GetReservesForBranch {
694     my ($frombranch) = @_;
695     my $dbh          = C4::Context->dbh;
696         my $query        = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
697         FROM   reserves 
698         WHERE   priority='0'
699             AND found='W' ";
700     if ($frombranch){
701         $query .= " AND branchcode=? ";
702         }
703     $query .= "ORDER BY waitingdate" ;
704     my $sth = $dbh->prepare($query);
705     if ($frombranch){
706                 $sth->execute($frombranch);
707         }
708     else {
709                 $sth->execute();
710         }
711     my @transreserv;
712     my $i = 0;
713     while ( my $data = $sth->fetchrow_hashref ) {
714         $transreserv[$i] = $data;
715         $i++;
716     }
717     return (@transreserv);
718 }
719
720 sub GetReserveStatus {
721     my ($itemnumber) = @_;
722     
723     my $dbh = C4::Context->dbh;
724     
725     my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
726     
727     $itemstatus->execute($itemnumber);
728     my ($found) = $itemstatus->fetchrow_array;
729     return $found;
730 }
731
732 =head2 CheckReserves
733
734   ($status, $reserve) = &CheckReserves($itemnumber);
735   ($status, $reserve) = &CheckReserves(undef, $barcode);
736
737 Find a book in the reserves.
738
739 C<$itemnumber> is the book's item number.
740
741 As I understand it, C<&CheckReserves> looks for the given item in the
742 reserves. If it is found, that's a match, and C<$status> is set to
743 C<Waiting>.
744
745 Otherwise, it finds the most important item in the reserves with the
746 same biblio number as this book (I'm not clear on this) and returns it
747 with C<$status> set to C<Reserved>.
748
749 C<&CheckReserves> returns a two-element list:
750
751 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
752
753 C<$reserve> is the reserve item that matched. It is a
754 reference-to-hash whose keys are mostly the fields of the reserves
755 table in the Koha database.
756
757 =cut
758
759 sub CheckReserves {
760     my ( $item, $barcode ) = @_;
761     my $dbh = C4::Context->dbh;
762     my $sth;
763     my $select = "
764     SELECT items.biblionumber,
765            items.biblioitemnumber,
766            itemtypes.notforloan,
767            items.notforloan AS itemnotforloan,
768            items.itemnumber
769     FROM   items
770     LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
771     LEFT JOIN itemtypes   ON biblioitems.itemtype   = itemtypes.itemtype
772     ";
773
774     if ($item) {
775         $sth = $dbh->prepare("$select WHERE itemnumber = ?");
776         $sth->execute($item);
777     }
778     else {
779         $sth = $dbh->prepare("$select WHERE barcode = ?");
780         $sth->execute($barcode);
781     }
782     # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
783     my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
784
785     return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
786
787     # if item is not for loan it cannot be reserved either.....
788     #    execpt where items.notforloan < 0 :  This indicates the item is holdable. 
789     return ( 0, 0 ) if  ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
790
791     # Find this item in the reserves
792     my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
793
794     # $priority and $highest are used to find the most important item
795     # in the list returned by &_Findgroupreserve. (The lower $priority,
796     # the more important the item.)
797     # $highest is the most important item we've seen so far.
798     my $highest;
799     if (scalar @reserves) {
800         my $priority = 10000000;
801         foreach my $res (@reserves) {
802             if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
803                 return ( "Waiting", $res ); # Found it
804             } else {
805                 # See if this item is more important than what we've got so far
806                 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
807                     my $borrowerinfo=C4::Members::GetMemberDetails($res->{'borrowernumber'});
808                     my $iteminfo=C4::Items::GetItem($itemnumber);
809                     my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo);
810                     my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
811                     next if ($branchitemrule->{'holdallowed'} == 0);
812                     next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
813                     $priority = $res->{'priority'};
814                     $highest  = $res;
815                 }
816             }
817         }
818     }
819
820     # If we get this far, then no exact match was found.
821     # We return the most important (i.e. next) reservation.
822     if ($highest) {
823         $highest->{'itemnumber'} = $item;
824         return ( "Reserved", $highest );
825     }
826     else {
827         return ( 0, 0 );
828     }
829 }
830
831 =head2 CancelExpiredReserves
832
833   CancelExpiredReserves();
834
835 Cancels all reserves with an expiration date from before today.
836
837 =cut
838
839 sub CancelExpiredReserves {
840
841     my $dbh = C4::Context->dbh;
842     my $sth = $dbh->prepare( "
843         SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) 
844         AND expirationdate IS NOT NULL
845     " );
846     $sth->execute();
847
848     while ( my $res = $sth->fetchrow_hashref() ) {
849         CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
850     }
851   
852 }
853
854 =head2 CancelReserve
855
856   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
857
858 Cancels a reserve.
859
860 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
861 cancel, but not both: if both are given, C<&CancelReserve> does
862 nothing.
863
864 C<$borrowernumber> is the borrower number of the patron on whose
865 behalf the book was reserved.
866
867 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
868 priorities of the other people who are waiting on the book.
869
870 =cut
871
872 sub CancelReserve {
873     my ( $biblio, $item, $borr ) = @_;
874     my $dbh = C4::Context->dbh;
875         if ( $item and $borr ) {
876         # removing a waiting reserve record....
877         # update the database...
878         my $query = "
879             UPDATE reserves
880             SET    cancellationdate = now(),
881                    found            = Null,
882                    priority         = 0
883             WHERE  itemnumber       = ?
884              AND   borrowernumber   = ?
885         ";
886         my $sth = $dbh->prepare($query);
887         $sth->execute( $item, $borr );
888         $sth->finish;
889         $query = "
890             INSERT INTO old_reserves
891             SELECT * FROM reserves
892             WHERE  itemnumber       = ?
893              AND   borrowernumber   = ?
894         ";
895         $sth = $dbh->prepare($query);
896         $sth->execute( $item, $borr );
897         $query = "
898             DELETE FROM reserves
899             WHERE  itemnumber       = ?
900              AND   borrowernumber   = ?
901         ";
902         $sth = $dbh->prepare($query);
903         $sth->execute( $item, $borr );
904     }
905     else {
906         # removing a reserve record....
907         # get the prioritiy on this record....
908         my $priority;
909         my $query = qq/
910             SELECT priority FROM reserves
911             WHERE biblionumber   = ?
912               AND borrowernumber = ?
913               AND cancellationdate IS NULL
914               AND itemnumber IS NULL
915         /;
916         my $sth = $dbh->prepare($query);
917         $sth->execute( $biblio, $borr );
918         ($priority) = $sth->fetchrow_array;
919         $sth->finish;
920         $query = qq/
921             UPDATE reserves
922             SET    cancellationdate = now(),
923                    found            = Null,
924                    priority         = 0
925             WHERE  biblionumber     = ?
926               AND  borrowernumber   = ?
927         /;
928
929         # update the database, removing the record...
930         $sth = $dbh->prepare($query);
931         $sth->execute( $biblio, $borr );
932         $sth->finish;
933
934         $query = qq/
935             INSERT INTO old_reserves
936             SELECT * FROM reserves
937             WHERE  biblionumber     = ?
938               AND  borrowernumber   = ?
939         /;
940         $sth = $dbh->prepare($query);
941         $sth->execute( $biblio, $borr );
942
943         $query = qq/
944             DELETE FROM reserves
945             WHERE  biblionumber     = ?
946               AND  borrowernumber   = ?
947         /;
948         $sth = $dbh->prepare($query);
949         $sth->execute( $biblio, $borr );
950
951         # now fix the priority on the others....
952         _FixPriority( $biblio, $borr );
953     }
954 }
955
956 =head2 ModReserve
957
958   ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
959
960 Change a hold request's priority or cancel it.
961
962 C<$rank> specifies the effect of the change.  If C<$rank>
963 is 'W' or 'n', nothing happens.  This corresponds to leaving a
964 request alone when changing its priority in the holds queue
965 for a bib.
966
967 If C<$rank> is 'del', the hold request is cancelled.
968
969 If C<$rank> is an integer greater than zero, the priority of
970 the request is set to that value.  Since priority != 0 means
971 that the item is not waiting on the hold shelf, setting the 
972 priority to a non-zero value also sets the request's found
973 status and waiting date to NULL. 
974
975 The optional C<$itemnumber> parameter is used only when
976 C<$rank> is a non-zero integer; if supplied, the itemnumber 
977 of the hold request is set accordingly; if omitted, the itemnumber
978 is cleared.
979
980 B<FIXME:> Note that the forgoing can have the effect of causing
981 item-level hold requests to turn into title-level requests.  This
982 will be fixed once reserves has separate columns for requested
983 itemnumber and supplying itemnumber.
984
985 =cut
986
987 sub ModReserve {
988     #subroutine to update a reserve
989     my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
990      return if $rank eq "W";
991      return if $rank eq "n";
992     my $dbh = C4::Context->dbh;
993     if ( $rank eq "del" ) {
994         my $query = qq/
995             UPDATE reserves
996             SET    cancellationdate=now()
997             WHERE  biblionumber   = ?
998              AND   borrowernumber = ?
999         /;
1000         my $sth = $dbh->prepare($query);
1001         $sth->execute( $biblio, $borrower );
1002         $sth->finish;
1003         $query = qq/
1004             INSERT INTO old_reserves
1005             SELECT *
1006             FROM   reserves 
1007             WHERE  biblionumber   = ?
1008              AND   borrowernumber = ?
1009         /;
1010         $sth = $dbh->prepare($query);
1011         $sth->execute( $biblio, $borrower );
1012         $query = qq/
1013             DELETE FROM reserves 
1014             WHERE  biblionumber   = ?
1015              AND   borrowernumber = ?
1016         /;
1017         $sth = $dbh->prepare($query);
1018         $sth->execute( $biblio, $borrower );
1019         
1020     }
1021     elsif ($rank =~ /^\d+/ and $rank > 0) {
1022         my $query = qq/
1023         UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1024             WHERE biblionumber   = ?
1025              AND borrowernumber = ?
1026         /;
1027         my $sth = $dbh->prepare($query);
1028         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1029         $sth->finish;
1030         _FixPriority( $biblio, $borrower, $rank);
1031     }
1032 }
1033
1034 =head2 ModReserveFill
1035
1036   &ModReserveFill($reserve);
1037
1038 Fill a reserve. If I understand this correctly, this means that the
1039 reserved book has been found and given to the patron who reserved it.
1040
1041 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1042 whose keys are fields from the reserves table in the Koha database.
1043
1044 =cut
1045
1046 sub ModReserveFill {
1047     my ($res) = @_;
1048     my $dbh = C4::Context->dbh;
1049     # fill in a reserve record....
1050     my $biblionumber = $res->{'biblionumber'};
1051     my $borrowernumber    = $res->{'borrowernumber'};
1052     my $resdate = $res->{'reservedate'};
1053
1054     # get the priority on this record....
1055     my $priority;
1056     my $query = "SELECT priority
1057                  FROM   reserves
1058                  WHERE  biblionumber   = ?
1059                   AND   borrowernumber = ?
1060                   AND   reservedate    = ?";
1061     my $sth = $dbh->prepare($query);
1062     $sth->execute( $biblionumber, $borrowernumber, $resdate );
1063     ($priority) = $sth->fetchrow_array;
1064     $sth->finish;
1065
1066     # update the database...
1067     $query = "UPDATE reserves
1068                   SET    found            = 'F',
1069                          priority         = 0
1070                  WHERE  biblionumber     = ?
1071                     AND reservedate      = ?
1072                     AND borrowernumber   = ?
1073                 ";
1074     $sth = $dbh->prepare($query);
1075     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1076     $sth->finish;
1077
1078     # move to old_reserves
1079     $query = "INSERT INTO old_reserves
1080                  SELECT * FROM reserves
1081                  WHERE  biblionumber     = ?
1082                     AND reservedate      = ?
1083                     AND borrowernumber   = ?
1084                 ";
1085     $sth = $dbh->prepare($query);
1086     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1087     $query = "DELETE FROM reserves
1088                  WHERE  biblionumber     = ?
1089                     AND reservedate      = ?
1090                     AND borrowernumber   = ?
1091                 ";
1092     $sth = $dbh->prepare($query);
1093     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1094     
1095     # now fix the priority on the others (if the priority wasn't
1096     # already sorted!)....
1097     unless ( $priority == 0 ) {
1098         _FixPriority( $biblionumber, $borrowernumber );
1099     }
1100 }
1101
1102 =head2 ModReserveStatus
1103
1104   &ModReserveStatus($itemnumber, $newstatus);
1105
1106 Update the reserve status for the active (priority=0) reserve.
1107
1108 $itemnumber is the itemnumber the reserve is on
1109
1110 $newstatus is the new status.
1111
1112 =cut
1113
1114 sub ModReserveStatus {
1115
1116     #first : check if we have a reservation for this item .
1117     my ($itemnumber, $newstatus) = @_;
1118     my $dbh          = C4::Context->dbh;
1119     my $query = " UPDATE reserves
1120     SET    found=?,waitingdate = now()
1121     WHERE itemnumber=?
1122       AND found IS NULL
1123       AND priority = 0
1124     ";
1125     my $sth_set = $dbh->prepare($query);
1126     $sth_set->execute( $newstatus, $itemnumber );
1127
1128     if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1129       CartToShelf( $itemnumber );
1130     }
1131 }
1132
1133 =head2 ModReserveAffect
1134
1135   &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1136
1137 This function affect an item and a status for a given reserve
1138 The itemnumber parameter is used to find the biblionumber.
1139 with the biblionumber & the borrowernumber, we can affect the itemnumber
1140 to the correct reserve.
1141
1142 if $transferToDo is not set, then the status is set to "Waiting" as well.
1143 otherwise, a transfer is on the way, and the end of the transfer will 
1144 take care of the waiting status
1145
1146 =cut
1147
1148 sub ModReserveAffect {
1149     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1150     my $dbh = C4::Context->dbh;
1151
1152     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1153     # attached to $itemnumber
1154     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1155     $sth->execute($itemnumber);
1156     my ($biblionumber) = $sth->fetchrow;
1157
1158     # get request - need to find out if item is already
1159     # waiting in order to not send duplicate hold filled notifications
1160     my $request = GetReserveInfo($borrowernumber, $biblionumber);
1161     my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1162
1163     # If we affect a reserve that has to be transfered, don't set to Waiting
1164     my $query;
1165     if ($transferToDo) {
1166     $query = "
1167         UPDATE reserves
1168         SET    priority = 0,
1169                itemnumber = ?,
1170                found = 'T'
1171         WHERE borrowernumber = ?
1172           AND biblionumber = ?
1173     ";
1174     }
1175     else {
1176     # affect the reserve to Waiting as well.
1177     $query = "
1178         UPDATE reserves
1179         SET     priority = 0,
1180                 found = 'W',
1181                 waitingdate=now(),
1182                 itemnumber = ?
1183         WHERE borrowernumber = ?
1184           AND biblionumber = ?
1185     ";
1186     }
1187     $sth = $dbh->prepare($query);
1188     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1189     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1190
1191     if ( C4::Context->preference("ReturnToShelvingCart") ) {
1192       CartToShelf( $itemnumber );
1193     }
1194
1195     return;
1196 }
1197
1198 =head2 ModReserveCancelAll
1199
1200   ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1201
1202 function to cancel reserv,check other reserves, and transfer document if it's necessary
1203
1204 =cut
1205
1206 sub ModReserveCancelAll {
1207     my $messages;
1208     my $nextreservinfo;
1209     my ( $itemnumber, $borrowernumber ) = @_;
1210
1211     #step 1 : cancel the reservation
1212     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1213
1214     #step 2 launch the subroutine of the others reserves
1215     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1216
1217     return ( $messages, $nextreservinfo );
1218 }
1219
1220 =head2 ModReserveMinusPriority
1221
1222   &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1223
1224 Reduce the values of queuded list     
1225
1226 =cut
1227
1228 sub ModReserveMinusPriority {
1229     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1230
1231     #first step update the value of the first person on reserv
1232     my $dbh   = C4::Context->dbh;
1233     my $query = "
1234         UPDATE reserves
1235         SET    priority = 0 , itemnumber = ? 
1236         WHERE  borrowernumber=?
1237           AND  biblionumber=?
1238     ";
1239     my $sth_upd = $dbh->prepare($query);
1240     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1241     # second step update all others reservs
1242     _FixPriority($biblionumber, $borrowernumber, '0');
1243 }
1244
1245 =head2 GetReserveInfo
1246
1247   &GetReserveInfo($borrowernumber,$biblionumber);
1248
1249 Get item and borrower details for a current hold.
1250 Current implementation this query should have a single result.
1251
1252 =cut
1253
1254 sub GetReserveInfo {
1255         my ( $borrowernumber, $biblionumber ) = @_;
1256     my $dbh = C4::Context->dbh;
1257         my $strsth="SELECT 
1258                        reservedate, 
1259                        reservenotes, 
1260                        reserves.borrowernumber,
1261                                    reserves.biblionumber, 
1262                                    reserves.branchcode,
1263                                    reserves.waitingdate,
1264                                    notificationdate, 
1265                                    reminderdate, 
1266                                    priority, 
1267                                    found,
1268                                    firstname, 
1269                                    surname, 
1270                                    phone, 
1271                                    email, 
1272                                    address, 
1273                                    address2,
1274                                    cardnumber, 
1275                                    city, 
1276                                    zipcode,
1277                                    biblio.title, 
1278                                    biblio.author,
1279                                    items.holdingbranch, 
1280                                    items.itemcallnumber, 
1281                                    items.itemnumber,
1282                                    items.location, 
1283                                    barcode, 
1284                                    notes
1285                         FROM reserves 
1286                          LEFT JOIN items USING(itemnumber) 
1287                      LEFT JOIN borrowers USING(borrowernumber)
1288                      LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber) 
1289                         WHERE 
1290                                 reserves.borrowernumber=?
1291                                 AND reserves.biblionumber=?";
1292         my $sth = $dbh->prepare($strsth); 
1293         $sth->execute($borrowernumber,$biblionumber);
1294
1295         my $data = $sth->fetchrow_hashref;
1296         return $data;
1297
1298 }
1299
1300 =head2 IsAvailableForItemLevelRequest
1301
1302   my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1303
1304 Checks whether a given item record is available for an
1305 item-level hold request.  An item is available if
1306
1307 * it is not lost AND 
1308 * it is not damaged AND 
1309 * it is not withdrawn AND 
1310 * does not have a not for loan value > 0
1311
1312 Whether or not the item is currently on loan is 
1313 also checked - if the AllowOnShelfHolds system preference
1314 is ON, an item can be requested even if it is currently
1315 on loan to somebody else.  If the system preference
1316 is OFF, an item that is currently checked out cannot
1317 be the target of an item-level hold request.
1318
1319 Note that IsAvailableForItemLevelRequest() does not
1320 check if the staff operator is authorized to place
1321 a request on the item - in particular,
1322 this routine does not check IndependantBranches
1323 and canreservefromotherbranches.
1324
1325 =cut
1326
1327 sub IsAvailableForItemLevelRequest {
1328     my $itemnumber = shift;
1329    
1330     my $item = GetItem($itemnumber);
1331
1332     # must check the notforloan setting of the itemtype
1333     # FIXME - a lot of places in the code do this
1334     #         or something similar - need to be
1335     #         consolidated
1336     my $dbh = C4::Context->dbh;
1337     my $notforloan_query;
1338     if (C4::Context->preference('item-level_itypes')) {
1339         $notforloan_query = "SELECT itemtypes.notforloan
1340                              FROM items
1341                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1342                              WHERE itemnumber = ?";
1343     } else {
1344         $notforloan_query = "SELECT itemtypes.notforloan
1345                              FROM items
1346                              JOIN biblioitems USING (biblioitemnumber)
1347                              JOIN itemtypes USING (itemtype)
1348                              WHERE itemnumber = ?";
1349     }
1350     my $sth = $dbh->prepare($notforloan_query);
1351     $sth->execute($itemnumber);
1352     my $notforloan_per_itemtype = 0;
1353     if (my ($notforloan) = $sth->fetchrow_array) {
1354         $notforloan_per_itemtype = 1 if $notforloan;
1355     }
1356
1357     my $available_per_item = 1;
1358     $available_per_item = 0 if $item->{itemlost} or
1359                                ( $item->{notforloan} > 0 ) or
1360                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1361                                $item->{wthdrawn} or
1362                                $notforloan_per_itemtype;
1363
1364
1365     if (C4::Context->preference('AllowOnShelfHolds')) {
1366         return $available_per_item;
1367     } else {
1368         return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); 
1369     }
1370 }
1371
1372 =head2 AlterPriority
1373
1374   AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1375
1376 This function changes a reserve's priority up, down, to the top, or to the bottom.
1377 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1378
1379 =cut
1380
1381 sub AlterPriority {
1382     my ( $where, $borrowernumber, $biblionumber ) = @_;
1383
1384     my $dbh = C4::Context->dbh;
1385
1386     ## Find this reserve
1387     my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1388     $sth->execute( $biblionumber, $borrowernumber );
1389     my $reserve = $sth->fetchrow_hashref();
1390     $sth->finish();
1391
1392     if ( $where eq 'up' || $where eq 'down' ) {
1393     
1394       my $priority = $reserve->{'priority'};        
1395       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1396       _FixPriority( $biblionumber, $borrowernumber, $priority )
1397
1398     } elsif ( $where eq 'top' ) {
1399
1400       _FixPriority( $biblionumber, $borrowernumber, '1' )
1401
1402     } elsif ( $where eq 'bottom' ) {
1403
1404       _FixPriority( $biblionumber, $borrowernumber, '999999' )
1405
1406     }
1407 }
1408
1409 =head2 ToggleLowestPriority
1410
1411   ToggleLowestPriority( $borrowernumber, $biblionumber );
1412
1413 This function sets the lowestPriority field to true if is false, and false if it is true.
1414
1415 =cut
1416
1417 sub ToggleLowestPriority {
1418     my ( $borrowernumber, $biblionumber ) = @_;
1419
1420     my $dbh = C4::Context->dbh;
1421
1422     my $sth = $dbh->prepare(
1423         "UPDATE reserves SET lowestPriority = NOT lowestPriority
1424          WHERE biblionumber = ?
1425          AND borrowernumber = ?"
1426     );
1427     $sth->execute(
1428         $biblionumber,
1429         $borrowernumber,
1430     );
1431     $sth->finish;
1432     
1433     _FixPriority( $biblionumber, $borrowernumber, '999999' );
1434 }
1435
1436 =head2 _FixPriority
1437
1438   &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1439
1440 Only used internally (so don't export it)
1441 Changed how this functions works #
1442 Now just gets an array of reserves in the rank order and updates them with
1443 the array index (+1 as array starts from 0)
1444 and if $rank is supplied will splice item from the array and splice it back in again
1445 in new priority rank
1446
1447 =cut 
1448
1449 sub _FixPriority {
1450     my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1451     my $dbh = C4::Context->dbh;
1452      if ( $rank eq "del" ) {
1453          CancelReserve( $biblio, undef, $borrowernumber );
1454      }
1455     if ( $rank eq "W" || $rank eq "0" ) {
1456
1457         # make sure priority for waiting or in-transit items is 0
1458         my $query = qq/
1459             UPDATE reserves
1460             SET    priority = 0
1461             WHERE biblionumber = ?
1462               AND borrowernumber = ?
1463               AND found IN ('W', 'T')
1464         /;
1465         my $sth = $dbh->prepare($query);
1466         $sth->execute( $biblio, $borrowernumber );
1467     }
1468     my @priority;
1469     my @reservedates;
1470
1471     # get whats left
1472 # FIXME adding a new security in returned elements for changing priority,
1473 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1474         # This is wrong a waiting reserve has W set
1475         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1476     my $query = qq/
1477         SELECT borrowernumber, reservedate, constrainttype
1478         FROM   reserves
1479         WHERE  biblionumber   = ?
1480           AND  ((found <> 'W' AND found <> 'T') or found is NULL)
1481         ORDER BY priority ASC
1482     /;
1483     my $sth = $dbh->prepare($query);
1484     $sth->execute($biblio);
1485     while ( my $line = $sth->fetchrow_hashref ) {
1486         push( @reservedates, $line );
1487         push( @priority,     $line );
1488     }
1489
1490     # To find the matching index
1491     my $i;
1492     my $key = -1;    # to allow for 0 to be a valid result
1493     for ( $i = 0 ; $i < @priority ; $i++ ) {
1494         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1495             $key = $i;    # save the index
1496             last;
1497         }
1498     }
1499
1500     # if index exists in array then move it to new position
1501     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1502         my $new_rank = $rank -
1503           1;    # $new_rank is what you want the new index to be in the array
1504         my $moving_item = splice( @priority, $key, 1 );
1505         splice( @priority, $new_rank, 0, $moving_item );
1506     }
1507
1508     # now fix the priority on those that are left....
1509     $query = "
1510             UPDATE reserves
1511             SET    priority = ?
1512                 WHERE  biblionumber = ?
1513                  AND borrowernumber   = ?
1514                  AND reservedate = ?
1515          AND found IS NULL
1516     ";
1517     $sth = $dbh->prepare($query);
1518     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1519         $sth->execute(
1520             $j + 1, $biblio,
1521             $priority[$j]->{'borrowernumber'},
1522             $priority[$j]->{'reservedate'}
1523         );
1524         $sth->finish;
1525     }
1526     
1527     $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1528     $sth->execute();
1529     
1530     unless ( $ignoreSetLowestRank ) {
1531       while ( my $res = $sth->fetchrow_hashref() ) {
1532         _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1533       }
1534     }
1535 }
1536
1537 =head2 _Findgroupreserve
1538
1539   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1540
1541 Looks for an item-specific match first, then for a title-level match, returning the
1542 first match found.  If neither, then we look for a 3rd kind of match based on
1543 reserve constraints.
1544
1545 TODO: add more explanation about reserve constraints
1546
1547 C<&_Findgroupreserve> returns :
1548 C<@results> is an array of references-to-hash whose keys are mostly
1549 fields from the reserves table of the Koha database, plus
1550 C<biblioitemnumber>.
1551
1552 =cut
1553
1554 sub _Findgroupreserve {
1555     my ( $bibitem, $biblio, $itemnumber ) = @_;
1556     my $dbh   = C4::Context->dbh;
1557
1558     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1559     # check for exact targetted match
1560     my $item_level_target_query = qq/
1561         SELECT reserves.biblionumber        AS biblionumber,
1562                reserves.borrowernumber      AS borrowernumber,
1563                reserves.reservedate         AS reservedate,
1564                reserves.branchcode          AS branchcode,
1565                reserves.cancellationdate    AS cancellationdate,
1566                reserves.found               AS found,
1567                reserves.reservenotes        AS reservenotes,
1568                reserves.priority            AS priority,
1569                reserves.timestamp           AS timestamp,
1570                biblioitems.biblioitemnumber AS biblioitemnumber,
1571                reserves.itemnumber          AS itemnumber
1572         FROM reserves
1573         JOIN biblioitems USING (biblionumber)
1574         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1575         WHERE found IS NULL
1576         AND priority > 0
1577         AND item_level_request = 1
1578         AND itemnumber = ?
1579         AND reservedate <= CURRENT_DATE()
1580     /;
1581     my $sth = $dbh->prepare($item_level_target_query);
1582     $sth->execute($itemnumber);
1583     my @results;
1584     if ( my $data = $sth->fetchrow_hashref ) {
1585         push( @results, $data );
1586     }
1587     return @results if @results;
1588     
1589     # check for title-level targetted match
1590     my $title_level_target_query = qq/
1591         SELECT reserves.biblionumber        AS biblionumber,
1592                reserves.borrowernumber      AS borrowernumber,
1593                reserves.reservedate         AS reservedate,
1594                reserves.branchcode          AS branchcode,
1595                reserves.cancellationdate    AS cancellationdate,
1596                reserves.found               AS found,
1597                reserves.reservenotes        AS reservenotes,
1598                reserves.priority            AS priority,
1599                reserves.timestamp           AS timestamp,
1600                biblioitems.biblioitemnumber AS biblioitemnumber,
1601                reserves.itemnumber          AS itemnumber
1602         FROM reserves
1603         JOIN biblioitems USING (biblionumber)
1604         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1605         WHERE found IS NULL
1606         AND priority > 0
1607         AND item_level_request = 0
1608         AND hold_fill_targets.itemnumber = ?
1609         AND reservedate <= CURRENT_DATE()
1610     /;
1611     $sth = $dbh->prepare($title_level_target_query);
1612     $sth->execute($itemnumber);
1613     @results = ();
1614     if ( my $data = $sth->fetchrow_hashref ) {
1615         push( @results, $data );
1616     }
1617     return @results if @results;
1618
1619     my $query = qq/
1620         SELECT reserves.biblionumber               AS biblionumber,
1621                reserves.borrowernumber             AS borrowernumber,
1622                reserves.reservedate                AS reservedate,
1623                reserves.waitingdate                AS waitingdate,
1624                reserves.branchcode                 AS branchcode,
1625                reserves.cancellationdate           AS cancellationdate,
1626                reserves.found                      AS found,
1627                reserves.reservenotes               AS reservenotes,
1628                reserves.priority                   AS priority,
1629                reserves.timestamp                  AS timestamp,
1630                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1631                reserves.itemnumber                 AS itemnumber
1632         FROM reserves
1633           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1634         WHERE reserves.biblionumber = ?
1635           AND ( ( reserveconstraints.biblioitemnumber = ?
1636           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1637           AND reserves.reservedate    = reserveconstraints.reservedate )
1638           OR  reserves.constrainttype='a' )
1639           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1640           AND reserves.reservedate <= CURRENT_DATE()
1641     /;
1642     $sth = $dbh->prepare($query);
1643     $sth->execute( $biblio, $bibitem, $itemnumber );
1644     @results = ();
1645     while ( my $data = $sth->fetchrow_hashref ) {
1646         push( @results, $data );
1647     }
1648     return @results;
1649 }
1650
1651 =head2 _koha_notify_reserve
1652
1653   _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1654
1655 Sends a notification to the patron that their hold has been filled (through
1656 ModReserveAffect, _not_ ModReserveFill)
1657
1658 =cut
1659
1660 sub _koha_notify_reserve {
1661     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1662
1663     my $dbh = C4::Context->dbh;
1664     my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1665     my $letter_code;
1666     my $print_mode = 0;
1667     my $messagingprefs;
1668     if ( $borrower->{'email'} || $borrower->{'smsalertnumber'} ) {
1669         $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } );
1670
1671         return if ( !defined( $messagingprefs->{'letter_code'} ) );
1672         $letter_code = $messagingprefs->{'letter_code'};
1673     } else {
1674         $letter_code = 'HOLD_PRINT';
1675         $print_mode = 1;
1676     }
1677
1678     my $sth = $dbh->prepare("
1679         SELECT *
1680         FROM   reserves
1681         WHERE  borrowernumber = ?
1682             AND biblionumber = ?
1683     ");
1684     $sth->execute( $borrowernumber, $biblionumber );
1685     my $reserve = $sth->fetchrow_hashref;
1686     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1687
1688     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1689
1690     my $letter = getletter( 'reserves', $letter_code );
1691     die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1692
1693     C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1694     C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1695     C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1696     C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1697
1698     if ( $reserve->{'itemnumber'} ) {
1699         C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1700     }
1701     my $today = C4::Dates->new()->output();
1702     $letter->{'title'} =~ s/<<today>>/$today/g;
1703     $letter->{'content'} =~ s/<<today>>/$today/g;
1704     $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1705
1706     if ( $print_mode ) {
1707         C4::Letters::EnqueueLetter( {
1708             letter => $letter,
1709             borrowernumber => $borrowernumber,
1710             message_transport_type => 'print',
1711         } );
1712         
1713         return;
1714     }
1715
1716     if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1717         # aka, 'email' in ->{'transports'}
1718         C4::Letters::EnqueueLetter(
1719             {   letter                 => $letter,
1720                 borrowernumber         => $borrowernumber,
1721                 message_transport_type => 'email',
1722                 from_address           => $admin_email_address,
1723             }
1724         );
1725     }
1726
1727     if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1728         C4::Letters::EnqueueLetter(
1729             {   letter                 => $letter,
1730                 borrowernumber         => $borrowernumber,
1731                 message_transport_type => 'sms',
1732             }
1733         );
1734     }
1735 }
1736
1737 =head2 _ShiftPriorityByDateAndPriority
1738
1739   $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1740
1741 This increments the priority of all reserves after the one
1742 with either the lowest date after C<$reservedate>
1743 or the lowest priority after C<$priority>.
1744
1745 It effectively makes room for a new reserve to be inserted with a certain
1746 priority, which is returned.
1747
1748 This is most useful when the reservedate can be set by the user.  It allows
1749 the new reserve to be placed before other reserves that have a later
1750 reservedate.  Since priority also is set by the form in reserves/request.pl
1751 the sub accounts for that too.
1752
1753 =cut
1754
1755 sub _ShiftPriorityByDateAndPriority {
1756     my ( $biblio, $resdate, $new_priority ) = @_;
1757
1758     my $dbh = C4::Context->dbh;
1759     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1760     my $sth = $dbh->prepare( $query );
1761     $sth->execute( $biblio, $resdate, $new_priority );
1762     my $min_priority = $sth->fetchrow;
1763     # if no such matches are found, $new_priority remains as original value
1764     $new_priority = $min_priority if ( $min_priority );
1765
1766     # Shift the priority up by one; works in conjunction with the next SQL statement
1767     $query = "UPDATE reserves
1768               SET priority = priority+1
1769               WHERE biblionumber = ?
1770               AND borrowernumber = ?
1771               AND reservedate = ?
1772               AND found IS NULL";
1773     my $sth_update = $dbh->prepare( $query );
1774
1775     # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1776     $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1777     $sth = $dbh->prepare( $query );
1778     $sth->execute( $new_priority, $biblio );
1779     while ( my $row = $sth->fetchrow_hashref ) {
1780         $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1781     }
1782
1783     return $new_priority;  # so the caller knows what priority they wind up receiving
1784 }
1785
1786 =head1 AUTHOR
1787
1788 Koha Development Team <http://koha-community.org/>
1789
1790 =cut
1791
1792 1;