bug 2518: remove useless patron and branch lookups
[koha_fer] / 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     # if item is not for loan it cannot be reserved either.....
638     #    execption to notforloan is where items.notforloan < 0 :  This indicates the item is holdable. 
639     return ( 0, 0 ) if  ( $notforloan_per_item > 0 ) 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 =over 4
789
790 ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
791
792 =back
793
794 Change a hold request's priority or cancel it.
795
796 C<$rank> specifies the effect of the change.  If C<$rank>
797 is 'W' or 'n', nothing happens.  This corresponds to leaving a
798 request alone when changing its priority in the holds queue
799 for a bib.
800
801 If C<$rank> is 'del', the hold request is cancelled.
802
803 If C<$rank> is an integer greater than zero, the priority of
804 the request is set to that value.  Since priority != 0 means
805 that the item is not waiting on the hold shelf, setting the 
806 priority to a non-zero value also sets the request's found
807 status and waiting date to NULL. 
808
809 The optional C<$itemnumber> parameter is used only when
810 C<$rank> is a non-zero integer; if supplied, the itemnumber 
811 of the hold request is set accordingly; if omitted, the itemnumber
812 is cleared.
813
814 FIXME: Note that the forgoing can have the effect of causing
815 item-level hold requests to turn into title-level requests.  This
816 will be fixed once reserves has separate columns for requested
817 itemnumber and supplying itemnumber.
818
819 =cut
820
821 sub ModReserve {
822     #subroutine to update a reserve
823     my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
824      return if $rank eq "W";
825      return if $rank eq "n";
826     my $dbh = C4::Context->dbh;
827     if ( $rank eq "del" ) {
828         my $query = qq/
829             UPDATE reserves
830             SET    cancellationdate=now()
831             WHERE  biblionumber   = ?
832              AND   borrowernumber = ?
833         /;
834         my $sth = $dbh->prepare($query);
835         $sth->execute( $biblio, $borrower );
836         $sth->finish;
837         $query = qq/
838             INSERT INTO old_reserves
839             SELECT *
840             FROM   reserves 
841             WHERE  biblionumber   = ?
842              AND   borrowernumber = ?
843         /;
844         $sth = $dbh->prepare($query);
845         $sth->execute( $biblio, $borrower );
846         $query = qq/
847             DELETE FROM reserves 
848             WHERE  biblionumber   = ?
849              AND   borrowernumber = ?
850         /;
851         $sth = $dbh->prepare($query);
852         $sth->execute( $biblio, $borrower );
853         
854     }
855     elsif ($rank =~ /^\d+/ and $rank > 0) {
856         my $query = qq/
857         UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
858             WHERE biblionumber   = ?
859              AND borrowernumber = ?
860         /;
861         my $sth = $dbh->prepare($query);
862         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
863         $sth->finish;
864         _FixPriority( $biblio, $borrower, $rank);
865     }
866 }
867
868 =item ModReserveFill
869
870   &ModReserveFill($reserve);
871
872 Fill a reserve. If I understand this correctly, this means that the
873 reserved book has been found and given to the patron who reserved it.
874
875 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
876 whose keys are fields from the reserves table in the Koha database.
877
878 =cut
879
880 sub ModReserveFill {
881     my ($res) = @_;
882     my $dbh = C4::Context->dbh;
883     # fill in a reserve record....
884     my $biblionumber = $res->{'biblionumber'};
885     my $borrowernumber    = $res->{'borrowernumber'};
886     my $resdate = $res->{'reservedate'};
887
888     # get the priority on this record....
889     my $priority;
890     my $query = "SELECT priority
891                  FROM   reserves
892                  WHERE  biblionumber   = ?
893                   AND   borrowernumber = ?
894                   AND   reservedate    = ?";
895     my $sth = $dbh->prepare($query);
896     $sth->execute( $biblionumber, $borrowernumber, $resdate );
897     ($priority) = $sth->fetchrow_array;
898     $sth->finish;
899
900     # update the database...
901     $query = "UPDATE reserves
902                   SET    found            = 'F',
903                          priority         = 0
904                  WHERE  biblionumber     = ?
905                     AND reservedate      = ?
906                     AND borrowernumber   = ?
907                 ";
908     $sth = $dbh->prepare($query);
909     $sth->execute( $biblionumber, $resdate, $borrowernumber );
910     $sth->finish;
911
912     # move to old_reserves
913     $query = "INSERT INTO old_reserves
914                  SELECT * FROM reserves
915                  WHERE  biblionumber     = ?
916                     AND reservedate      = ?
917                     AND borrowernumber   = ?
918                 ";
919     $sth = $dbh->prepare($query);
920     $sth->execute( $biblionumber, $resdate, $borrowernumber );
921     $query = "DELETE FROM reserves
922                  WHERE  biblionumber     = ?
923                     AND reservedate      = ?
924                     AND borrowernumber   = ?
925                 ";
926     $sth = $dbh->prepare($query);
927     $sth->execute( $biblionumber, $resdate, $borrowernumber );
928     
929     # now fix the priority on the others (if the priority wasn't
930     # already sorted!)....
931     unless ( $priority == 0 ) {
932         _FixPriority( $priority, $biblionumber );
933     }
934 }
935
936 =item ModReserveStatus
937
938 &ModReserveStatus($itemnumber, $newstatus);
939
940 Update the reserve status for the active (priority=0) reserve.
941
942 $itemnumber is the itemnumber the reserve is on
943
944 $newstatus is the new status.
945
946 =cut
947
948 sub ModReserveStatus {
949
950     #first : check if we have a reservation for this item .
951     my ($itemnumber, $newstatus) = @_;
952     my $dbh          = C4::Context->dbh;
953     my $query = " UPDATE reserves
954     SET    found=?,waitingdate = now()
955     WHERE itemnumber=?
956       AND found IS NULL
957       AND priority = 0
958     ";
959     my $sth_set = $dbh->prepare($query);
960     $sth_set->execute( $newstatus, $itemnumber );
961     $sth_set->finish;
962 }
963
964 =item ModReserveAffect
965
966 &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
967
968 This function affect an item and a status for a given reserve
969 The itemnumber parameter is used to find the biblionumber.
970 with the biblionumber & the borrowernumber, we can affect the itemnumber
971 to the correct reserve.
972
973 if $transferToDo is not set, then the status is set to "Waiting" as well.
974 otherwise, a transfer is on the way, and the end of the transfer will 
975 take care of the waiting status
976 =cut
977
978 sub ModReserveAffect {
979     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
980     my $dbh = C4::Context->dbh;
981
982     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
983     # attached to $itemnumber
984     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
985     $sth->execute($itemnumber);
986     my ($biblionumber) = $sth->fetchrow;
987     # If we affect a reserve that has to be transfered, don't set to Waiting
988     my $query;
989     if ($transferToDo) {
990     $query = "
991         UPDATE reserves
992         SET    priority = 0,
993                itemnumber = ?
994         WHERE borrowernumber = ?
995           AND biblionumber = ?
996     ";
997     }
998     else {
999     # affect the reserve to Waiting as well.
1000     $query = "
1001         UPDATE reserves
1002         SET     priority = 0,
1003                 found = 'W',
1004                 waitingdate=now(),
1005                 itemnumber = ?
1006         WHERE borrowernumber = ?
1007           AND biblionumber = ?
1008     ";
1009     }
1010     $sth = $dbh->prepare($query);
1011     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1012     $sth->finish;
1013     return;
1014 }
1015
1016 =item ModReserveCancelAll
1017
1018 ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1019
1020     function to cancel reserv,check other reserves, and transfer document if it's necessary
1021
1022 =cut
1023
1024 sub ModReserveCancelAll {
1025     my $messages;
1026     my $nextreservinfo;
1027     my ( $itemnumber, $borrowernumber ) = @_;
1028
1029     #step 1 : cancel the reservation
1030     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1031
1032     #step 2 launch the subroutine of the others reserves
1033     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1034
1035     return ( $messages, $nextreservinfo );
1036 }
1037
1038 =item ModReserveMinusPriority
1039
1040 &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1041
1042 Reduce the values of queuded list     
1043
1044 =cut
1045
1046 sub ModReserveMinusPriority {
1047     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1048
1049     #first step update the value of the first person on reserv
1050     my $dbh   = C4::Context->dbh;
1051     my $query = "
1052         UPDATE reserves
1053         SET    priority = 0 , itemnumber = ? 
1054         WHERE  borrowernumber=?
1055           AND  biblionumber=?
1056     ";
1057     my $sth_upd = $dbh->prepare($query);
1058     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1059     $sth_upd->finish;
1060     # second step update all others reservs
1061     $query = "
1062             UPDATE reserves
1063             SET    priority = priority-1
1064             WHERE  biblionumber = ?
1065             AND priority > 0
1066     ";
1067     $sth_upd = $dbh->prepare($query);
1068     $sth_upd->execute( $biblionumber );
1069     $sth_upd->finish;
1070     $sth_upd->finish;
1071 }
1072
1073 =item GetReserveInfo
1074
1075 &GetReserveInfo($borrowernumber,$biblionumber);
1076
1077  Get item and borrower details for a current hold.
1078  Current implementation this query should have a single result.
1079 =cut
1080
1081 sub GetReserveInfo {
1082         my ( $borrowernumber, $biblionumber ) = @_;
1083     my $dbh = C4::Context->dbh;
1084         my $strsth="SELECT reservedate, reservenotes, reserves.borrowernumber,
1085                                 reserves.biblionumber, reserves.branchcode,
1086                                 notificationdate, reminderdate, priority, found,
1087                                 firstname, surname, phone, 
1088                                 email, address, address2,
1089                                 cardnumber, city, zipcode,
1090                                 biblio.title, biblio.author,
1091                                 items.holdingbranch, items.itemcallnumber, items.itemnumber, 
1092                                 barcode, notes
1093                         FROM reserves left join items 
1094                                 ON items.itemnumber=reserves.itemnumber , 
1095                                 borrowers, biblio 
1096                         WHERE 
1097                                 reserves.borrowernumber=?  &&
1098                                 reserves.biblionumber=? && 
1099                                 reserves.borrowernumber=borrowers.borrowernumber && 
1100                                 reserves.biblionumber=biblio.biblionumber ";
1101         my $sth = $dbh->prepare($strsth); 
1102         $sth->execute($borrowernumber,$biblionumber);
1103
1104         my $data = $sth->fetchrow_hashref;
1105         return $data;
1106
1107 }
1108
1109 =item IsAvailableForItemLevelRequest
1110
1111 =over 4
1112
1113 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1114
1115 =back
1116
1117 Checks whether a given item record is available for an
1118 item-level hold request.  An item is available if
1119
1120 * it is not lost AND 
1121 * it is not damaged AND 
1122 * it is not withdrawn AND 
1123 * does not have a not for loan value > 0
1124
1125 Whether or not the item is currently on loan is 
1126 also checked - if the AllowOnShelfHolds system preference
1127 is ON, an item can be requested even if it is currently
1128 on loan to somebody else.  If the system preference
1129 is OFF, an item that is currently checked out cannot
1130 be the target of an item-level hold request.
1131
1132 Note that IsAvailableForItemLevelRequest() does not
1133 check if the staff operator is authorized to place
1134 a request on the item - in particular,
1135 this routine does not check IndependantBranches
1136 and canreservefromotherbranches.
1137
1138 =cut
1139
1140 sub IsAvailableForItemLevelRequest {
1141     my $itemnumber = shift;
1142    
1143     my $item = GetItem($itemnumber);
1144
1145     # must check the notforloan setting of the itemtype
1146     # FIXME - a lot of places in the code do this
1147     #         or something similar - need to be
1148     #         consolidated
1149     my $dbh = C4::Context->dbh;
1150     my $notforloan_query;
1151     if (C4::Context->preference('item-level_itypes')) {
1152         $notforloan_query = "SELECT itemtypes.notforloan
1153                              FROM items
1154                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1155                              WHERE itemnumber = ?";
1156     } else {
1157         $notforloan_query = "SELECT itemtypes.notforloan
1158                              FROM items
1159                              JOIN biblioitems USING (biblioitemnumber)
1160                              JOIN itemtypes USING (itemtype)
1161                              WHERE itemnumber = ?";
1162     }
1163     my $sth = $dbh->prepare($notforloan_query);
1164     $sth->execute($itemnumber);
1165     my $notforloan_per_itemtype = 0;
1166     if (my ($notforloan) = $sth->fetchrow_array) {
1167         $notforloan_per_itemtype = 1 if $notforloan;
1168     }
1169
1170     my $available_per_item = 1;
1171     $available_per_item = 0 if $item->{itemlost} or
1172                                ( $item->{notforloan} > 0 ) or
1173                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1174                                $item->{wthdrawn} or
1175                                $notforloan_per_itemtype;
1176
1177     if (C4::Context->preference('AllowOnShelfHolds')) {
1178         return $available_per_item;
1179     } else {
1180         return ($available_per_item and $item->{onloan}); 
1181     }
1182 }
1183
1184 =item _FixPriority
1185
1186 &_FixPriority($biblio,$borrowernumber,$rank);
1187
1188  Only used internally (so don't export it)
1189  Changed how this functions works #
1190  Now just gets an array of reserves in the rank order and updates them with
1191  the array index (+1 as array starts from 0)
1192  and if $rank is supplied will splice item from the array and splice it back in again
1193  in new priority rank
1194
1195 =cut 
1196
1197 sub _FixPriority {
1198     my ( $biblio, $borrowernumber, $rank ) = @_;
1199     my $dbh = C4::Context->dbh;
1200      if ( $rank eq "del" ) {
1201          CancelReserve( $biblio, undef, $borrowernumber );
1202      }
1203     if ( $rank eq "W" || $rank eq "0" ) {
1204
1205         # make sure priority for waiting items is 0
1206         my $query = qq/
1207             UPDATE reserves
1208             SET    priority = 0
1209             WHERE biblionumber = ?
1210               AND borrowernumber = ?
1211               AND found ='W'
1212         /;
1213         my $sth = $dbh->prepare($query);
1214         $sth->execute( $biblio, $borrowernumber );
1215     }
1216     my @priority;
1217     my @reservedates;
1218
1219     # get whats left
1220 # FIXME adding a new security in returned elements for changing priority,
1221 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1222         # This is wrong a waiting reserve has W set
1223         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1224     my $query = qq/
1225         SELECT borrowernumber, reservedate, constrainttype
1226         FROM   reserves
1227         WHERE  biblionumber   = ?
1228           AND  ((found <> 'W') or found is NULL)
1229         ORDER BY priority ASC
1230     /;
1231     my $sth = $dbh->prepare($query);
1232     $sth->execute($biblio);
1233     while ( my $line = $sth->fetchrow_hashref ) {
1234         push( @reservedates, $line );
1235         push( @priority,     $line );
1236     }
1237
1238     # To find the matching index
1239     my $i;
1240     my $key = -1;    # to allow for 0 to be a valid result
1241     for ( $i = 0 ; $i < @priority ; $i++ ) {
1242         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1243             $key = $i;    # save the index
1244             last;
1245         }
1246     }
1247
1248     # if index exists in array then move it to new position
1249     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1250         my $new_rank = $rank -
1251           1;    # $new_rank is what you want the new index to be in the array
1252         my $moving_item = splice( @priority, $key, 1 );
1253         splice( @priority, $new_rank, 0, $moving_item );
1254     }
1255
1256     # now fix the priority on those that are left....
1257     $query = "
1258             UPDATE reserves
1259             SET    priority = ?
1260                 WHERE  biblionumber = ?
1261                  AND borrowernumber   = ?
1262                  AND reservedate = ?
1263          AND found IS NULL
1264     ";
1265     $sth = $dbh->prepare($query);
1266     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1267         $sth->execute(
1268             $j + 1, $biblio,
1269             $priority[$j]->{'borrowernumber'},
1270             $priority[$j]->{'reservedate'}
1271         );
1272         $sth->finish;
1273     }
1274 }
1275
1276 =item _Findgroupreserve
1277
1278   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1279
1280 ****** FIXME ******
1281 I don't know what this does, because I don't understand how reserve
1282 constraints work. I think the idea is that you reserve a particular
1283 biblio, and the constraint allows you to restrict it to a given
1284 biblioitem (e.g., if you want to borrow the audio book edition of "The
1285 Prophet", rather than the first available publication).
1286
1287 C<&_Findgroupreserve> returns :
1288 C<@results> is an array of references-to-hash whose keys are mostly
1289 fields from the reserves table of the Koha database, plus
1290 C<biblioitemnumber>.
1291
1292 =cut
1293
1294 sub _Findgroupreserve {
1295     my ( $bibitem, $biblio, $itemnumber ) = @_;
1296     my $dbh   = C4::Context->dbh;
1297     my $query = qq/
1298         SELECT reserves.biblionumber AS biblionumber,
1299                reserves.borrowernumber AS borrowernumber,
1300                reserves.reservedate AS reservedate,
1301                reserves.branchcode AS branchcode,
1302                reserves.cancellationdate AS cancellationdate,
1303                reserves.found AS found,
1304                reserves.reservenotes AS reservenotes,
1305                reserves.priority AS priority,
1306                reserves.timestamp AS timestamp,
1307                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1308                reserves.itemnumber AS itemnumber
1309         FROM reserves
1310           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1311         WHERE reserves.biblionumber = ?
1312           AND ( ( reserveconstraints.biblioitemnumber = ?
1313           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1314           AND reserves.reservedate    =reserveconstraints.reservedate )
1315           OR  reserves.constrainttype='a' )
1316           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1317     /;
1318     my $sth = $dbh->prepare($query);
1319     $sth->execute( $biblio, $bibitem, $itemnumber );
1320     my @results;
1321     while ( my $data = $sth->fetchrow_hashref ) {
1322         push( @results, $data );
1323     }
1324     $sth->finish;
1325     return @results;
1326 }
1327
1328 =back
1329
1330 =head1 AUTHOR
1331
1332 Koha Developement team <info@koha.org>
1333
1334 =cut
1335
1336 1;