BUGFIX for 1309 (displayXXX in Koha.pm)
[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 # $Id$
26
27 use strict;
28 require Exporter;
29 use C4::Context;
30 use C4::Biblio;
31 use C4::Search;
32 use C4::Circulation;
33
34 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,%EXPORT_TAGS);
35
36 my $library_name = C4::Context->preference("LibraryName");
37
38 # set the version for version checking
39 $VERSION = do { my @v = '$Revision$' =~ /\d+/g; shift(@v) . "." . join( "_", map { sprintf "%03d", $_ } @v ); };
40
41 =head1 NAME
42
43 C4::Reserves - Koha functions for dealing with reservation.
44
45 =head1 SYNOPSIS
46
47   use C4::Reserves;
48
49 =head1 DESCRIPTION
50
51   this modules provides somes functions to deal with reservations.
52   
53   Reserves are stored in reserves table.
54   The following columns contains important values :
55   - priority >0      : then the reserve is at 1st stage, and not yet affected to any item.
56              =0      : then the reserve is being dealed
57   - found : NULL       : means the patron requested the 1st available, and we haven't choosen the item
58             W(aiting)  : the reserve has an itemnumber affected, and is on the way
59             F(inished) : the reserve has been completed, and is done
60   - itemnumber : empty : the reserve is still unaffected to an item
61                  filled: the reserve is attached to an item
62   The complete workflow is :
63   ==== 1st use case ====
64   patron request a document, 1st available :                      P >0, F=NULL, I=NULL
65   a library having it run "transfertodo", and clic on the list    
66          if there is no transfer to do, the reserve waiting
67          patron can pick it up                                    P =0, F=W,    I=filled 
68          if there is a transfer to do, write in branchtransfer    P =0, F=NULL, I=filled
69            The pickup library recieve the book, it check in       P =0, F=W,    I=filled
70   The patron borrow the book                                      P =0, F=F,    I=filled
71   
72   ==== 2nd use case ====
73   patron requests a document, a given item,
74     If pickup is holding branch                                   P =0, F=W,   I=filled
75     If transfer needed, write in branchtransfer                   P =0, F=NULL, I=filled
76         The pickup library recieve the book, it checks it in      P =0, F=W,    I=filled
77   The patron borrow the book                                      P =0, F=F,    I=filled
78   
79 =head1 FUNCTIONS
80
81 =over 2
82
83 =cut
84
85 @ISA = qw(Exporter);
86
87 @EXPORT = qw(
88   &AddReserve
89   
90   &GetReservesFromItemnumber
91   &GetReservesFromBiblionumber
92   &GetReservesFromBorrowernumber
93   &GetReservesForBranch
94   &GetReservesToBranch
95   &GetReserveCount
96   &GetReserveFee
97   &GetReservesForBranch
98   &GetReservesToBranch
99   &GetOtherReserves
100   
101   &ModReserveFill
102   &ModReserveAffect
103   &ModReserve
104   &ModReserveStatus
105   &ModReserveCancelAll
106   &ModReserveMinusPriority
107
108   &CheckReserves
109   &CancelReserve
110 );
111
112
113 =item AddReserve
114
115     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$notes,$title,$checkitem,$found)
116
117 =cut
118
119 sub AddReserve {
120     my (
121         $branch,    $borrowernumber, $biblionumber,
122         $constraint, $bibitems,  $priority,       $notes,
123         $title,      $checkitem, $found
124     ) = @_;
125     my $fee =
126           GetReserveFee($borrowernumber, $biblionumber, $constraint,
127             $bibitems );
128     my $dbh     = C4::Context->dbh;
129     my $const   = lc substr( $constraint, 0, 1 );
130     my @datearr = localtime(time);
131     my $resdate =
132       ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
133     my $waitingdate;
134
135     # If the reserv had the waiting status, we had the value of the resdate
136     if ( $found eq 'W' ) {
137         $waitingdate = $resdate;
138     }
139
140     #eval {
141     # updates take place here
142     if ( $fee > 0 ) {
143         my $nextacctno = &getnextacctno( $borrowernumber );
144         my $query      = qq/
145         INSERT INTO accountlines
146             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
147         VALUES
148             (?,?,now(),?,?,'Res',?)
149     /;
150         my $usth = $dbh->prepare($query);
151         $usth->execute( $borrowernumber, $nextacctno, $fee,
152             "Reserve Charge - $title", $fee );
153         $usth->finish;
154     }
155
156     #if ($const eq 'a'){
157     my $query = qq/
158         INSERT INTO reserves
159             (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
160             priority,reservenotes,itemnumber,found,waitingdate)
161         VALUES
162              (?,?,?,?,?,
163              ?,?,?,?,?)
164     /;
165     my $sth = $dbh->prepare($query);
166     $sth->execute(
167         $borrowernumber, $biblionumber, $resdate, $branch,
168         $const,          $priority,     $notes,   $checkitem,
169         $found,          $waitingdate
170     );
171     $sth->finish;
172
173     #}
174     if ( ( $const eq "o" ) || ( $const eq "e" ) ) {
175         my $numitems = @$bibitems;
176         my $i        = 0;
177         while ( $i < $numitems ) {
178             my $biblioitem = @$bibitems[$i];
179             my $query      = qq/
180           INSERT INTO reserveconstraints
181               (borrowernumber,biblionumber,reservedate,biblioitemnumber)
182           VALUES
183             (?,?,?,?)
184       /;
185             my $sth = $dbh->prepare("");
186             $sth->execute( $borrowernumber, $biblionumber, $resdate,
187                 $biblioitem );
188             $sth->finish;
189             $i++;
190         }
191     }
192     return;
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         FROM     reserves
221         WHERE     cancellationdate IS NULL
222         AND    (found <> \'F\' OR found IS NULL)
223         AND biblionumber = ?
224         ORDER BY priority";
225     my $sth = $dbh->prepare($query);
226     $sth->execute($biblionumber);
227     my @results;
228     my $i = 0;
229     while ( my $data = $sth->fetchrow_hashref ) {
230
231         # FIXME - What is this if-statement doing? How do constraints work?
232         if ( $data->{constrainttype} eq 'o' ) {
233             $query = '
234                 SELECT biblioitemnumber
235                 FROM reserveconstraints
236                 WHERE biblionumber   = ?
237                     AND borrowernumber = ?
238                 AND reservedate    = ?
239             ';
240             my $csth = $dbh->prepare($query);
241             $csth->execute( $data->{biblionumber}, $data->{borrowernumber},
242                 $data->{reservedate}, );
243
244             my @bibitemno;
245             while ( my $bibitemnos = $csth->fetchrow_array ) {
246                 push( @bibitemno, $bibitemnos );
247             }
248             my $count = @bibitemno;
249
250             # if we have two or more different specific itemtypes
251             # reserved by same person on same day
252             my $bdata;
253             if ( $count > 1 ) {
254                 $bdata = GetBiblioItemData( $bibitemno[$i] );
255                 $i++;
256             }
257             else {
258
259                 # Look up the book we just found.
260                 $bdata = GetBiblioItemData( $bibitemno[0] );
261             }
262             $csth->finish;
263
264             # Add the results of this latest search to the current
265             # results.
266             # FIXME - An 'each' would probably be more efficient.
267             foreach my $key ( keys %$bdata ) {
268                 $data->{$key} = $bdata->{$key};
269             }
270         }
271         push @results, $data;
272     }
273     $sth->finish;
274     return ( $#results + 1, \@results );
275 }
276
277 =item GetReservesFromItemnumber
278
279  ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
280
281    TODO :: Description here
282
283 =cut
284
285 sub GetReservesFromItemnumber {
286     my ( $itemnumber ) = @_;
287     my $dbh   = C4::Context->dbh;
288     my $query = "
289     SELECT reservedate,borrowernumber,branchcode
290     FROM   reserves
291     WHERE  itemnumber=?
292         AND  cancellationdate IS NULL
293         AND  (found <> 'F' OR found IS NULL)
294     ";
295     my $sth_res = $dbh->prepare($query);
296     $sth_res->execute($itemnumber);
297     my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
298     return ( $reservedate, $borrowernumber, $branchcode );
299 }
300
301 =item GetReservesFromBorrowernumber
302
303     $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
304     
305     TODO :: Descritpion
306     
307 =cut
308
309 sub GetReservesFromBorrowernumber {
310     my ( $borrowernumber, $status ) = @_;
311     my $dbh   = C4::Context->dbh;
312     my $sth;
313     if ($status) {
314         $sth = $dbh->prepare("
315             SELECT *
316             FROM   reserves
317             WHERE  borrowernumber=?
318                 AND  cancellationdate IS NULL
319                 AND found =?
320             ORDER BY reservedate
321         ");
322         $sth->execute($borrowernumber,$status);
323     } else {
324         $sth = $dbh->prepare("
325             SELECT *
326             FROM   reserves
327             WHERE  borrowernumber=?
328                 AND  cancellationdate IS NULL
329                 AND (found != 'F' or found is null)
330             ORDER BY reservedate
331         ");
332         $sth->execute($borrowernumber);
333     }
334     my $data = $sth->fetchall_arrayref({});
335     return @$data;
336 }
337 #-------------------------------------------------------------------------------------
338
339 =item GetReserveCount
340
341 $number = &GetReserveCount($borrowernumber);
342
343 this function returns the number of reservation for a borrower given on input arg.
344
345 =cut
346
347 sub GetReserveCount {
348     my ($borrowernumber) = @_;
349
350     my $dbh = C4::Context->dbh;
351
352     my $query = '
353         SELECT COUNT(*) AS counter
354         FROM reserves
355           WHERE borrowernumber = ?
356           AND cancellationdate IS NULL
357           AND (found != \'F\' OR found IS NULL)
358     ';
359     my $sth = $dbh->prepare($query);
360     $sth->execute($borrowernumber);
361     my $row = $sth->fetchrow_hashref;
362     $sth->finish;
363
364     return $row->{counter};
365 }
366
367 =item GetOtherReserves
368
369 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
370
371 Check queued list of this document and check if this document must be  transfered
372
373 =cut
374
375 sub GetOtherReserves {
376     my ($itemnumber) = @_;
377     my $messages;
378     my $nextreservinfo;
379     my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
380     if ($checkreserves) {
381         my $iteminfo = GetItem($itemnumber);
382         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
383             $messages->{'transfert'} = $checkreserves->{'branchcode'};
384             #minus priorities of others reservs
385             ModReserveMinusPriority(
386                 $itemnumber,
387                 $checkreserves->{'borrowernumber'},
388                 $iteminfo->{'biblionumber'}
389             );
390
391             #launch the subroutine dotransfer
392             C4::Circulation::ModItemTransfer(
393                 $itemnumber,
394                 $iteminfo->{'holdingbranch'},
395                 $checkreserves->{'branchcode'}
396               ),
397               ;
398         }
399
400      #step 2b : case of a reservation on the same branch, set the waiting status
401         else {
402             $messages->{'waiting'} = 1;
403             ModReserveMinusPriority(
404                 $itemnumber,
405                 $checkreserves->{'borrowernumber'},
406                 $iteminfo->{'biblionumber'}
407             );
408             ModReserveStatus($itemnumber,'W');
409         }
410
411         $nextreservinfo = $checkreserves->{'borrowernumber'};
412     }
413
414     return ( $messages, $nextreservinfo );
415 }
416
417 =item GetReserveFee
418
419 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
420
421 Calculate the fee for a reserve
422
423 =cut
424
425 sub GetReserveFee {
426     my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
427
428     #check for issues;
429     my $dbh   = C4::Context->dbh;
430     my $const = lc substr( $constraint, 0, 1 );
431     my $query = qq/
432       SELECT * FROM borrowers,categories
433     WHERE borrowernumber = ?
434       AND borrowers.categorycode = categories.categorycode
435     /;
436     my $sth = $dbh->prepare($query);
437     $sth->execute($borrowernumber);
438     my $data = $sth->fetchrow_hashref;
439     $sth->finish();
440     my $fee      = $data->{'reservefee'};
441     my $cntitems = @- > $bibitems;
442
443     if ( $fee > 0 ) {
444
445         # check for items on issue
446         # first find biblioitem records
447         my @biblioitems;
448         my $sth1 = $dbh->prepare(
449             "SELECT * FROM biblio,biblioitems
450                    WHERE (biblio.biblionumber = ?)
451                      AND (biblio.biblionumber = biblioitems.biblionumber)"
452         );
453         $sth1->execute($biblionumber);
454         while ( my $data1 = $sth1->fetchrow_hashref ) {
455             if ( $const eq "a" ) {
456                 push @biblioitems, $data1;
457             }
458             else {
459                 my $found = 0;
460                 my $x     = 0;
461                 while ( $x < $cntitems ) {
462                     if ( @$bibitems->{'biblioitemnumber'} ==
463                         $data->{'biblioitemnumber'} )
464                     {
465                         $found = 1;
466                     }
467                     $x++;
468                 }
469                 if ( $const eq 'o' ) {
470                     if ( $found == 1 ) {
471                         push @biblioitems, $data1;
472                     }
473                 }
474                 else {
475                     if ( $found == 0 ) {
476                         push @biblioitems, $data1;
477                     }
478                 }
479             }
480         }
481         $sth1->finish;
482         my $cntitemsfound = @biblioitems;
483         my $issues        = 0;
484         my $x             = 0;
485         my $allissued     = 1;
486         while ( $x < $cntitemsfound ) {
487             my $bitdata = $biblioitems[$x];
488             my $sth2    = $dbh->prepare(
489                 "SELECT * FROM items
490                      WHERE biblioitemnumber = ?"
491             );
492             $sth2->execute( $bitdata->{'biblioitemnumber'} );
493             while ( my $itdata = $sth2->fetchrow_hashref ) {
494                 my $sth3 = $dbh->prepare(
495                     "SELECT * FROM issues
496                        WHERE itemnumber = ?
497                          AND returndate IS NULL"
498                 );
499                 $sth3->execute( $itdata->{'itemnumber'} );
500                 if ( my $isdata = $sth3->fetchrow_hashref ) {
501                 }
502                 else {
503                     $allissued = 0;
504                 }
505             }
506             $x++;
507         }
508         if ( $allissued == 0 ) {
509             my $rsth =
510               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
511             $rsth->execute($biblionumber);
512             if ( my $rdata = $rsth->fetchrow_hashref ) {
513             }
514             else {
515                 $fee = 0;
516             }
517         }
518     }
519     return $fee;
520 }
521
522 =item GetReservesToBranch
523
524 @transreserv = GetReservesToBranch( $frombranch );
525
526 Get reserve list for a given branch
527
528 =cut
529
530 sub GetReservesToBranch {
531     my ( $frombranch ) = @_;
532     my $dbh = C4::Context->dbh;
533     my $sth = $dbh->prepare(
534         "SELECT borrowernumber,reservedate,itemnumber,timestamp
535          FROM reserves 
536          WHERE priority='0' AND cancellationdate is null  
537            AND branchcode=?
538            AND found IS NULL "
539     );
540     $sth->execute( $frombranch );
541     my @transreserv;
542     my $i = 0;
543     while ( my $data = $sth->fetchrow_hashref ) {
544         $transreserv[$i] = $data;
545         $i++;
546     }
547     $sth->finish;
548     return (@transreserv);
549 }
550
551 =item GetReservesForBranch
552
553 @transreserv = GetReservesForBranch($frombranch);
554
555 =cut
556
557 sub GetReservesForBranch {
558     my ($frombranch) = @_;
559     my $dbh          = C4::Context->dbh;
560     my $sth          = $dbh->prepare( "
561         SELECT borrowernumber,reservedate,itemnumber,waitingdate
562         FROM   reserves 
563         WHERE   priority='0'
564             AND cancellationdate IS NULL 
565             AND found='W' 
566             AND branchcode=?
567         ORDER BY waitingdate" );
568     $sth->execute($frombranch);
569     my @transreserv;
570     my $i = 0;
571     while ( my $data = $sth->fetchrow_hashref ) {
572         $transreserv[$i] = $data;
573         $i++;
574     }
575     $sth->finish;
576     return (@transreserv);
577 }
578
579 =item CheckReserves
580
581   ($status, $reserve) = &CheckReserves($itemnumber);
582
583 Find a book in the reserves.
584
585 C<$itemnumber> is the book's item number.
586
587 As I understand it, C<&CheckReserves> looks for the given item in the
588 reserves. If it is found, that's a match, and C<$status> is set to
589 C<Waiting>.
590
591 Otherwise, it finds the most important item in the reserves with the
592 same biblio number as this book (I'm not clear on this) and returns it
593 with C<$status> set to C<Reserved>.
594
595 C<&CheckReserves> returns a two-element list:
596
597 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
598
599 C<$reserve> is the reserve item that matched. It is a
600 reference-to-hash whose keys are mostly the fields of the reserves
601 table in the Koha database.
602
603 =cut
604
605 sub CheckReserves {
606     my ( $item, $barcode ) = @_;
607     my $dbh = C4::Context->dbh;
608     my $sth;
609     if ($item) {
610         my $qitem = $dbh->quote($item);
611         # Look up the item by itemnumber
612         my $query = "
613             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
614             FROM   items, biblioitems, itemtypes
615             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
616                AND biblioitems.itemtype = itemtypes.itemtype
617                AND itemnumber=$qitem
618         ";
619         $sth = $dbh->prepare($query);
620     }
621     else {
622         my $qbc = $dbh->quote($barcode);
623         # Look up the item by barcode
624         my $query = "
625             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
626             FROM   items, biblioitems, itemtypes
627             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
628               AND biblioitems.itemtype = itemtypes.itemtype
629               AND barcode=$qbc
630         ";
631         $sth = $dbh->prepare($query);
632
633         # FIXME - This function uses $item later on. Ought to set it here.
634     }
635     $sth->execute;
636     my ( $biblio, $bibitem, $notforloan ) = $sth->fetchrow_array;
637     $sth->finish;
638
639     # if item is not for loan it cannot be reserved either.....
640     return ( 0, 0 ) if $notforloan;
641
642     # get the reserves...
643     # Find this item in the reserves
644     my @reserves = _Findgroupreserve( $bibitem, $biblio );
645     my $count    = scalar @reserves;
646
647     # $priority and $highest are used to find the most important item
648     # in the list returned by &_Findgroupreserve. (The lower $priority,
649     # the more important the item.)
650     # $highest is the most important item we've seen so far.
651     my $priority = 10000000;
652     my $highest;
653     if ($count) {
654         foreach my $res (@reserves) {
655             # FIXME - $item might be undefined or empty: the caller
656             # might be searching by barcode.
657             if ( $res->{'itemnumber'} == $item ) {
658                 # Found it
659                 return ( "Waiting", $res );
660             }
661             else {
662                 # See if this item is more important than what we've got
663                 # so far.
664                 if ( $res->{'priority'} != 0 && $res->{'priority'} < $priority )
665                 {
666                     $priority = $res->{'priority'};
667                     $highest  = $res;
668                 }
669             }
670         }
671     }
672
673     # If we get this far, then no exact match was found. Print the
674     # most important item on the list. I think this tells us who's
675     # next in line to get this book.
676     if ($highest) {    # FIXME - $highest might be undefined
677         $highest->{'itemnumber'} = $item;
678         return ( "Reserved", $highest );
679     }
680     else {
681         return ( 0, 0 );
682     }
683 }
684
685 =item CancelReserve
686
687   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
688
689 Cancels a reserve.
690
691 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
692 cancel, but not both: if both are given, C<&CancelReserve> does
693 nothing.
694
695 C<$borrowernumber> is the borrower number of the patron on whose
696 behalf the book was reserved.
697
698 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
699 priorities of the other people who are waiting on the book.
700
701 =cut
702
703 sub CancelReserve {
704     my ( $biblio, $item, $borr ) = @_;
705     my $dbh = C4::Context->dbh;
706         if ( ( $item and $borr ) and ( not $biblio ) ) {
707         # removing a waiting reserve record....
708         # update the database...
709         my $query = "
710             UPDATE reserves
711             SET    cancellationdate = now(),
712                    found            = Null,
713                    priority         = 0
714             WHERE  itemnumber       = ?
715              AND   borrowernumber   = ?
716         ";
717         my $sth = $dbh->prepare($query);
718         $sth->execute( $item, $borr );
719         $sth->finish;
720     }
721     if ( ( $biblio and $borr ) and ( not $item ) ) {
722         # removing a reserve record....
723         # get the prioritiy on this record....
724         my $priority;
725         my $query = qq/
726             SELECT priority FROM reserves
727             WHERE biblionumber   = ?
728               AND borrowernumber = ?
729               AND cancellationdate IS NULL
730               AND itemnumber IS NULL
731               AND (found <> 'F' OR found IS NULL)
732         /;
733         my $sth = $dbh->prepare($query);
734         $sth->execute( $biblio, $borr );
735         ($priority) = $sth->fetchrow_array;
736         $sth->finish;
737         $query = qq/
738             UPDATE reserves
739             SET    cancellationdate = now(),
740                    found            = Null,
741                    priority         = 0
742             WHERE  biblionumber     = ?
743               AND  borrowernumber   = ?
744               AND cancellationdate IS NULL
745               AND (found <> 'F' or found IS NULL)
746         /;
747
748         # update the database, removing the record...
749         $sth = $dbh->prepare($query);
750         $sth->execute( $biblio, $borr );
751         $sth->finish;
752
753         # now fix the priority on the others....
754         _FixPriority( $priority, $biblio );
755     }
756 }
757
758 =item ModReserve
759
760 &ModReserve($rank,$biblio,$borrower,$branch)
761
762 =cut
763
764 sub ModReserve {
765     #subroutine to update a reserve
766     my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
767      return if $rank eq "W";
768      return if $rank eq "n";
769     my $dbh = C4::Context->dbh;
770     if ( $rank eq "del" ) {
771         my $query = qq/
772             UPDATE reserves
773             SET    cancellationdate=now()
774             WHERE  biblionumber   = ?
775              AND   borrowernumber = ?
776              AND   cancellationdate is NULL
777              AND   (found <> 'F' or found is NULL)
778         /;
779         my $sth = $dbh->prepare($query);
780         $sth->execute( $biblio, $borrower );
781         $sth->finish;
782         
783     }
784     else {
785         my $query = qq/
786         UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL
787             WHERE biblionumber   = ?
788              AND borrowernumber = ?
789              AND cancellationdate is NULL
790              AND (found <> 'F' or found is NULL)
791         /;
792         my $sth = $dbh->prepare($query);
793         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
794         $sth->finish;
795         _FixPriority( $biblio, $borrower, $rank);
796     }
797 }
798
799 =item ModReserveFill
800
801   &ModReserveFill($reserve);
802
803 Fill a reserve. If I understand this correctly, this means that the
804 reserved book has been found and given to the patron who reserved it.
805
806 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
807 whose keys are fields from the reserves table in the Koha database.
808
809 =cut
810
811 sub ModReserveFill {
812     my ($res) = @_;
813     my $dbh = C4::Context->dbh;
814     # fill in a reserve record....
815     my $biblionumber = $res->{'biblionumber'};
816     my $borrowernumber    = $res->{'borrowernumber'};
817     my $resdate = $res->{'reservedate'};
818
819     # get the priority on this record....
820     my $priority;
821     my $query = "SELECT priority
822                  FROM   reserves
823                  WHERE  biblionumber   = ?
824                   AND   borrowernumber = ?
825                   AND   reservedate    = ?";
826     my $sth = $dbh->prepare($query);
827     $sth->execute( $biblionumber, $borrowernumber, $resdate );
828     ($priority) = $sth->fetchrow_array;
829     $sth->finish;
830
831     # update the database...
832     $query = "UPDATE reserves
833                   SET    found            = 'F',
834                          priority         = 0
835                  WHERE  biblionumber     = ?
836                     AND reservedate      = ?
837                     AND borrowernumber   = ?
838                 ";
839     $sth = $dbh->prepare($query);
840     $sth->execute( $biblionumber, $resdate, $borrowernumber );
841     $sth->finish;
842
843     # now fix the priority on the others (if the priority wasn't
844     # already sorted!)....
845     unless ( $priority == 0 ) {
846         _FixPriority( $priority, $biblionumber );
847     }
848 }
849
850 =item ModReserveStatus
851
852 &ModReserveStatus($itemnumber, $newstatus);
853
854 Update the reserve status for the active (priority=0) reserve.
855
856 $itemnumber is the itemnumber the reserve is on
857
858 $newstatus is the new status.
859
860 =cut
861
862 sub ModReserveStatus {
863
864     #first : check if we have a reservation for this item .
865     my ($itemnumber, $newstatus) = @_;
866     my $dbh          = C4::Context->dbh;
867     my $query = " UPDATE reserves
868     SET    found=?,waitingdate = now()
869     WHERE itemnumber=?
870       AND found IS NULL
871       AND priority = 0
872     ";
873     my $sth_set = $dbh->prepare($query);
874     $sth_set->execute( $newstatus, $itemnumber );
875     $sth_set->finish;
876 }
877
878 =item ModReserveAffect
879
880 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
881
882 This function affect an item and a status for a given reserve
883 The itemnumber parameter is used to find the biblionumber.
884 with the biblionumber & the borrowernumber, we can affect the itemnumber
885 to the correct reserve.
886
887 if $transferToDo is not set, then the status is set to "Waiting" as well.
888 otherwise, a transfer is on the way, and the end of the transfer will 
889 take care of the waiting status
890 =cut
891
892 sub ModReserveAffect {
893     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
894     my $dbh = C4::Context->dbh;
895
896     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
897     # attached to $itemnumber
898     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
899     $sth->execute($itemnumber);
900     my ($biblionumber) = $sth->fetchrow;
901     # If we affect a reserve that has to be transfered, don't set to Waiting
902     my $query;
903     if ($transferToDo) {
904     $query = "
905         UPDATE reserves
906         SET    priority = 0,
907                itemnumber = ?
908         WHERE borrowernumber = ?
909           AND biblionumber = ?
910           AND reserves.cancellationdate IS NULL
911           AND (reserves.found <> 'F' OR reserves.found IS NULL)
912     ";
913     }
914     else {
915     # affect the reserve to Waiting as well.
916     $query = "
917         UPDATE reserves
918         SET     priority = 0,
919                 found = 'W',
920                 waitingdate=now(),
921                 itemnumber = ?
922         WHERE borrowernumber = ?
923           AND biblionumber = ?
924           AND reserves.cancellationdate IS NULL
925           AND (reserves.found <> 'F' OR reserves.found IS NULL)
926     ";
927     }
928     $sth = $dbh->prepare($query);
929     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
930     $sth->finish;
931     return;
932 }
933
934 =item ModReserveCancelAll
935
936 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
937
938     function to cancel reserv,check other reserves, and transfer document if it's necessary
939
940 =cut
941
942 sub ModReserveCancelAll {
943     my $messages;
944     my $nextreservinfo;
945     my ( $itemnumber, $borrowernumber ) = @_;
946
947     #step 1 : cancel the reservation
948     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
949
950     #step 2 launch the subroutine of the others reserves
951     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
952
953     return ( $messages, $nextreservinfo );
954 }
955
956 =item ModReserveMinusPriority
957
958 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
959
960 Reduce the values of queuded list     
961
962 =cut
963
964 sub ModReserveMinusPriority {
965     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
966
967     #first step update the value of the first person on reserv
968     my $dbh   = C4::Context->dbh;
969     my $query = "
970         UPDATE reserves
971         SET    priority = 0 , itemnumber = ? 
972         WHERE  cancellationdate IS NULL 
973           AND  borrowernumber=?
974           AND  biblionumber=?
975     ";
976     my $sth_upd = $dbh->prepare($query);
977     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
978     $sth_upd->finish;
979     # second step update all others reservs
980     $query = "
981             UPDATE reserves
982             SET    priority = priority-1
983             WHERE  biblionumber = ?
984             AND priority > 0
985             AND cancellationdate IS NULL
986     ";
987     $sth_upd = $dbh->prepare($query);
988     $sth_upd->execute( $biblionumber );
989     $sth_upd->finish;
990     $sth_upd->finish;
991 }
992
993 =item _FixPriority
994
995 &_FixPriority($biblio,$borrowernumber,$rank);
996
997  Only used internally (so don't export it)
998  Changed how this functions works #
999  Now just gets an array of reserves in the rank order and updates them with
1000  the array index (+1 as array starts from 0)
1001  and if $rank is supplied will splice item from the array and splice it back in again
1002  in new priority rank
1003
1004 =cut 
1005
1006 sub _FixPriority {
1007     my ( $biblio, $borrowernumber, $rank ) = @_;
1008     my $dbh = C4::Context->dbh;
1009      if ( $rank eq "del" ) {
1010          CancelReserve( $biblio, undef, $borrowernumber );
1011      }
1012     if ( $rank eq "W" || $rank eq "0" ) {
1013
1014         # make sure priority for waiting items is 0
1015         my $query = qq/
1016             UPDATE reserves
1017             SET    priority = 0
1018             WHERE biblionumber = ?
1019               AND borrowernumber = ?
1020               AND cancellationdate IS NULL
1021               AND found ='W'
1022         /;
1023         my $sth = $dbh->prepare($query);
1024         $sth->execute( $biblio, $borrowernumber );
1025     }
1026     my @priority;
1027     my @reservedates;
1028
1029     # get whats left
1030 # FIXME adding a new security in returned elements for changing priority,
1031 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1032     my $query = qq/
1033         SELECT borrowernumber, reservedate, constrainttype
1034         FROM   reserves
1035         WHERE  biblionumber   = ?
1036           AND  cancellationdate IS NULL
1037           AND  itemnumber IS NULL
1038           AND  ((found <> 'F' and found <> 'W') or found is NULL)
1039         ORDER BY priority ASC
1040     /;
1041     my $sth = $dbh->prepare($query);
1042     $sth->execute($biblio);
1043     while ( my $line = $sth->fetchrow_hashref ) {
1044         push( @reservedates, $line );
1045         push( @priority,     $line );
1046     }
1047
1048     # To find the matching index
1049     my $i;
1050     my $key = -1;    # to allow for 0 to be a valid result
1051     for ( $i = 0 ; $i < @priority ; $i++ ) {
1052         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1053             $key = $i;    # save the index
1054             last;
1055         }
1056     }
1057
1058     # if index exists in array then move it to new position
1059     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1060         my $new_rank = $rank -
1061           1;    # $new_rank is what you want the new index to be in the array
1062         my $moving_item = splice( @priority, $key, 1 );
1063         splice( @priority, $new_rank, 0, $moving_item );
1064     }
1065
1066     # now fix the priority on those that are left....
1067     $query = "
1068             UPDATE reserves
1069             SET    priority = ?
1070                 WHERE  biblionumber = ?
1071                  AND borrowernumber   = ?
1072                  AND reservedate = ?
1073          AND found IS NULL
1074     ";
1075     $sth = $dbh->prepare($query);
1076     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1077         $sth->execute(
1078             $j + 1, $biblio,
1079             $priority[$j]->{'borrowernumber'},
1080             $priority[$j]->{'reservedate'}
1081         );
1082         $sth->finish;
1083     }
1084 }
1085
1086 =item _Findgroupreserve
1087
1088   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber);
1089
1090 ****** FIXME ******
1091 I don't know what this does, because I don't understand how reserve
1092 constraints work. I think the idea is that you reserve a particular
1093 biblio, and the constraint allows you to restrict it to a given
1094 biblioitem (e.g., if you want to borrow the audio book edition of "The
1095 Prophet", rather than the first available publication).
1096
1097 C<&_Findgroupreserve> returns :
1098 C<@results> is an array of references-to-hash whose keys are mostly
1099 fields from the reserves table of the Koha database, plus
1100 C<biblioitemnumber>.
1101
1102 =cut
1103
1104 sub _Findgroupreserve {
1105     my ( $bibitem, $biblio ) = @_;
1106     my $dbh   = C4::Context->dbh;
1107     my $query = qq/
1108         SELECT reserves.biblionumber AS biblionumber,
1109                reserves.borrowernumber AS borrowernumber,
1110                reserves.reservedate AS reservedate,
1111                reserves.branchcode AS branchcode,
1112                reserves.cancellationdate AS cancellationdate,
1113                reserves.found AS found,
1114                reserves.reservenotes AS reservenotes,
1115                reserves.priority AS priority,
1116                reserves.timestamp AS timestamp,
1117                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1118                reserves.itemnumber AS itemnumber
1119         FROM reserves
1120           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1121         WHERE reserves.biblionumber = ?
1122           AND ( ( reserveconstraints.biblioitemnumber = ?
1123           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1124           AND reserves.reservedate    =reserveconstraints.reservedate )
1125           OR  reserves.constrainttype='a' )
1126           AND reserves.cancellationdate is NULL
1127           AND (reserves.found <> 'F' or reserves.found is NULL)
1128     /;
1129     my $sth = $dbh->prepare($query);
1130     $sth->execute( $biblio, $bibitem );
1131     my @results;
1132     while ( my $data = $sth->fetchrow_hashref ) {
1133         push( @results, $data );
1134     }
1135     $sth->finish;
1136     return @results;
1137 }
1138
1139 =back
1140
1141 =head1 AUTHOR
1142
1143 Koha Developement team <info@koha.org>
1144
1145 =cut
1146