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