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