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