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