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