bug 2522 [2/3]: C4::Reserves support for request targeting
[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::Items::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     _FixPriority($biblionumber, $borrowernumber, '0');
1043 }
1044
1045 =item GetReserveInfo
1046
1047 &GetReserveInfo($borrowernumber,$biblionumber);
1048
1049  Get item and borrower details for a current hold.
1050  Current implementation this query should have a single result.
1051 =cut
1052
1053 sub GetReserveInfo {
1054         my ( $borrowernumber, $biblionumber ) = @_;
1055     my $dbh = C4::Context->dbh;
1056         my $strsth="SELECT reservedate, reservenotes, reserves.borrowernumber,
1057                                 reserves.biblionumber, reserves.branchcode,
1058                                 notificationdate, reminderdate, priority, found,
1059                                 firstname, surname, phone, 
1060                                 email, address, address2,
1061                                 cardnumber, city, zipcode,
1062                                 biblio.title, biblio.author,
1063                                 items.holdingbranch, items.itemcallnumber, items.itemnumber, 
1064                                 barcode, notes
1065                         FROM reserves left join items 
1066                                 ON items.itemnumber=reserves.itemnumber , 
1067                                 borrowers, biblio 
1068                         WHERE 
1069                                 reserves.borrowernumber=?  &&
1070                                 reserves.biblionumber=? && 
1071                                 reserves.borrowernumber=borrowers.borrowernumber && 
1072                                 reserves.biblionumber=biblio.biblionumber ";
1073         my $sth = $dbh->prepare($strsth); 
1074         $sth->execute($borrowernumber,$biblionumber);
1075
1076         my $data = $sth->fetchrow_hashref;
1077         return $data;
1078
1079 }
1080
1081 =item IsAvailableForItemLevelRequest
1082
1083 =over 4
1084
1085 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1086
1087 =back
1088
1089 Checks whether a given item record is available for an
1090 item-level hold request.  An item is available if
1091
1092 * it is not lost AND 
1093 * it is not damaged AND 
1094 * it is not withdrawn AND 
1095 * does not have a not for loan value > 0
1096
1097 Whether or not the item is currently on loan is 
1098 also checked - if the AllowOnShelfHolds system preference
1099 is ON, an item can be requested even if it is currently
1100 on loan to somebody else.  If the system preference
1101 is OFF, an item that is currently checked out cannot
1102 be the target of an item-level hold request.
1103
1104 Note that IsAvailableForItemLevelRequest() does not
1105 check if the staff operator is authorized to place
1106 a request on the item - in particular,
1107 this routine does not check IndependantBranches
1108 and canreservefromotherbranches.
1109
1110 =cut
1111
1112 sub IsAvailableForItemLevelRequest {
1113     my $itemnumber = shift;
1114    
1115     my $item = GetItem($itemnumber);
1116
1117     # must check the notforloan setting of the itemtype
1118     # FIXME - a lot of places in the code do this
1119     #         or something similar - need to be
1120     #         consolidated
1121     my $dbh = C4::Context->dbh;
1122     my $notforloan_query;
1123     if (C4::Context->preference('item-level_itypes')) {
1124         $notforloan_query = "SELECT itemtypes.notforloan
1125                              FROM items
1126                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1127                              WHERE itemnumber = ?";
1128     } else {
1129         $notforloan_query = "SELECT itemtypes.notforloan
1130                              FROM items
1131                              JOIN biblioitems USING (biblioitemnumber)
1132                              JOIN itemtypes USING (itemtype)
1133                              WHERE itemnumber = ?";
1134     }
1135     my $sth = $dbh->prepare($notforloan_query);
1136     $sth->execute($itemnumber);
1137     my $notforloan_per_itemtype = 0;
1138     if (my ($notforloan) = $sth->fetchrow_array) {
1139         $notforloan_per_itemtype = 1 if $notforloan;
1140     }
1141
1142     my $available_per_item = 1;
1143     $available_per_item = 0 if $item->{itemlost} or
1144                                ( $item->{notforloan} > 0 ) or
1145                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1146                                $item->{wthdrawn} or
1147                                $notforloan_per_itemtype;
1148
1149     if (C4::Context->preference('AllowOnShelfHolds')) {
1150         return $available_per_item;
1151     } else {
1152         return ($available_per_item and $item->{onloan}); 
1153     }
1154 }
1155
1156 =item _FixPriority
1157
1158 &_FixPriority($biblio,$borrowernumber,$rank);
1159
1160  Only used internally (so don't export it)
1161  Changed how this functions works #
1162  Now just gets an array of reserves in the rank order and updates them with
1163  the array index (+1 as array starts from 0)
1164  and if $rank is supplied will splice item from the array and splice it back in again
1165  in new priority rank
1166
1167 =cut 
1168
1169 sub _FixPriority {
1170     my ( $biblio, $borrowernumber, $rank ) = @_;
1171     my $dbh = C4::Context->dbh;
1172      if ( $rank eq "del" ) {
1173          CancelReserve( $biblio, undef, $borrowernumber );
1174      }
1175     if ( $rank eq "W" || $rank eq "0" ) {
1176
1177         # make sure priority for waiting items is 0
1178         my $query = qq/
1179             UPDATE reserves
1180             SET    priority = 0
1181             WHERE biblionumber = ?
1182               AND borrowernumber = ?
1183               AND found ='W'
1184         /;
1185         my $sth = $dbh->prepare($query);
1186         $sth->execute( $biblio, $borrowernumber );
1187     }
1188     my @priority;
1189     my @reservedates;
1190
1191     # get whats left
1192 # FIXME adding a new security in returned elements for changing priority,
1193 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1194         # This is wrong a waiting reserve has W set
1195         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1196     my $query = qq/
1197         SELECT borrowernumber, reservedate, constrainttype
1198         FROM   reserves
1199         WHERE  biblionumber   = ?
1200           AND  ((found <> 'W') or found is NULL)
1201         ORDER BY priority ASC
1202     /;
1203     my $sth = $dbh->prepare($query);
1204     $sth->execute($biblio);
1205     while ( my $line = $sth->fetchrow_hashref ) {
1206         push( @reservedates, $line );
1207         push( @priority,     $line );
1208     }
1209
1210     # To find the matching index
1211     my $i;
1212     my $key = -1;    # to allow for 0 to be a valid result
1213     for ( $i = 0 ; $i < @priority ; $i++ ) {
1214         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1215             $key = $i;    # save the index
1216             last;
1217         }
1218     }
1219
1220     # if index exists in array then move it to new position
1221     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1222         my $new_rank = $rank -
1223           1;    # $new_rank is what you want the new index to be in the array
1224         my $moving_item = splice( @priority, $key, 1 );
1225         splice( @priority, $new_rank, 0, $moving_item );
1226     }
1227
1228     # now fix the priority on those that are left....
1229     $query = "
1230             UPDATE reserves
1231             SET    priority = ?
1232                 WHERE  biblionumber = ?
1233                  AND borrowernumber   = ?
1234                  AND reservedate = ?
1235          AND found IS NULL
1236     ";
1237     $sth = $dbh->prepare($query);
1238     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1239         $sth->execute(
1240             $j + 1, $biblio,
1241             $priority[$j]->{'borrowernumber'},
1242             $priority[$j]->{'reservedate'}
1243         );
1244         $sth->finish;
1245     }
1246 }
1247
1248 =item _Findgroupreserve
1249
1250   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1251
1252 ****** FIXME ******
1253 I don't know what this does, because I don't understand how reserve
1254 constraints work. I think the idea is that you reserve a particular
1255 biblio, and the constraint allows you to restrict it to a given
1256 biblioitem (e.g., if you want to borrow the audio book edition of "The
1257 Prophet", rather than the first available publication).
1258
1259 C<&_Findgroupreserve> returns :
1260 C<@results> is an array of references-to-hash whose keys are mostly
1261 fields from the reserves table of the Koha database, plus
1262 C<biblioitemnumber>.
1263
1264 =cut
1265
1266 sub _Findgroupreserve {
1267     my ( $bibitem, $biblio, $itemnumber ) = @_;
1268     my $dbh   = C4::Context->dbh;
1269
1270     # check for exact targetted match
1271     my $item_level_target_query = qq/
1272         SELECT reserves.biblionumber AS biblionumber,
1273                reserves.borrowernumber AS borrowernumber,
1274                reserves.reservedate AS reservedate,
1275                reserves.branchcode AS branchcode,
1276                reserves.cancellationdate AS cancellationdate,
1277                reserves.found AS found,
1278                reserves.reservenotes AS reservenotes,
1279                reserves.priority AS priority,
1280                reserves.timestamp AS timestamp,
1281                biblioitems.biblioitemnumber AS biblioitemnumber,
1282                reserves.itemnumber AS itemnumber
1283         FROM reserves
1284         JOIN biblioitems USING (biblionumber)
1285         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1286         WHERE found IS NULL
1287         AND priority > 0
1288         AND item_level_request = 1
1289         AND itemnumber = ?
1290     /;
1291     my $sth = $dbh->prepare($item_level_target_query);
1292     $sth->execute($itemnumber);
1293     my @results;
1294     if ( my $data = $sth->fetchrow_hashref ) {
1295         push( @results, $data );
1296     }
1297     return @results if @results;
1298     
1299     # check for title-level targetted match
1300     my $title_level_target_query = qq/
1301         SELECT reserves.biblionumber AS biblionumber,
1302                reserves.borrowernumber AS borrowernumber,
1303                reserves.reservedate AS reservedate,
1304                reserves.branchcode AS branchcode,
1305                reserves.cancellationdate AS cancellationdate,
1306                reserves.found AS found,
1307                reserves.reservenotes AS reservenotes,
1308                reserves.priority AS priority,
1309                reserves.timestamp AS timestamp,
1310                biblioitems.biblioitemnumber AS biblioitemnumber,
1311                reserves.itemnumber AS itemnumber
1312         FROM reserves
1313         JOIN biblioitems USING (biblionumber)
1314         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1315         WHERE found IS NULL
1316         AND priority > 0
1317         AND item_level_request = 0
1318         AND hold_fill_targets.itemnumber = ?
1319     /;
1320     $sth = $dbh->prepare($title_level_target_query);
1321     $sth->execute($itemnumber);
1322     @results = ();
1323     if ( my $data = $sth->fetchrow_hashref ) {
1324         push( @results, $data );
1325     }
1326     return @results if @results;
1327
1328     my $query = qq/
1329         SELECT reserves.biblionumber AS biblionumber,
1330                reserves.borrowernumber AS borrowernumber,
1331                reserves.reservedate AS reservedate,
1332                reserves.branchcode AS branchcode,
1333                reserves.cancellationdate AS cancellationdate,
1334                reserves.found AS found,
1335                reserves.reservenotes AS reservenotes,
1336                reserves.priority AS priority,
1337                reserves.timestamp AS timestamp,
1338                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1339                reserves.itemnumber AS itemnumber
1340         FROM reserves
1341           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1342         WHERE reserves.biblionumber = ?
1343           AND ( ( reserveconstraints.biblioitemnumber = ?
1344           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1345           AND reserves.reservedate    =reserveconstraints.reservedate )
1346           OR  reserves.constrainttype='a' )
1347           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1348     /;
1349     $sth = $dbh->prepare($query);
1350     $sth->execute( $biblio, $bibitem, $itemnumber );
1351     @results = ();
1352     while ( my $data = $sth->fetchrow_hashref ) {
1353         push( @results, $data );
1354     }
1355     return @results;
1356 }
1357
1358 =back
1359
1360 =head1 AUTHOR
1361
1362 Koha Developement team <info@koha.org>
1363
1364 =cut
1365
1366 1;