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