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