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