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