Fix for loanlength to make the default length work properly
[koha.git] / C4 / Circulation / Circ2.pm
1 # -*- tab-width: 8 -*-
2 # Please use 8-character tabs for this file (indents are every 4 characters)
3
4 package C4::Circulation::Circ2;
5
6 # $Id$
7
8 #package to deal with Returns
9 #written 3/11/99 by olwen@katipo.co.nz
10
11
12 # Copyright 2000-2002 Katipo Communications
13 #
14 # This file is part of Koha.
15 #
16 # Koha is free software; you can redistribute it and/or modify it under the
17 # terms of the GNU General Public License as published by the Free Software
18 # Foundation; either version 2 of the License, or (at your option) any later
19 # version.
20 #
21 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
22 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
23 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License along with
26 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
27 # Suite 330, Boston, MA  02111-1307 USA
28
29 use strict;
30 # use warnings;
31 require Exporter;
32 use DBI;
33 use C4::Context;
34 use C4::Stats;
35 use C4::Reserves2;
36 use C4::Koha;
37 use C4::Accounts;
38 use Date::Manip;
39
40 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
41
42 # set the version for version checking
43 $VERSION = 0.01;
44
45 =head1 NAME
46
47 C4::Circulation::Circ2 - Koha circulation module
48
49 =head1 SYNOPSIS
50
51   use C4::Circulation::Circ2;
52
53 =head1 DESCRIPTION
54
55 The functions in this module deal with circulation, issues, and
56 returns, as well as general information about the library.
57 Also deals with stocktaking.
58
59 =head1 FUNCTIONS
60
61 =over 2
62
63 =cut
64
65 @ISA = qw(Exporter);
66 @EXPORT = qw(&getpatroninformation
67         &currentissues &getissues &getiteminformation &renewstatus &renewbook
68         &canbookbeissued &issuebook &returnbook &find_reserves &transferbook &decode
69         &calc_charges &listitemsforinventory &itemseen &fixdate);
70
71 # &getbranches &getprinters &getbranch &getprinter => moved to C4::Koha.pm
72
73 =head2 itemseen
74
75 &itemseen($itemnum)
76 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking
77 C<$itemnum> is the item number
78
79 =cut
80
81 sub itemseen {
82         my ($itemnum) = @_;
83         my $dbh = C4::Context->dbh;
84         my $sth = $dbh->prepare("update items set itemlost=0, datelastseen  = now() where items.itemnumber = ?");
85         $sth->execute($itemnum);
86         return;
87 }
88
89 sub listitemsforinventory {
90         my ($minlocation,$maxlocation,$datelastseen,$offset,$size) = @_;
91         my $dbh = C4::Context->dbh;
92         my $sth = $dbh->prepare("select itemnumber,barcode,itemcallnumber,title,author from items,biblio where items.biblionumber=biblio.biblionumber and itemcallnumber>= ? and itemcallnumber <=? and (datelastseen< ? or datelastseen is null) order by itemcallnumber,title");
93         $sth->execute($minlocation,$maxlocation,$datelastseen);
94         my @results;
95         while (my $row = $sth->fetchrow_hashref) {
96                 $offset-- if ($offset);
97                 if ((!$offset) && $size) {
98                         push @results,$row;
99                         $size--;
100                 }
101         }
102         return \@results;
103 }
104
105 =head2 getpatroninformation
106
107   ($borrower, $flags) = &getpatroninformation($env, $borrowernumber, $cardnumber);
108
109 Looks up a patron and returns information about him or her. If
110 C<$borrowernumber> is true (nonzero), C<&getpatroninformation> looks
111 up the borrower by number; otherwise, it looks up the borrower by card
112 number.
113
114 C<$env> is effectively ignored, but should be a reference-to-hash.
115
116 C<$borrower> is a reference-to-hash whose keys are the fields of the
117 borrowers table in the Koha database. In addition,
118 C<$borrower-E<gt>{flags}> is a hash giving more detailed information
119 about the patron. Its keys act as flags :
120
121         if $borrower->{flags}->{LOST} {
122                 # Patron's card was reported lost
123         }
124
125 Each flag has a C<message> key, giving a human-readable explanation of
126 the flag. If the state of a flag means that the patron should not be
127 allowed to borrow any more books, then it will have a C<noissues> key
128 with a true value.
129
130 The possible flags are:
131
132 =head3 CHARGES
133
134 =over 4
135
136 Shows the patron's credit or debt, if any.
137
138 =back
139
140 =head3 GNA
141
142 =over 4
143
144 (Gone, no address.) Set if the patron has left without giving a
145 forwarding address.
146
147 =back
148
149 =head3 LOST
150
151 =over 4
152
153 Set if the patron's card has been reported as lost.
154
155 =back
156
157 =head3 DBARRED
158
159 =over 4
160
161 Set if the patron has been debarred.
162
163 =back
164
165 =head3 NOTES
166
167 =over 4
168
169 Any additional notes about the patron.
170
171 =back
172
173 =head3 ODUES
174
175 =over 4
176
177 Set if the patron has overdue items. This flag has several keys:
178
179 C<$flags-E<gt>{ODUES}{itemlist}> is a reference-to-array listing the
180 overdue items. Its elements are references-to-hash, each describing an
181 overdue item. The keys are selected fields from the issues, biblio,
182 biblioitems, and items tables of the Koha database.
183
184 C<$flags-E<gt>{ODUES}{itemlist}> is a string giving a text listing of
185 the overdue items, one per line.
186
187 =back
188
189 =head3 WAITING
190
191 =over 4
192
193 Set if any items that the patron has reserved are available.
194
195 C<$flags-E<gt>{WAITING}{itemlist}> is a reference-to-array listing the
196 available items. Each element is a reference-to-hash whose keys are
197 fields from the reserves table of the Koha database.
198
199 =back
200
201 =back
202
203 =cut
204
205
206 sub getpatroninformation {
207 # returns
208         my ($env, $borrowernumber,$cardnumber) = @_;
209         my $dbh = C4::Context->dbh;
210         my $query;
211         my $sth;
212         if ($borrowernumber) {
213                 $sth = $dbh->prepare("select * from borrowers where borrowernumber=?");
214                 $sth->execute($borrowernumber);
215         } elsif ($cardnumber) {
216                 $sth = $dbh->prepare("select * from borrowers where cardnumber=?");
217                 $sth->execute($cardnumber);
218         } else {
219                 $env->{'apierror'} = "invalid borrower information passed to getpatroninformation subroutine";
220                 return();
221         }
222         my $borrower = $sth->fetchrow_hashref;
223         my $amount = checkaccount($env, $borrowernumber, $dbh);
224         $borrower->{'amountoutstanding'} = $amount;
225         my $flags = patronflags($env, $borrower, $dbh);
226         my $accessflagshash;
227  
228         $sth=$dbh->prepare("select bit,flag from userflags");
229         $sth->execute;
230         while (my ($bit, $flag) = $sth->fetchrow) {
231                 if ($borrower->{'flags'} && $borrower->{'flags'} & 2**$bit) {
232                 $accessflagshash->{$flag}=1;
233                 }
234         }
235         $sth->finish;
236         $borrower->{'flags'}=$flags;
237         $borrower->{'authflags'} = $accessflagshash;
238         return ($borrower); #, $flags, $accessflagshash);
239 }
240
241 =head2 decode
242
243 =over 4
244
245 =head3 $str = &decode($chunk);
246
247 =over 4
248
249 Decodes a segment of a string emitted by a CueCat barcode scanner and
250 returns it.
251
252 =back
253
254 =back
255
256 =cut
257
258 # FIXME - At least, I'm pretty sure this is for decoding CueCat stuff.
259 sub decode {
260         my ($encoded) = @_;
261         my $seq = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
262         my @s = map { index($seq,$_); } split(//,$encoded);
263         my $l = ($#s+1) % 4;
264         if ($l)
265         {
266                 if ($l == 1)
267                 {
268                         print "Error!";
269                         return;
270                 }
271                 $l = 4-$l;
272                 $#s += $l;
273         }
274         my $r = '';
275         while ($#s >= 0)
276         {
277                 my $n = (($s[0] << 6 | $s[1]) << 6 | $s[2]) << 6 | $s[3];
278                 $r .=chr(($n >> 16) ^ 67) .
279                 chr(($n >> 8 & 255) ^ 67) .
280                 chr(($n & 255) ^ 67);
281                 @s = @s[4..$#s];
282         }
283         $r = substr($r,0,length($r)-$l);
284         return $r;
285 }
286
287 =head2 getiteminformation
288
289 =over 4
290
291 $item = &getiteminformation($env, $itemnumber, $barcode);
292
293 Looks up information about an item, given either its item number or
294 its barcode. If C<$itemnumber> is a nonzero value, it is used;
295 otherwise, C<$barcode> is used.
296
297 C<$env> is effectively ignored, but should be a reference-to-hash.
298
299 C<$item> is a reference-to-hash whose keys are fields from the biblio,
300 items, and biblioitems tables of the Koha database. It may also
301 contain the following keys:
302
303 =head3 date_due
304
305 =over 4
306
307 The due date on this item, if it has been borrowed and not returned
308 yet. The date is in YYYY-MM-DD format.
309
310 =back
311
312 =head3 notforloan
313
314 =over 4
315
316 True if the item may not be borrowed.
317
318 =back
319
320 =back
321
322 =cut
323
324
325 sub getiteminformation {
326 # returns a hash of item information given either the itemnumber or the barcode
327         my ($env, $itemnumber, $barcode) = @_;
328         my $dbh = C4::Context->dbh;
329         my $sth;
330         if ($itemnumber) {
331                 $sth=$dbh->prepare("select * from biblio,items,biblioitems where items.itemnumber=? and biblio.biblionumber=items.biblionumber and biblioitems.biblioitemnumber = items.biblioitemnumber");
332                 $sth->execute($itemnumber);
333         } elsif ($barcode) {
334                 $sth=$dbh->prepare("select * from biblio,items,biblioitems where items.barcode=? and biblio.biblionumber=items.biblionumber and biblioitems.biblioitemnumber = items.biblioitemnumber");
335                 $sth->execute($barcode);
336         } else {
337                 $env->{'apierror'}="getiteminformation() subroutine must be called with either an itemnumber or a barcode";
338                 # Error condition.
339                 return();
340         }
341         my $iteminformation=$sth->fetchrow_hashref;
342         $sth->finish;
343         if ($iteminformation) {
344                 $sth=$dbh->prepare("select date_due from issues where itemnumber=? and isnull(returndate)");
345                 $sth->execute($iteminformation->{'itemnumber'});
346                 my ($date_due) = $sth->fetchrow;
347                 $iteminformation->{'date_due'}=$date_due;
348                 $sth->finish;
349                 ($iteminformation->{'dewey'} == 0) && ($iteminformation->{'dewey'}='');
350                 $sth=$dbh->prepare("select * from itemtypes where itemtype=?");
351                 $sth->execute($iteminformation->{'itemtype'});
352                 my $itemtype=$sth->fetchrow_hashref;
353                 # if specific item notforloan, don't use itemtype notforloan field.
354                 # otherwise, use itemtype notforloan value to see if item can be issued.
355                 $iteminformation->{'notforloan'}=$itemtype->{'notforloan'} unless $iteminformation->{'notforloan'};
356                 $sth->finish;
357         }
358         return($iteminformation);
359 }
360
361 =head2 transferbook
362
363 =over 4
364
365 ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, $barcode, $ignore_reserves);
366
367 Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
368
369 C<$newbranch> is the code for the branch to which the item should be transferred.
370
371 C<$barcode> is the barcode of the item to be transferred.
372
373 If C<$ignore_reserves> is true, C<&transferbook> ignores reserves.
374 Otherwise, if an item is reserved, the transfer fails.
375
376 Returns three values:
377
378 =head3 $dotransfer 
379
380 is true if the transfer was successful.
381
382 =head3 $messages
383  
384 is a reference-to-hash which may have any of the following keys:
385
386 =over 4
387
388 C<BadBarcode>
389
390 There is no item in the catalog with the given barcode. The value is C<$barcode>.
391
392 C<IsPermanent>
393
394 The item's home branch is permanent. This doesn't prevent the item from being transferred, though. The value is the code of the item's home branch.
395
396 C<DestinationEqualsHolding>
397
398 The item is already at the branch to which it is being transferred. The transfer is nonetheless considered to have failed. The value should be ignored.
399
400 C<WasReturned>
401
402 The item was on loan, and C<&transferbook> automatically returned it before transferring it. The value is the borrower number of the patron who had the item.
403
404 C<ResFound>
405
406 The item was reserved. The value is a reference-to-hash whose keys are fields from the reserves table of the Koha database, and C<biblioitemnumber>. It also has the key C<ResFound>, whose value is either C<Waiting> or C<Reserved>.
407
408 C<WasTransferred>
409
410 The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
411
412 =back
413
414 =back
415
416 =back
417
418 =cut
419
420 #'
421 # FIXME - This function tries to do too much, and its API is clumsy.
422 # If it didn't also return books, it could be used to change the home
423 # branch of a book while the book is on loan.
424 #
425 # Is there any point in returning the item information? The caller can
426 # look that up elsewhere if ve cares.
427 #
428 # This leaves the ($dotransfer, $messages) tuple. This seems clumsy.
429 # If the transfer succeeds, that's all the caller should need to know.
430 # Thus, this function could simply return 1 or 0 to indicate success
431 # or failure, and set $C4::Circulation::Circ2::errmsg in case of
432 # failure. Or this function could return undef if successful, and an
433 # error message in case of failure (this would feel more like C than
434 # Perl, though).
435 sub transferbook {
436 # transfer book code....
437         my ($tbr, $barcode, $ignoreRs) = @_;
438         my $messages;
439         my %env;
440         my $dotransfer = 1;
441         my $branches = getbranches();
442         my $iteminformation = getiteminformation(\%env, 0, $barcode);
443         # bad barcode..
444         if (not $iteminformation) {
445                 $messages->{'BadBarcode'} = $barcode;
446                 $dotransfer = 0;
447         }
448         # get branches of book...
449         my $hbr = $iteminformation->{'homebranch'};
450         my $fbr = $iteminformation->{'holdingbranch'};
451         # if is permanent...
452         if ($hbr && $branches->{$hbr}->{'PE'}) {
453                 $messages->{'IsPermanent'} = $hbr;
454         }
455         # can't transfer book if is already there....
456         # FIXME - Why not? Shouldn't it trivially succeed?
457         if ($fbr eq $tbr) {
458                 $messages->{'DestinationEqualsHolding'} = 1;
459                 $dotransfer = 0;
460         }
461         # check if it is still issued to someone, return it...
462         my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'});
463         if ($currentborrower) {
464                 returnbook($barcode, $fbr);
465                 $messages->{'WasReturned'} = $currentborrower;
466         }
467         # find reserves.....
468         # FIXME - Don't call &CheckReserves unless $ignoreRs is true.
469         # That'll save a database query.
470         my ($resfound, $resrec) = CheckReserves($iteminformation->{'itemnumber'});
471         if ($resfound and not $ignoreRs) {
472                 $resrec->{'ResFound'} = $resfound;
473                 $messages->{'ResFound'} = $resrec;
474                 $dotransfer = 0;
475         }
476         #actually do the transfer....
477         if ($dotransfer) {
478                 dotransfer($iteminformation->{'itemnumber'}, $fbr, $tbr);
479                 $messages->{'WasTransfered'} = 1;
480         }
481         return ($dotransfer, $messages, $iteminformation);
482 }
483
484 # Not exported
485 # FIXME - This is only used in &transferbook. Why bother making it a
486 # separate function?
487 sub dotransfer {
488         my ($itm, $fbr, $tbr) = @_;
489         my $dbh = C4::Context->dbh;
490         $itm = $dbh->quote($itm);
491         $fbr = $dbh->quote($fbr);
492         $tbr = $dbh->quote($tbr);
493         #new entry in branchtransfers....
494         $dbh->do("INSERT INTO   branchtransfers (itemnumber, frombranch, datearrived, tobranch)
495                                         VALUES ($itm, $fbr, now(), $tbr)");
496         #update holdingbranch in items .....
497         $dbh->do("UPDATE items set holdingbranch = $tbr WHERE   items.itemnumber = $itm");
498         &itemseen($itm);
499         return;
500 }
501
502 =head2 canbookbeissued
503
504 Check if a book can be issued.
505
506 my ($issuingimpossible,$needsconfirmation) = canbookbeissued($env,$borrower,$barcode,$year,$month,$day);
507
508 =over 4
509
510 C<$env> Environment variable. Should be empty usually, but used by other subs. Next code cleaning could drop it.
511
512 C<$borrower> hash with borrower informations (from getpatroninformation)
513
514 C<$barcode> is the bar code of the book being issued.
515
516 C<$year> C<$month> C<$day> contains the date of the return (in case it's forced by "stickyduedate".
517
518 =back
519
520 Returns :
521
522 =over 4
523
524 C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
525 Possible values are :
526
527 =head3 INVALID_DATE 
528
529 sticky due date is invalid
530
531 =head3 GNA
532
533 borrower gone with no address
534
535 =head3 CARD_LOST
536  
537 borrower declared it's card lost
538
539 =head3 DEBARRED
540
541 borrower debarred
542
543 =head3 UNKNOWN_BARCODE
544
545 barcode unknown
546
547 =head3 NOT_FOR_LOAN
548
549 item is not for loan
550
551 =head3 WTHDRAWN
552
553 item withdrawn.
554
555 =head3 RESTRICTED
556
557 item is restricted (set by ??)
558
559 =back
560
561 C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
562 Possible values are :
563
564 =head3 DEBT
565
566 borrower has debts.
567
568 =head3 RENEW_ISSUE
569
570 renewing, not issuing
571
572 =head3 ISSUED_TO_ANOTHER
573
574 issued to someone else.
575
576 =head3 RESERVED
577
578 reserved for someone else.
579
580 =head3 INVALID_DATE
581
582 sticky due date is invalid
583
584 =head3 TOO_MANY
585
586 if the borrower borrows to much things
587
588 =cut
589
590 # check if a book can be issued.
591 # returns an array with errors if any
592
593 sub TooMany ($$){
594         my $borrower = shift;
595         my $iteminformation = shift;
596         my $cat_borrower = $borrower->{'categorycode'};
597         my $branch_borrower = $borrower->{'branchcode'};
598         my $dbh = C4::Context->dbh;
599         
600
601         my $sth = $dbh->prepare('select itemtype from biblioitems where biblionumber = ?');
602         $sth->execute($iteminformation->{'biblionumber'});
603         my $type = $sth->fetchrow;
604         $sth = $dbh->prepare('select * from issuingrules where categorycode = ? and itemtype = ? and branchcode = ?');
605         my $sth2 = $dbh->prepare("select COUNT(*) from issues i, biblioitems s where i.borrowernumber = ? and i.returndate is null and i.itemnumber = s.biblioitemnumber and s.itemtype like ?");
606         my $sth3 = $dbh->prepare('select COUNT(*) from issues where borrowernumber = ? and returndate is null');
607         my $alreadyissued;
608         # check the 3 parameters
609         $sth->execute($cat_borrower, $type, $branch_borrower);
610         my $result = $sth->fetchrow_hashref;
611 #       warn "==>".$result->{maxissueqty};
612         if (defined($result)) {
613                 $sth2->execute($borrower->{'borrowernumber'}, "%$type%");
614                 my $alreadyissued = $sth2->fetchrow;
615                 return ("a $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
616         }
617         # check for branch=*
618         $sth->execute($cat_borrower, $type, "");
619         my $result = $sth->fetchrow_hashref;
620         if (defined($result)) {
621                 $sth2->execute($borrower->{'borrowernumber'}, "%$type%");
622                 my $alreadyissued = $sth2->fetchrow;
623                 return ("b $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
624         }
625         # check for itemtype=*
626         $sth->execute($cat_borrower, "*", $branch_borrower);
627         my $result = $sth->fetchrow_hashref;
628         if (defined($result)) {
629                 $sth3->execute($borrower->{'borrowernumber'});
630                 my ($alreadyissued) = $sth3->fetchrow;
631                 warn "HERE : $alreadyissued / ($result->{maxissueqty} for $borrower->{'borrowernumber'}";
632                 return ("c $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
633         }
634         #check for borrowertype=*
635         $sth->execute("*", $type, $branch_borrower);
636         my $result = $sth->fetchrow_hashref;
637         if (defined($result)) {
638                 $sth2->execute($borrower->{'borrowernumber'}, "%$type%");
639                 my $alreadyissued = $sth2->fetchrow;
640                 return ("d $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
641         }
642
643         $sth->execute("*", "*", $branch_borrower);
644         my $result = $sth->fetchrow_hashref;
645         if (defined($result)) {
646                 $sth3->execute($borrower->{'borrowernumber'});
647                 my $alreadyissued = $sth3->fetchrow;
648                 return ("e $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
649         }
650
651         $sth->execute("*", $type, "");
652         my $result = $sth->fetchrow_hashref;
653         if (defined($result) && $result->{maxissueqty}>=0) {
654                 $sth2->execute($borrower->{'borrowernumber'}, "%$type%");
655                 my $alreadyissued = $sth2->fetchrow;
656                 return ("f $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
657         }
658
659         $sth->execute($cat_borrower, "*", "");
660         my $result = $sth->fetchrow_hashref;
661         if (defined($result)) {
662                 $sth2->execute($borrower->{'borrowernumber'}, "%$type%");
663                 my $alreadyissued = $sth2->fetchrow;
664                 return ("g $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
665         }
666
667         $sth->execute("*", "*", "");
668         my $result = $sth->fetchrow_hashref;
669         if (defined($result)) {
670                 $sth3->execute($borrower->{'borrowernumber'});
671                 my $alreadyissued = $sth3->fetchrow;
672                 return ("h $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued);
673         }
674         return;
675 }
676
677
678 sub canbookbeissued {
679         my ($env,$borrower,$barcode,$year,$month,$day) = @_;
680         my %needsconfirmation; # filled with problems that needs confirmations
681         my %issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE
682         my $iteminformation = getiteminformation($env, 0, $barcode);
683         my $dbh = C4::Context->dbh;
684 #
685 # DUE DATE is OK ?
686 #
687         my ($duedate, $invalidduedate) = fixdate($year, $month, $day);
688         $issuingimpossible{INVALID_DATE} = 1 if ($invalidduedate);
689
690 #
691 # BORROWER STATUS
692 #
693         if ($borrower->{flags}->{GNA}) {
694                 $issuingimpossible{GNA} = 1;
695         }
696         if ($borrower->{flags}->{'LOST'}) {
697                 $issuingimpossible{CARD_LOST} = 1;
698         }
699         if ($borrower->{flags}->{'DBARRED'}) {
700                 $issuingimpossible{DEBARRED} = 1;
701         }
702         if (&Date_Cmp(&ParseDate($borrower->{expiry}),&ParseDate("today"))<0) {
703                 $issuingimpossible{EXPIRED} = 1;
704         }
705 #
706 # BORROWER STATUS
707 #
708
709 # DEBTS
710         my $amount = checkaccount($env,$borrower->{'borrowernumber'}, $dbh,$duedate);
711         if ($amount >0) {
712                 $needsconfirmation{DEBT} = $amount;
713         }
714
715
716 #
717 # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
718 #
719         my $toomany = TooMany($borrower, $iteminformation);
720         $needsconfirmation{TOO_MANY} =  $toomany if $toomany;
721
722 #
723 # ITEM CHECKING
724 #
725         unless ($iteminformation->{barcode}) {
726                 $issuingimpossible{UNKNOWN_BARCODE} = 1;
727         }
728         if ($iteminformation->{'notforloan'} > 0) {
729                 $issuingimpossible{NOT_FOR_LOAN} = 1;
730         }
731         if ($iteminformation->{'itemtype'} &&$iteminformation->{'itemtype'} eq 'REF') {
732                 $issuingimpossible{NOT_FOR_LOAN} = 1;
733         }
734         if ($iteminformation->{'wthdrawn'} && $iteminformation->{'wthdrawn'} == 1) {
735                 $issuingimpossible{WTHDRAWN} = 1;
736         }
737         if ($iteminformation->{'restricted'} && $iteminformation->{'restricted'} == 1) {
738                 $issuingimpossible{RESTRICTED} = 1;
739         }
740
741
742
743 #
744 # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
745 #
746         my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'});
747         if ($currentborrower && $currentborrower eq $borrower->{'borrowernumber'}) {
748 # Already issued to current borrower. Ask whether the loan should
749 # be renewed.
750                 my ($renewstatus) = renewstatus($env, $borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'});
751                 if ($renewstatus == 0) { # no more renewals allowed
752                         $issuingimpossible{NO_MORE_RENEWALS} = 1;
753                 } else {
754                         $needsconfirmation{RENEW_ISSUE} = 1;
755                 }
756         } elsif ($currentborrower) {
757 # issued to someone else
758                 my $currborinfo = getpatroninformation(0,$currentborrower);
759 #               warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
760                 $needsconfirmation{ISSUED_TO_ANOTHER} = "$currborinfo->{'reservedate'} : $currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
761         }
762 # See if the item is on reserve.
763         my ($restype, $res) = CheckReserves($iteminformation->{'itemnumber'});
764         if ($restype) {
765                 my $resbor = $res->{'borrowernumber'};
766                 if ($resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting") {
767                         # The item is on reserve and waiting, but has been
768                         # reserved by some other patron.
769                         my ($resborrower, $flags)=getpatroninformation($env, $resbor,0);
770                         my $branches = getbranches();
771                         my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'};
772                         $needsconfirmation{RESERVE_WAITING} = "$resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'}, $branchname)";
773                         # CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'}); Doesn't belong in a checking subroutine.
774                 } elsif ($restype eq "Reserved") {
775                         # The item is on reserve for someone else.
776                         my ($resborrower, $flags)=getpatroninformation($env, $resbor,0);
777                         my $branches = getbranches();
778                         my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'};
779                         $needsconfirmation{RESERVED} = "$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})";
780                 }
781         }
782         return(\%issuingimpossible,\%needsconfirmation);
783 }
784
785 =head2 issuebook
786
787 Issue a book. Does no check, they are done in canbookbeissued. If we reach this sub, it means the user confirmed if needed.
788
789 &issuebook($env,$borrower,$barcode,$date)
790
791 =over 4
792
793 C<$env> Environment variable. Should be empty usually, but used by other subs. Next code cleaning could drop it.
794
795 C<$borrower> hash with borrower informations (from getpatroninformation)
796
797 C<$barcode> is the bar code of the book being issued.
798
799 C<$date> contains the max date of return. calculated if empty.
800
801 =cut
802
803 #
804 # issuing book. We already have checked it can be issued, so, just issue it !
805 #
806 sub issuebook {
807         my ($env,$borrower,$barcode,$date,$cancelreserve) = @_;
808         my $dbh = C4::Context->dbh;
809 #       my ($borrower, $flags) = &getpatroninformation($env, $borrowernumber, 0);
810         my $iteminformation = getiteminformation($env, 0, $barcode);
811 #               warn "B : ".$borrower->{borrowernumber}." / I : ".$iteminformation->{'itemnumber'};
812 #
813 # check if we just renew the issue.
814 #
815         my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'});
816         if ($currentborrower eq $borrower->{'borrowernumber'}) {
817                 my ($charge,$itemtype) = calc_charges($env, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'});
818                 if ($charge > 0) {
819                         createcharge($env, $dbh, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}, $charge);
820                         $iteminformation->{'charge'} = $charge;
821                 }
822                 &UpdateStats($env,$env->{'branchcode'},'renew',$charge,'',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'});
823                 renewbook($env, $borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'});
824         } else {
825 #
826 # NOT a renewal
827 #
828                 if ($currentborrower ne '') {
829                         # This book is currently on loan, but not to the person
830                         # who wants to borrow it now. mark it returned before issuing to the new borrower
831                         returnbook($iteminformation->{'barcode'}, $env->{'branchcode'});
832                 }
833                 # See if the item is on reserve.
834                 my ($restype, $res) = CheckReserves($iteminformation->{'itemnumber'});
835                 if ($restype) {
836                         my $resbor = $res->{'borrowernumber'};
837                         if ($resbor eq $borrower->{'borrowernumber'}) {
838                                 # The item is on reserve to the current patron
839                                 FillReserve($res);
840                                 warn "FillReserve";
841                         } elsif ($restype eq "Waiting") {
842                                 warn "Waiting";
843                                 # The item is on reserve and waiting, but has been
844                                 # reserved by some other patron.
845                                 my ($resborrower, $flags)=getpatroninformation($env, $resbor,0);
846                                 my $branches = getbranches();
847                                 my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'};
848                 if ($cancelreserve){
849                                 CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
850                 }
851                         } elsif ($restype eq "Reserved") {
852                                 warn "Reserved";
853                                 # The item is on reserve for someone else.
854                                 my ($resborrower, $flags)=getpatroninformation($env, $resbor,0);
855                                 my $branches = getbranches();
856                                 my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'};
857                                 if ($cancelreserve) {
858                                         # cancel reserves on this item
859                                         CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
860                                         # also cancel reserve on biblio related to this item
861                                         #my $st_Fbiblio = $dbh->prepare("select biblionumber from items where itemnumber=?");
862                                         #$st_Fbiblio->execute($res->{'itemnumber'});
863                                         #my $biblionumber = $st_Fbiblio->fetchrow;
864                                         #CancelReserve($biblionumber,0,$res->{'borrowernumber'});
865                                         #warn "CancelReserve $res->{'itemnumber'}, $res->{'borrowernumber'}";
866                                 } else {
867 #                                       my $tobrcd = ReserveWaiting($res->{'itemnumber'}, $res->{'borrowernumber'});
868 #                                       transferbook($tobrcd,$barcode, 1);
869                                         warn "transferbook";
870                                 }
871                         }
872                 }
873                 # Record in the database the fact that the book was issued.
874                 my $sth=$dbh->prepare("insert into issues (borrowernumber, itemnumber, date_due, branchcode) values (?,?,?,?)");
875                 my $loanlength = getLoanLength($borrower->{'categorycode'},$iteminformation->{'itemtype'},$borrower->{'branchcode'});
876                 my $datedue=time+($loanlength)*86400;
877                 my @datearr = localtime($datedue);
878                 my $dateduef = (1900+$datearr[5])."-".($datearr[4]+1)."-".$datearr[3];
879                 if ($date) {
880                         $dateduef=$date;
881                 }
882                 # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
883                 if (C4::Context->preference('ReturnBeforeExpiry') && $dateduef gt $borrower->{expiry}) {
884                         $dateduef=$borrower->{expiry};
885                 }
886                 $sth->execute($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}, $dateduef, $env->{'branchcode'});
887                 $sth->finish;
888                 $iteminformation->{'issues'}++;
889                 $sth=$dbh->prepare("update items set issues=? where itemnumber=?");
890                 $sth->execute($iteminformation->{'issues'},$iteminformation->{'itemnumber'});
891                 $sth->finish;
892                 &itemseen($iteminformation->{'itemnumber'});
893                 # If it costs to borrow this book, charge it to the patron's account.
894                 my ($charge,$itemtype)=calc_charges($env, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'});
895                 if ($charge > 0) {
896                         createcharge($env, $dbh, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}, $charge);
897                         $iteminformation->{'charge'}=$charge;
898                 }
899                 # Record the fact that this book was issued.
900                 &UpdateStats($env,$env->{'branchcode'},'issue',$charge,'',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'});
901         }
902 }
903
904 =head2 getLoanLength
905
906 Get loan length for an itemtype, a borrower type and a branch
907
908 my $loanlength = &getLoanLength($borrowertype,$itemtype,branchcode)
909
910 =cut
911
912 sub getLoanLength {
913         my ($borrowertype,$itemtype,$branchcode) = @_;
914         my $dbh = C4::Context->dbh;
915         my $sth = $dbh->prepare("select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=?");
916         # try to find issuelength & return the 1st available.
917         # check with borrowertype, itemtype and branchcode, then without one of those parameters
918         $sth->execute($borrowertype,$itemtype,$branchcode);
919         my $loanlength = $sth->fetchrow_hashref;
920         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
921         
922         $sth->execute($borrowertype,$itemtype,"");
923         my $loanlength = $sth->fetchrow_hashref;
924         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
925         
926         $sth->execute($borrowertype,"*",$branchcode);
927         my $loanlength = $sth->fetchrow_hashref;
928         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
929
930         $sth->execute("*",$itemtype,$branchcode);
931         my $loanlength = $sth->fetchrow_hashref;
932         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
933
934         $sth->execute($borrowertype,"*","");
935         my $loanlength = $sth->fetchrow_hashref;
936         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
937
938         $sth->execute("*","*",$branchcode);
939         my $loanlength = $sth->fetchrow_hashref;
940         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
941
942         $sth->execute("*",$itemtype,"");
943         my $loanlength = $sth->fetchrow_hashref;
944         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
945
946         $sth->execute("*","*","");
947         my $loanlength = $sth->fetchrow_hashref;
948         return $loanlength->{issuelength} if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
949
950         # if no rule is set => 21 days (hardcoded)
951         return 21;
952 }
953 =head2 returnbook
954
955   ($doreturn, $messages, $iteminformation, $borrower) =
956           &returnbook($barcode, $branch);
957
958 Returns a book.
959
960 C<$barcode> is the bar code of the book being returned. C<$branch> is
961 the code of the branch where the book is being returned.
962
963 C<&returnbook> returns a list of four items:
964
965 C<$doreturn> is true iff the return succeeded.
966
967 C<$messages> is a reference-to-hash giving the reason for failure:
968
969 =over 4
970
971 =item C<BadBarcode>
972
973 No item with this barcode exists. The value is C<$barcode>.
974
975 =item C<NotIssued>
976
977 The book is not currently on loan. The value is C<$barcode>.
978
979 =item C<IsPermanent>
980
981 The book's home branch is a permanent collection. If you have borrowed
982 this book, you are not allowed to return it. The value is the code for
983 the book's home branch.
984
985 =item C<wthdrawn>
986
987 This book has been withdrawn/cancelled. The value should be ignored.
988
989 =item C<ResFound>
990
991 The item was reserved. The value is a reference-to-hash whose keys are
992 fields from the reserves table of the Koha database, and
993 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
994 either C<Waiting>, C<Reserved>, or 0.
995
996 =back
997
998 C<$borrower> is a reference-to-hash, giving information about the
999 patron who last borrowed the book.
1000
1001 =cut
1002
1003 # FIXME - This API is bogus. There's no need to return $borrower and
1004 # $iteminformation; the caller can ask about those separately, if it
1005 # cares (it'd be inefficient to make two database calls instead of
1006 # one, but &getpatroninformation and &getiteminformation can be
1007 # memoized if this is an issue).
1008 #
1009 # The ($doreturn, $messages) tuple is redundant: if the return
1010 # succeeded, that's all the caller needs to know. So &returnbook can
1011 # return 1 and 0 on success and failure, and set
1012 # $C4::Circulation::Circ2::errmsg to indicate the error. Or it can
1013 # return undef for success, and an error message on error (though this
1014 # is more C-ish than Perl-ish).
1015
1016 sub returnbook {
1017         my ($barcode, $branch) = @_;
1018         my %env;
1019         my $messages;
1020         my $dbh = C4::Context->dbh;
1021         my $doreturn = 1;
1022         die '$branch not defined' unless defined $branch; # just in case (bug 170)
1023         # get information on item
1024         my ($iteminformation) = getiteminformation(\%env, 0, $barcode);
1025         if (not $iteminformation) {
1026                 $messages->{'BadBarcode'} = $barcode;
1027                 $doreturn = 0;
1028         }
1029         # find the borrower
1030         my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'});
1031         if ((not $currentborrower) && $doreturn) {
1032                 $messages->{'NotIssued'} = $barcode;
1033                 $doreturn = 0;
1034         }
1035         # check if the book is in a permanent collection....
1036         my $hbr = $iteminformation->{'homebranch'};
1037         my $branches = getbranches();
1038         if ($hbr && $branches->{$hbr}->{'PE'}) {
1039                 $messages->{'IsPermanent'} = $hbr;
1040         }
1041         # check that the book has been cancelled
1042         if ($iteminformation->{'wthdrawn'}) {
1043                 $messages->{'wthdrawn'} = 1;
1044                 $doreturn = 0;
1045         }
1046         # update issues, thereby returning book (should push this out into another subroutine
1047         my ($borrower) = getpatroninformation(\%env, $currentborrower, 0);
1048         if ($doreturn) {
1049                 my $sth = $dbh->prepare("update issues set returndate = now() where (borrowernumber = ?) and (itemnumber = ?) and (returndate is null)");
1050                 $sth->execute($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'});
1051                 $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right?
1052         }
1053         itemseen($iteminformation->{'itemnumber'});
1054         ($borrower) = getpatroninformation(\%env, $currentborrower, 0);
1055         # transfer book to the current branch
1056         my ($transfered, $mess, $item) = transferbook($branch, $barcode, 1);
1057         if ($transfered) {
1058                 $messages->{'WasTransfered'} = 1; # FIXME is the "= 1" right?
1059         }
1060         # fix up the accounts.....
1061         if ($iteminformation->{'itemlost'}) {
1062                 fixaccountforlostandreturned($iteminformation, $borrower);
1063                 $messages->{'WasLost'} = 1; # FIXME is the "= 1" right?
1064         }
1065         # fix up the overdues in accounts...
1066         fixoverduesonreturn($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'});
1067         # find reserves.....
1068         my ($resfound, $resrec) = CheckReserves($iteminformation->{'itemnumber'});
1069         if ($resfound) {
1070         #       my $tobrcd = ReserveWaiting($resrec->{'itemnumber'}, $resrec->{'borrowernumber'});
1071                 $resrec->{'ResFound'} = $resfound;
1072                 $messages->{'ResFound'} = $resrec;
1073         }
1074         # update stats?
1075         # Record the fact that this book was returned.
1076         UpdateStats(\%env, $branch ,'return','0','',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'});
1077         return ($doreturn, $messages, $iteminformation, $borrower);
1078 }
1079
1080 =head2 fixaccountforlostandreturned
1081
1082         &fixaccountforlostandreturned($iteminfo,$borrower);
1083
1084 Calculates the charge for a book lost and returned (Not exported & used only once)
1085
1086 C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used.
1087
1088 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1089
1090 =cut
1091
1092 sub fixaccountforlostandreturned {
1093         my ($iteminfo, $borrower) = @_;
1094         my %env;
1095         my $dbh = C4::Context->dbh;
1096         my $itm = $iteminfo->{'itemnumber'};
1097         # check for charge made for lost book
1098         my $sth = $dbh->prepare("select * from accountlines where (itemnumber = ?) and (accounttype='L' or accounttype='Rep') order by date desc");
1099         $sth->execute($itm);
1100         if (my $data = $sth->fetchrow_hashref) {
1101         # writeoff this amount
1102                 my $offset;
1103                 my $amount = $data->{'amount'};
1104                 my $acctno = $data->{'accountno'};
1105                 my $amountleft;
1106                 if ($data->{'amountoutstanding'} == $amount) {
1107                 $offset = $data->{'amount'};
1108                 $amountleft = 0;
1109                 } else {
1110                 $offset = $amount - $data->{'amountoutstanding'};
1111                 $amountleft = $data->{'amountoutstanding'} - $amount;
1112                 }
1113                 my $usth = $dbh->prepare("update accountlines set accounttype = 'LR',amountoutstanding='0'
1114                         where (borrowernumber = ?)
1115                         and (itemnumber = ?) and (accountno = ?) ");
1116                 $usth->execute($data->{'borrowernumber'},$itm,$acctno);
1117                 $usth->finish;
1118         #check if any credit is left if so writeoff other accounts
1119                 my $nextaccntno = getnextacctno(\%env,$data->{'borrowernumber'},$dbh);
1120                 if ($amountleft < 0){
1121                 $amountleft*=-1;
1122                 }
1123                 if ($amountleft > 0){
1124                 my $msth = $dbh->prepare("select * from accountlines where (borrowernumber = ?)
1125                                                         and (amountoutstanding >0) order by date");
1126                 $msth->execute($data->{'borrowernumber'});
1127         # offset transactions
1128                 my $newamtos;
1129                 my $accdata;
1130                 while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1131                         if ($accdata->{'amountoutstanding'} < $amountleft) {
1132                         $newamtos = 0;
1133                         $amountleft -= $accdata->{'amountoutstanding'};
1134                         }  else {
1135                         $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1136                         $amountleft = 0;
1137                         }
1138                         my $thisacct = $accdata->{'accountno'};
1139                         my $usth = $dbh->prepare("update accountlines set amountoutstanding= ?
1140                                         where (borrowernumber = ?)
1141                                         and (accountno=?)");
1142                         $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');
1143                         $usth->finish;
1144                         $usth = $dbh->prepare("insert into accountoffsets
1145                                 (borrowernumber, accountno, offsetaccount,  offsetamount)
1146                                 values
1147                                 (?,?,?,?)");
1148                         $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1149                         $usth->finish;
1150                 }
1151                 $msth->finish;
1152                 }
1153                 if ($amountleft > 0){
1154                         $amountleft*=-1;
1155                 }
1156                 my $desc="Book Returned ".$iteminfo->{'barcode'};
1157                 $usth = $dbh->prepare("insert into accountlines
1158                         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1159                         values (?,?,now(),?,?,'CR',?)");
1160                 $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1161                 $usth->finish;
1162                 $usth = $dbh->prepare("insert into accountoffsets
1163                         (borrowernumber, accountno, offsetaccount,  offsetamount)
1164                         values (?,?,?,?)");
1165                 $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset);
1166                 $usth->finish;
1167                 $usth = $dbh->prepare("update items set paidfor='' where itemnumber=?");
1168                 $usth->execute($itm);
1169                 $usth->finish;
1170         }
1171         $sth->finish;
1172         return;
1173 }
1174
1175 =head2 fixoverdueonreturn
1176
1177         &fixoverdueonreturn($brn,$itm);
1178
1179 ??
1180
1181 C<$brn> borrowernumber
1182
1183 C<$itm> itemnumber
1184
1185 =cut
1186
1187 sub fixoverduesonreturn {
1188         my ($brn, $itm) = @_;
1189         my $dbh = C4::Context->dbh;
1190         # check for overdue fine
1191         my $sth = $dbh->prepare("select * from accountlines where (borrowernumber = ?) and (itemnumber = ?) and (accounttype='FU' or accounttype='O')");
1192         $sth->execute($brn,$itm);
1193         # alter fine to show that the book has been returned
1194         if (my $data = $sth->fetchrow_hashref) {
1195                 my $usth=$dbh->prepare("update accountlines set accounttype='F' where (borrowernumber = ?) and (itemnumber = ?) and (acccountno = ?)");
1196                 $usth->execute($brn,$itm,$data->{'accountno'});
1197                 $usth->finish();
1198         }
1199         $sth->finish();
1200         return;
1201 }
1202
1203 # Not exported
1204 #
1205 # NOTE!: If you change this function, be sure to update the POD for
1206 # &getpatroninformation.
1207 #
1208 # $flags = &patronflags($env, $patron, $dbh);
1209 #
1210 # $flags->{CHARGES}
1211 #               {message}       Message showing patron's credit or debt
1212 #               {noissues}      Set if patron owes >$5.00
1213 #         {GNA}                 Set if patron gone w/o address
1214 #               {message}       "Borrower has no valid address"
1215 #               {noissues}      Set.
1216 #         {LOST}                Set if patron's card reported lost
1217 #               {message}       Message to this effect
1218 #               {noissues}      Set.
1219 #         {DBARRED}             Set is patron is debarred
1220 #               {message}       Message to this effect
1221 #               {noissues}      Set.
1222 #         {NOTES}               Set if patron has notes
1223 #               {message}       Notes about patron
1224 #         {ODUES}               Set if patron has overdue books
1225 #               {message}       "Yes"
1226 #               {itemlist}      ref-to-array: list of overdue books
1227 #               {itemlisttext}  Text list of overdue items
1228 #         {WAITING}             Set if there are items available that the
1229 #                               patron reserved
1230 #               {message}       Message to this effect
1231 #               {itemlist}      ref-to-array: list of available items
1232 sub patronflags {
1233 # Original subroutine for Circ2.pm
1234         my %flags;
1235         my ($env, $patroninformation, $dbh) = @_;
1236         my $amount = checkaccount($env, $patroninformation->{'borrowernumber'}, $dbh);
1237         if ($amount > 0) {
1238                 my %flaginfo;
1239                 my $noissuescharge = C4::Context->preference("noissuescharge");
1240                 $flaginfo{'message'}= sprintf "Patron owes \$%.02f", $amount;
1241                 if ($amount > $noissuescharge) {
1242                 $flaginfo{'noissues'} = 1;
1243                 }
1244                 $flags{'CHARGES'} = \%flaginfo;
1245         } elsif ($amount < 0){
1246         my %flaginfo;
1247         $flaginfo{'message'} = sprintf "Patron has credit of \$%.02f", -$amount;
1248                 $flags{'CHARGES'} = \%flaginfo;
1249         }
1250         if ($patroninformation->{'gonenoaddress'} == 1) {
1251                 my %flaginfo;
1252                 $flaginfo{'message'} = 'Borrower has no valid address.';
1253                 $flaginfo{'noissues'} = 1;
1254                 $flags{'GNA'} = \%flaginfo;
1255         }
1256         if ($patroninformation->{'lost'} == 1) {
1257                 my %flaginfo;
1258                 $flaginfo{'message'} = 'Borrower\'s card reported lost.';
1259                 $flaginfo{'noissues'} = 1;
1260                 $flags{'LOST'} = \%flaginfo;
1261         }
1262         if ($patroninformation->{'debarred'} == 1) {
1263                 my %flaginfo;
1264                 $flaginfo{'message'} = 'Borrower is Debarred.';
1265                 $flaginfo{'noissues'} = 1;
1266                 $flags{'DBARRED'} = \%flaginfo;
1267         }
1268         if ($patroninformation->{'borrowernotes'}) {
1269                 my %flaginfo;
1270                 $flaginfo{'message'} = "$patroninformation->{'borrowernotes'}";
1271                 $flags{'NOTES'} = \%flaginfo;
1272         }
1273         my ($odues, $itemsoverdue)
1274                         = checkoverdues($env, $patroninformation->{'borrowernumber'}, $dbh);
1275         if ($odues > 0) {
1276                 my %flaginfo;
1277                 $flaginfo{'message'} = "Yes";
1278                 $flaginfo{'itemlist'} = $itemsoverdue;
1279                 foreach (sort {$a->{'date_due'} cmp $b->{'date_due'}} @$itemsoverdue) {
1280                 $flaginfo{'itemlisttext'}.="$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n";
1281                 }
1282                 $flags{'ODUES'} = \%flaginfo;
1283         }
1284         my ($nowaiting, $itemswaiting)
1285                         = CheckWaiting($patroninformation->{'borrowernumber'});
1286         if ($nowaiting > 0) {
1287                 my %flaginfo;
1288                 $flaginfo{'message'} = "Reserved items available";
1289                 $flaginfo{'itemlist'} = $itemswaiting;
1290                 $flags{'WAITING'} = \%flaginfo;
1291         }
1292         return(\%flags);
1293 }
1294
1295
1296 # Not exported
1297 sub checkoverdues {
1298 # From Main.pm, modified to return a list of overdueitems, in addition to a count
1299   #checks whether a borrower has overdue items
1300         my ($env, $bornum, $dbh)=@_;
1301         my @datearr = localtime;
1302         my $today = ($datearr[5] + 1900)."-".($datearr[4]+1)."-".$datearr[3];
1303         my @overdueitems;
1304         my $count = 0;
1305         my $sth = $dbh->prepare("SELECT * FROM issues,biblio,biblioitems,items
1306                         WHERE items.biblioitemnumber = biblioitems.biblioitemnumber
1307                                 AND items.biblionumber     = biblio.biblionumber
1308                                 AND issues.itemnumber      = items.itemnumber
1309                                 AND issues.borrowernumber  = ?
1310                                 AND issues.returndate is NULL
1311                                 AND issues.date_due < ?");
1312         $sth->execute($bornum,$today);
1313         while (my $data = $sth->fetchrow_hashref) {
1314         push (@overdueitems, $data);
1315         $count++;
1316         }
1317         $sth->finish;
1318         return ($count, \@overdueitems);
1319 }
1320
1321 # Not exported
1322 sub currentborrower {
1323 # Original subroutine for Circ2.pm
1324         my ($itemnumber) = @_;
1325         my $dbh = C4::Context->dbh;
1326         my $q_itemnumber = $dbh->quote($itemnumber);
1327         my $sth=$dbh->prepare("select borrowers.borrowernumber from
1328         issues,borrowers where issues.itemnumber=$q_itemnumber and
1329         issues.borrowernumber=borrowers.borrowernumber and issues.returndate is
1330         NULL");
1331         $sth->execute;
1332         my ($borrower) = $sth->fetchrow;
1333         return($borrower);
1334 }
1335
1336 # FIXME - Not exported, but used in 'updateitem.pl' anyway.
1337 sub checkreserve_to_delete {
1338 # Stolen from Main.pm
1339 # Check for reserves for biblio
1340         my ($env,$dbh,$itemnum)=@_;
1341         my $resbor = "";
1342         my $sth = $dbh->prepare("select * from reserves,items
1343         where (items.itemnumber = ?)
1344         and (reserves.cancellationdate is NULL)
1345         and (items.biblionumber = reserves.biblionumber)
1346         and ((reserves.found = 'W')
1347         or (reserves.found is null))
1348         order by priority");
1349         $sth->execute($itemnum);
1350         my $resrec;
1351         my $data=$sth->fetchrow_hashref;
1352         while ($data && $resbor eq '') {
1353         $resrec=$data;
1354         my $const = $data->{'constrainttype'};
1355         if ($const eq "a") {
1356         $resbor = $data->{'borrowernumber'};
1357         } else {
1358         my $found = 0;
1359         my $csth = $dbh->prepare("select * from reserveconstraints,items
1360                 where (borrowernumber=?)
1361                 and reservedate=?
1362                 and reserveconstraints.biblionumber=?
1363                 and (items.itemnumber=? and
1364                 items.biblioitemnumber = reserveconstraints.biblioitemnumber)");
1365         $csth->execute($data->{'borrowernumber'},$data->{'biblionumber'},$data->{'reservedate'},$itemnum);
1366         if (my $cdata=$csth->fetchrow_hashref) {$found = 1;}
1367         if ($const eq 'o') {
1368                 if ($found eq 1) {$resbor = $data->{'borrowernumber'};}
1369         } else {
1370                 if ($found eq 0) {$resbor = $data->{'borrowernumber'};}
1371         }
1372         $csth->finish();
1373         }
1374         $data=$sth->fetchrow_hashref;
1375         }
1376         $sth->finish;
1377         return ($resbor,$resrec);
1378 }
1379
1380 =head2 currentissues
1381
1382   $issues = &currentissues($env, $borrower);
1383
1384 Returns a list of books currently on loan to a patron.
1385
1386 If C<$env-E<gt>{todaysissues}> is set and true, C<&currentissues> only
1387 returns information about books issued today. If
1388 C<$env-E<gt>{nottodaysissues}> is set and true, C<&currentissues> only
1389 returns information about books issued before today. If both are
1390 specified, C<$env-E<gt>{todaysissues}> is ignored. If neither is
1391 specified, C<&currentissues> returns all of the patron's issues.
1392
1393 C<$borrower->{borrowernumber}> is the borrower number of the patron
1394 whose issues we want to list.
1395
1396 C<&currentissues> returns a PHP-style array: C<$issues> is a
1397 reference-to-hash whose keys are integers in the range 1...I<n>, where
1398 I<n> is the number of items on issue (either today or before today).
1399 C<$issues-E<gt>{I<n>}> is a reference-to-hash whose keys are all of
1400 the fields of the biblio, biblioitems, items, and issues fields of the
1401 Koha database for that particular item.
1402
1403 =cut
1404
1405 #'
1406 sub currentissues {
1407 # New subroutine for Circ2.pm
1408         my ($env, $borrower) = @_;
1409         my $dbh = C4::Context->dbh;
1410         my %currentissues;
1411         my $counter=1;
1412         my $borrowernumber = $borrower->{'borrowernumber'};
1413         my $crit='';
1414
1415         # Figure out whether to get the books issued today, or earlier.
1416         # FIXME - $env->{todaysissues} and $env->{nottodaysissues} can
1417         # both be specified, but are mutually-exclusive. This is bogus.
1418         # Make this a flag. Or better yet, return everything in (reverse)
1419         # chronological order and let the caller figure out which books
1420         # were issued today.
1421         if ($env->{'todaysissues'}) {
1422                 # FIXME - Could use
1423                 #       $today = POSIX::strftime("%Y%m%d", localtime);
1424                 # FIXME - Since $today will be used in either case, move it
1425                 # out of the two if-blocks.
1426                 my @datearr = localtime(time());
1427                 my $today = (1900+$datearr[5]).sprintf "%02d", ($datearr[4]+1).sprintf "%02d", $datearr[3];
1428                 # FIXME - MySQL knows about dates. Just use
1429                 #       and issues.timestamp = curdate();
1430                 $crit=" and issues.timestamp like '$today%' ";
1431         }
1432         if ($env->{'nottodaysissues'}) {
1433                 # FIXME - Could use
1434                 #       $today = POSIX::strftime("%Y%m%d", localtime);
1435                 # FIXME - Since $today will be used in either case, move it
1436                 # out of the two if-blocks.
1437                 my @datearr = localtime(time());
1438                 my $today = (1900+$datearr[5]).sprintf "%02d", ($datearr[4]+1).sprintf "%02d", $datearr[3];
1439                 # FIXME - MySQL knows about dates. Just use
1440                 #       and issues.timestamp < curdate();
1441                 $crit=" and !(issues.timestamp like '$today%') ";
1442         }
1443
1444         # FIXME - Does the caller really need every single field from all
1445         # four tables?
1446         my $sth=$dbh->prepare("select * from issues,items,biblioitems,biblio where
1447         borrowernumber=? and issues.itemnumber=items.itemnumber and
1448         items.biblionumber=biblio.biblionumber and
1449         items.biblioitemnumber=biblioitems.biblioitemnumber and returndate is null
1450         $crit order by issues.date_due");
1451         $sth->execute($borrowernumber);
1452         while (my $data = $sth->fetchrow_hashref) {
1453                 # FIXME - The Dewey code is a string, not a number.
1454                 $data->{'dewey'}=~s/0*$//;
1455                 ($data->{'dewey'} == 0) && ($data->{'dewey'}='');
1456                 # FIXME - Could use
1457                 #       $todaysdate = POSIX::strftime("%Y%m%d", localtime)
1458                 # or better yet, just reuse $today which was calculated above.
1459                 # This function isn't going to run until midnight, is it?
1460                 # Alternately, use
1461                 #       $todaysdate = POSIX::strftime("%Y-%m-%d", localtime)
1462                 #       if ($data->{'date_due'} lt $todaysdate)
1463                 #               ...
1464                 # Either way, the date should be be formatted outside of the
1465                 # loop.
1466                 my @datearr = localtime(time());
1467                 my $todaysdate = (1900+$datearr[5]).sprintf ("%0.2d", ($datearr[4]+1)).sprintf ("%0.2d", $datearr[3]);
1468                 my $datedue=$data->{'date_due'};
1469                 $datedue=~s/-//g;
1470                 if ($datedue < $todaysdate) {
1471                         $data->{'overdue'}=1;
1472                 }
1473                 my $itemnumber=$data->{'itemnumber'};
1474                 # FIXME - Consecutive integers as hash keys? You have GOT to
1475                 # be kidding me! Use an array, fercrissakes!
1476                 $currentissues{$counter}=$data;
1477                 $counter++;
1478         }
1479         $sth->finish;
1480         return(\%currentissues);
1481 }
1482
1483 =head2 getissues
1484
1485   $issues = &getissues($borrowernumber);
1486
1487 Returns the set of books currently on loan to a patron.
1488
1489 C<$borrowernumber> is the patron's borrower number.
1490
1491 C<&getissues> returns a PHP-style array: C<$issues> is a
1492 reference-to-hash whose keys are integers in the range 0..I<n>-1,
1493 where I<n> is the number of books the patron currently has on loan.
1494
1495 The values of C<$issues> are references-to-hash whose keys are
1496 selected fields from the issues, items, biblio, and biblioitems tables
1497 of the Koha database.
1498
1499 =cut
1500 #'
1501 sub getissues {
1502 # New subroutine for Circ2.pm
1503         my ($borrower) = @_;
1504         my $dbh = C4::Context->dbh;
1505         my $borrowernumber = $borrower->{'borrowernumber'};
1506         my %currentissues;
1507         my $select = "SELECT items.*,issues.timestamp      AS timestamp,
1508                                 issues.date_due       AS date_due,
1509                                 items.barcode         AS barcode,
1510                                 biblio.title          AS title,
1511                                 biblio.author         AS author,
1512                                 biblioitems.dewey     AS dewey,
1513                                 itemtypes.description AS itemtype,
1514                                 biblioitems.subclass  AS subclass,
1515                                 biblioitems.classification AS classification
1516                         FROM issues,items,biblioitems,biblio, itemtypes
1517                         WHERE issues.borrowernumber  = ?
1518                         AND issues.itemnumber      = items.itemnumber
1519                         AND items.biblionumber     = biblio.biblionumber
1520                         AND items.biblioitemnumber = biblioitems.biblioitemnumber
1521                         AND itemtypes.itemtype     = biblioitems.itemtype
1522                         AND issues.returndate      IS NULL
1523                         ORDER BY issues.date_due";
1524         #    print $select;
1525         my $sth=$dbh->prepare($select);
1526         $sth->execute($borrowernumber);
1527         my $counter = 0;
1528         while (my $data = $sth->fetchrow_hashref) {
1529                 $data->{'dewey'} =~ s/0*$//;
1530                 ($data->{'dewey'} == 0) && ($data->{'dewey'} = '');
1531                         # FIXME - The Dewey code is a string, not a number.
1532                 # FIXME - Use POSIX::strftime to get a text version of today's
1533                 # date. That's what it's for.
1534                 # FIXME - Move the date calculation outside of the loop.
1535                 my @datearr = localtime(time());
1536                 my $todaysdate = (1900+$datearr[5]).sprintf ("%0.2d", ($datearr[4]+1)).sprintf ("%0.2d", $datearr[3]);
1537
1538                 # FIXME - Instead of converting the due date to YYYYMMDD, just
1539                 # use
1540                 #       $todaysdate = POSIX::strftime("%Y-%m-%d", localtime);
1541                 #       ...
1542                 #       if ($date->{date_due} lt $todaysdate)
1543                 my $datedue = $data->{'date_due'};
1544                 $datedue =~ s/-//g;
1545                 if ($datedue < $todaysdate) {
1546                         $data->{'overdue'} = 1;
1547                 }
1548                 $currentissues{$counter} = $data;
1549                 $counter++;
1550                         # FIXME - This is ludicrous. If you want to return an
1551                         # array of values, just use an array. That's what
1552                         # they're there for.
1553         }
1554         $sth->finish;
1555         return(\%currentissues);
1556 }
1557
1558 # Not exported
1559 sub checkwaiting {
1560 #Stolen from Main.pm
1561 # check for reserves waiting
1562         my ($env,$dbh,$bornum)=@_;
1563         my @itemswaiting;
1564         my $sth = $dbh->prepare("select * from reserves where (borrowernumber = ?) and (reserves.found='W') and cancellationdate is NULL");
1565         $sth->execute($bornum);
1566         my $cnt=0;
1567         if (my $data=$sth->fetchrow_hashref) {
1568                 $itemswaiting[$cnt] =$data;
1569                 $cnt ++
1570         }
1571         $sth->finish;
1572         return ($cnt,\@itemswaiting);
1573 }
1574
1575 =head2 renewstatus
1576
1577   $ok = &renewstatus($env, $dbh, $borrowernumber, $itemnumber);
1578
1579 Find out whether a borrowed item may be renewed.
1580
1581 C<$env> is ignored.
1582
1583 C<$dbh> is a DBI handle to the Koha database.
1584
1585 C<$borrowernumber> is the borrower number of the patron who currently
1586 has the item on loan.
1587
1588 C<$itemnumber> is the number of the item to renew.
1589
1590 C<$renewstatus> returns a true value iff the item may be renewed. The
1591 item must currently be on loan to the specified borrower; renewals
1592 must be allowed for the item's type; and the borrower must not have
1593 already renewed the loan.
1594
1595 =cut
1596
1597 sub renewstatus {
1598         # check renewal status
1599         my ($env,$bornum,$itemno)=@_;
1600         my $dbh = C4::Context->dbh;
1601         my $renews = 1;
1602         my $renewokay = 0;
1603         # Look in the issues table for this item, lent to this borrower,
1604         # and not yet returned.
1605         
1606         # FIXME - I think this function could be redone to use only one SQL call.
1607         my $sth1 = $dbh->prepare("select * from issues
1608                                                                 where (borrowernumber = ?)
1609                                                                 and (itemnumber = ?)
1610                                                                 and returndate is null");
1611         $sth1->execute($bornum,$itemno);
1612         if (my $data1 = $sth1->fetchrow_hashref) {
1613                 # Found a matching item
1614         
1615                 # See if this item may be renewed. This query is convoluted
1616                 # because it's a bit messy: given the item number, we need to find
1617                 # the biblioitem, which gives us the itemtype, which tells us
1618                 # whether it may be renewed.
1619                 my $sth2 = $dbh->prepare("select renewalsallowed from items,biblioitems,itemtypes
1620                 where (items.itemnumber = ?)
1621                 and (items.biblioitemnumber = biblioitems.biblioitemnumber)
1622                 and (biblioitems.itemtype = itemtypes.itemtype)");
1623                 $sth2->execute($itemno);
1624                 if (my $data2=$sth2->fetchrow_hashref) {
1625                         $renews = $data2->{'renewalsallowed'};
1626                 }
1627                 if ($renews && $renews > $data1->{'renewals'}) {
1628                         $renewokay = 1;
1629                 }
1630                 $sth2->finish;
1631                 my ($resfound, $resrec) = CheckReserves($itemno);
1632                 if ($resfound) {
1633                         $renewokay = 0;
1634                 }
1635                 my ($resfound, $resrec) = CheckReserves($itemno);
1636                 if ($resfound) {
1637                         $renewokay = 0;
1638                 }
1639
1640         }
1641         $sth1->finish;
1642         return($renewokay);
1643 }
1644
1645 =head2 renewbook
1646
1647   &renewbook($env, $borrowernumber, $itemnumber, $datedue);
1648
1649 Renews a loan.
1650
1651 C<$env-E<gt>{branchcode}> is the code of the branch where the
1652 renewal is taking place.
1653
1654 C<$env-E<gt>{usercode}> is the value to log in C<statistics.usercode>
1655 in the Koha database.
1656
1657 C<$borrowernumber> is the borrower number of the patron who currently
1658 has the item.
1659
1660 C<$itemnumber> is the number of the item to renew.
1661
1662 C<$datedue> can be used to set the due date. If C<$datedue> is the
1663 empty string, C<&renewbook> will calculate the due date automatically
1664 from the book's item type. If you wish to set the due date manually,
1665 C<$datedue> should be in the form YYYY-MM-DD.
1666
1667 =cut
1668
1669 sub renewbook {
1670         # mark book as renewed
1671         my ($env,$bornum,$itemno,$datedue)=@_;
1672         my $dbh = C4::Context->dbh;
1673
1674         # If the due date wasn't specified, calculate it by adding the
1675         # book's loan length to today's date.
1676         if ($datedue eq "" ) {
1677                 #debug_msg($env, "getting date");
1678                 my $iteminformation = getiteminformation($env, $itemno,0);
1679                 my $borrower = getpatroninformation($env,$bornum,0);
1680                 my $loanlength = getLoanLength($borrower->{'categorycode'},$iteminformation->{'itemtype'},$borrower->{'branchcode'});
1681                 $datedue = UnixDate(DateCalc("today","$loanlength days"),"%Y-%m-%d");
1682         }
1683
1684         # Find the issues record for this book
1685         my $sth=$dbh->prepare("select * from issues where borrowernumber=? and itemnumber=? and returndate is null");
1686         $sth->execute($bornum,$itemno);
1687         my $issuedata=$sth->fetchrow_hashref;
1688         $sth->finish;
1689
1690         # Update the issues record to have the new due date, and a new count
1691         # of how many times it has been renewed.
1692         my $renews = $issuedata->{'renewals'} +1;
1693         $sth=$dbh->prepare("update issues set date_due = ?, renewals = ?
1694                 where borrowernumber=? and itemnumber=? and returndate is null");
1695         $sth->execute($datedue,$renews,$bornum,$itemno);
1696         $sth->finish;
1697
1698         # Log the renewal
1699         UpdateStats($env,$env->{'branchcode'},'renew','','',$itemno);
1700
1701         # Charge a new rental fee, if applicable?
1702         my ($charge,$type)=calc_charges($env, $itemno, $bornum);
1703         if ($charge > 0){
1704                 my $accountno=getnextacctno($env,$bornum,$dbh);
1705                 my $item=getiteminformation($env, $itemno);
1706                 $sth=$dbh->prepare("Insert into accountlines (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber)
1707                                                         values (?,?,now(),?,?,?,?,?)");
1708                 $sth->execute($bornum,$accountno,$charge,"Renewal of Rental Item $item->{'title'} $item->{'barcode'}",'Rent',$charge,$itemno);
1709                 $sth->finish;
1710         #     print $account;
1711         }
1712         
1713         #  return();
1714 }
1715
1716
1717
1718 =item calc_charges
1719
1720   ($charge, $item_type) = &calc_charges($env, $itemnumber, $borrowernumber);
1721
1722 Calculate how much it would cost for a given patron to borrow a given
1723 item, including any applicable discounts.
1724
1725 C<$env> is ignored.
1726
1727 C<$itemnumber> is the item number of item the patron wishes to borrow.
1728
1729 C<$borrowernumber> is the patron's borrower number.
1730
1731 C<&calc_charges> returns two values: C<$charge> is the rental charge,
1732 and C<$item_type> is the code for the item's item type (e.g., C<VID>
1733 if it's a video).
1734
1735 =cut
1736
1737 sub calc_charges {
1738         # calculate charges due
1739         my ($env, $itemno, $bornum)=@_;
1740         my $charge=0;
1741         my $dbh = C4::Context->dbh;
1742         my $item_type;
1743         
1744         # Get the book's item type and rental charge (via its biblioitem).
1745         my $sth1= $dbh->prepare("select itemtypes.itemtype,rentalcharge from items,biblioitems,itemtypes
1746                                                                 where (items.itemnumber =?)
1747                                                                 and (biblioitems.biblioitemnumber = items.biblioitemnumber)
1748                                                                 and (biblioitems.itemtype = itemtypes.itemtype)");
1749         $sth1->execute($itemno);
1750         my $data1=$sth1->fetchrow_hashref;
1751         $item_type = $data1->{'itemtype'};
1752         $charge = $data1->{'rentalcharge'};
1753         $sth1->finish;
1754         return ($charge,$item_type);
1755 }
1756
1757
1758 # FIXME - A virtually identical function appears in
1759 # C4::Circulation::Issues. Pick one and stick with it.
1760 sub createcharge {
1761 #Stolen from Issues.pm
1762     my ($env,$dbh,$itemno,$bornum,$charge) = @_;
1763     my $nextaccntno = getnextacctno($env,$bornum,$dbh);
1764     my $sth = $dbh->prepare(<<EOT);
1765         INSERT INTO     accountlines
1766                         (borrowernumber, itemnumber, accountno,
1767                          date, amount, description, accounttype,
1768                          amountoutstanding)
1769         VALUES          (?, ?, ?,
1770                          now(), ?, 'Rental', 'Rent',
1771                          ?)
1772 EOT
1773     $sth->execute($bornum, $itemno, $nextaccntno, $charge, $charge);
1774     $sth->finish;
1775 }
1776
1777
1778 sub getnextacctno {
1779 # Stolen from Accounts.pm
1780     my ($env,$bornumber,$dbh)=@_;
1781     my $nextaccntno = 1;
1782     my $sth = $dbh->prepare("select * from accountlines where (borrowernumber = ?) order by accountno desc");
1783     $sth->execute($bornumber);
1784     if (my $accdata=$sth->fetchrow_hashref){
1785         $nextaccntno = $accdata->{'accountno'} + 1;
1786     }
1787     $sth->finish;
1788     return($nextaccntno);
1789 }
1790
1791 =item find_reserves
1792
1793   ($status, $record) = &find_reserves($itemnumber);
1794
1795 Looks up an item in the reserves.
1796
1797 C<$itemnumber> is the itemnumber to look up.
1798
1799 C<$status> is true iff the search was successful.
1800
1801 C<$record> is a reference-to-hash describing the reserve. Its keys are
1802 the fields from the reserves table of the Koha database.
1803
1804 =cut
1805 #'
1806 # FIXME - This API is bogus: just return the record, or undef if none
1807 # was found.
1808 # FIXME - There's also a &C4::Circulation::Returns::find_reserves, but
1809 # that one looks rather different.
1810 sub find_reserves {
1811 # Stolen from Returns.pm
1812     my ($itemno) = @_;
1813     my %env;
1814     my $dbh = C4::Context->dbh;
1815     my ($itemdata) = getiteminformation(\%env, $itemno,0);
1816     my $bibno = $dbh->quote($itemdata->{'biblionumber'});
1817     my $bibitm = $dbh->quote($itemdata->{'biblioitemnumber'});
1818     my $sth = $dbh->prepare("select * from reserves where ((found = 'W') or (found is null)) and biblionumber = ? and cancellationdate is NULL order by priority, reservedate");
1819     $sth->execute($bibno);
1820     my $resfound = 0;
1821     my $resrec;
1822     my $lastrec;
1823 # print $query;
1824
1825     # FIXME - I'm not really sure what's going on here, but since we
1826     # only want one result, wouldn't it be possible (and far more
1827     # efficient) to do something clever in SQL that only returns one
1828     # set of values?
1829     while (($resrec = $sth->fetchrow_hashref) && (not $resfound)) {
1830                 # FIXME - Unlike Pascal, Perl allows you to exit loops
1831                 # early. Take out the "&& (not $resfound)" and just
1832                 # use "last" at the appropriate point in the loop.
1833                 # (Oh, and just in passing: if you'd used "!" instead
1834                 # of "not", you wouldn't have needed the parentheses.)
1835         $lastrec = $resrec;
1836         my $brn = $dbh->quote($resrec->{'borrowernumber'});
1837         my $rdate = $dbh->quote($resrec->{'reservedate'});
1838         my $bibno = $dbh->quote($resrec->{'biblionumber'});
1839         if ($resrec->{'found'} eq "W") {
1840             if ($resrec->{'itemnumber'} eq $itemno) {
1841                 $resfound = 1;
1842             }
1843         } else {
1844             # FIXME - Use 'elsif' to avoid unnecessary indentation.
1845             if ($resrec->{'constrainttype'} eq "a") {
1846                 $resfound = 1;
1847             } else {
1848                         my $consth = $dbh->prepare("select * from reserveconstraints where borrowernumber = ? and reservedate = ? and biblionumber = ? and biblioitemnumber = ?");
1849                         $consth->execute($brn,$rdate,$bibno,$bibitm);
1850                         if (my $conrec = $consth->fetchrow_hashref) {
1851                                 if ($resrec->{'constrainttype'} eq "o") {
1852                                 $resfound = 1;
1853                                 }
1854                         }
1855                 $consth->finish;
1856                 }
1857         }
1858         if ($resfound) {
1859             my $updsth = $dbh->prepare("update reserves set found = 'W', itemnumber = ? where borrowernumber = ? and reservedate = ? and biblionumber = ?");
1860             $updsth->execute($itemno,$brn,$rdate,$bibno);
1861             $updsth->finish;
1862             # FIXME - "last;" here to break out of the loop early.
1863         }
1864     }
1865     $sth->finish;
1866     return ($resfound,$lastrec);
1867 }
1868
1869 sub fixdate {
1870     my ($year, $month, $day) = @_;
1871     my $invalidduedate;
1872     my $date;
1873     if ($year && $month && $day){
1874         if (($year eq 0 ) && ($month eq 0) && ($year eq 0)) {
1875 #       $env{'datedue'}='';
1876         } else {
1877             if (($year eq 0) || ($month eq 0) || ($year eq 0)) {
1878                 $invalidduedate=1;
1879             } else {
1880                 if (($day>30) && (($month==4) || ($month==6) || ($month==9) || ($month==11))) {
1881                     $invalidduedate = 1;
1882                 } 
1883                 elsif (($day > 29) && ($month == 2)) {
1884                     $invalidduedate=1;
1885                 } 
1886                 elsif (($month == 2) && ($day > 28) && (($year%4) && ((!($year%100) || ($year%400))))) {
1887                     $invalidduedate=1;
1888                 } 
1889                 else {
1890                 $date="$year-$month-$day";
1891                 }
1892             }
1893         }
1894     }
1895     return ($date, $invalidduedate);
1896         
1897 }
1898
1899 1;
1900 __END__
1901
1902 =back
1903
1904 =head1 AUTHOR
1905
1906 Koha Developement team <info@koha.org>
1907
1908 =cut