bug 2522 [2/3]: C4::Reserves support for request targeting
[koha.git] / C4 / Reserves.pm
1 # -*- tab-width: 8 -*-
2 # NOTE: This file uses standard 8-character tabs
3
4 package C4::Reserves;
5
6 # Copyright 2000-2002 Katipo Communications
7 #           2006 SAN Ouest Provence
8 #           2007 BibLibre Paul POULAIN
9 #
10 # This file is part of Koha.
11 #
12 # Koha is free software; you can redistribute it and/or modify it under the
13 # terms of the GNU General Public License as published by the Free Software
14 # Foundation; either version 2 of the License, or (at your option) any later
15 # version.
16 #
17 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
18 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License along with
22 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23 # Suite 330, Boston, MA  02111-1307 USA
24
25
26 use strict;
27 use C4::Context;
28 use C4::Biblio;
29 use C4::Items;
30 use C4::Search;
31 use C4::Circulation;
32 use C4::Accounts;
33
34 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
35
36 my $library_name = C4::Context->preference("LibraryName");
37
38 =head1 NAME
39
40 C4::Reserves - Koha functions for dealing with reservation.
41
42 =head1 SYNOPSIS
43
44   use C4::Reserves;
45
46 =head1 DESCRIPTION
47
48   this modules provides somes functions to deal with reservations.
49   
50   Reserves are stored in reserves table.
51   The following columns contains important values :
52   - priority >0      : then the reserve is at 1st stage, and not yet affected to any item.
53              =0      : then the reserve is being dealed
54   - found : NULL       : means the patron requested the 1st available, and we haven't choosen the item
55             W(aiting)  : the reserve has an itemnumber affected, and is on the way
56             F(inished) : the reserve has been completed, and is done
57   - itemnumber : empty : the reserve is still unaffected to an item
58                  filled: the reserve is attached to an item
59   The complete workflow is :
60   ==== 1st use case ====
61   patron request a document, 1st available :                      P >0, F=NULL, I=NULL
62   a library having it run "transfertodo", and clic on the list    
63          if there is no transfer to do, the reserve waiting
64          patron can pick it up                                    P =0, F=W,    I=filled 
65          if there is a transfer to do, write in branchtransfer    P =0, F=NULL, I=filled
66            The pickup library recieve the book, it check in       P =0, F=W,    I=filled
67   The patron borrow the book                                      P =0, F=F,    I=filled
68   
69   ==== 2nd use case ====
70   patron requests a document, a given item,
71     If pickup is holding branch                                   P =0, F=W,   I=filled
72     If transfer needed, write in branchtransfer                   P =0, F=NULL, I=filled
73         The pickup library recieve the book, it checks it in      P =0, F=W,    I=filled
74   The patron borrow the book                                      P =0, F=F,    I=filled
75   
76 =head1 FUNCTIONS
77
78 =over 2
79
80 =cut
81
82 BEGIN {
83     # set the version for version checking
84     $VERSION = 3.01;
85         require Exporter;
86     @ISA = qw(Exporter);
87     @EXPORT = qw(
88         &AddReserve
89   
90         &GetReservesFromItemnumber
91         &GetReservesFromBiblionumber
92         &GetReservesFromBorrowernumber
93         &GetReservesForBranch
94         &GetReservesToBranch
95         &GetReserveCount
96         &GetReserveFee
97                 &GetReserveInfo
98     
99         &GetOtherReserves
100         
101         &ModReserveFill
102         &ModReserveAffect
103         &ModReserve
104         &ModReserveStatus
105         &ModReserveCancelAll
106         &ModReserveMinusPriority
107         
108         &CheckReserves
109         &CancelReserve
110
111         &IsAvailableForItemLevelRequest
112     );
113 }    
114
115 =item AddReserve
116
117     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$notes,$title,$checkitem,$found)
118
119 =cut
120
121 sub AddReserve {
122     my (
123         $branch,    $borrowernumber, $biblionumber,
124         $constraint, $bibitems,  $priority,       $notes,
125         $title,      $checkitem, $found
126     ) = @_;
127     my $fee =
128           GetReserveFee($borrowernumber, $biblionumber, $constraint,
129             $bibitems );
130     my $dbh     = C4::Context->dbh;
131     my $const   = lc substr( $constraint, 0, 1 );
132     my @datearr = localtime(time);
133     my $resdate =
134       ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
135     my $waitingdate;
136
137     # If the reserv had the waiting status, we had the value of the resdate
138     if ( $found eq 'W' ) {
139         $waitingdate = $resdate;
140     }
141
142     #eval {
143     # updates take place here
144     if ( $fee > 0 ) {
145         my $nextacctno = &getnextacctno( $borrowernumber );
146         my $query      = qq/
147         INSERT INTO accountlines
148             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
149         VALUES
150             (?,?,now(),?,?,'Res',?)
151     /;
152         my $usth = $dbh->prepare($query);
153         $usth->execute( $borrowernumber, $nextacctno, $fee,
154             "Reserve Charge - $title", $fee );
155         $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     _FixPriority($biblionumber, $borrowernumber, '0');
1062 }
1063
1064 =item GetReserveInfo
1065
1066 &GetReserveInfo($borrowernumber,$biblionumber);
1067
1068  Get item and borrower details for a current hold.
1069  Current implementation this query should have a single result.
1070 =cut
1071
1072 sub GetReserveInfo {
1073         my ( $borrowernumber, $biblionumber ) = @_;
1074     my $dbh = C4::Context->dbh;
1075         my $strsth="SELECT reservedate, reservenotes, reserves.borrowernumber,
1076                                 reserves.biblionumber, reserves.branchcode,
1077                                 notificationdate, reminderdate, priority, found,
1078                                 firstname, surname, phone, 
1079                                 email, address, address2,
1080                                 cardnumber, city, zipcode,
1081                                 biblio.title, biblio.author,
1082                                 items.holdingbranch, items.itemcallnumber, items.itemnumber, 
1083                                 barcode, notes
1084                         FROM reserves left join items 
1085                                 ON items.itemnumber=reserves.itemnumber , 
1086                                 borrowers, biblio 
1087                         WHERE 
1088                                 reserves.borrowernumber=?  &&
1089                                 reserves.biblionumber=? && 
1090                                 reserves.borrowernumber=borrowers.borrowernumber && 
1091                                 reserves.biblionumber=biblio.biblionumber ";
1092         my $sth = $dbh->prepare($strsth); 
1093         $sth->execute($borrowernumber,$biblionumber);
1094
1095         my $data = $sth->fetchrow_hashref;
1096         return $data;
1097
1098 }
1099
1100 =item IsAvailableForItemLevelRequest
1101
1102 =over 4
1103
1104 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1105
1106 =back
1107
1108 Checks whether a given item record is available for an
1109 item-level hold request.  An item is available if
1110
1111 * it is not lost AND 
1112 * it is not damaged AND 
1113 * it is not withdrawn AND 
1114 * does not have a not for loan value > 0
1115
1116 Whether or not the item is currently on loan is 
1117 also checked - if the AllowOnShelfHolds system preference
1118 is ON, an item can be requested even if it is currently
1119 on loan to somebody else.  If the system preference
1120 is OFF, an item that is currently checked out cannot
1121 be the target of an item-level hold request.
1122
1123 Note that IsAvailableForItemLevelRequest() does not
1124 check if the staff operator is authorized to place
1125 a request on the item - in particular,
1126 this routine does not check IndependantBranches
1127 and canreservefromotherbranches.
1128
1129 =cut
1130
1131 sub IsAvailableForItemLevelRequest {
1132     my $itemnumber = shift;
1133    
1134     my $item = GetItem($itemnumber);
1135
1136     # must check the notforloan setting of the itemtype
1137     # FIXME - a lot of places in the code do this
1138     #         or something similar - need to be
1139     #         consolidated
1140     my $dbh = C4::Context->dbh;
1141     my $notforloan_query;
1142     if (C4::Context->preference('item-level_itypes')) {
1143         $notforloan_query = "SELECT itemtypes.notforloan
1144                              FROM items
1145                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1146                              WHERE itemnumber = ?";
1147     } else {
1148         $notforloan_query = "SELECT itemtypes.notforloan
1149                              FROM items
1150                              JOIN biblioitems USING (biblioitemnumber)
1151                              JOIN itemtypes USING (itemtype)
1152                              WHERE itemnumber = ?";
1153     }
1154     my $sth = $dbh->prepare($notforloan_query);
1155     $sth->execute($itemnumber);
1156     my $notforloan_per_itemtype = 0;
1157     if (my ($notforloan) = $sth->fetchrow_array) {
1158         $notforloan_per_itemtype = 1 if $notforloan;
1159     }
1160
1161     my $available_per_item = 1;
1162     $available_per_item = 0 if $item->{itemlost} or
1163                                ( $item->{notforloan} > 0 ) or
1164                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1165                                $item->{wthdrawn} or
1166                                $notforloan_per_itemtype;
1167
1168     if (C4::Context->preference('AllowOnShelfHolds')) {
1169         return $available_per_item;
1170     } else {
1171         return ($available_per_item and $item->{onloan}); 
1172     }
1173 }
1174
1175 =item _FixPriority
1176
1177 &_FixPriority($biblio,$borrowernumber,$rank);
1178
1179  Only used internally (so don't export it)
1180  Changed how this functions works #
1181  Now just gets an array of reserves in the rank order and updates them with
1182  the array index (+1 as array starts from 0)
1183  and if $rank is supplied will splice item from the array and splice it back in again
1184  in new priority rank
1185
1186 =cut 
1187
1188 sub _FixPriority {
1189     my ( $biblio, $borrowernumber, $rank ) = @_;
1190     my $dbh = C4::Context->dbh;
1191      if ( $rank eq "del" ) {
1192          CancelReserve( $biblio, undef, $borrowernumber );
1193      }
1194     if ( $rank eq "W" || $rank eq "0" ) {
1195
1196         # make sure priority for waiting items is 0
1197         my $query = qq/
1198             UPDATE reserves
1199             SET    priority = 0
1200             WHERE biblionumber = ?
1201               AND borrowernumber = ?
1202               AND found ='W'
1203         /;
1204         my $sth = $dbh->prepare($query);
1205         $sth->execute( $biblio, $borrowernumber );
1206     }
1207     my @priority;
1208     my @reservedates;
1209
1210     # get whats left
1211 # FIXME adding a new security in returned elements for changing priority,
1212 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1213         # This is wrong a waiting reserve has W set
1214         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1215     my $query = qq/
1216         SELECT borrowernumber, reservedate, constrainttype
1217         FROM   reserves
1218         WHERE  biblionumber   = ?
1219           AND  ((found <> 'W') or found is NULL)
1220         ORDER BY priority ASC
1221     /;
1222     my $sth = $dbh->prepare($query);
1223     $sth->execute($biblio);
1224     while ( my $line = $sth->fetchrow_hashref ) {
1225         push( @reservedates, $line );
1226         push( @priority,     $line );
1227     }
1228
1229     # To find the matching index
1230     my $i;
1231     my $key = -1;    # to allow for 0 to be a valid result
1232     for ( $i = 0 ; $i < @priority ; $i++ ) {
1233         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1234             $key = $i;    # save the index
1235             last;
1236         }
1237     }
1238
1239     # if index exists in array then move it to new position
1240     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1241         my $new_rank = $rank -
1242           1;    # $new_rank is what you want the new index to be in the array
1243         my $moving_item = splice( @priority, $key, 1 );
1244         splice( @priority, $new_rank, 0, $moving_item );
1245     }
1246
1247     # now fix the priority on those that are left....
1248     $query = "
1249             UPDATE reserves
1250             SET    priority = ?
1251                 WHERE  biblionumber = ?
1252                  AND borrowernumber   = ?
1253                  AND reservedate = ?
1254          AND found IS NULL
1255     ";
1256     $sth = $dbh->prepare($query);
1257     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1258         $sth->execute(
1259             $j + 1, $biblio,
1260             $priority[$j]->{'borrowernumber'},
1261             $priority[$j]->{'reservedate'}
1262         );
1263         $sth->finish;
1264     }
1265 }
1266
1267 =item _Findgroupreserve
1268
1269   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1270
1271 ****** FIXME ******
1272 I don't know what this does, because I don't understand how reserve
1273 constraints work. I think the idea is that you reserve a particular
1274 biblio, and the constraint allows you to restrict it to a given
1275 biblioitem (e.g., if you want to borrow the audio book edition of "The
1276 Prophet", rather than the first available publication).
1277
1278 C<&_Findgroupreserve> returns :
1279 C<@results> is an array of references-to-hash whose keys are mostly
1280 fields from the reserves table of the Koha database, plus
1281 C<biblioitemnumber>.
1282
1283 =cut
1284
1285 sub _Findgroupreserve {
1286     my ( $bibitem, $biblio, $itemnumber ) = @_;
1287     my $dbh   = C4::Context->dbh;
1288
1289     # check for exact targetted match
1290     my $item_level_target_query = qq/
1291         SELECT reserves.biblionumber AS biblionumber,
1292                reserves.borrowernumber AS borrowernumber,
1293                reserves.reservedate AS reservedate,
1294                reserves.branchcode AS branchcode,
1295                reserves.cancellationdate AS cancellationdate,
1296                reserves.found AS found,
1297                reserves.reservenotes AS reservenotes,
1298                reserves.priority AS priority,
1299                reserves.timestamp AS timestamp,
1300                biblioitems.biblioitemnumber AS biblioitemnumber,
1301                reserves.itemnumber AS itemnumber
1302         FROM reserves
1303         JOIN biblioitems USING (biblionumber)
1304         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1305         WHERE found IS NULL
1306         AND priority > 0
1307         AND item_level_request = 1
1308         AND itemnumber = ?
1309     /;
1310     my $sth = $dbh->prepare($item_level_target_query);
1311     $sth->execute($itemnumber);
1312     my @results;
1313     if ( my $data = $sth->fetchrow_hashref ) {
1314         push( @results, $data );
1315     }
1316     return @results if @results;
1317     
1318     # check for title-level targetted match
1319     my $title_level_target_query = qq/
1320         SELECT reserves.biblionumber AS biblionumber,
1321                reserves.borrowernumber AS borrowernumber,
1322                reserves.reservedate AS reservedate,
1323                reserves.branchcode AS branchcode,
1324                reserves.cancellationdate AS cancellationdate,
1325                reserves.found AS found,
1326                reserves.reservenotes AS reservenotes,
1327                reserves.priority AS priority,
1328                reserves.timestamp AS timestamp,
1329                biblioitems.biblioitemnumber AS biblioitemnumber,
1330                reserves.itemnumber AS itemnumber
1331         FROM reserves
1332         JOIN biblioitems USING (biblionumber)
1333         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1334         WHERE found IS NULL
1335         AND priority > 0
1336         AND item_level_request = 0
1337         AND hold_fill_targets.itemnumber = ?
1338     /;
1339     $sth = $dbh->prepare($title_level_target_query);
1340     $sth->execute($itemnumber);
1341     @results = ();
1342     if ( my $data = $sth->fetchrow_hashref ) {
1343         push( @results, $data );
1344     }
1345     return @results if @results;
1346
1347     my $query = qq/
1348         SELECT reserves.biblionumber AS biblionumber,
1349                reserves.borrowernumber AS borrowernumber,
1350                reserves.reservedate AS reservedate,
1351                reserves.branchcode AS branchcode,
1352                reserves.cancellationdate AS cancellationdate,
1353                reserves.found AS found,
1354                reserves.reservenotes AS reservenotes,
1355                reserves.priority AS priority,
1356                reserves.timestamp AS timestamp,
1357                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1358                reserves.itemnumber AS itemnumber
1359         FROM reserves
1360           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1361         WHERE reserves.biblionumber = ?
1362           AND ( ( reserveconstraints.biblioitemnumber = ?
1363           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1364           AND reserves.reservedate    =reserveconstraints.reservedate )
1365           OR  reserves.constrainttype='a' )
1366           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1367     /;
1368     $sth = $dbh->prepare($query);
1369     $sth->execute( $biblio, $bibitem, $itemnumber );
1370     @results = ();
1371     while ( my $data = $sth->fetchrow_hashref ) {
1372         push( @results, $data );
1373     }
1374     $sth->finish;
1375     return @results;
1376 }
1377
1378 =back
1379
1380 =head1 AUTHOR
1381
1382 Koha Developement team <info@koha.org>
1383
1384 =cut
1385
1386 1;