Fix for Bug 3256, "Long email address cut off in patron windows"
[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
942 =item ModReserveAffect
943
944 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
945
946 This function affect an item and a status for a given reserve
947 The itemnumber parameter is used to find the biblionumber.
948 with the biblionumber & the borrowernumber, we can affect the itemnumber
949 to the correct reserve.
950
951 if $transferToDo is not set, then the status is set to "Waiting" as well.
952 otherwise, a transfer is on the way, and the end of the transfer will 
953 take care of the waiting status
954 =cut
955
956 sub ModReserveAffect {
957     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
958     my $dbh = C4::Context->dbh;
959
960     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
961     # attached to $itemnumber
962     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
963     $sth->execute($itemnumber);
964     my ($biblionumber) = $sth->fetchrow;
965
966     # get request - need to find out if item is already
967     # waiting in order to not send duplicate hold filled notifications
968     my $request = GetReserveInfo($borrowernumber, $biblionumber);
969     my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
970
971     # If we affect a reserve that has to be transfered, don't set to Waiting
972     my $query;
973     if ($transferToDo) {
974     $query = "
975         UPDATE reserves
976         SET    priority = 0,
977                itemnumber = ?
978         WHERE borrowernumber = ?
979           AND biblionumber = ?
980     ";
981     }
982     else {
983     # affect the reserve to Waiting as well.
984     $query = "
985         UPDATE reserves
986         SET     priority = 0,
987                 found = 'W',
988                 waitingdate=now(),
989                 itemnumber = ?
990         WHERE borrowernumber = ?
991           AND biblionumber = ?
992     ";
993     }
994     $sth = $dbh->prepare($query);
995     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
996     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
997
998     return;
999 }
1000
1001 =item ModReserveCancelAll
1002
1003 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1004
1005     function to cancel reserv,check other reserves, and transfer document if it's necessary
1006
1007 =cut
1008
1009 sub ModReserveCancelAll {
1010     my $messages;
1011     my $nextreservinfo;
1012     my ( $itemnumber, $borrowernumber ) = @_;
1013
1014     #step 1 : cancel the reservation
1015     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1016
1017     #step 2 launch the subroutine of the others reserves
1018     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1019
1020     return ( $messages, $nextreservinfo );
1021 }
1022
1023 =item ModReserveMinusPriority
1024
1025 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1026
1027 Reduce the values of queuded list     
1028
1029 =cut
1030
1031 sub ModReserveMinusPriority {
1032     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1033
1034     #first step update the value of the first person on reserv
1035     my $dbh   = C4::Context->dbh;
1036     my $query = "
1037         UPDATE reserves
1038         SET    priority = 0 , itemnumber = ? 
1039         WHERE  borrowernumber=?
1040           AND  biblionumber=?
1041     ";
1042     my $sth_upd = $dbh->prepare($query);
1043     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1044     # second step update all others reservs
1045     _FixPriority($biblionumber, $borrowernumber, '0');
1046 }
1047
1048 =item GetReserveInfo
1049
1050 &GetReserveInfo($borrowernumber,$biblionumber);
1051
1052  Get item and borrower details for a current hold.
1053  Current implementation this query should have a single result.
1054 =cut
1055
1056 sub GetReserveInfo {
1057         my ( $borrowernumber, $biblionumber ) = @_;
1058     my $dbh = C4::Context->dbh;
1059         my $strsth="SELECT reservedate, reservenotes, reserves.borrowernumber,
1060                                 reserves.biblionumber, reserves.branchcode,
1061                                 notificationdate, reminderdate, priority, found,
1062                                 firstname, surname, phone, 
1063                                 email, address, address2,
1064                                 cardnumber, city, zipcode,
1065                                 biblio.title, biblio.author,
1066                                 items.holdingbranch, items.itemcallnumber, items.itemnumber, 
1067                                 barcode, notes
1068                         FROM reserves left join items 
1069                                 ON items.itemnumber=reserves.itemnumber , 
1070                                 borrowers, biblio 
1071                         WHERE 
1072                                 reserves.borrowernumber=?  &&
1073                                 reserves.biblionumber=? && 
1074                                 reserves.borrowernumber=borrowers.borrowernumber && 
1075                                 reserves.biblionumber=biblio.biblionumber ";
1076         my $sth = $dbh->prepare($strsth); 
1077         $sth->execute($borrowernumber,$biblionumber);
1078
1079         my $data = $sth->fetchrow_hashref;
1080         return $data;
1081
1082 }
1083
1084 =item IsAvailableForItemLevelRequest
1085
1086 =over 4
1087
1088 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1089
1090 =back
1091
1092 Checks whether a given item record is available for an
1093 item-level hold request.  An item is available if
1094
1095 * it is not lost AND 
1096 * it is not damaged AND 
1097 * it is not withdrawn AND 
1098 * does not have a not for loan value > 0
1099
1100 Whether or not the item is currently on loan is 
1101 also checked - if the AllowOnShelfHolds system preference
1102 is ON, an item can be requested even if it is currently
1103 on loan to somebody else.  If the system preference
1104 is OFF, an item that is currently checked out cannot
1105 be the target of an item-level hold request.
1106
1107 Note that IsAvailableForItemLevelRequest() does not
1108 check if the staff operator is authorized to place
1109 a request on the item - in particular,
1110 this routine does not check IndependantBranches
1111 and canreservefromotherbranches.
1112
1113 =cut
1114
1115 sub IsAvailableForItemLevelRequest {
1116     my $itemnumber = shift;
1117    
1118     my $item = GetItem($itemnumber);
1119
1120     # must check the notforloan setting of the itemtype
1121     # FIXME - a lot of places in the code do this
1122     #         or something similar - need to be
1123     #         consolidated
1124     my $dbh = C4::Context->dbh;
1125     my $notforloan_query;
1126     if (C4::Context->preference('item-level_itypes')) {
1127         $notforloan_query = "SELECT itemtypes.notforloan
1128                              FROM items
1129                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1130                              WHERE itemnumber = ?";
1131     } else {
1132         $notforloan_query = "SELECT itemtypes.notforloan
1133                              FROM items
1134                              JOIN biblioitems USING (biblioitemnumber)
1135                              JOIN itemtypes USING (itemtype)
1136                              WHERE itemnumber = ?";
1137     }
1138     my $sth = $dbh->prepare($notforloan_query);
1139     $sth->execute($itemnumber);
1140     my $notforloan_per_itemtype = 0;
1141     if (my ($notforloan) = $sth->fetchrow_array) {
1142         $notforloan_per_itemtype = 1 if $notforloan;
1143     }
1144
1145     my $available_per_item = 1;
1146     $available_per_item = 0 if $item->{itemlost} or
1147                                ( $item->{notforloan} > 0 ) or
1148                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1149                                $item->{wthdrawn} or
1150                                $notforloan_per_itemtype;
1151
1152
1153     if (C4::Context->preference('AllowOnShelfHolds')) {
1154         return $available_per_item;
1155     } else {
1156         return ($available_per_item and $item->{onloan}); 
1157     }
1158 }
1159
1160 =item _FixPriority
1161
1162 &_FixPriority($biblio,$borrowernumber,$rank);
1163
1164  Only used internally (so don't export it)
1165  Changed how this functions works #
1166  Now just gets an array of reserves in the rank order and updates them with
1167  the array index (+1 as array starts from 0)
1168  and if $rank is supplied will splice item from the array and splice it back in again
1169  in new priority rank
1170
1171 =cut 
1172
1173 sub _FixPriority {
1174     my ( $biblio, $borrowernumber, $rank ) = @_;
1175     my $dbh = C4::Context->dbh;
1176      if ( $rank eq "del" ) {
1177          CancelReserve( $biblio, undef, $borrowernumber );
1178      }
1179     if ( $rank eq "W" || $rank eq "0" ) {
1180
1181         # make sure priority for waiting items is 0
1182         my $query = qq/
1183             UPDATE reserves
1184             SET    priority = 0
1185             WHERE biblionumber = ?
1186               AND borrowernumber = ?
1187               AND found ='W'
1188         /;
1189         my $sth = $dbh->prepare($query);
1190         $sth->execute( $biblio, $borrowernumber );
1191     }
1192     my @priority;
1193     my @reservedates;
1194
1195     # get whats left
1196 # FIXME adding a new security in returned elements for changing priority,
1197 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1198         # This is wrong a waiting reserve has W set
1199         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1200     my $query = qq/
1201         SELECT borrowernumber, reservedate, constrainttype
1202         FROM   reserves
1203         WHERE  biblionumber   = ?
1204           AND  ((found <> 'W') or found is NULL)
1205         ORDER BY priority ASC
1206     /;
1207     my $sth = $dbh->prepare($query);
1208     $sth->execute($biblio);
1209     while ( my $line = $sth->fetchrow_hashref ) {
1210         push( @reservedates, $line );
1211         push( @priority,     $line );
1212     }
1213
1214     # To find the matching index
1215     my $i;
1216     my $key = -1;    # to allow for 0 to be a valid result
1217     for ( $i = 0 ; $i < @priority ; $i++ ) {
1218         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1219             $key = $i;    # save the index
1220             last;
1221         }
1222     }
1223
1224     # if index exists in array then move it to new position
1225     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1226         my $new_rank = $rank -
1227           1;    # $new_rank is what you want the new index to be in the array
1228         my $moving_item = splice( @priority, $key, 1 );
1229         splice( @priority, $new_rank, 0, $moving_item );
1230     }
1231
1232     # now fix the priority on those that are left....
1233     $query = "
1234             UPDATE reserves
1235             SET    priority = ?
1236                 WHERE  biblionumber = ?
1237                  AND borrowernumber   = ?
1238                  AND reservedate = ?
1239          AND found IS NULL
1240     ";
1241     $sth = $dbh->prepare($query);
1242     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1243         $sth->execute(
1244             $j + 1, $biblio,
1245             $priority[$j]->{'borrowernumber'},
1246             $priority[$j]->{'reservedate'}
1247         );
1248         $sth->finish;
1249     }
1250 }
1251
1252 =item _Findgroupreserve
1253
1254   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1255
1256 Looks for an item-specific match first, then for a title-level match, returning the
1257 first match found.  If neither, then we look for a 3rd kind of match based on
1258 reserve constraints.
1259
1260 TODO: add more explanation about reserve constraints
1261
1262 C<&_Findgroupreserve> returns :
1263 C<@results> is an array of references-to-hash whose keys are mostly
1264 fields from the reserves table of the Koha database, plus
1265 C<biblioitemnumber>.
1266
1267 =cut
1268
1269 sub _Findgroupreserve {
1270     my ( $bibitem, $biblio, $itemnumber ) = @_;
1271     my $dbh   = C4::Context->dbh;
1272
1273     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1274     # check for exact targetted match
1275     my $item_level_target_query = qq/
1276         SELECT reserves.biblionumber        AS biblionumber,
1277                reserves.borrowernumber      AS borrowernumber,
1278                reserves.reservedate         AS reservedate,
1279                reserves.branchcode          AS branchcode,
1280                reserves.cancellationdate    AS cancellationdate,
1281                reserves.found               AS found,
1282                reserves.reservenotes        AS reservenotes,
1283                reserves.priority            AS priority,
1284                reserves.timestamp           AS timestamp,
1285                biblioitems.biblioitemnumber AS biblioitemnumber,
1286                reserves.itemnumber          AS itemnumber
1287         FROM reserves
1288         JOIN biblioitems USING (biblionumber)
1289         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1290         WHERE found IS NULL
1291         AND priority > 0
1292         AND item_level_request = 1
1293         AND itemnumber = ?
1294         AND reservedate <= CURRENT_DATE()
1295     /;
1296     my $sth = $dbh->prepare($item_level_target_query);
1297     $sth->execute($itemnumber);
1298     my @results;
1299     if ( my $data = $sth->fetchrow_hashref ) {
1300         push( @results, $data );
1301     }
1302     return @results if @results;
1303     
1304     # check for title-level targetted match
1305     my $title_level_target_query = qq/
1306         SELECT reserves.biblionumber        AS biblionumber,
1307                reserves.borrowernumber      AS borrowernumber,
1308                reserves.reservedate         AS reservedate,
1309                reserves.branchcode          AS branchcode,
1310                reserves.cancellationdate    AS cancellationdate,
1311                reserves.found               AS found,
1312                reserves.reservenotes        AS reservenotes,
1313                reserves.priority            AS priority,
1314                reserves.timestamp           AS timestamp,
1315                biblioitems.biblioitemnumber AS biblioitemnumber,
1316                reserves.itemnumber          AS itemnumber
1317         FROM reserves
1318         JOIN biblioitems USING (biblionumber)
1319         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1320         WHERE found IS NULL
1321         AND priority > 0
1322         AND item_level_request = 0
1323         AND hold_fill_targets.itemnumber = ?
1324         AND reservedate <= CURRENT_DATE()
1325     /;
1326     $sth = $dbh->prepare($title_level_target_query);
1327     $sth->execute($itemnumber);
1328     @results = ();
1329     if ( my $data = $sth->fetchrow_hashref ) {
1330         push( @results, $data );
1331     }
1332     return @results if @results;
1333
1334     my $query = qq/
1335         SELECT reserves.biblionumber               AS biblionumber,
1336                reserves.borrowernumber             AS borrowernumber,
1337                reserves.reservedate                AS reservedate,
1338                reserves.branchcode                 AS branchcode,
1339                reserves.cancellationdate           AS cancellationdate,
1340                reserves.found                      AS found,
1341                reserves.reservenotes               AS reservenotes,
1342                reserves.priority                   AS priority,
1343                reserves.timestamp                  AS timestamp,
1344                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1345                reserves.itemnumber                 AS itemnumber
1346         FROM reserves
1347           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1348         WHERE reserves.biblionumber = ?
1349           AND ( ( reserveconstraints.biblioitemnumber = ?
1350           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1351           AND reserves.reservedate    = reserveconstraints.reservedate )
1352           OR  reserves.constrainttype='a' )
1353           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1354           AND reserves.reservedate <= CURRENT_DATE()
1355     /;
1356     $sth = $dbh->prepare($query);
1357     $sth->execute( $biblio, $bibitem, $itemnumber );
1358     @results = ();
1359     while ( my $data = $sth->fetchrow_hashref ) {
1360         push( @results, $data );
1361     }
1362     return @results;
1363 }
1364
1365 =item _koha_notify_reserve
1366
1367 =over 4
1368
1369 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1370
1371 =back
1372
1373 Sends a notification to the patron that their hold has been filled (through
1374 ModReserveAffect, _not_ ModReserveFill)
1375
1376 =cut
1377
1378 sub _koha_notify_reserve {
1379     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1380
1381     my $dbh = C4::Context->dbh;
1382     my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } );
1383
1384     return if ( !defined( $messagingprefs->{'letter_code'} ) );
1385
1386     my $sth = $dbh->prepare("
1387         SELECT *
1388         FROM   reserves
1389         WHERE  borrowernumber = ?
1390             AND biblionumber = ?
1391     ");
1392     $sth->execute( $borrowernumber, $biblionumber );
1393     my $reserve = $sth->fetchrow_hashref;
1394     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1395
1396     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1397
1398     my $letter = getletter( 'reserves', $messagingprefs->{'letter_code'} );
1399
1400     C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1401     C4::Letters::parseletter( $letter, 'borrowers', $reserve->{'borrowernumber'} );
1402     C4::Letters::parseletter( $letter, 'biblio', $reserve->{'biblionumber'} );
1403     C4::Letters::parseletter( $letter, 'reserves', $reserve->{'borrowernumber'}, $reserve->{'biblionumber'} );
1404
1405     if ( $reserve->{'itemnumber'} ) {
1406         C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1407     }
1408     $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1409
1410     if ( -1 !=  firstidx { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1411         # aka, 'email' in ->{'transports'}
1412         C4::Letters::EnqueueLetter(
1413             {   letter                 => $letter,
1414                 borrowernumber         => $borrowernumber,
1415                 message_transport_type => 'email',
1416                 from_address           => $admin_email_address,
1417             }
1418         );
1419     }
1420
1421     if ( -1 != firstidx { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1422         C4::Letters::EnqueueLetter(
1423             {   letter                 => $letter,
1424                 borrowernumber         => $borrowernumber,
1425                 message_transport_type => 'sms',
1426             }
1427         );
1428     }
1429 }
1430
1431 =item _ShiftPriorityByDateAndPriority
1432
1433 =over 4
1434
1435 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1436
1437 =back
1438
1439 This increments the priority of all reserves after the one
1440  with either the lowest date after C<$reservedate>
1441  or the lowest priority after C<$priority>.
1442
1443 It effectively makes room for a new reserve to be inserted with a certain
1444  priority, which is returned.
1445
1446 This is most useful when the reservedate can be set by the user.  It allows
1447  the new reserve to be placed before other reserves that have a later
1448  reservedate.  Since priority also is set by the form in reserves/request.pl
1449  the sub accounts for that too.
1450
1451 =cut
1452
1453 sub _ShiftPriorityByDateAndPriority {
1454     my ( $biblio, $resdate, $new_priority ) = @_;
1455
1456     my $dbh = C4::Context->dbh;
1457     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC";
1458     my $sth = $dbh->prepare( $query );
1459     $sth->execute( $biblio, $resdate, $new_priority );
1460     my ( $min_priority ) = $sth->fetchrow;
1461     $sth->finish;  # $sth might have more data.
1462     $new_priority = $min_priority if ( $min_priority );
1463     my $updated_priority = $new_priority + 1;
1464
1465     $query = "
1466  UPDATE reserves
1467     SET priority = ?
1468   WHERE biblionumber = ?
1469     AND borrowernumber = ?
1470     AND reservedate = ?
1471     AND found IS NULL";
1472     my $sth_update = $dbh->prepare( $query );
1473
1474     $query = "SELECT * FROM reserves WHERE priority >= ?";
1475     $sth = $dbh->prepare( $query );
1476     $sth->execute( $new_priority );
1477     while ( my $row = $sth->fetchrow_hashref ) {
1478         $sth_update->execute( $updated_priority, $biblio, $row->{borrowernumber}, $row->{reservedate} );
1479         $updated_priority++;
1480     }
1481
1482     return $new_priority;  # so the caller knows what priority they end up at
1483 }
1484
1485 =back
1486
1487 =head1 AUTHOR
1488
1489 Koha Developement team <info@koha.org>
1490
1491 =cut
1492
1493 1;