oups, fixing bug in EXPORT...
[koha.git] / C4 / Reserves2.pm
1 # -*- tab-width: 8 -*-
2 # NOTE: This file uses standard 8-character tabs
3
4 package C4::Reserves2;
5
6 # Copyright 2000-2002 Katipo Communications
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
13 # version.
14 #
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License along with
20 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
21 # Suite 330, Boston, MA  02111-1307 USA
22
23 # $Id$
24
25 use strict;
26 require Exporter;
27 use C4::Context;
28 use C4::Biblio;
29 use C4::Search;
30 use C4::Circulation::Circ2;
31
32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
33 my $library_name = C4::Context->preference("LibraryName");
34
35 # set the version for version checking
36 $VERSION = do { my @v = '$Revision$' =~ /\d+/g; shift(@v) . "." . join( "_", map { sprintf "%03d", $_ } @v ); };
37
38 =head1 NAME
39
40 C4::Reserves2 - Koha functions for dealing with reservation.
41
42 =head1 SYNOPSIS
43
44   use C4::Reserves2;
45
46 =head1 DESCRIPTION
47
48 this modules provides somes functions to deal with reservations.
49
50 =head1 FUNCTIONS
51
52 =over 2
53
54 =cut
55
56 @ISA = qw(Exporter);
57
58 @EXPORT = qw(
59   &FindReserves
60   &CheckReserves
61   &GetWaitingReserves
62   &CancelReserve
63   &CalcReserveFee
64   &FillReserve
65   &ReserveWaiting
66   &CreateReserve
67   &UpdateReserve
68   &GetReserveTitle
69   &GetReservations
70   &SetWaitingStatus
71   &GlobalCancel
72   &MinusPriority
73   &OtherReserves
74   &GetFirstReserveDateFromItem
75   &CountReservesFromBorrower
76   &FixPriority
77   &FindReservesInQueue
78 );
79
80 # make all your functions, whether exported or not;
81
82 =item GlobalCancel
83
84 ($messages,$nextreservinfo) = &GlobalCancel($itemnumber,$borrowernumber);
85
86     New op dev for the circulation based on item, global is a function to cancel reserv,check other reserves, and transfer document if it's necessary
87
88 =cut
89
90 #'
91 sub GlobalCancel {
92     my $messages;
93     my $nextreservinfo;
94     my ( $itemnumber, $borrowernumber ) = @_;
95
96     #step 1 : cancel the reservation
97     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
98
99     #step 2 launch the subroutine of the others reserves
100     ( $messages, $nextreservinfo ) = OtherReserves($itemnumber);
101
102     return ( $messages, $nextreservinfo );
103 }
104
105 =item OtherReserves
106
107 ($messages,$nextreservinfo)=$OtherReserves(itemnumber);
108
109 Check queued list of this document and check if this document must be  transfered
110
111 =cut
112
113 #'
114 sub OtherReserves {
115     my ($itemnumber) = @_;
116     my $messages;
117     my $nextreservinfo;
118     my ( $restype, $checkreserves ) = CheckReserves($itemnumber);
119     if ($checkreserves) {
120         my $iteminfo = C4::Circulation::Circ2::getiteminformation($itemnumber,undef);
121         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
122             $messages->{'transfert'} = $checkreserves->{'branchcode'};
123             #minus priorities of others reservs
124             MinusPriority(
125                 $itemnumber,
126                 $checkreserves->{'borrowernumber'},
127                 $iteminfo->{'biblionumber'}
128             );
129
130             #launch the subroutine dotransfer
131             C4::Circulation::Circ2::dotransfer(
132                 $itemnumber,
133                 $iteminfo->{'holdingbranch'},
134                 $checkreserves->{'branchcode'}
135               ),
136               ;
137         }
138
139      #step 2b : case of a reservation on the same branch, set the waiting status
140         else {
141             $messages->{'waiting'} = 1;
142             MinusPriority(
143                 $itemnumber,
144                 $checkreserves->{'borrowernumber'},
145                 $iteminfo->{'biblionumber'}
146             );
147             SetWaitingStatus($itemnumber);
148         }
149
150         $nextreservinfo = $checkreserves->{'borrowernumber'};
151     }
152
153     return ( $messages, $nextreservinfo );
154 }
155
156 =item MinusPriority
157
158 &MinusPriority($itemnumber,$borrowernumber,$biblionumber)
159
160 Reduce the values of queuded list     
161
162 =cut
163
164 #'
165 sub MinusPriority {
166     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
167
168     #first step update the value of the first person on reserv
169     my $dbh   = C4::Context->dbh;
170     my $query = qq/
171         UPDATE reserves
172         SET    priority = 0 , itemnumber = ? 
173         WHERE  cancellationdate IS NULL 
174           AND  borrowernumber=?
175           AND  biblionumber=?
176     /;
177     my $sth_upd = $dbh->prepare($query);
178     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
179     $sth_upd->finish;
180     # second step update all others reservs
181     $query = qq/
182         SELECT priority,borrowernumber,biblionumber,reservedate
183         FROM   reserves
184         WHERE  priority !='0'
185         AND biblionumber = ?
186           AND  cancellationdate IS NULL
187     /;
188     my $sth_oth = $dbh->prepare($query);
189     $sth_oth->execute($biblionumber);
190     while ( my ( $priority, $borrowernumber, $biblionumber, $reservedate ) =
191         $sth_oth->fetchrow_array )
192     {
193         $priority--;
194         $query = qq/
195              UPDATE reserves
196              SET    priority = ?
197              WHERE  biblionumber = ?
198                AND  borrowernumber   = ?
199                AND  reservedate      = ?
200         /;
201         my $sth_upd_oth = $dbh->prepare($query);
202         $sth_upd_oth->execute( $priority, $biblionumber, $borrowernumber,
203             $reservedate );
204         $sth_upd_oth->finish;
205     }
206     $sth_oth->finish;
207 }
208
209 =item SetWaitingStatus
210
211 &SetWaitingStatus($itemnumber);
212
213 we check if we have a reserves with itemnumber (New op system of reserves), if we found one, we update the status of the reservation when we have : 'priority' = 0, and we have an itemnumber 
214
215 =cut
216
217 sub SetWaitingStatus {
218
219     #first : check if we have a reservation for this item .
220     my ($itemnumber) = @_;
221     my $dbh          = C4::Context->dbh;
222     my $query        = qq/
223         SELECT priority,borrowernumber
224         FROM   reserves
225         WHERE  itemnumber=?
226            AND cancellationdate IS NULL
227            AND found IS NULL AND priority='0'
228     /;
229     my $sth_find = $dbh->prepare($query);
230     $sth_find->execute($itemnumber);
231     my ( $priority, $borrowernumber ) = $sth_find->fetchrow_array;
232     $sth_find->finish;
233     return unless $borrowernumber;
234
235 # step 2 : if we have a borrowernumber, we update the value found to 'W' to notify the borrower
236     $query = qq/
237     UPDATE reserves
238     SET    found='W',waitingdate = now()
239     WHERE  borrowernumber=?
240       AND itemnumber=?
241       AND found IS NULL
242     /;
243     my $sth_set = $dbh->prepare($query);
244     $sth_set->execute( $borrowernumber, $itemnumber );
245     $sth_set->finish;
246 }
247
248 =item GetReservations
249
250 @borrowerreserv=&GetReservations($itemnumber,$borrowernumber);
251
252 this function get the list of reservation for an C<$itemnumber> or C<$borrowernumber>
253 given on input arg. You should give $itemnumber OR $borrowernumber but not both.
254
255 =cut
256
257 sub GetReservations {
258     my ( $itemnumber, $borrowernumber ) = @_;
259     if ($itemnumber) {
260         my $dbh   = C4::Context->dbh;
261         my $query = qq/
262             SELECT reservedate,borrowernumber
263             FROM   reserves
264             WHERE  itemnumber=?
265               AND  cancellationdate IS NULL
266               AND  (found <> 'F' OR found IS NULL)
267         /;
268         my $sth_res = $dbh->prepare($query);
269         $sth_res->execute($itemnumber);
270         my ( $reservedate, $borrowernumber ) = $sth_res->fetchrow_array;
271         return ( $reservedate, $borrowernumber );
272     }
273     if ($borrowernumber) {
274         my $dbh   = C4::Context->dbh;
275         my $query = qq/
276             SELECT *
277             FROM   reserves
278             WHERE  borrowernumber=?
279               AND  cancellationdate IS NULL
280               AND (found != 'F' or found is null)
281             ORDER BY reservedate
282         /;
283
284         my $sth_find = $dbh->prepare($query);
285         $sth_find->execute($borrowernumber);
286         my @borrowerreserv;
287         while ( my $data = $sth_find->fetchrow_hashref ) {
288             push @borrowerreserv, $data;
289         }
290         return @borrowerreserv;
291     }
292 }
293
294 =item FindReserves
295
296   $results = &FindReserves($biblionumber, $borrowernumber);
297
298 Looks books up in the reserves. C<$biblionumber> is the biblionumber
299 of the book to look up. C<$borrowernumber> is the borrower number of a
300 patron whose books to look up.
301
302 Either C<$biblionumber> or C<$borrowernumber> may be the empty string,
303 but not both. If both are specified, C<&FindReserves> looks up the
304 given book for the given patron. If only C<$biblionumber> is
305 specified, C<&FindReserves> looks up that book for all patrons. If
306 only C<$borrowernumber> is specified, C<&FindReserves> looks up all of
307 that patron's reserves. If neither is specified, C<&FindReserves>
308 barfs.
309
310 For each book thus found, C<&FindReserves> checks the reserve
311 constraints and does something I don't understand.
312
313 C<&FindReserves> returns a two-element array:
314
315 C<$results> is a reference to an array of references of hashes. Each hash
316 has for keys a list of column from reserves table (see details in function).
317
318 =cut
319
320 #'
321 sub FindReserves {
322     my ( $biblionumber, $bor ) = @_;
323     my $dbh = C4::Context->dbh;
324     my @bind;
325
326     # Find the desired items in the reserves
327     my $query = qq/
328         SELECT  branchcode,
329                 timestamp AS rtimestamp,
330                 priority,
331                 biblionumber,
332                 borrowernumber,
333                 reservedate,
334                 constrainttype,
335                 found,
336                 itemnumber
337           FROM     reserves
338           WHERE     cancellationdate IS NULL
339           AND    (found <> \'F\' OR found IS NULL)
340     /;
341
342     if ( $biblionumber ne '' ) {
343         $query .= '
344             AND biblionumber = ?
345         ';
346         push @bind, $biblionumber;
347     }
348
349     if ( $bor ne '' ) {
350         $query .= '
351             AND borrowernumber = ?
352         ';
353         push @bind, $bor;
354     }
355
356     $query .= '
357           ORDER BY priority
358     ';
359     my $sth = $dbh->prepare($query);
360     $sth->execute(@bind);
361     my @results;
362     my $i = 0;
363     while ( my $data = $sth->fetchrow_hashref ) {
364
365         # FIXME - What is this if-statement doing? How do constraints work?
366         if ( $data->{constrainttype} eq 'o' ) {
367             $query = '
368                 SELECT biblioitemnumber
369                 FROM reserveconstraints
370                 WHERE biblionumber   = ?
371                     AND borrowernumber = ?
372                   AND reservedate    = ?
373             ';
374             my $csth = $dbh->prepare($query);
375             $csth->execute( $data->{biblionumber}, $data->{borrowernumber},
376                 $data->{reservedate}, );
377
378             my @bibitemno;
379             while ( my $bibitemnos = $csth->fetchrow_array ) {
380                 push( @bibitemno, $bibitemnos );
381             }
382             my $count = @bibitemno;
383
384             # if we have two or more different specific itemtypes
385             # reserved by same person on same day
386             my $bdata;
387             if ( $count > 1 ) {
388                 $bdata = GetBiblioItemData( $bibitemno[$i] );
389                 $i++;
390             }
391             else {
392
393                 # Look up the book we just found.
394                 $bdata = GetBiblioItemData( $bibitemno[0] );
395             }
396             $csth->finish;
397
398             # Add the results of this latest search to the current
399             # results.
400             # FIXME - An 'each' would probably be more efficient.
401             foreach my $key ( keys %$bdata ) {
402                 $data->{$key} = $bdata->{$key};
403             }
404         }
405         push @results, $data;
406     }
407     $sth->finish;
408
409     return ( $#results + 1, \@results );
410 }
411
412 #-------------------------------------------------------------------------------------
413
414 =item CountReservesFromBorrower
415
416 $number = &CountReservesFromBorrower($borrowernumber);
417
418 this function returns the number of reservation for a borrower given on input arg.
419
420 =cut
421
422 sub CountReservesFromBorrower {
423     my ($borrowernumber) = @_;
424
425     my $dbh = C4::Context->dbh;
426
427     my $query = '
428         SELECT COUNT(*) AS counter
429         FROM reserves
430           WHERE borrowernumber = ?
431           AND cancellationdate IS NULL
432           AND (found != \'F\' OR found IS NULL)
433     ';
434     my $sth = $dbh->prepare($query);
435     $sth->execute($borrowernumber);
436     my $row = $sth->fetchrow_hashref;
437     $sth->finish;
438
439     return $row->{counter};
440 }
441
442 #-------------------------------------------------------------------------------------
443
444 =item GetFirstReserveDateFromItem
445
446 $date = GetFirstReserveDateFromItem($itemnumber)
447
448 this function returns the first date a item has been reserved.
449
450 =cut
451
452 sub GetFirstReserveDateFromItem {
453     my ($itemnumber) = @_;
454
455     my $dbh = C4::Context->dbh;
456
457     my $query = '
458         SELECT reservedate,
459         borrowernumber,
460         branchcode
461         FROM   reserves
462         WHERE  itemnumber = ?
463           AND  cancellationdate IS NULL
464           AND (found != \'F\' OR found IS NULL)
465     ';
466     my $sth = $dbh->prepare($query);
467     $sth->execute($itemnumber);
468     my $row = $sth->fetchrow_hashref;
469
470     return ($row->{reservedate},$row->{borrowernumber},$row->{branchcode});
471 }
472
473 #-------------------------------------------------------------------------------------
474
475 =item CheckReserves
476
477   ($status, $reserve) = &CheckReserves($itemnumber, $barcode);
478
479 Find a book in the reserves.
480
481 C<$itemnumber> is the book's item number. C<$barcode> is its barcode.
482 Either one, but not both, may be false. If both are specified,
483 C<&CheckReserves> uses C<$itemnumber>.
484
485 $itemnubmer can be false, in which case uses the barcode. (Never uses
486 both. $itemnumber gets priority).
487
488 As I understand it, C<&CheckReserves> looks for the given item in the
489 reserves. If it is found, that's a match, and C<$status> is set to
490 C<Waiting>.
491
492 Otherwise, it finds the most important item in the reserves with the
493 same biblio number as this book (I'm not clear on this) and returns it
494 with C<$status> set to C<Reserved>.
495
496 C<&CheckReserves> returns a two-element list:
497
498 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
499
500 C<$reserve> is the reserve item that matched. It is a
501 reference-to-hash whose keys are mostly the fields of the reserves
502 table in the Koha database.
503
504 =cut
505
506 #'
507 sub CheckReserves {
508     my ( $item, $barcode ) = @_;
509     my $dbh = C4::Context->dbh;
510     my $sth;
511     if ($item) {
512         my $qitem = $dbh->quote($item);
513         # Look up the item by itemnumber
514         my $query = qq(
515             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
516             FROM   items, biblioitems, itemtypes
517             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
518                AND biblioitems.itemtype = itemtypes.itemtype
519                AND itemnumber=$qitem
520         );
521         $sth = $dbh->prepare($query);
522     }
523     else {
524         my $qbc = $dbh->quote($barcode);
525         # Look up the item by barcode
526         my $query = qq(
527             SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan
528             FROM   items, biblioitems, itemtypes
529             WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
530               AND biblioitems.itemtype = itemtypes.itemtype
531               AND barcode=$qbc
532         );
533         $sth = $dbh->prepare($query);
534
535         # FIXME - This function uses $item later on. Ought to set it here.
536     }
537     $sth->execute;
538     my ( $biblio, $bibitem, $notforloan ) = $sth->fetchrow_array;
539     $sth->finish;
540
541     # if item is not for loan it cannot be reserved either.....
542     return ( 0, 0 ) if $notforloan;
543
544     # get the reserves...
545     # Find this item in the reserves
546     my @reserves = Findgroupreserve( $bibitem, $biblio );
547     my $count    = scalar @reserves;
548
549     # $priority and $highest are used to find the most important item
550     # in the list returned by &Findgroupreserve. (The lower $priority,
551     # the more important the item.)
552     # $highest is the most important item we've seen so far.
553     my $priority = 10000000;
554     my $highest;
555     if ($count) {
556         foreach my $res (@reserves) {
557             # FIXME - $item might be undefined or empty: the caller
558             # might be searching by barcode.
559             if ( $res->{'itemnumber'} == $item ) {
560                 # Found it
561                 return ( "Waiting", $res );
562             }
563             else {
564                 # See if this item is more important than what we've got
565                 # so far.
566                 if ( $res->{'priority'} != 0 && $res->{'priority'} < $priority )
567                 {
568                     $priority = $res->{'priority'};
569                     $highest  = $res;
570                 }
571             }
572         }
573     }
574
575     # If we get this far, then no exact match was found. Print the
576     # most important item on the list. I think this tells us who's
577     # next in line to get this book.
578     if ($highest) {    # FIXME - $highest might be undefined
579         $highest->{'itemnumber'} = $item;
580         return ( "Reserved", $highest );
581     }
582     else {
583         return ( 0, 0 );
584     }
585 }
586
587 #-------------------------------------------------------------------------------------
588
589 =item CancelReserve
590
591   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
592
593 Cancels a reserve.
594
595 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
596 cancel, but not both: if both are given, C<&CancelReserve> does
597 nothing.
598
599 C<$borrowernumber> is the borrower number of the patron on whose
600 behalf the book was reserved.
601
602 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
603 priorities of the other people who are waiting on the book.
604
605 =cut
606
607 #'
608 sub CancelReserve {
609     my ( $biblio, $item, $borr ) = @_;
610     my $dbh = C4::Context->dbh;
611         if ( ( $item and $borr ) and ( not $biblio ) ) {
612         # removing a waiting reserve record....
613         # update the database...
614         my $query = qq/
615             UPDATE reserves
616             SET    cancellationdate = now(),
617                    found            = Null,
618                    priority         = 0
619             WHERE  itemnumber       = ?
620              AND   borrowernumber   = ?
621         /;
622         my $sth = $dbh->prepare($query);
623         $sth->execute( $item, $borr );
624         $sth->finish;
625     }
626     if ( ( $biblio and $borr ) and ( not $item ) ) {
627         # removing a reserve record....
628         # get the prioritiy on this record....
629         my $priority;
630         my $query = qq/
631             SELECT priority FROM reserves
632             WHERE biblionumber   = ?
633               AND borrowernumber = ?
634               AND cancellationdate IS NULL
635               AND itemnumber IS NULL
636               AND (found <> 'F' OR found IS NULL)
637         /;
638         my $sth = $dbh->prepare($query);
639         $sth->execute( $biblio, $borr );
640         ($priority) = $sth->fetchrow_array;
641         $sth->finish;
642         $query = qq/
643             UPDATE reserves
644             SET    cancellationdate = now(),
645                    found            = Null,
646                    priority         = 0
647             WHERE  biblionumber     = ?
648               AND  borrowernumber   = ?
649               AND cancellationdate IS NULL
650               AND (found <> 'F' or found IS NULL)
651         /;
652
653         # update the database, removing the record...
654         $sth = $dbh->prepare($query);
655         $sth->execute( $biblio, $borr );
656         $sth->finish;
657
658         # now fix the priority on the others....
659         FixPriority( $priority, $biblio );
660     }
661 }
662
663 #-------------------------------------------------------------------------------------
664
665 =item FillReserve
666
667   &FillReserve($reserve);
668
669 Fill a reserve. If I understand this correctly, this means that the
670 reserved book has been found and given to the patron who reserved it.
671
672 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
673 whose keys are fields from the reserves table in the Koha database.
674
675 =cut
676
677 #'
678 sub FillReserve {
679     my ($res) = @_;
680     my $dbh = C4::Context->dbh;
681     # fill in a reserve record....
682     my $qbiblio = $res->{'biblionumber'};
683     my $borr    = $res->{'borrowernumber'};
684     my $resdate = $res->{'reservedate'};
685
686     # get the priority on this record....
687     my $priority;
688     my $query = "SELECT priority
689                  FROM   reserves
690                  WHERE  biblionumber   = ?
691                   AND   borrowernumber = ?
692                   AND   reservedate    = ?";
693     my $sth = $dbh->prepare($query);
694     $sth->execute( $qbiblio, $borr, $resdate );
695     ($priority) = $sth->fetchrow_array;
696     $sth->finish;
697
698     # update the database...
699     $query = "UPDATE reserves
700                   SET    found            = 'F',
701                          priority         = 0
702                  WHERE  biblionumber     = ?
703                     AND reservedate      = ?
704                     AND borrowernumber   = ?
705                 ";
706     $sth = $dbh->prepare($query);
707     $sth->execute( $qbiblio, $resdate, $borr );
708     $sth->finish;
709
710     # now fix the priority on the others (if the priority wasn't
711     # already sorted!)....
712     unless ( $priority == 0 ) {
713         FixPriority( $priority, $qbiblio );
714     }
715 }
716
717 #-------------------------------------------------------------------------------------
718
719 =item FixPriority
720
721 &FixPriority($biblio,$borrowernumber,$rank);
722
723  Only used internally (so don't export it)
724  Changed how this functions works #
725  Now just gets an array of reserves in the rank order and updates them with
726  the array index (+1 as array starts from 0)
727  and if $rank is supplied will splice item from the array and splice it back in again
728  in new priority rank
729
730 =cut 
731
732 sub FixPriority {
733     my ( $biblio, $borrowernumber, $rank ) = @_;
734     my $dbh = C4::Context->dbh;
735      if ( $rank eq "del" ) {
736          CancelReserve( $biblio, undef, $borrowernumber );
737      }
738     if ( $rank eq "W" || $rank eq "0" ) {
739
740         # make sure priority for waiting items is 0
741         my $query = qq/
742             UPDATE reserves
743             SET    priority = 0
744             WHERE biblionumber = ?
745               AND borrowernumber = ?
746               AND cancellationdate IS NULL
747               AND found ='W'
748         /;
749         my $sth = $dbh->prepare($query);
750         $sth->execute( $biblio, $borrowernumber );
751     }
752     my @priority;
753     my @reservedates;
754
755     # get whats left
756 # FIXME adding a new security in returned elements for changing priority,
757 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
758     my $query = qq/
759         SELECT borrowernumber, reservedate, constrainttype
760         FROM   reserves
761         WHERE  biblionumber   = ?
762           AND  cancellationdate IS NULL
763           AND  itemnumber IS NULL
764           AND  ((found <> 'F' and found <> 'W') or found is NULL)
765         ORDER BY priority ASC
766     /;
767     my $sth = $dbh->prepare($query);
768     $sth->execute($biblio);
769     while ( my $line = $sth->fetchrow_hashref ) {
770         push( @reservedates, $line );
771         push( @priority,     $line );
772     }
773
774     # To find the matching index
775     my $i;
776     my $key = -1;    # to allow for 0 to be a valid result
777     for ( $i = 0 ; $i < @priority ; $i++ ) {
778         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
779             $key = $i;    # save the index
780             last;
781         }
782     }
783
784     # if index exists in array then move it to new position
785     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
786         my $new_rank = $rank -
787           1;    # $new_rank is what you want the new index to be in the array
788         my $moving_item = splice( @priority, $key, 1 );
789         splice( @priority, $new_rank, 0, $moving_item );
790     }
791
792     # now fix the priority on those that are left....
793     $query = "
794             UPDATE reserves
795             SET    priority = ?
796                 WHERE  biblionumber = ?
797                  AND borrowernumber   = ?
798                  AND reservedate = ?
799          AND found IS NULL
800     ";
801     $sth = $dbh->prepare($query);
802     for ( my $j = 0 ; $j < @priority ; $j++ ) {
803         $sth->execute(
804             $j + 1, $biblio,
805             $priority[$j]->{'borrowernumber'},
806             $priority[$j]->{'reservedate'}
807         );
808         $sth->finish;
809     }
810 }
811
812 #-------------------------------------------------------------------------------------
813
814 =item ReserveWaiting
815
816 branchcode = &ReserveWaiting($item,$borr);
817 this function set FOUND to 'W' for Waiting into the database.
818
819 =cut
820
821 sub ReserveWaiting {
822     my ( $item, $borr,$diffBranchSend ) = @_;
823     my $dbh = C4::Context->dbh;
824
825     # get priority and biblionumber....
826     my $query = qq/
827         SELECT reserves.priority as priority,
828                reserves.biblionumber as biblionumber,
829                reserves.branchcode as branchcode,
830                reserves.timestamp as timestamp
831         FROM   reserves,items
832         WHERE  reserves.biblionumber = items.biblionumber
833           AND  items.itemnumber = ?
834           AND reserves.borrowernumber = ?
835           AND reserves.cancellationdate IS NULL
836           AND (reserves.found <> 'F' OR reserves.found IS NULL)
837     /;
838     my $sth = $dbh->prepare($query);
839     $sth->execute( $item, $borr );
840     my $data = $sth->fetchrow_hashref;
841     $sth->finish;
842     my $biblio    = $data->{'biblionumber'};
843     my $timestamp = $data->{'timestamp'};
844
845     # update reserves record....
846     if ($diffBranchSend) {
847     $query = qq/
848         UPDATE reserves
849         SET    priority = 0,
850                itemnumber = ?
851         WHERE borrowernumber = ?
852           AND biblionumber = ?
853           AND timestamp = ?
854     /;
855     }
856     else {
857     $query = qq/
858         UPDATE reserves
859         SET    priority = 0,
860                found = 'W',
861             waitingdate=now(),
862                itemnumber = ?
863         WHERE borrowernumber = ?
864           AND biblionumber = ?
865           AND timestamp = ?
866     /;
867     }
868     $sth = $dbh->prepare($query);
869     $sth->execute( $item, $borr, $biblio, $timestamp );
870     $sth->finish;
871
872     # now fix up the remaining priorities....
873     FixPriority( $data->{'priority'}, $biblio );
874     my $branchcode = $data->{'branchcode'};
875     return $branchcode;
876 }
877
878 #-------------------------------------------------------------------------------------
879
880 =item GetWaitingReserves
881
882 \@itemswaiting=GetWaitingReserves($borr);
883
884 this funtion fetch the list of waiting reserves from database.
885
886 =cut
887
888 sub GetWaitingReserves {
889     my ($borr) = @_;
890     my $dbh = C4::Context->dbh;
891     my @itemswaiting;
892     my $query = qq/
893         SELECT *
894         FROM reserves
895         WHERE borrowernumber = ?
896           AND reserves.found = 'W'
897           AND cancellationdate IS NULL
898     /;
899     my $sth = $dbh->prepare($query);
900     $sth->execute($borr);
901     while ( my $data = $sth->fetchrow_hashref ) {
902         push( @itemswaiting, $data );
903     }
904     $sth->finish;
905     return \@itemswaiting;
906 }
907
908 #-------------------------------------------------------------------------------------
909
910 =item Findgroupreserve
911
912   @results = &Findgroupreserve($biblioitemnumber, $biblionumber);
913
914 ****** FIXME ******
915 I don't know what this does, because I don't understand how reserve
916 constraints work. I think the idea is that you reserve a particular
917 biblio, and the constraint allows you to restrict it to a given
918 biblioitem (e.g., if you want to borrow the audio book edition of "The
919 Prophet", rather than the first available publication).
920
921 C<&Findgroupreserve> returns :
922 C<@results> is an array of references-to-hash whose keys are mostly
923 fields from the reserves table of the Koha database, plus
924 C<biblioitemnumber>.
925
926 =cut
927
928 #'
929 sub Findgroupreserve {
930     my ( $bibitem, $biblio ) = @_;
931     my $dbh   = C4::Context->dbh;
932     my $query = qq/
933         SELECT reserves.biblionumber AS biblionumber,
934                reserves.borrowernumber AS borrowernumber,
935                reserves.reservedate AS reservedate,
936                reserves.branchcode AS branchcode,
937                reserves.cancellationdate AS cancellationdate,
938                reserves.found AS found,
939                reserves.reservenotes AS reservenotes,
940                reserves.priority AS priority,
941                reserves.timestamp AS timestamp,
942                reserveconstraints.biblioitemnumber AS biblioitemnumber,
943                reserves.itemnumber AS itemnumber
944         FROM reserves
945           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
946         WHERE reserves.biblionumber = ?
947           AND ( ( reserveconstraints.biblioitemnumber = ?
948           AND reserves.borrowernumber = reserveconstraints.borrowernumber
949           AND reserves.reservedate    =reserveconstraints.reservedate )
950           OR  reserves.constrainttype='a' )
951           AND reserves.cancellationdate is NULL
952           AND (reserves.found <> 'F' or reserves.found is NULL)
953     /;
954     my $sth = $dbh->prepare($query);
955     $sth->execute( $biblio, $bibitem );
956     my @results;
957     while ( my $data = $sth->fetchrow_hashref ) {
958         push( @results, $data );
959     }
960     $sth->finish;
961     return @results;
962 }
963
964 =item CreateReserve
965
966 CreateReserve($env,$branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$notes,$title,$checkitem,$found)
967
968 FIXME - A somewhat different version of this function appears in
969 C4::Reserves. Pick one and stick with it.
970
971 =cut
972
973 sub CreateReserve {
974     my (
975         $env,        $branch,    $borrowernumber, $biblionumber,
976         $constraint, $bibitems,  $priority,       $notes,
977         $title,      $checkitem, $found
978     ) = @_;
979     my $fee;
980     if ( $library_name =~ /Horowhenua/ ) {
981         $fee =
982           CalcHLTReserveFee( $env, $borrowernumber, $biblionumber, $constraint,
983             $bibitems );
984     }
985     else {
986         $fee =
987           CalcReserveFee( $env, $borrowernumber, $biblionumber, $constraint,
988             $bibitems );
989     }
990     my $dbh     = C4::Context->dbh;
991     my $const   = lc substr( $constraint, 0, 1 );
992     my @datearr = localtime(time);
993     my $resdate =
994       ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
995     my $waitingdate;
996
997     # If the reserv had the waiting status, we had the value of the resdate
998     if ( $found eq 'W' ) {
999         $waitingdate = $resdate;
1000     }
1001
1002     #eval {
1003     # updates take place here
1004     if ( $fee > 0 ) {
1005         my $nextacctno = &getnextacctno( $env, $borrowernumber, $dbh );
1006         my $query      = qq/
1007         INSERT INTO accountlines
1008             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1009         VALUES
1010             (?,?,now(),?,?,'Res',?)
1011     /;
1012         my $usth = $dbh->prepare($query);
1013         $usth->execute( $borrowernumber, $nextacctno, $fee,
1014             "Reserve Charge - $title", $fee );
1015         $usth->finish;
1016     }
1017
1018     #if ($const eq 'a'){
1019     my $query = qq/
1020         INSERT INTO reserves
1021             (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
1022             priority,reservenotes,itemnumber,found,waitingdate)
1023         VALUES
1024              (?,?,?,?,?,
1025              ?,?,?,?,?)
1026     /;
1027     my $sth = $dbh->prepare($query);
1028     $sth->execute(
1029         $borrowernumber, $biblionumber, $resdate, $branch,
1030         $const,          $priority,     $notes,   $checkitem,
1031         $found,          $waitingdate
1032     );
1033     $sth->finish;
1034
1035     #}
1036     if ( ( $const eq "o" ) || ( $const eq "e" ) ) {
1037         my $numitems = @$bibitems;
1038         my $i        = 0;
1039         while ( $i < $numitems ) {
1040             my $biblioitem = @$bibitems[$i];
1041             my $query      = qq/
1042           INSERT INTO reserveconstraints
1043               (borrowernumber,biblionumber,reservedate,biblioitemnumber)
1044           VALUES
1045             (?,?,?,?)
1046       /;
1047             my $sth = $dbh->prepare("");
1048             $sth->execute( $borrowernumber, $biblionumber, $resdate,
1049                 $biblioitem );
1050             $sth->finish;
1051             $i++;
1052         }
1053     }
1054     return;
1055 }
1056
1057 # FIXME - A functionally identical version of this function appears in
1058 # C4::Reserves. Pick one and stick with it.
1059 # XXX - Internal use only
1060 # FIXME - opac-reserves.pl need to use it, temporarily put into @EXPORT
1061
1062 sub CalcReserveFee {
1063     my ( $env, $borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
1064
1065     #check for issues;
1066     my $dbh   = C4::Context->dbh;
1067     my $const = lc substr( $constraint, 0, 1 );
1068     my $query = qq/
1069       SELECT * FROM borrowers,categories
1070     WHERE borrowernumber = ?
1071       AND borrowers.categorycode = categories.categorycode
1072     /;
1073     my $sth = $dbh->prepare($query);
1074     $sth->execute($borrowernumber);
1075     my $data = $sth->fetchrow_hashref;
1076     $sth->finish();
1077     my $fee      = $data->{'reservefee'};
1078     my $cntitems = @- > $bibitems;
1079
1080     if ( $fee > 0 ) {
1081
1082         # check for items on issue
1083         # first find biblioitem records
1084         my @biblioitems;
1085         my $sth1 = $dbh->prepare(
1086             "SELECT * FROM biblio,biblioitems
1087                    WHERE (biblio.biblionumber = ?)
1088                      AND (biblio.biblionumber = biblioitems.biblionumber)"
1089         );
1090         $sth1->execute($biblionumber);
1091         while ( my $data1 = $sth1->fetchrow_hashref ) {
1092             if ( $const eq "a" ) {
1093                 push @biblioitems, $data1;
1094             }
1095             else {
1096                 my $found = 0;
1097                 my $x     = 0;
1098                 while ( $x < $cntitems ) {
1099                     if ( @$bibitems->{'biblioitemnumber'} ==
1100                         $data->{'biblioitemnumber'} )
1101                     {
1102                         $found = 1;
1103                     }
1104                     $x++;
1105                 }
1106                 if ( $const eq 'o' ) {
1107                     if ( $found == 1 ) {
1108                         push @biblioitems, $data1;
1109                     }
1110                 }
1111                 else {
1112                     if ( $found == 0 ) {
1113                         push @biblioitems, $data1;
1114                     }
1115                 }
1116             }
1117         }
1118         $sth1->finish;
1119         my $cntitemsfound = @biblioitems;
1120         my $issues        = 0;
1121         my $x             = 0;
1122         my $allissued     = 1;
1123         while ( $x < $cntitemsfound ) {
1124             my $bitdata = $biblioitems[$x];
1125             my $sth2    = $dbh->prepare(
1126                 "SELECT * FROM items
1127                      WHERE biblioitemnumber = ?"
1128             );
1129             $sth2->execute( $bitdata->{'biblioitemnumber'} );
1130             while ( my $itdata = $sth2->fetchrow_hashref ) {
1131                 my $sth3 = $dbh->prepare(
1132                     "SELECT * FROM issues
1133                        WHERE itemnumber = ?
1134                          AND returndate IS NULL"
1135                 );
1136                 $sth3->execute( $itdata->{'itemnumber'} );
1137                 if ( my $isdata = $sth3->fetchrow_hashref ) {
1138                 }
1139                 else {
1140                     $allissued = 0;
1141                 }
1142             }
1143             $x++;
1144         }
1145         if ( $allissued == 0 ) {
1146             my $rsth =
1147               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
1148             $rsth->execute($biblionumber);
1149             if ( my $rdata = $rsth->fetchrow_hashref ) {
1150             }
1151             else {
1152                 $fee = 0;
1153             }
1154         }
1155     }
1156
1157     #  print "fee $fee";
1158     return $fee;
1159 }
1160
1161 # The following are junior and young adult item types that should not incur a
1162 # reserve charge.
1163 #
1164 # Juniors: BJC, BJCN, BJF, BJK, BJM, BJN, BJP, BJSF, BJSN, DJ, DJP, FJ, JVID,
1165 #  VJ, VJP, PJ, TJ, TJP, VJ, VJP.
1166 #
1167 # Young adults: BYF, BYN, BYP, DY, DYP, PY, PYP, TY, TYP, VY, VYP.
1168 #
1169 # All other item types should incur a reserve charge.
1170 sub CalcHLTReserveFee {
1171     my ( $env, $borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
1172     my $dbh = C4::Context->dbh;
1173     my $sth = $dbh->prepare(
1174         "SELECT * FROM borrowers,categories
1175                   WHERE (borrowernumber = ?)
1176                     AND (borrowers.categorycode = categories.categorycode)"
1177     );
1178     $sth->execute($borrowernumber);
1179     my $data = $sth->fetchrow_hashref;
1180     $sth->finish();
1181     my $fee = $data->{'reservefee'};
1182
1183     my $matchno;
1184     my @nocharge =
1185       qw/BJC BJCN BJF BJK BJM BJN BJP BJSF BJSN DJ DJP FJ NJ CJ VJ VJP PJ TJ TJP BYF BYN BYP DY DYP PY PYP TY TYP VY VYP/;
1186     $sth = $dbh->prepare(
1187         "SELECT * FROM biblio,biblioitems
1188                      WHERE (biblio.biblionumber = ?)
1189                        AND (biblio.biblionumber = biblioitems.biblionumber)"
1190     );
1191     $sth->execute($biblionumber);
1192     $data = $sth->fetchrow_hashref;
1193     my $itemtype = $data->{'itemtype'};
1194     for ( my $i = 0 ; $i < @nocharge ; $i++ ) {
1195         if ( $itemtype eq $nocharge[$i] ) {
1196             $matchno++;
1197             last;
1198         }
1199     }
1200
1201     if ( $matchno > 0 ) {
1202         $fee = 0;
1203     }
1204     return $fee;
1205 }
1206
1207 =item GetNextAccountNumber
1208
1209 GetNextAccountNumber()
1210
1211 =cut
1212
1213 sub GetNextAccountNumber {
1214     my ( $env, $borrowernumber, $dbh ) = @_;
1215     my $nextaccntno = 1;
1216     my $sth         = $dbh->prepare(
1217         "select * from accountlines
1218   where (borrowernumber = ?)
1219   order by accountno desc"
1220     );
1221     $sth->execute($borrowernumber);
1222     if ( my $accdata = $sth->fetchrow_hashref ) {
1223         $nextaccntno = $accdata->{'accountno'} + 1;
1224     }
1225     $sth->finish;
1226     return ($nextaccntno);
1227 }
1228
1229 #-------------------------------------------------------------------------------------
1230
1231 =item UpdateReserve
1232
1233 &UpdateReserve($rank,$biblio,$borrower,$branch)
1234
1235 =cut
1236
1237 sub UpdateReserve {
1238     #subroutine to update a reserve
1239     my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_;
1240      return if $rank eq "W";
1241      return if $rank eq "n";
1242     my $dbh = C4::Context->dbh;
1243     if ( $rank eq "del" ) {
1244         my $query = qq/
1245             UPDATE reserves
1246             SET    cancellationdate=now()
1247             WHERE  biblionumber   = ?
1248              AND   borrowernumber = ?
1249              AND   cancellationdate is NULL
1250              AND   (found <> 'F' or found is NULL)
1251         /;
1252         my $sth = $dbh->prepare($query);
1253         $sth->execute( $biblio, $borrower );
1254         $sth->finish;
1255         
1256     }
1257     else {
1258         my $query = qq/
1259         UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL
1260             WHERE biblionumber   = ?
1261              AND borrowernumber = ?
1262              AND cancellationdate is NULL
1263              AND (found <> 'F' or found is NULL)
1264         /;
1265         my $sth = $dbh->prepare($query);
1266         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1267         $sth->finish;
1268         FixPriority( $biblio, $borrower, $rank);
1269     }
1270 }
1271
1272 =item GetReserveTitle
1273
1274 $data = GetReserveTitle($biblio,$bor,$date,$timestamp);
1275
1276 =cut
1277
1278 sub GetReserveTitle {
1279     my ( $biblio, $bor, $date, $timestamp ) = @_;
1280     my $dbh   = C4::Context->dbh;
1281     my $query = qq/
1282         SELECT *
1283         FROM   reserveconstraints,biblioitems
1284         WHERE  reserveconstraints.biblioitemnumber=biblioitems.biblioitemnumber
1285           AND   reserveconstraints.biblionumber=?
1286          AND   reserveconstraints.borrowernumber = ?
1287          AND   reserveconstraints.reservedate=?
1288          AND   reserveconstraints.timestamp=?
1289     /;
1290     my $sth = $dbh->prepare($query);
1291     $sth->execute( $biblio, $bor, $date, $timestamp );
1292     my $data = $sth->fetchrow_hashref;
1293     $sth->finish;
1294     return $data;
1295 }
1296
1297 =item FindReservesInQueue
1298
1299   $results = &FindReservesInQueue($biblionumber);
1300
1301 Simple variant of FindReserves, exept the result is now displaying only the queue list of reservations with the same biblionumber (At this time only displayed in request.pl)
1302
1303 C<&FindReservesInQueue> returns a two-element array:
1304
1305 C<$results> is a reference to an array of references of hashes. Each hash
1306 has for keys a list of column from reserves table (see details in function).
1307
1308 =cut
1309
1310 #'
1311
1312 sub FindReservesInQueue {
1313     my ($biblionumber) = @_;
1314     my $dbh = C4::Context->dbh;
1315
1316     # Find the desired items in the reserves
1317     my $query = qq/
1318         SELECT  branchcode,
1319                 timestamp AS rtimestamp,
1320                 priority,
1321                 biblionumber,
1322                 borrowernumber,
1323                 reservedate,
1324                 constrainttype,
1325                 found,
1326                 itemnumber
1327           FROM     reserves
1328           WHERE     cancellationdate IS NULL
1329         AND biblionumber = ?
1330           AND    (found <> \'F\' OR found IS NULL)
1331           AND priority <> \'0\'
1332           ORDER BY priority
1333     /;
1334     my $sth = $dbh->prepare($query);
1335     $sth->execute($biblionumber);
1336     my @results;
1337     my $i = 0;
1338     while ( my $data = $sth->fetchrow_hashref ) {
1339
1340         # FIXME - What is this if-statement doing? How do constraints work?
1341         if ( $data->{constrainttype} eq 'o' ) {
1342             $query = '
1343                 SELECT biblioitemnumber
1344                 FROM reserveconstraints
1345                 WHERE biblionumber   = ?
1346                     AND borrowernumber = ?
1347                   AND reservedate    = ?
1348             ';
1349             my $csth = $dbh->prepare($query);
1350             $csth->execute( $data->{biblionumber}, $data->{borrowernumber},
1351                 $data->{reservedate}, );
1352
1353             my @bibitemno;
1354             while ( my $bibitemnos = $csth->fetchrow_array ) {
1355                 push( @bibitemno, $bibitemnos );
1356             }
1357             my $count = @bibitemno;
1358
1359             # if we have two or more different specific itemtypes
1360             # reserved by same person on same day
1361             my $bdata;
1362             if ( $count > 1 ) {
1363                 $bdata = GetBiblioItemData( $bibitemno[$i] );
1364                 $i++;
1365             }
1366             else {
1367                 # Look up the book we just found.
1368                 $bdata = GetBiblioItemData( $bibitemno[0] );
1369             }
1370             $csth->finish;
1371
1372             # Add the results of this latest search to the current
1373             # results.
1374             # FIXME - An 'each' would probably be more efficient.
1375             foreach my $key ( keys %$bdata ) {
1376                 $data->{$key} = $bdata->{$key};
1377             }
1378         }
1379         push @results, $data;
1380     }
1381     $sth->finish;
1382
1383     return ( $#results + 1, \@results );
1384 }
1385
1386 =back
1387
1388 =head1 AUTHOR
1389
1390 Koha Developement team <info@koha.org>
1391
1392 =cut
1393