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