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