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