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