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