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