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