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