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