chmod 644 on all modules
[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,categories
434     WHERE borrowernumber = ?
435       AND borrowers.categorycode = categories.categorycode
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,biblioitems
451                    WHERE (biblio.biblionumber = ?)
452                      AND (biblio.biblionumber = biblioitems.biblionumber)"
453         );
454         $sth1->execute($biblionumber);
455         while ( my $data1 = $sth1->fetchrow_hashref ) {
456             if ( $const eq "a" ) {
457                 push @biblioitems, $data1;
458             }
459             else {
460                 my $found = 0;
461                 my $x     = 0;
462                 while ( $x < $cntitems ) {
463                     if ( @$bibitems->{'biblioitemnumber'} ==
464                         $data->{'biblioitemnumber'} )
465                     {
466                         $found = 1;
467                     }
468                     $x++;
469                 }
470                 if ( $const eq 'o' ) {
471                     if ( $found == 1 ) {
472                         push @biblioitems, $data1;
473                     }
474                 }
475                 else {
476                     if ( $found == 0 ) {
477                         push @biblioitems, $data1;
478                     }
479                 }
480             }
481         }
482         $sth1->finish;
483         my $cntitemsfound = @biblioitems;
484         my $issues        = 0;
485         my $x             = 0;
486         my $allissued     = 1;
487         while ( $x < $cntitemsfound ) {
488             my $bitdata = $biblioitems[$x];
489             my $sth2    = $dbh->prepare(
490                 "SELECT * FROM items
491                      WHERE biblioitemnumber = ?"
492             );
493             $sth2->execute( $bitdata->{'biblioitemnumber'} );
494             while ( my $itdata = $sth2->fetchrow_hashref ) {
495                 my $sth3 = $dbh->prepare(
496                     "SELECT * FROM issues
497                        WHERE itemnumber = ?
498                          AND returndate IS NULL"
499                 );
500                 $sth3->execute( $itdata->{'itemnumber'} );
501                 if ( my $isdata = $sth3->fetchrow_hashref ) {
502                 }
503                 else {
504                     $allissued = 0;
505                 }
506             }
507             $x++;
508         }
509         if ( $allissued == 0 ) {
510             my $rsth =
511               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
512             $rsth->execute($biblionumber);
513             if ( my $rdata = $rsth->fetchrow_hashref ) {
514             }
515             else {
516                 $fee = 0;
517             }
518         }
519     }
520     return $fee;
521 }
522
523 =item GetReservesToBranch
524
525 @transreserv = GetReservesToBranch( $frombranch );
526
527 Get reserve list for a given branch
528
529 =cut
530
531 sub GetReservesToBranch {
532     my ( $frombranch ) = @_;
533     my $dbh = C4::Context->dbh;
534     my $sth = $dbh->prepare(
535         "SELECT borrowernumber,reservedate,itemnumber,timestamp
536          FROM reserves 
537          WHERE priority='0' AND cancellationdate is null  
538            AND branchcode=?
539            AND found IS NULL "
540     );
541     $sth->execute( $frombranch );
542     my @transreserv;
543     my $i = 0;
544     while ( my $data = $sth->fetchrow_hashref ) {
545         $transreserv[$i] = $data;
546         $i++;
547     }
548     $sth->finish;
549     return (@transreserv);
550 }
551
552 =item GetReservesForBranch
553
554 @transreserv = GetReservesForBranch($frombranch);
555
556 =cut
557
558 sub GetReservesForBranch {
559     my ($frombranch) = @_;
560     my $dbh          = C4::Context->dbh;
561     my $sth          = $dbh->prepare( "
562         SELECT borrowernumber,reservedate,itemnumber,waitingdate
563         FROM   reserves 
564         WHERE   priority='0'
565             AND cancellationdate IS NULL 
566             AND found='W' 
567             AND branchcode=?
568         ORDER BY waitingdate" );
569     $sth->execute($frombranch);
570     my @transreserv;
571     my $i = 0;
572     while ( my $data = $sth->fetchrow_hashref ) {
573         $transreserv[$i] = $data;
574         $i++;
575     }
576     $sth->finish;
577     return (@transreserv);
578 }
579
580 =item CheckReserves
581
582   ($status, $reserve) = &CheckReserves($itemnumber);
583
584 Find a book in the reserves.
585
586 C<$itemnumber> is the book's item number.
587
588 As I understand it, C<&CheckReserves> looks for the given item in the
589 reserves. If it is found, that's a match, and C<$status> is set to
590 C<Waiting>.
591
592 Otherwise, it finds the most important item in the reserves with the
593 same biblio number as this book (I'm not clear on this) and returns it
594 with C<$status> set to C<Reserved>.
595
596 C<&CheckReserves> returns a two-element list:
597
598 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
599
600 C<$reserve> is the reserve item that matched. It is a
601 reference-to-hash whose keys are mostly the fields of the reserves
602 table in the Koha database.
603
604 =cut
605
606 sub CheckReserves {
607     my ( $item, $barcode ) = @_;
608     my $dbh = C4::Context->dbh;
609     my $sth;
610     if ($item) {
611         my $qitem = $dbh->quote($item);
612         # Look up the item by itemnumber
613         my $query = "
614             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
615             FROM   items, biblioitems, itemtypes
616             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
617                AND biblioitems.itemtype = itemtypes.itemtype
618                AND itemnumber=$qitem
619         ";
620         $sth = $dbh->prepare($query);
621     }
622     else {
623         my $qbc = $dbh->quote($barcode);
624         # Look up the item by barcode
625         my $query = "
626             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
627             FROM   items, biblioitems, itemtypes
628             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
629               AND biblioitems.itemtype = itemtypes.itemtype
630               AND barcode=$qbc
631         ";
632         $sth = $dbh->prepare($query);
633
634         # FIXME - This function uses $item later on. Ought to set it here.
635     }
636     $sth->execute;
637     my ( $biblio, $bibitem, $notforloan ) = $sth->fetchrow_array;
638     $sth->finish;
639
640     # if item is not for loan it cannot be reserved either.....
641     return ( 0, 0 ) if $notforloan;
642
643     # get the reserves...
644     # Find this item in the reserves
645     my @reserves = _Findgroupreserve( $bibitem, $biblio );
646     my $count    = scalar @reserves;
647
648     # $priority and $highest are used to find the most important item
649     # in the list returned by &_Findgroupreserve. (The lower $priority,
650     # the more important the item.)
651     # $highest is the most important item we've seen so far.
652     my $priority = 10000000;
653     my $highest;
654     if ($count) {
655         foreach my $res (@reserves) {
656             # FIXME - $item might be undefined or empty: the caller
657             # might be searching by barcode.
658             if ( $res->{'itemnumber'} == $item ) {
659                 # Found it
660                 return ( "Waiting", $res );
661             }
662             else {
663                 # See if this item is more important than what we've got
664                 # so far.
665                 if ( $res->{'priority'} != 0 && $res->{'priority'} < $priority )
666                 {
667                     $priority = $res->{'priority'};
668                     $highest  = $res;
669                 }
670             }
671         }
672     }
673
674     # If we get this far, then no exact match was found. Print the
675     # most important item on the list. I think this tells us who's
676     # next in line to get this book.
677     if ($highest) {    # FIXME - $highest might be undefined
678         $highest->{'itemnumber'} = $item;
679         return ( "Reserved", $highest );
680     }
681     else {
682         return ( 0, 0 );
683     }
684 }
685
686 =item CancelReserve
687
688   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
689
690 Cancels a reserve.
691
692 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
693 cancel, but not both: if both are given, C<&CancelReserve> does
694 nothing.
695
696 C<$borrowernumber> is the borrower number of the patron on whose
697 behalf the book was reserved.
698
699 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
700 priorities of the other people who are waiting on the book.
701
702 =cut
703
704 sub CancelReserve {
705     my ( $biblio, $item, $borr ) = @_;
706     my $dbh = C4::Context->dbh;
707         if ( ( $item and $borr ) and ( not $biblio ) ) {
708         # removing a waiting reserve record....
709         # update the database...
710         my $query = "
711             UPDATE reserves
712             SET    cancellationdate = now(),
713                    found            = Null,
714                    priority         = 0
715             WHERE  itemnumber       = ?
716              AND   borrowernumber   = ?
717         ";
718         my $sth = $dbh->prepare($query);
719         $sth->execute( $item, $borr );
720         $sth->finish;
721     }
722     if ( ( $biblio and $borr ) and ( not $item ) ) {
723         # removing a reserve record....
724         # get the prioritiy on this record....
725         my $priority;
726         my $query = qq/
727             SELECT priority FROM reserves
728             WHERE biblionumber   = ?
729               AND borrowernumber = ?
730               AND cancellationdate IS NULL
731               AND itemnumber IS NULL
732               AND (found <> 'F' OR found IS NULL)
733         /;
734         my $sth = $dbh->prepare($query);
735         $sth->execute( $biblio, $borr );
736         ($priority) = $sth->fetchrow_array;
737         $sth->finish;
738         $query = qq/
739             UPDATE reserves
740             SET    cancellationdate = now(),
741                    found            = Null,
742                    priority         = 0
743             WHERE  biblionumber     = ?
744               AND  borrowernumber   = ?
745               AND cancellationdate IS NULL
746               AND (found <> 'F' or found IS NULL)
747         /;
748
749         # update the database, removing the record...
750         $sth = $dbh->prepare($query);
751         $sth->execute( $biblio, $borr );
752         $sth->finish;
753
754         # now fix the priority on the others....
755         _FixPriority( $priority, $biblio );
756     }
757 }
758
759 =item ModReserve
760
761 &ModReserve($rank,$biblio,$borrower,$branch)
762
763 =cut
764
765 sub ModReserve {
766     #subroutine to update a reserve
767     my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
768      return if $rank eq "W";
769      return if $rank eq "n";
770     my $dbh = C4::Context->dbh;
771     if ( $rank eq "del" ) {
772         my $query = qq/
773             UPDATE reserves
774             SET    cancellationdate=now()
775             WHERE  biblionumber   = ?
776              AND   borrowernumber = ?
777              AND   cancellationdate is NULL
778              AND   (found <> 'F' or found is NULL)
779         /;
780         my $sth = $dbh->prepare($query);
781         $sth->execute( $biblio, $borrower );
782         $sth->finish;
783         
784     }
785     else {
786         my $query = qq/
787         UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL
788             WHERE biblionumber   = ?
789              AND borrowernumber = ?
790              AND cancellationdate is NULL
791              AND (found <> 'F' or found is NULL)
792         /;
793         my $sth = $dbh->prepare($query);
794         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
795         $sth->finish;
796         _FixPriority( $biblio, $borrower, $rank);
797     }
798 }
799
800 =item ModReserveFill
801
802   &ModReserveFill($reserve);
803
804 Fill a reserve. If I understand this correctly, this means that the
805 reserved book has been found and given to the patron who reserved it.
806
807 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
808 whose keys are fields from the reserves table in the Koha database.
809
810 =cut
811
812 sub ModReserveFill {
813     my ($res) = @_;
814     my $dbh = C4::Context->dbh;
815     # fill in a reserve record....
816     my $biblionumber = $res->{'biblionumber'};
817     my $borrowernumber    = $res->{'borrowernumber'};
818     my $resdate = $res->{'reservedate'};
819
820     # get the priority on this record....
821     my $priority;
822     my $query = "SELECT priority
823                  FROM   reserves
824                  WHERE  biblionumber   = ?
825                   AND   borrowernumber = ?
826                   AND   reservedate    = ?";
827     my $sth = $dbh->prepare($query);
828     $sth->execute( $biblionumber, $borrowernumber, $resdate );
829     ($priority) = $sth->fetchrow_array;
830     $sth->finish;
831
832     # update the database...
833     $query = "UPDATE reserves
834                   SET    found            = 'F',
835                          priority         = 0
836                  WHERE  biblionumber     = ?
837                     AND reservedate      = ?
838                     AND borrowernumber   = ?
839                 ";
840     $sth = $dbh->prepare($query);
841     $sth->execute( $biblionumber, $resdate, $borrowernumber );
842     $sth->finish;
843
844     # now fix the priority on the others (if the priority wasn't
845     # already sorted!)....
846     unless ( $priority == 0 ) {
847         _FixPriority( $priority, $biblionumber );
848     }
849 }
850
851 =item ModReserveStatus
852
853 &ModReserveStatus($itemnumber, $newstatus);
854
855 Update the reserve status for the active (priority=0) reserve.
856
857 $itemnumber is the itemnumber the reserve is on
858
859 $newstatus is the new status.
860
861 =cut
862
863 sub ModReserveStatus {
864
865     #first : check if we have a reservation for this item .
866     my ($itemnumber, $newstatus) = @_;
867     my $dbh          = C4::Context->dbh;
868     my $query = " UPDATE reserves
869     SET    found=?,waitingdate = now()
870     WHERE itemnumber=?
871       AND found IS NULL
872       AND priority = 0
873     ";
874     my $sth_set = $dbh->prepare($query);
875     $sth_set->execute( $newstatus, $itemnumber );
876     $sth_set->finish;
877 }
878
879 =item ModReserveAffect
880
881 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
882
883 This function affect an item and a status for a given reserve
884 The itemnumber parameter is used to find the biblionumber.
885 with the biblionumber & the borrowernumber, we can affect the itemnumber
886 to the correct reserve.
887
888 if $transferToDo is not set, then the status is set to "Waiting" as well.
889 otherwise, a transfer is on the way, and the end of the transfer will 
890 take care of the waiting status
891 =cut
892
893 sub ModReserveAffect {
894     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
895     my $dbh = C4::Context->dbh;
896
897     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
898     # attached to $itemnumber
899     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
900     $sth->execute($itemnumber);
901     my ($biblionumber) = $sth->fetchrow;
902     # If we affect a reserve that has to be transfered, don't set to Waiting
903     my $query;
904     if ($transferToDo) {
905     $query = "
906         UPDATE reserves
907         SET    priority = 0,
908                itemnumber = ?
909         WHERE borrowernumber = ?
910           AND biblionumber = ?
911           AND reserves.cancellationdate IS NULL
912           AND (reserves.found <> 'F' OR reserves.found IS NULL)
913     ";
914     }
915     else {
916     # affect the reserve to Waiting as well.
917     $query = "
918         UPDATE reserves
919         SET     priority = 0,
920                 found = 'W',
921                 waitingdate=now(),
922                 itemnumber = ?
923         WHERE borrowernumber = ?
924           AND biblionumber = ?
925           AND reserves.cancellationdate IS NULL
926           AND (reserves.found <> 'F' OR reserves.found IS NULL)
927     ";
928     }
929     $sth = $dbh->prepare($query);
930     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
931     $sth->finish;
932     return;
933 }
934
935 =item ModReserveCancelAll
936
937 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
938
939     function to cancel reserv,check other reserves, and transfer document if it's necessary
940
941 =cut
942
943 sub ModReserveCancelAll {
944     my $messages;
945     my $nextreservinfo;
946     my ( $itemnumber, $borrowernumber ) = @_;
947
948     #step 1 : cancel the reservation
949     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
950
951     #step 2 launch the subroutine of the others reserves
952     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
953
954     return ( $messages, $nextreservinfo );
955 }
956
957 =item ModReserveMinusPriority
958
959 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
960
961 Reduce the values of queuded list     
962
963 =cut
964
965 sub ModReserveMinusPriority {
966     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
967
968     #first step update the value of the first person on reserv
969     my $dbh   = C4::Context->dbh;
970     my $query = "
971         UPDATE reserves
972         SET    priority = 0 , itemnumber = ? 
973         WHERE  cancellationdate IS NULL 
974           AND  borrowernumber=?
975           AND  biblionumber=?
976     ";
977     my $sth_upd = $dbh->prepare($query);
978     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
979     $sth_upd->finish;
980     # second step update all others reservs
981     $query = "
982             UPDATE reserves
983             SET    priority = priority-1
984             WHERE  biblionumber = ?
985             AND priority > 0
986             AND cancellationdate IS NULL
987     ";
988     $sth_upd = $dbh->prepare($query);
989     $sth_upd->execute( $biblionumber );
990     $sth_upd->finish;
991     $sth_upd->finish;
992 }
993
994 =item _FixPriority
995
996 &_FixPriority($biblio,$borrowernumber,$rank);
997
998  Only used internally (so don't export it)
999  Changed how this functions works #
1000  Now just gets an array of reserves in the rank order and updates them with
1001  the array index (+1 as array starts from 0)
1002  and if $rank is supplied will splice item from the array and splice it back in again
1003  in new priority rank
1004
1005 =cut 
1006
1007 sub _FixPriority {
1008     my ( $biblio, $borrowernumber, $rank ) = @_;
1009     my $dbh = C4::Context->dbh;
1010      if ( $rank eq "del" ) {
1011          CancelReserve( $biblio, undef, $borrowernumber );
1012      }
1013     if ( $rank eq "W" || $rank eq "0" ) {
1014
1015         # make sure priority for waiting items is 0
1016         my $query = qq/
1017             UPDATE reserves
1018             SET    priority = 0
1019             WHERE biblionumber = ?
1020               AND borrowernumber = ?
1021               AND cancellationdate IS NULL
1022               AND found ='W'
1023         /;
1024         my $sth = $dbh->prepare($query);
1025         $sth->execute( $biblio, $borrowernumber );
1026     }
1027     my @priority;
1028     my @reservedates;
1029
1030     # get whats left
1031 # FIXME adding a new security in returned elements for changing priority,
1032 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1033     my $query = qq/
1034         SELECT borrowernumber, reservedate, constrainttype
1035         FROM   reserves
1036         WHERE  biblionumber   = ?
1037           AND  cancellationdate IS NULL
1038           AND  itemnumber IS NULL
1039           AND  ((found <> 'F' and found <> 'W') or found is NULL)
1040         ORDER BY priority ASC
1041     /;
1042     my $sth = $dbh->prepare($query);
1043     $sth->execute($biblio);
1044     while ( my $line = $sth->fetchrow_hashref ) {
1045         push( @reservedates, $line );
1046         push( @priority,     $line );
1047     }
1048
1049     # To find the matching index
1050     my $i;
1051     my $key = -1;    # to allow for 0 to be a valid result
1052     for ( $i = 0 ; $i < @priority ; $i++ ) {
1053         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1054             $key = $i;    # save the index
1055             last;
1056         }
1057     }
1058
1059     # if index exists in array then move it to new position
1060     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1061         my $new_rank = $rank -
1062           1;    # $new_rank is what you want the new index to be in the array
1063         my $moving_item = splice( @priority, $key, 1 );
1064         splice( @priority, $new_rank, 0, $moving_item );
1065     }
1066
1067     # now fix the priority on those that are left....
1068     $query = "
1069             UPDATE reserves
1070             SET    priority = ?
1071                 WHERE  biblionumber = ?
1072                  AND borrowernumber   = ?
1073                  AND reservedate = ?
1074          AND found IS NULL
1075     ";
1076     $sth = $dbh->prepare($query);
1077     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1078         $sth->execute(
1079             $j + 1, $biblio,
1080             $priority[$j]->{'borrowernumber'},
1081             $priority[$j]->{'reservedate'}
1082         );
1083         $sth->finish;
1084     }
1085 }
1086
1087 =item _Findgroupreserve
1088
1089   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber);
1090
1091 ****** FIXME ******
1092 I don't know what this does, because I don't understand how reserve
1093 constraints work. I think the idea is that you reserve a particular
1094 biblio, and the constraint allows you to restrict it to a given
1095 biblioitem (e.g., if you want to borrow the audio book edition of "The
1096 Prophet", rather than the first available publication).
1097
1098 C<&_Findgroupreserve> returns :
1099 C<@results> is an array of references-to-hash whose keys are mostly
1100 fields from the reserves table of the Koha database, plus
1101 C<biblioitemnumber>.
1102
1103 =cut
1104
1105 sub _Findgroupreserve {
1106     my ( $bibitem, $biblio ) = @_;
1107     my $dbh   = C4::Context->dbh;
1108     my $query = qq/
1109         SELECT reserves.biblionumber AS biblionumber,
1110                reserves.borrowernumber AS borrowernumber,
1111                reserves.reservedate AS reservedate,
1112                reserves.branchcode AS branchcode,
1113                reserves.cancellationdate AS cancellationdate,
1114                reserves.found AS found,
1115                reserves.reservenotes AS reservenotes,
1116                reserves.priority AS priority,
1117                reserves.timestamp AS timestamp,
1118                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1119                reserves.itemnumber AS itemnumber
1120         FROM reserves
1121           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1122         WHERE reserves.biblionumber = ?
1123           AND ( ( reserveconstraints.biblioitemnumber = ?
1124           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1125           AND reserves.reservedate    =reserveconstraints.reservedate )
1126           OR  reserves.constrainttype='a' )
1127           AND reserves.cancellationdate is NULL
1128           AND (reserves.found <> 'F' or reserves.found is NULL)
1129     /;
1130     my $sth = $dbh->prepare($query);
1131     $sth->execute( $biblio, $bibitem );
1132     my @results;
1133     while ( my $data = $sth->fetchrow_hashref ) {
1134         push( @results, $data );
1135     }
1136     $sth->finish;
1137     return @results;
1138 }
1139
1140 =back
1141
1142 =head1 AUTHOR
1143
1144 Koha Developement team <info@koha.org>
1145
1146 =cut
1147