call number work part 3 -- now using new routines to generate call number sort keys
[koha.git] / C4 / Reserves.pm
1 # -*- tab-width: 8 -*-
2 # NOTE: This file uses standard 8-character tabs
3
4 package C4::Reserves;
5
6 # Copyright 2000-2002 Katipo Communications
7 #           2006 SAN Ouest Provence
8 #           2007 BibLibre Paul POULAIN
9 #
10 # This file is part of Koha.
11 #
12 # Koha is free software; you can redistribute it and/or modify it under the
13 # terms of the GNU General Public License as published by the Free Software
14 # Foundation; either version 2 of the License, or (at your option) any later
15 # version.
16 #
17 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
18 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License along with
22 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23 # Suite 330, Boston, MA  02111-1307 USA
24
25
26 use strict;
27 require Exporter;
28 use C4::Context;
29 use C4::Biblio;
30 use C4::Search;
31 use C4::Circulation;
32 use C4::Accounts;
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 = 3.00;
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
98   &GetOtherReserves
99   
100   &ModReserveFill
101   &ModReserveAffect
102   &ModReserve
103   &ModReserveStatus
104   &ModReserveCancelAll
105   &ModReserveMinusPriority
106
107   &CheckReserves
108   &CancelReserve
109 );
110
111
112 =item AddReserve
113
114     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$notes,$title,$checkitem,$found)
115
116 =cut
117
118 sub AddReserve {
119     my (
120         $branch,    $borrowernumber, $biblionumber,
121         $constraint, $bibitems,  $priority,       $notes,
122         $title,      $checkitem, $found
123     ) = @_;
124     my $fee =
125           GetReserveFee($borrowernumber, $biblionumber, $constraint,
126             $bibitems );
127     my $dbh     = C4::Context->dbh;
128     my $const   = lc substr( $constraint, 0, 1 );
129     my @datearr = localtime(time);
130     my $resdate =
131       ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
132     my $waitingdate;
133
134     # If the reserv had the waiting status, we had the value of the resdate
135     if ( $found eq 'W' ) {
136         $waitingdate = $resdate;
137     }
138
139     #eval {
140     # updates take place here
141     if ( $fee > 0 ) {
142         my $nextacctno = &getnextacctno( $borrowernumber );
143         my $query      = qq/
144         INSERT INTO accountlines
145             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
146         VALUES
147             (?,?,now(),?,?,'Res',?)
148     /;
149         my $usth = $dbh->prepare($query);
150         $usth->execute( $borrowernumber, $nextacctno, $fee,
151             "Reserve Charge - $title", $fee );
152         $usth->finish;
153     }
154
155     #if ($const eq 'a'){
156     my $query = qq/
157         INSERT INTO reserves
158             (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
159             priority,reservenotes,itemnumber,found,waitingdate)
160         VALUES
161              (?,?,?,?,?,
162              ?,?,?,?,?)
163     /;
164     my $sth = $dbh->prepare($query);
165     $sth->execute(
166         $borrowernumber, $biblionumber, $resdate, $branch,
167         $const,          $priority,     $notes,   $checkitem,
168         $found,          $waitingdate
169     );
170     $sth->finish;
171
172     #}
173     if ( ( $const eq "o" ) || ( $const eq "e" ) ) {
174         my $numitems = @$bibitems;
175         my $i        = 0;
176         while ( $i < $numitems ) {
177             my $biblioitem = @$bibitems[$i];
178             my $query      = qq/
179           INSERT INTO reserveconstraints
180               (borrowernumber,biblionumber,reservedate,biblioitemnumber)
181           VALUES
182             (?,?,?,?)
183       /;
184             my $sth = $dbh->prepare("");
185             $sth->execute( $borrowernumber, $biblionumber, $resdate,
186                 $biblioitem );
187             $sth->finish;
188             $i++;
189         }
190     }
191     return;
192 }
193
194 =item GetReservesFromBiblionumber
195
196 @borrowerreserv=&GetReserves($biblionumber,$itemnumber,$borrowernumber);
197
198 this function get the list of reservation for an C<$biblionumber>, C<$itemnumber> or C<$borrowernumber>
199 given on input arg. 
200 Only 1 argument has to be passed.
201
202 =cut
203
204 sub GetReservesFromBiblionumber {
205     my ( $biblionumber, $itemnumber, $borrowernumber ) = @_;
206     my $dbh   = C4::Context->dbh;
207
208     # Find the desired items in the reserves
209     my $query = "
210         SELECT  branchcode,
211                 timestamp AS rtimestamp,
212                 priority,
213                 biblionumber,
214                 borrowernumber,
215                 reservedate,
216                 constrainttype,
217                 found,
218                 itemnumber
219         FROM     reserves
220         WHERE     cancellationdate IS NULL
221         AND    (found <> \'F\' OR found IS NULL)
222         AND biblionumber = ?
223         ORDER BY priority";
224     my $sth = $dbh->prepare($query);
225     $sth->execute($biblionumber);
226     my @results;
227     my $i = 0;
228     while ( my $data = $sth->fetchrow_hashref ) {
229
230         # FIXME - What is this if-statement doing? How do constraints work?
231         if ( $data->{constrainttype} eq 'o' ) {
232             $query = '
233                 SELECT biblioitemnumber
234                 FROM reserveconstraints
235                 WHERE biblionumber   = ?
236                     AND borrowernumber = ?
237                 AND reservedate    = ?
238             ';
239             my $csth = $dbh->prepare($query);
240             $csth->execute( $data->{biblionumber}, $data->{borrowernumber},
241                 $data->{reservedate}, );
242
243             my @bibitemno;
244             while ( my $bibitemnos = $csth->fetchrow_array ) {
245                 push( @bibitemno, $bibitemnos );
246             }
247             my $count = @bibitemno;
248
249             # if we have two or more different specific itemtypes
250             # reserved by same person on same day
251             my $bdata;
252             if ( $count > 1 ) {
253                 $bdata = GetBiblioItemData( $bibitemno[$i] );
254                 $i++;
255             }
256             else {
257
258                 # Look up the book we just found.
259                 $bdata = GetBiblioItemData( $bibitemno[0] );
260             }
261             $csth->finish;
262
263             # Add the results of this latest search to the current
264             # results.
265             # FIXME - An 'each' would probably be more efficient.
266             foreach my $key ( keys %$bdata ) {
267                 $data->{$key} = $bdata->{$key};
268             }
269         }
270         push @results, $data;
271     }
272     $sth->finish;
273     return ( $#results + 1, \@results );
274 }
275
276 =item GetReservesFromItemnumber
277
278  ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
279
280    TODO :: Description here
281
282 =cut
283
284 sub GetReservesFromItemnumber {
285     my ( $itemnumber ) = @_;
286     my $dbh   = C4::Context->dbh;
287     my $query = "
288     SELECT reservedate,borrowernumber,branchcode
289     FROM   reserves
290     WHERE  itemnumber=?
291         AND  cancellationdate IS NULL
292         AND  (found <> 'F' OR found IS NULL)
293     ";
294     my $sth_res = $dbh->prepare($query);
295     $sth_res->execute($itemnumber);
296     my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
297     return ( $reservedate, $borrowernumber, $branchcode );
298 }
299
300 =item GetReservesFromBorrowernumber
301
302     $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
303     
304     TODO :: Descritpion
305     
306 =cut
307
308 sub GetReservesFromBorrowernumber {
309     my ( $borrowernumber, $status ) = @_;
310     my $dbh   = C4::Context->dbh;
311     my $sth;
312     if ($status) {
313         $sth = $dbh->prepare("
314             SELECT *
315             FROM   reserves
316             WHERE  borrowernumber=?
317                 AND  cancellationdate IS NULL
318                 AND found =?
319             ORDER BY reservedate
320         ");
321         $sth->execute($borrowernumber,$status);
322     } else {
323         $sth = $dbh->prepare("
324             SELECT *
325             FROM   reserves
326             WHERE  borrowernumber=?
327                 AND  cancellationdate IS NULL
328                 AND (found != 'F' or found is null)
329             ORDER BY reservedate
330         ");
331         $sth->execute($borrowernumber);
332     }
333     my $data = $sth->fetchall_arrayref({});
334     return @$data;
335 }
336 #-------------------------------------------------------------------------------------
337
338 =item GetReserveCount
339
340 $number = &GetReserveCount($borrowernumber);
341
342 this function returns the number of reservation for a borrower given on input arg.
343
344 =cut
345
346 sub GetReserveCount {
347     my ($borrowernumber) = @_;
348
349     my $dbh = C4::Context->dbh;
350
351     my $query = '
352         SELECT COUNT(*) AS counter
353         FROM reserves
354           WHERE borrowernumber = ?
355           AND cancellationdate IS NULL
356           AND (found != \'F\' OR found IS NULL)
357     ';
358     my $sth = $dbh->prepare($query);
359     $sth->execute($borrowernumber);
360     my $row = $sth->fetchrow_hashref;
361     $sth->finish;
362
363     return $row->{counter};
364 }
365
366 =item GetOtherReserves
367
368 ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
369
370 Check queued list of this document and check if this document must be  transfered
371
372 =cut
373
374 sub GetOtherReserves {
375     my ($itemnumber) = @_;
376     my $messages;
377     my $nextreservinfo;
378     my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
379     if ($checkreserves) {
380         my $iteminfo = GetItem($itemnumber);
381         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
382             $messages->{'transfert'} = $checkreserves->{'branchcode'};
383             #minus priorities of others reservs
384             ModReserveMinusPriority(
385                 $itemnumber,
386                 $checkreserves->{'borrowernumber'},
387                 $iteminfo->{'biblionumber'}
388             );
389
390             #launch the subroutine dotransfer
391             C4::Circulation::ModItemTransfer(
392                 $itemnumber,
393                 $iteminfo->{'holdingbranch'},
394                 $checkreserves->{'branchcode'}
395               ),
396               ;
397         }
398
399      #step 2b : case of a reservation on the same branch, set the waiting status
400         else {
401             $messages->{'waiting'} = 1;
402             ModReserveMinusPriority(
403                 $itemnumber,
404                 $checkreserves->{'borrowernumber'},
405                 $iteminfo->{'biblionumber'}
406             );
407             ModReserveStatus($itemnumber,'W');
408         }
409
410         $nextreservinfo = $checkreserves->{'borrowernumber'};
411     }
412
413     return ( $messages, $nextreservinfo );
414 }
415
416 =item GetReserveFee
417
418 $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
419
420 Calculate the fee for a reserve
421
422 =cut
423
424 sub GetReserveFee {
425     my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
426
427     #check for issues;
428     my $dbh   = C4::Context->dbh;
429     my $const = lc substr( $constraint, 0, 1 );
430     my $query = qq/
431       SELECT * FROM borrowers
432     LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
433     WHERE borrowernumber = ?
434     /;
435     my $sth = $dbh->prepare($query);
436     $sth->execute($borrowernumber);
437     my $data = $sth->fetchrow_hashref;
438     $sth->finish();
439     my $fee      = $data->{'reservefee'};
440     my $cntitems = @- > $bibitems;
441
442     if ( $fee > 0 ) {
443
444         # check for items on issue
445         # first find biblioitem records
446         my @biblioitems;
447         my $sth1 = $dbh->prepare(
448             "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
449                    WHERE (biblio.biblionumber = ?)"
450         );
451         $sth1->execute($biblionumber);
452         while ( my $data1 = $sth1->fetchrow_hashref ) {
453             if ( $const eq "a" ) {
454                 push @biblioitems, $data1;
455             }
456             else {
457                 my $found = 0;
458                 my $x     = 0;
459                 while ( $x < $cntitems ) {
460                     if ( @$bibitems->{'biblioitemnumber'} ==
461                         $data->{'biblioitemnumber'} )
462                     {
463                         $found = 1;
464                     }
465                     $x++;
466                 }
467                 if ( $const eq 'o' ) {
468                     if ( $found == 1 ) {
469                         push @biblioitems, $data1;
470                     }
471                 }
472                 else {
473                     if ( $found == 0 ) {
474                         push @biblioitems, $data1;
475                     }
476                 }
477             }
478         }
479         $sth1->finish;
480         my $cntitemsfound = @biblioitems;
481         my $issues        = 0;
482         my $x             = 0;
483         my $allissued     = 1;
484         while ( $x < $cntitemsfound ) {
485             my $bitdata = $biblioitems[$x];
486             my $sth2    = $dbh->prepare(
487                 "SELECT * FROM items
488                      WHERE biblioitemnumber = ?"
489             );
490             $sth2->execute( $bitdata->{'biblioitemnumber'} );
491             while ( my $itdata = $sth2->fetchrow_hashref ) {
492                 my $sth3 = $dbh->prepare(
493                     "SELECT * FROM issues
494                        WHERE itemnumber = ?
495                          AND returndate IS NULL"
496                 );
497                 $sth3->execute( $itdata->{'itemnumber'} );
498                 if ( my $isdata = $sth3->fetchrow_hashref ) {
499                 }
500                 else {
501                     $allissued = 0;
502                 }
503             }
504             $x++;
505         }
506         if ( $allissued == 0 ) {
507             my $rsth =
508               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
509             $rsth->execute($biblionumber);
510             if ( my $rdata = $rsth->fetchrow_hashref ) {
511             }
512             else {
513                 $fee = 0;
514             }
515         }
516     }
517     return $fee;
518 }
519
520 =item GetReservesToBranch
521
522 @transreserv = GetReservesToBranch( $frombranch );
523
524 Get reserve list for a given branch
525
526 =cut
527
528 sub GetReservesToBranch {
529     my ( $frombranch ) = @_;
530     my $dbh = C4::Context->dbh;
531     my $sth = $dbh->prepare(
532         "SELECT borrowernumber,reservedate,itemnumber,timestamp
533          FROM reserves 
534          WHERE priority='0' AND cancellationdate is null  
535            AND branchcode=?
536            AND found IS NULL "
537     );
538     $sth->execute( $frombranch );
539     my @transreserv;
540     my $i = 0;
541     while ( my $data = $sth->fetchrow_hashref ) {
542         $transreserv[$i] = $data;
543         $i++;
544     }
545     $sth->finish;
546     return (@transreserv);
547 }
548
549 =item GetReservesForBranch
550
551 @transreserv = GetReservesForBranch($frombranch);
552
553 =cut
554
555 sub GetReservesForBranch {
556     my ($frombranch) = @_;
557     my $dbh          = C4::Context->dbh;
558     my $sth          = $dbh->prepare( "
559         SELECT borrowernumber,reservedate,itemnumber,waitingdate
560         FROM   reserves 
561         WHERE   priority='0'
562             AND cancellationdate IS NULL 
563             AND found='W' 
564             AND branchcode=?
565         ORDER BY waitingdate" );
566     $sth->execute($frombranch);
567     my @transreserv;
568     my $i = 0;
569     while ( my $data = $sth->fetchrow_hashref ) {
570         $transreserv[$i] = $data;
571         $i++;
572     }
573     $sth->finish;
574     return (@transreserv);
575 }
576
577 =item CheckReserves
578
579   ($status, $reserve) = &CheckReserves($itemnumber);
580
581 Find a book in the reserves.
582
583 C<$itemnumber> is the book's item number.
584
585 As I understand it, C<&CheckReserves> looks for the given item in the
586 reserves. If it is found, that's a match, and C<$status> is set to
587 C<Waiting>.
588
589 Otherwise, it finds the most important item in the reserves with the
590 same biblio number as this book (I'm not clear on this) and returns it
591 with C<$status> set to C<Reserved>.
592
593 C<&CheckReserves> returns a two-element list:
594
595 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
596
597 C<$reserve> is the reserve item that matched. It is a
598 reference-to-hash whose keys are mostly the fields of the reserves
599 table in the Koha database.
600
601 =cut
602
603 sub CheckReserves {
604     my ( $item, $barcode ) = @_;
605     my $dbh = C4::Context->dbh;
606     my $sth;
607     if ($item) {
608         my $qitem = $dbh->quote($item);
609         # Look up the item by itemnumber
610         my $query = "
611             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
612             FROM   items
613             LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
614             LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
615             WHERE  itemnumber=$qitem
616         ";
617         $sth = $dbh->prepare($query);
618     }
619     else {
620         my $qbc = $dbh->quote($barcode);
621         # Look up the item by barcode
622         my $query = "
623             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
624             FROM   items
625             LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
626             LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
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 ) {
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     else {
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