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