# -*- tab-width: 8 -*- # Please use 8-character tabs for this file (indents are every 4 characters) package C4::Circulation::Circ2; # $Id$ #package to deal with Returns #written 3/11/99 by olwen@katipo.co.nz # Copyright 2000-2002 Katipo Communications # # This file is part of Koha. # # Koha is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # Koha is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place, # Suite 330, Boston, MA 02111-1307 USA use strict; # use warnings; require Exporter; use DBI; use C4::Context; use C4::Stats; use C4::Reserves2; use C4::Koha; use C4::Accounts; use Date::Manip; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); # set the version for version checking $VERSION = 0.01; =head1 NAME C4::Circulation::Circ2 - Koha circulation module =head1 SYNOPSIS use C4::Circulation::Circ2; =head1 DESCRIPTION The functions in this module deal with circulation, issues, and returns, as well as general information about the library. Also deals with stocktaking. =head1 FUNCTIONS =over 2 =cut @ISA = qw(Exporter); @EXPORT = qw(&getpatroninformation ¤tissues &getissues &getiteminformation &renewstatus &renewbook &canbookbeissued &issuebook &returnbook &find_reserves &transferbook &decode &calc_charges &listitemsforinventory &itemseen &fixdate); # &getbranches &getprinters &getbranch &getprinter => moved to C4::Koha.pm =head2 itemseen &itemseen($itemnum) Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking C<$itemnum> is the item number =cut sub itemseen { my ($itemnum) = @_; my $dbh = C4::Context->dbh; my $sth = $dbh->prepare("update items set itemlost=0, datelastseen = now() where items.itemnumber = ?"); $sth->execute($itemnum); return; } sub listitemsforinventory { my ($minlocation,$maxlocation,$datelastseen,$offset,$size) = @_; my $dbh = C4::Context->dbh; 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"); $sth->execute($minlocation,$maxlocation,$datelastseen); my @results; while (my $row = $sth->fetchrow_hashref) { $offset-- if ($offset); if ((!$offset) && $size) { push @results,$row; $size--; } } return \@results; } =head2 getpatroninformation ($borrower, $flags) = &getpatroninformation($env, $borrowernumber, $cardnumber); Looks up a patron and returns information about him or her. If C<$borrowernumber> is true (nonzero), C<&getpatroninformation> looks up the borrower by number; otherwise, it looks up the borrower by card number. C<$env> is effectively ignored, but should be a reference-to-hash. C<$borrower> is a reference-to-hash whose keys are the fields of the borrowers table in the Koha database. In addition, C<$borrower-E{flags}> is a hash giving more detailed information about the patron. Its keys act as flags : if $borrower->{flags}->{LOST} { # Patron's card was reported lost } Each flag has a C key, giving a human-readable explanation of the flag. If the state of a flag means that the patron should not be allowed to borrow any more books, then it will have a C key with a true value. The possible flags are: =head3 CHARGES =over 4 Shows the patron's credit or debt, if any. =back =head3 GNA =over 4 (Gone, no address.) Set if the patron has left without giving a forwarding address. =back =head3 LOST =over 4 Set if the patron's card has been reported as lost. =back =head3 DBARRED =over 4 Set if the patron has been debarred. =back =head3 NOTES =over 4 Any additional notes about the patron. =back =head3 ODUES =over 4 Set if the patron has overdue items. This flag has several keys: C<$flags-E{ODUES}{itemlist}> is a reference-to-array listing the overdue items. Its elements are references-to-hash, each describing an overdue item. The keys are selected fields from the issues, biblio, biblioitems, and items tables of the Koha database. C<$flags-E{ODUES}{itemlist}> is a string giving a text listing of the overdue items, one per line. =back =head3 WAITING =over 4 Set if any items that the patron has reserved are available. C<$flags-E{WAITING}{itemlist}> is a reference-to-array listing the available items. Each element is a reference-to-hash whose keys are fields from the reserves table of the Koha database. =back =back =cut sub getpatroninformation { # returns my ($env, $borrowernumber,$cardnumber) = @_; my $dbh = C4::Context->dbh; my $query; my $sth; if ($borrowernumber) { $sth = $dbh->prepare("select * from borrowers where borrowernumber=?"); $sth->execute($borrowernumber); } elsif ($cardnumber) { $sth = $dbh->prepare("select * from borrowers where cardnumber=?"); $sth->execute($cardnumber); } else { $env->{'apierror'} = "invalid borrower information passed to getpatroninformation subroutine"; return(); } my $borrower = $sth->fetchrow_hashref; my $amount = checkaccount($env, $borrowernumber, $dbh); $borrower->{'amountoutstanding'} = $amount; my $flags = patronflags($env, $borrower, $dbh); my $accessflagshash; $sth=$dbh->prepare("select bit,flag from userflags"); $sth->execute; while (my ($bit, $flag) = $sth->fetchrow) { if ($borrower->{'flags'} && $borrower->{'flags'} & 2**$bit) { $accessflagshash->{$flag}=1; } } $sth->finish; $borrower->{'flags'}=$flags; $borrower->{'authflags'} = $accessflagshash; return ($borrower); #, $flags, $accessflagshash); } =head2 decode =over 4 =head3 $str = &decode($chunk); =over 4 Decodes a segment of a string emitted by a CueCat barcode scanner and returns it. =back =back =cut # FIXME - At least, I'm pretty sure this is for decoding CueCat stuff. sub decode { my ($encoded) = @_; my $seq = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-'; my @s = map { index($seq,$_); } split(//,$encoded); my $l = ($#s+1) % 4; if ($l) { if ($l == 1) { print "Error!"; return; } $l = 4-$l; $#s += $l; } my $r = ''; while ($#s >= 0) { my $n = (($s[0] << 6 | $s[1]) << 6 | $s[2]) << 6 | $s[3]; $r .=chr(($n >> 16) ^ 67) . chr(($n >> 8 & 255) ^ 67) . chr(($n & 255) ^ 67); @s = @s[4..$#s]; } $r = substr($r,0,length($r)-$l); return $r; } =head2 getiteminformation =over 4 $item = &getiteminformation($env, $itemnumber, $barcode); Looks up information about an item, given either its item number or its barcode. If C<$itemnumber> is a nonzero value, it is used; otherwise, C<$barcode> is used. C<$env> is effectively ignored, but should be a reference-to-hash. C<$item> is a reference-to-hash whose keys are fields from the biblio, items, and biblioitems tables of the Koha database. It may also contain the following keys: =head3 date_due =over 4 The due date on this item, if it has been borrowed and not returned yet. The date is in YYYY-MM-DD format. =back =head3 notforloan =over 4 True if the item may not be borrowed. =back =back =cut sub getiteminformation { # returns a hash of item information given either the itemnumber or the barcode my ($env, $itemnumber, $barcode) = @_; my $dbh = C4::Context->dbh; my $sth; if ($itemnumber) { $sth=$dbh->prepare("select * from biblio,items,biblioitems where items.itemnumber=? and biblio.biblionumber=items.biblionumber and biblioitems.biblioitemnumber = items.biblioitemnumber"); $sth->execute($itemnumber); } elsif ($barcode) { $sth=$dbh->prepare("select * from biblio,items,biblioitems where items.barcode=? and biblio.biblionumber=items.biblionumber and biblioitems.biblioitemnumber = items.biblioitemnumber"); $sth->execute($barcode); } else { $env->{'apierror'}="getiteminformation() subroutine must be called with either an itemnumber or a barcode"; # Error condition. return(); } my $iteminformation=$sth->fetchrow_hashref; $sth->finish; if ($iteminformation) { $sth=$dbh->prepare("select date_due from issues where itemnumber=? and isnull(returndate)"); $sth->execute($iteminformation->{'itemnumber'}); my ($date_due) = $sth->fetchrow; $iteminformation->{'date_due'}=$date_due; $sth->finish; ($iteminformation->{'dewey'} == 0) && ($iteminformation->{'dewey'}=''); $sth=$dbh->prepare("select * from itemtypes where itemtype=?"); $sth->execute($iteminformation->{'itemtype'}); my $itemtype=$sth->fetchrow_hashref; # if specific item notforloan, don't use itemtype notforloan field. # otherwise, use itemtype notforloan value to see if item can be issued. $iteminformation->{'notforloan'}=$itemtype->{'notforloan'} unless $iteminformation->{'notforloan'}; $sth->finish; } return($iteminformation); } =head2 transferbook =over 4 ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, $barcode, $ignore_reserves); Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer. C<$newbranch> is the code for the branch to which the item should be transferred. C<$barcode> is the barcode of the item to be transferred. If C<$ignore_reserves> is true, C<&transferbook> ignores reserves. Otherwise, if an item is reserved, the transfer fails. Returns three values: =head3 $dotransfer is true if the transfer was successful. =head3 $messages is a reference-to-hash which may have any of the following keys: =over 4 C There is no item in the catalog with the given barcode. The value is C<$barcode>. C 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. C 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. C 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. C 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. It also has the key C, whose value is either C or C. C The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored. =back =back =back =cut #' # FIXME - This function tries to do too much, and its API is clumsy. # If it didn't also return books, it could be used to change the home # branch of a book while the book is on loan. # # Is there any point in returning the item information? The caller can # look that up elsewhere if ve cares. # # This leaves the ($dotransfer, $messages) tuple. This seems clumsy. # If the transfer succeeds, that's all the caller should need to know. # Thus, this function could simply return 1 or 0 to indicate success # or failure, and set $C4::Circulation::Circ2::errmsg in case of # failure. Or this function could return undef if successful, and an # error message in case of failure (this would feel more like C than # Perl, though). sub transferbook { # transfer book code.... my ($tbr, $barcode, $ignoreRs) = @_; my $messages; my %env; my $dotransfer = 1; my $branches = getbranches(); my $iteminformation = getiteminformation(\%env, 0, $barcode); # bad barcode.. if (not $iteminformation) { $messages->{'BadBarcode'} = $barcode; $dotransfer = 0; } # get branches of book... my $hbr = $iteminformation->{'homebranch'}; my $fbr = $iteminformation->{'holdingbranch'}; # if is permanent... if ($hbr && $branches->{$hbr}->{'PE'}) { $messages->{'IsPermanent'} = $hbr; } # can't transfer book if is already there.... # FIXME - Why not? Shouldn't it trivially succeed? if ($fbr eq $tbr) { $messages->{'DestinationEqualsHolding'} = 1; $dotransfer = 0; } # check if it is still issued to someone, return it... my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'}); if ($currentborrower) { returnbook($barcode, $fbr); $messages->{'WasReturned'} = $currentborrower; } # find reserves..... # FIXME - Don't call &CheckReserves unless $ignoreRs is true. # That'll save a database query. my ($resfound, $resrec) = CheckReserves($iteminformation->{'itemnumber'}); if ($resfound and not $ignoreRs) { $resrec->{'ResFound'} = $resfound; $messages->{'ResFound'} = $resrec; $dotransfer = 0; } #actually do the transfer.... if ($dotransfer) { dotransfer($iteminformation->{'itemnumber'}, $fbr, $tbr); $messages->{'WasTransfered'} = 1; } return ($dotransfer, $messages, $iteminformation); } # Not exported # FIXME - This is only used in &transferbook. Why bother making it a # separate function? sub dotransfer { my ($itm, $fbr, $tbr) = @_; my $dbh = C4::Context->dbh; $itm = $dbh->quote($itm); $fbr = $dbh->quote($fbr); $tbr = $dbh->quote($tbr); #new entry in branchtransfers.... $dbh->do("INSERT INTO branchtransfers (itemnumber, frombranch, datearrived, tobranch) VALUES ($itm, $fbr, now(), $tbr)"); #update holdingbranch in items ..... $dbh->do("UPDATE items set holdingbranch = $tbr WHERE items.itemnumber = $itm"); &itemseen($itm); return; } =head2 canbookbeissued Check if a book can be issued. my ($issuingimpossible,$needsconfirmation) = canbookbeissued($env,$borrower,$barcode,$year,$month,$day); =over 4 C<$env> Environment variable. Should be empty usually, but used by other subs. Next code cleaning could drop it. C<$borrower> hash with borrower informations (from getpatroninformation) C<$barcode> is the bar code of the book being issued. C<$year> C<$month> C<$day> contains the date of the return (in case it's forced by "stickyduedate". =back Returns : =over 4 C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible. Possible values are : =head3 INVALID_DATE sticky due date is invalid =head3 GNA borrower gone with no address =head3 CARD_LOST borrower declared it's card lost =head3 DEBARRED borrower debarred =head3 UNKNOWN_BARCODE barcode unknown =head3 NOT_FOR_LOAN item is not for loan =head3 WTHDRAWN item withdrawn. =head3 RESTRICTED item is restricted (set by ??) =back C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible. Possible values are : =head3 DEBT borrower has debts. =head3 RENEW_ISSUE renewing, not issuing =head3 ISSUED_TO_ANOTHER issued to someone else. =head3 RESERVED reserved for someone else. =head3 INVALID_DATE sticky due date is invalid =head3 TOO_MANY if the borrower borrows to much things =cut # check if a book can be issued. # returns an array with errors if any sub TooMany ($$){ my $borrower = shift; my $iteminformation = shift; my $cat_borrower = $borrower->{'categorycode'}; my $branch_borrower = $borrower->{'branchcode'}; my $dbh = C4::Context->dbh; my $sth = $dbh->prepare('select itemtype from biblioitems where biblionumber = ?'); $sth->execute($iteminformation->{'biblionumber'}); my $type = $sth->fetchrow; $sth = $dbh->prepare('select * from issuingrules where categorycode = ? and itemtype = ? and branchcode = ?'); 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 ?"); my $sth3 = $dbh->prepare('select COUNT(*) from issues where borrowernumber = ? and returndate is null'); my $alreadyissued; # check the 3 parameters $sth->execute($cat_borrower, $type, $branch_borrower); my $result = $sth->fetchrow_hashref; # warn "==>".$result->{maxissueqty}; if (defined($result)) { $sth2->execute($borrower->{'borrowernumber'}, "%$type%"); my $alreadyissued = $sth2->fetchrow; return ("a $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } # check for branch=* $sth->execute($cat_borrower, $type, ""); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth2->execute($borrower->{'borrowernumber'}, "%$type%"); my $alreadyissued = $sth2->fetchrow; return ("b $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } # check for itemtype=* $sth->execute($cat_borrower, "*", $branch_borrower); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth3->execute($borrower->{'borrowernumber'}); my ($alreadyissued) = $sth3->fetchrow; warn "HERE : $alreadyissued / ($result->{maxissueqty} for $borrower->{'borrowernumber'}"; return ("c $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } #check for borrowertype=* $sth->execute("*", $type, $branch_borrower); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth2->execute($borrower->{'borrowernumber'}, "%$type%"); my $alreadyissued = $sth2->fetchrow; return ("d $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } $sth->execute("*", "*", $branch_borrower); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth3->execute($borrower->{'borrowernumber'}); my $alreadyissued = $sth3->fetchrow; return ("e $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } $sth->execute("*", $type, ""); my $result = $sth->fetchrow_hashref; if (defined($result) && $result->{maxissueqty}>=0) { $sth2->execute($borrower->{'borrowernumber'}, "%$type%"); my $alreadyissued = $sth2->fetchrow; return ("f $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } $sth->execute($cat_borrower, "*", ""); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth2->execute($borrower->{'borrowernumber'}, "%$type%"); my $alreadyissued = $sth2->fetchrow; return ("g $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } $sth->execute("*", "*", ""); my $result = $sth->fetchrow_hashref; if (defined($result)) { $sth3->execute($borrower->{'borrowernumber'}); my $alreadyissued = $sth3->fetchrow; return ("h $alreadyissued / ".($result->{maxissueqty}+0)) if ($result->{'maxissueqty'} <= $alreadyissued); } return; } sub canbookbeissued { my ($env,$borrower,$barcode,$year,$month,$day) = @_; my %needsconfirmation; # filled with problems that needs confirmations my %issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE my $iteminformation = getiteminformation($env, 0, $barcode); my $dbh = C4::Context->dbh; # # DUE DATE is OK ? # my ($duedate, $invalidduedate) = fixdate($year, $month, $day); $issuingimpossible{INVALID_DATE} = 1 if ($invalidduedate); # # BORROWER STATUS # if ($borrower->{flags}->{GNA}) { $issuingimpossible{GNA} = 1; } if ($borrower->{flags}->{'LOST'}) { $issuingimpossible{CARD_LOST} = 1; } if ($borrower->{flags}->{'DBARRED'}) { $issuingimpossible{DEBARRED} = 1; } if (&Date_Cmp(&ParseDate($borrower->{expiry}),&ParseDate("today"))<0) { $issuingimpossible{EXPIRED} = 1; } # # BORROWER STATUS # # DEBTS my $amount = checkaccount($env,$borrower->{'borrowernumber'}, $dbh,$duedate); if ($amount >0) { $needsconfirmation{DEBT} = $amount; } # # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS # my $toomany = TooMany($borrower, $iteminformation); $needsconfirmation{TOO_MANY} = $toomany if $toomany; # # ITEM CHECKING # unless ($iteminformation->{barcode}) { $issuingimpossible{UNKNOWN_BARCODE} = 1; } if ($iteminformation->{'notforloan'} > 0) { $issuingimpossible{NOT_FOR_LOAN} = 1; } if ($iteminformation->{'itemtype'} &&$iteminformation->{'itemtype'} eq 'REF') { $issuingimpossible{NOT_FOR_LOAN} = 1; } if ($iteminformation->{'wthdrawn'} && $iteminformation->{'wthdrawn'} == 1) { $issuingimpossible{WTHDRAWN} = 1; } if ($iteminformation->{'restricted'} && $iteminformation->{'restricted'} == 1) { $issuingimpossible{RESTRICTED} = 1; } # # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER # my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'}); if ($currentborrower && $currentborrower eq $borrower->{'borrowernumber'}) { # Already issued to current borrower. Ask whether the loan should # be renewed. my ($renewstatus) = renewstatus($env, $borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}); if ($renewstatus == 0) { # no more renewals allowed $issuingimpossible{NO_MORE_RENEWALS} = 1; } else { $needsconfirmation{RENEW_ISSUE} = 1; } } elsif ($currentborrower) { # issued to someone else my $currborinfo = getpatroninformation(0,$currentborrower); # warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})"; $needsconfirmation{ISSUED_TO_ANOTHER} = "$currborinfo->{'reservedate'} : $currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})"; } # See if the item is on reserve. my ($restype, $res) = CheckReserves($iteminformation->{'itemnumber'}); if ($restype) { my $resbor = $res->{'borrowernumber'}; if ($resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting") { # The item is on reserve and waiting, but has been # reserved by some other patron. my ($resborrower, $flags)=getpatroninformation($env, $resbor,0); my $branches = getbranches(); my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'}; $needsconfirmation{RESERVE_WAITING} = "$resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'}, $branchname)"; # CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'}); Doesn't belong in a checking subroutine. } elsif ($restype eq "Reserved") { # The item is on reserve for someone else. my ($resborrower, $flags)=getpatroninformation($env, $resbor,0); my $branches = getbranches(); my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'}; $needsconfirmation{RESERVED} = "$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})"; } } return(\%issuingimpossible,\%needsconfirmation); } =head2 issuebook Issue a book. Does no check, they are done in canbookbeissued. If we reach this sub, it means the user confirmed if needed. &issuebook($env,$borrower,$barcode,$date) =over 4 C<$env> Environment variable. Should be empty usually, but used by other subs. Next code cleaning could drop it. C<$borrower> hash with borrower informations (from getpatroninformation) C<$barcode> is the bar code of the book being issued. C<$date> contains the max date of return. calculated if empty. =cut # # issuing book. We already have checked it can be issued, so, just issue it ! # sub issuebook { my ($env,$borrower,$barcode,$date,$cancelreserve) = @_; my $dbh = C4::Context->dbh; # my ($borrower, $flags) = &getpatroninformation($env, $borrowernumber, 0); my $iteminformation = getiteminformation($env, 0, $barcode); # warn "B : ".$borrower->{borrowernumber}." / I : ".$iteminformation->{'itemnumber'}; # # check if we just renew the issue. # my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'}); if ($currentborrower eq $borrower->{'borrowernumber'}) { my ($charge,$itemtype) = calc_charges($env, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}); if ($charge > 0) { createcharge($env, $dbh, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}, $charge); $iteminformation->{'charge'} = $charge; } &UpdateStats($env,$env->{'branchcode'},'renew',$charge,'',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'}); renewbook($env, $borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}); } else { # # NOT a renewal # if ($currentborrower ne '') { # This book is currently on loan, but not to the person # who wants to borrow it now. mark it returned before issuing to the new borrower returnbook($iteminformation->{'barcode'}, $env->{'branchcode'}); } # See if the item is on reserve. my ($restype, $res) = CheckReserves($iteminformation->{'itemnumber'}); if ($restype) { my $resbor = $res->{'borrowernumber'}; if ($resbor eq $borrower->{'borrowernumber'}) { # The item is on reserve to the current patron FillReserve($res); warn "FillReserve"; } elsif ($restype eq "Waiting") { warn "Waiting"; # The item is on reserve and waiting, but has been # reserved by some other patron. my ($resborrower, $flags)=getpatroninformation($env, $resbor,0); my $branches = getbranches(); my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'}; if ($cancelreserve){ CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'}); } } elsif ($restype eq "Reserved") { warn "Reserved"; # The item is on reserve for someone else. my ($resborrower, $flags)=getpatroninformation($env, $resbor,0); my $branches = getbranches(); my $branchname = $branches->{$res->{'branchcode'}}->{'branchname'}; if ($cancelreserve) { # cancel reserves on this item CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'}); # also cancel reserve on biblio related to this item #my $st_Fbiblio = $dbh->prepare("select biblionumber from items where itemnumber=?"); #$st_Fbiblio->execute($res->{'itemnumber'}); #my $biblionumber = $st_Fbiblio->fetchrow; #CancelReserve($biblionumber,0,$res->{'borrowernumber'}); #warn "CancelReserve $res->{'itemnumber'}, $res->{'borrowernumber'}"; } else { # my $tobrcd = ReserveWaiting($res->{'itemnumber'}, $res->{'borrowernumber'}); # transferbook($tobrcd,$barcode, 1); warn "transferbook"; } } } # Record in the database the fact that the book was issued. my $sth=$dbh->prepare("insert into issues (borrowernumber, itemnumber, date_due, branchcode) values (?,?,?,?)"); my $loanlength = getLoanLength($borrower->{'categorycode'},$iteminformation->{'itemtype'},$borrower->{'branchcode'}); my $datedue=time+($loanlength)*86400; my @datearr = localtime($datedue); my $dateduef = (1900+$datearr[5])."-".($datearr[4]+1)."-".$datearr[3]; if ($date) { $dateduef=$date; } # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate if (C4::Context->preference('ReturnBeforeExpiry') && $dateduef gt $borrower->{expiry}) { $dateduef=$borrower->{expiry}; } $sth->execute($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}, $dateduef, $env->{'branchcode'}); $sth->finish; $iteminformation->{'issues'}++; $sth=$dbh->prepare("update items set issues=? where itemnumber=?"); $sth->execute($iteminformation->{'issues'},$iteminformation->{'itemnumber'}); $sth->finish; &itemseen($iteminformation->{'itemnumber'}); # If it costs to borrow this book, charge it to the patron's account. my ($charge,$itemtype)=calc_charges($env, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}); if ($charge > 0) { createcharge($env, $dbh, $iteminformation->{'itemnumber'}, $borrower->{'borrowernumber'}, $charge); $iteminformation->{'charge'}=$charge; } # Record the fact that this book was issued. &UpdateStats($env,$env->{'branchcode'},'issue',$charge,'',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'}); } } =head2 getLoanLength Get loan length for an itemtype, a borrower type and a branch my $loanlength = &getLoanLength($borrowertype,$itemtype,branchcode) =cut sub getLoanLength { my ($borrowertype,$itemtype,$branchcode) = @_; my $dbh = C4::Context->dbh; my $sth = $dbh->prepare("select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=?"); # try to find issuelength & return the 1st available. # check with borrowertype, itemtype and branchcode, then without one of those parameters $sth->execute($borrowertype,$itemtype,$branchcode); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute($borrowertype,$itemtype,""); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute($borrowertype,"*",$branchcode); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute("*",$itemtype,$branchcode); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute($borrowertype,"*",""); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute("*","*",$branchcode); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute("*",$itemtype,""); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); $sth->execute("*","*",""); my $loanlength = $sth->fetchrow_hashref; return $loanlength->{issuelength} if defined($loanlength); # if no rule is set => 21 days (hardcoded) return 21; } =head2 returnbook ($doreturn, $messages, $iteminformation, $borrower) = &returnbook($barcode, $branch); Returns a book. C<$barcode> is the bar code of the book being returned. C<$branch> is the code of the branch where the book is being returned. C<&returnbook> returns a list of four items: C<$doreturn> is true iff the return succeeded. C<$messages> is a reference-to-hash giving the reason for failure: =over 4 =item C No item with this barcode exists. The value is C<$barcode>. =item C The book is not currently on loan. The value is C<$barcode>. =item C The book's home branch is a permanent collection. If you have borrowed this book, you are not allowed to return it. The value is the code for the book's home branch. =item C This book has been withdrawn/cancelled. The value should be ignored. =item C 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. It also has the key C, whose value is either C, C, or 0. =back C<$borrower> is a reference-to-hash, giving information about the patron who last borrowed the book. =cut # FIXME - This API is bogus. There's no need to return $borrower and # $iteminformation; the caller can ask about those separately, if it # cares (it'd be inefficient to make two database calls instead of # one, but &getpatroninformation and &getiteminformation can be # memoized if this is an issue). # # The ($doreturn, $messages) tuple is redundant: if the return # succeeded, that's all the caller needs to know. So &returnbook can # return 1 and 0 on success and failure, and set # $C4::Circulation::Circ2::errmsg to indicate the error. Or it can # return undef for success, and an error message on error (though this # is more C-ish than Perl-ish). sub returnbook { my ($barcode, $branch) = @_; my %env; my $messages; my $dbh = C4::Context->dbh; my $doreturn = 1; die '$branch not defined' unless defined $branch; # just in case (bug 170) # get information on item my ($iteminformation) = getiteminformation(\%env, 0, $barcode); if (not $iteminformation) { $messages->{'BadBarcode'} = $barcode; $doreturn = 0; } # find the borrower my ($currentborrower) = currentborrower($iteminformation->{'itemnumber'}); if ((not $currentborrower) && $doreturn) { $messages->{'NotIssued'} = $barcode; $doreturn = 0; } # check if the book is in a permanent collection.... my $hbr = $iteminformation->{'homebranch'}; my $branches = getbranches(); if ($hbr && $branches->{$hbr}->{'PE'}) { $messages->{'IsPermanent'} = $hbr; } # check that the book has been cancelled if ($iteminformation->{'wthdrawn'}) { $messages->{'wthdrawn'} = 1; $doreturn = 0; } # update issues, thereby returning book (should push this out into another subroutine my ($borrower) = getpatroninformation(\%env, $currentborrower, 0); if ($doreturn) { my $sth = $dbh->prepare("update issues set returndate = now() where (borrowernumber = ?) and (itemnumber = ?) and (returndate is null)"); $sth->execute($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}); $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right? } itemseen($iteminformation->{'itemnumber'}); ($borrower) = getpatroninformation(\%env, $currentborrower, 0); # transfer book to the current branch my ($transfered, $mess, $item) = transferbook($branch, $barcode, 1); if ($transfered) { $messages->{'WasTransfered'} = 1; # FIXME is the "= 1" right? } # fix up the accounts..... if ($iteminformation->{'itemlost'}) { fixaccountforlostandreturned($iteminformation, $borrower); $messages->{'WasLost'} = 1; # FIXME is the "= 1" right? } # fix up the overdues in accounts... fixoverduesonreturn($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'}); # find reserves..... my ($resfound, $resrec) = CheckReserves($iteminformation->{'itemnumber'}); if ($resfound) { # my $tobrcd = ReserveWaiting($resrec->{'itemnumber'}, $resrec->{'borrowernumber'}); $resrec->{'ResFound'} = $resfound; $messages->{'ResFound'} = $resrec; } # update stats? # Record the fact that this book was returned. UpdateStats(\%env, $branch ,'return','0','',$iteminformation->{'itemnumber'},$iteminformation->{'itemtype'},$borrower->{'borrowernumber'}); return ($doreturn, $messages, $iteminformation, $borrower); } =head2 fixaccountforlostandreturned &fixaccountforlostandreturned($iteminfo,$borrower); Calculates the charge for a book lost and returned (Not exported & used only once) C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used. C<$borrower> is a hashref to borrower. Only {borrowernumber is used. =cut sub fixaccountforlostandreturned { my ($iteminfo, $borrower) = @_; my %env; my $dbh = C4::Context->dbh; my $itm = $iteminfo->{'itemnumber'}; # check for charge made for lost book my $sth = $dbh->prepare("select * from accountlines where (itemnumber = ?) and (accounttype='L' or accounttype='Rep') order by date desc"); $sth->execute($itm); if (my $data = $sth->fetchrow_hashref) { # writeoff this amount my $offset; my $amount = $data->{'amount'}; my $acctno = $data->{'accountno'}; my $amountleft; if ($data->{'amountoutstanding'} == $amount) { $offset = $data->{'amount'}; $amountleft = 0; } else { $offset = $amount - $data->{'amountoutstanding'}; $amountleft = $data->{'amountoutstanding'} - $amount; } my $usth = $dbh->prepare("update accountlines set accounttype = 'LR',amountoutstanding='0' where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?) "); $usth->execute($data->{'borrowernumber'},$itm,$acctno); $usth->finish; #check if any credit is left if so writeoff other accounts my $nextaccntno = getnextacctno(\%env,$data->{'borrowernumber'},$dbh); if ($amountleft < 0){ $amountleft*=-1; } if ($amountleft > 0){ my $msth = $dbh->prepare("select * from accountlines where (borrowernumber = ?) and (amountoutstanding >0) order by date"); $msth->execute($data->{'borrowernumber'}); # offset transactions my $newamtos; my $accdata; while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){ if ($accdata->{'amountoutstanding'} < $amountleft) { $newamtos = 0; $amountleft -= $accdata->{'amountoutstanding'}; } else { $newamtos = $accdata->{'amountoutstanding'} - $amountleft; $amountleft = 0; } my $thisacct = $accdata->{'accountno'}; my $usth = $dbh->prepare("update accountlines set amountoutstanding= ? where (borrowernumber = ?) and (accountno=?)"); $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct'); $usth->finish; $usth = $dbh->prepare("insert into accountoffsets (borrowernumber, accountno, offsetaccount, offsetamount) values (?,?,?,?)"); $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos); $usth->finish; } $msth->finish; } if ($amountleft > 0){ $amountleft*=-1; } my $desc="Book Returned ".$iteminfo->{'barcode'}; $usth = $dbh->prepare("insert into accountlines (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding) values (?,?,now(),?,?,'CR',?)"); $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft); $usth->finish; $usth = $dbh->prepare("insert into accountoffsets (borrowernumber, accountno, offsetaccount, offsetamount) values (?,?,?,?)"); $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset); $usth->finish; $usth = $dbh->prepare("update items set paidfor='' where itemnumber=?"); $usth->execute($itm); $usth->finish; } $sth->finish; return; } =head2 fixoverdueonreturn &fixoverdueonreturn($brn,$itm); ?? C<$brn> borrowernumber C<$itm> itemnumber =cut sub fixoverduesonreturn { my ($brn, $itm) = @_; my $dbh = C4::Context->dbh; # check for overdue fine my $sth = $dbh->prepare("select * from accountlines where (borrowernumber = ?) and (itemnumber = ?) and (accounttype='FU' or accounttype='O')"); $sth->execute($brn,$itm); # alter fine to show that the book has been returned if (my $data = $sth->fetchrow_hashref) { my $usth=$dbh->prepare("update accountlines set accounttype='F' where (borrowernumber = ?) and (itemnumber = ?) and (acccountno = ?)"); $usth->execute($brn,$itm,$data->{'accountno'}); $usth->finish(); } $sth->finish(); return; } # Not exported # # NOTE!: If you change this function, be sure to update the POD for # &getpatroninformation. # # $flags = &patronflags($env, $patron, $dbh); # # $flags->{CHARGES} # {message} Message showing patron's credit or debt # {noissues} Set if patron owes >$5.00 # {GNA} Set if patron gone w/o address # {message} "Borrower has no valid address" # {noissues} Set. # {LOST} Set if patron's card reported lost # {message} Message to this effect # {noissues} Set. # {DBARRED} Set is patron is debarred # {message} Message to this effect # {noissues} Set. # {NOTES} Set if patron has notes # {message} Notes about patron # {ODUES} Set if patron has overdue books # {message} "Yes" # {itemlist} ref-to-array: list of overdue books # {itemlisttext} Text list of overdue items # {WAITING} Set if there are items available that the # patron reserved # {message} Message to this effect # {itemlist} ref-to-array: list of available items sub patronflags { # Original subroutine for Circ2.pm my %flags; my ($env, $patroninformation, $dbh) = @_; my $amount = checkaccount($env, $patroninformation->{'borrowernumber'}, $dbh); if ($amount > 0) { my %flaginfo; my $noissuescharge = C4::Context->preference("noissuescharge"); $flaginfo{'message'}= sprintf "Patron owes \$%.02f", $amount; if ($amount > $noissuescharge) { $flaginfo{'noissues'} = 1; } $flags{'CHARGES'} = \%flaginfo; } elsif ($amount < 0){ my %flaginfo; $flaginfo{'message'} = sprintf "Patron has credit of \$%.02f", -$amount; $flags{'CHARGES'} = \%flaginfo; } if ($patroninformation->{'gonenoaddress'} == 1) { my %flaginfo; $flaginfo{'message'} = 'Borrower has no valid address.'; $flaginfo{'noissues'} = 1; $flags{'GNA'} = \%flaginfo; } if ($patroninformation->{'lost'} == 1) { my %flaginfo; $flaginfo{'message'} = 'Borrower\'s card reported lost.'; $flaginfo{'noissues'} = 1; $flags{'LOST'} = \%flaginfo; } if ($patroninformation->{'debarred'} == 1) { my %flaginfo; $flaginfo{'message'} = 'Borrower is Debarred.'; $flaginfo{'noissues'} = 1; $flags{'DBARRED'} = \%flaginfo; } if ($patroninformation->{'borrowernotes'}) { my %flaginfo; $flaginfo{'message'} = "$patroninformation->{'borrowernotes'}"; $flags{'NOTES'} = \%flaginfo; } my ($odues, $itemsoverdue) = checkoverdues($env, $patroninformation->{'borrowernumber'}, $dbh); if ($odues > 0) { my %flaginfo; $flaginfo{'message'} = "Yes"; $flaginfo{'itemlist'} = $itemsoverdue; foreach (sort {$a->{'date_due'} cmp $b->{'date_due'}} @$itemsoverdue) { $flaginfo{'itemlisttext'}.="$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n"; } $flags{'ODUES'} = \%flaginfo; } my ($nowaiting, $itemswaiting) = CheckWaiting($patroninformation->{'borrowernumber'}); if ($nowaiting > 0) { my %flaginfo; $flaginfo{'message'} = "Reserved items available"; $flaginfo{'itemlist'} = $itemswaiting; $flags{'WAITING'} = \%flaginfo; } return(\%flags); } # Not exported sub checkoverdues { # From Main.pm, modified to return a list of overdueitems, in addition to a count #checks whether a borrower has overdue items my ($env, $bornum, $dbh)=@_; my @datearr = localtime; my $today = ($datearr[5] + 1900)."-".($datearr[4]+1)."-".$datearr[3]; my @overdueitems; my $count = 0; my $sth = $dbh->prepare("SELECT * FROM issues,biblio,biblioitems,items WHERE items.biblioitemnumber = biblioitems.biblioitemnumber AND items.biblionumber = biblio.biblionumber AND issues.itemnumber = items.itemnumber AND issues.borrowernumber = ? AND issues.returndate is NULL AND issues.date_due < ?"); $sth->execute($bornum,$today); while (my $data = $sth->fetchrow_hashref) { push (@overdueitems, $data); $count++; } $sth->finish; return ($count, \@overdueitems); } # Not exported sub currentborrower { # Original subroutine for Circ2.pm my ($itemnumber) = @_; my $dbh = C4::Context->dbh; my $q_itemnumber = $dbh->quote($itemnumber); my $sth=$dbh->prepare("select borrowers.borrowernumber from issues,borrowers where issues.itemnumber=$q_itemnumber and issues.borrowernumber=borrowers.borrowernumber and issues.returndate is NULL"); $sth->execute; my ($borrower) = $sth->fetchrow; return($borrower); } # FIXME - Not exported, but used in 'updateitem.pl' anyway. sub checkreserve_to_delete { # Stolen from Main.pm # Check for reserves for biblio my ($env,$dbh,$itemnum)=@_; my $resbor = ""; my $sth = $dbh->prepare("select * from reserves,items where (items.itemnumber = ?) and (reserves.cancellationdate is NULL) and (items.biblionumber = reserves.biblionumber) and ((reserves.found = 'W') or (reserves.found is null)) order by priority"); $sth->execute($itemnum); my $resrec; my $data=$sth->fetchrow_hashref; while ($data && $resbor eq '') { $resrec=$data; my $const = $data->{'constrainttype'}; if ($const eq "a") { $resbor = $data->{'borrowernumber'}; } else { my $found = 0; my $csth = $dbh->prepare("select * from reserveconstraints,items where (borrowernumber=?) and reservedate=? and reserveconstraints.biblionumber=? and (items.itemnumber=? and items.biblioitemnumber = reserveconstraints.biblioitemnumber)"); $csth->execute($data->{'borrowernumber'},$data->{'biblionumber'},$data->{'reservedate'},$itemnum); if (my $cdata=$csth->fetchrow_hashref) {$found = 1;} if ($const eq 'o') { if ($found eq 1) {$resbor = $data->{'borrowernumber'};} } else { if ($found eq 0) {$resbor = $data->{'borrowernumber'};} } $csth->finish(); } $data=$sth->fetchrow_hashref; } $sth->finish; return ($resbor,$resrec); } =head2 currentissues $issues = ¤tissues($env, $borrower); Returns a list of books currently on loan to a patron. If C<$env-E{todaysissues}> is set and true, C<¤tissues> only returns information about books issued today. If C<$env-E{nottodaysissues}> is set and true, C<¤tissues> only returns information about books issued before today. If both are specified, C<$env-E{todaysissues}> is ignored. If neither is specified, C<¤tissues> returns all of the patron's issues. C<$borrower->{borrowernumber}> is the borrower number of the patron whose issues we want to list. C<¤tissues> returns a PHP-style array: C<$issues> is a reference-to-hash whose keys are integers in the range 1...I, where I is the number of items on issue (either today or before today). C<$issues-E{I}> is a reference-to-hash whose keys are all of the fields of the biblio, biblioitems, items, and issues fields of the Koha database for that particular item. =cut #' sub currentissues { # New subroutine for Circ2.pm my ($env, $borrower) = @_; my $dbh = C4::Context->dbh; my %currentissues; my $counter=1; my $borrowernumber = $borrower->{'borrowernumber'}; my $crit=''; # Figure out whether to get the books issued today, or earlier. # FIXME - $env->{todaysissues} and $env->{nottodaysissues} can # both be specified, but are mutually-exclusive. This is bogus. # Make this a flag. Or better yet, return everything in (reverse) # chronological order and let the caller figure out which books # were issued today. if ($env->{'todaysissues'}) { # FIXME - Could use # $today = POSIX::strftime("%Y%m%d", localtime); # FIXME - Since $today will be used in either case, move it # out of the two if-blocks. my @datearr = localtime(time()); my $today = (1900+$datearr[5]).sprintf "%02d", ($datearr[4]+1).sprintf "%02d", $datearr[3]; # FIXME - MySQL knows about dates. Just use # and issues.timestamp = curdate(); $crit=" and issues.timestamp like '$today%' "; } if ($env->{'nottodaysissues'}) { # FIXME - Could use # $today = POSIX::strftime("%Y%m%d", localtime); # FIXME - Since $today will be used in either case, move it # out of the two if-blocks. my @datearr = localtime(time()); my $today = (1900+$datearr[5]).sprintf "%02d", ($datearr[4]+1).sprintf "%02d", $datearr[3]; # FIXME - MySQL knows about dates. Just use # and issues.timestamp < curdate(); $crit=" and !(issues.timestamp like '$today%') "; } # FIXME - Does the caller really need every single field from all # four tables? my $sth=$dbh->prepare("select * from issues,items,biblioitems,biblio where borrowernumber=? and issues.itemnumber=items.itemnumber and items.biblionumber=biblio.biblionumber and items.biblioitemnumber=biblioitems.biblioitemnumber and returndate is null $crit order by issues.date_due"); $sth->execute($borrowernumber); while (my $data = $sth->fetchrow_hashref) { # FIXME - The Dewey code is a string, not a number. $data->{'dewey'}=~s/0*$//; ($data->{'dewey'} == 0) && ($data->{'dewey'}=''); # FIXME - Could use # $todaysdate = POSIX::strftime("%Y%m%d", localtime) # or better yet, just reuse $today which was calculated above. # This function isn't going to run until midnight, is it? # Alternately, use # $todaysdate = POSIX::strftime("%Y-%m-%d", localtime) # if ($data->{'date_due'} lt $todaysdate) # ... # Either way, the date should be be formatted outside of the # loop. my @datearr = localtime(time()); my $todaysdate = (1900+$datearr[5]).sprintf ("%0.2d", ($datearr[4]+1)).sprintf ("%0.2d", $datearr[3]); my $datedue=$data->{'date_due'}; $datedue=~s/-//g; if ($datedue < $todaysdate) { $data->{'overdue'}=1; } my $itemnumber=$data->{'itemnumber'}; # FIXME - Consecutive integers as hash keys? You have GOT to # be kidding me! Use an array, fercrissakes! $currentissues{$counter}=$data; $counter++; } $sth->finish; return(\%currentissues); } =head2 getissues $issues = &getissues($borrowernumber); Returns the set of books currently on loan to a patron. C<$borrowernumber> is the patron's borrower number. C<&getissues> returns a PHP-style array: C<$issues> is a reference-to-hash whose keys are integers in the range 0..I-1, where I is the number of books the patron currently has on loan. The values of C<$issues> are references-to-hash whose keys are selected fields from the issues, items, biblio, and biblioitems tables of the Koha database. =cut #' sub getissues { # New subroutine for Circ2.pm my ($borrower) = @_; my $dbh = C4::Context->dbh; my $borrowernumber = $borrower->{'borrowernumber'}; my %currentissues; my $select = "SELECT items.*,issues.timestamp AS timestamp, issues.date_due AS date_due, items.barcode AS barcode, biblio.title AS title, biblio.author AS author, biblioitems.dewey AS dewey, itemtypes.description AS itemtype, biblioitems.subclass AS subclass, biblioitems.classification AS classification FROM issues,items,biblioitems,biblio, itemtypes WHERE issues.borrowernumber = ? AND issues.itemnumber = items.itemnumber AND items.biblionumber = biblio.biblionumber AND items.biblioitemnumber = biblioitems.biblioitemnumber AND itemtypes.itemtype = biblioitems.itemtype AND issues.returndate IS NULL ORDER BY issues.date_due"; # print $select; my $sth=$dbh->prepare($select); $sth->execute($borrowernumber); my $counter = 0; while (my $data = $sth->fetchrow_hashref) { $data->{'dewey'} =~ s/0*$//; ($data->{'dewey'} == 0) && ($data->{'dewey'} = ''); # FIXME - The Dewey code is a string, not a number. # FIXME - Use POSIX::strftime to get a text version of today's # date. That's what it's for. # FIXME - Move the date calculation outside of the loop. my @datearr = localtime(time()); my $todaysdate = (1900+$datearr[5]).sprintf ("%0.2d", ($datearr[4]+1)).sprintf ("%0.2d", $datearr[3]); # FIXME - Instead of converting the due date to YYYYMMDD, just # use # $todaysdate = POSIX::strftime("%Y-%m-%d", localtime); # ... # if ($date->{date_due} lt $todaysdate) my $datedue = $data->{'date_due'}; $datedue =~ s/-//g; if ($datedue < $todaysdate) { $data->{'overdue'} = 1; } $currentissues{$counter} = $data; $counter++; # FIXME - This is ludicrous. If you want to return an # array of values, just use an array. That's what # they're there for. } $sth->finish; return(\%currentissues); } # Not exported sub checkwaiting { #Stolen from Main.pm # check for reserves waiting my ($env,$dbh,$bornum)=@_; my @itemswaiting; my $sth = $dbh->prepare("select * from reserves where (borrowernumber = ?) and (reserves.found='W') and cancellationdate is NULL"); $sth->execute($bornum); my $cnt=0; if (my $data=$sth->fetchrow_hashref) { $itemswaiting[$cnt] =$data; $cnt ++ } $sth->finish; return ($cnt,\@itemswaiting); } =head2 renewstatus $ok = &renewstatus($env, $dbh, $borrowernumber, $itemnumber); Find out whether a borrowed item may be renewed. C<$env> is ignored. C<$dbh> is a DBI handle to the Koha database. C<$borrowernumber> is the borrower number of the patron who currently has the item on loan. C<$itemnumber> is the number of the item to renew. C<$renewstatus> returns a true value iff the item may be renewed. The item must currently be on loan to the specified borrower; renewals must be allowed for the item's type; and the borrower must not have already renewed the loan. =cut sub renewstatus { # check renewal status my ($env,$bornum,$itemno)=@_; my $dbh = C4::Context->dbh; my $renews = 1; my $renewokay = 0; # Look in the issues table for this item, lent to this borrower, # and not yet returned. # FIXME - I think this function could be redone to use only one SQL call. my $sth1 = $dbh->prepare("select * from issues where (borrowernumber = ?) and (itemnumber = ?) and returndate is null"); $sth1->execute($bornum,$itemno); if (my $data1 = $sth1->fetchrow_hashref) { # Found a matching item # See if this item may be renewed. This query is convoluted # because it's a bit messy: given the item number, we need to find # the biblioitem, which gives us the itemtype, which tells us # whether it may be renewed. my $sth2 = $dbh->prepare("select renewalsallowed from items,biblioitems,itemtypes where (items.itemnumber = ?) and (items.biblioitemnumber = biblioitems.biblioitemnumber) and (biblioitems.itemtype = itemtypes.itemtype)"); $sth2->execute($itemno); if (my $data2=$sth2->fetchrow_hashref) { $renews = $data2->{'renewalsallowed'}; } if ($renews && $renews > $data1->{'renewals'}) { $renewokay = 1; } $sth2->finish; my ($resfound, $resrec) = CheckReserves($itemno); if ($resfound) { $renewokay = 0; } my ($resfound, $resrec) = CheckReserves($itemno); if ($resfound) { $renewokay = 0; } } $sth1->finish; return($renewokay); } =head2 renewbook &renewbook($env, $borrowernumber, $itemnumber, $datedue); Renews a loan. C<$env-E{branchcode}> is the code of the branch where the renewal is taking place. C<$env-E{usercode}> is the value to log in C in the Koha database. C<$borrowernumber> is the borrower number of the patron who currently has the item. C<$itemnumber> is the number of the item to renew. C<$datedue> can be used to set the due date. If C<$datedue> is the empty string, C<&renewbook> will calculate the due date automatically from the book's item type. If you wish to set the due date manually, C<$datedue> should be in the form YYYY-MM-DD. =cut sub renewbook { # mark book as renewed my ($env,$bornum,$itemno,$datedue)=@_; my $dbh = C4::Context->dbh; # If the due date wasn't specified, calculate it by adding the # book's loan length to today's date. if ($datedue eq "" ) { #debug_msg($env, "getting date"); my $iteminformation = getiteminformation($env, $itemno,0); my $borrower = getpatroninformation($env,$bornum,0); my $loanlength = getLoanLength($borrower->{'categorycode'},$iteminformation->{'itemtype'},$borrower->{'branchcode'}); $datedue = UnixDate(DateCalc("today","$loanlength days"),"%Y-%m-%d"); } # Find the issues record for this book my $sth=$dbh->prepare("select * from issues where borrowernumber=? and itemnumber=? and returndate is null"); $sth->execute($bornum,$itemno); my $issuedata=$sth->fetchrow_hashref; $sth->finish; # Update the issues record to have the new due date, and a new count # of how many times it has been renewed. my $renews = $issuedata->{'renewals'} +1; $sth=$dbh->prepare("update issues set date_due = ?, renewals = ? where borrowernumber=? and itemnumber=? and returndate is null"); $sth->execute($datedue,$renews,$bornum,$itemno); $sth->finish; # Log the renewal UpdateStats($env,$env->{'branchcode'},'renew','','',$itemno); # Charge a new rental fee, if applicable? my ($charge,$type)=calc_charges($env, $itemno, $bornum); if ($charge > 0){ my $accountno=getnextacctno($env,$bornum,$dbh); my $item=getiteminformation($env, $itemno); $sth=$dbh->prepare("Insert into accountlines (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber) values (?,?,now(),?,?,?,?,?)"); $sth->execute($bornum,$accountno,$charge,"Renewal of Rental Item $item->{'title'} $item->{'barcode'}",'Rent',$charge,$itemno); $sth->finish; # print $account; } # return(); } =item calc_charges ($charge, $item_type) = &calc_charges($env, $itemnumber, $borrowernumber); Calculate how much it would cost for a given patron to borrow a given item, including any applicable discounts. C<$env> is ignored. C<$itemnumber> is the item number of item the patron wishes to borrow. C<$borrowernumber> is the patron's borrower number. C<&calc_charges> returns two values: C<$charge> is the rental charge, and C<$item_type> is the code for the item's item type (e.g., C if it's a video). =cut sub calc_charges { # calculate charges due my ($env, $itemno, $bornum)=@_; my $charge=0; my $dbh = C4::Context->dbh; my $item_type; # Get the book's item type and rental charge (via its biblioitem). my $sth1= $dbh->prepare("select itemtypes.itemtype,rentalcharge from items,biblioitems,itemtypes where (items.itemnumber =?) and (biblioitems.biblioitemnumber = items.biblioitemnumber) and (biblioitems.itemtype = itemtypes.itemtype)"); $sth1->execute($itemno); my $data1=$sth1->fetchrow_hashref; $item_type = $data1->{'itemtype'}; $charge = $data1->{'rentalcharge'}; $sth1->finish; return ($charge,$item_type); } # FIXME - A virtually identical function appears in # C4::Circulation::Issues. Pick one and stick with it. sub createcharge { #Stolen from Issues.pm my ($env,$dbh,$itemno,$bornum,$charge) = @_; my $nextaccntno = getnextacctno($env,$bornum,$dbh); my $sth = $dbh->prepare(<execute($bornum, $itemno, $nextaccntno, $charge, $charge); $sth->finish; } sub getnextacctno { # Stolen from Accounts.pm my ($env,$bornumber,$dbh)=@_; my $nextaccntno = 1; my $sth = $dbh->prepare("select * from accountlines where (borrowernumber = ?) order by accountno desc"); $sth->execute($bornumber); if (my $accdata=$sth->fetchrow_hashref){ $nextaccntno = $accdata->{'accountno'} + 1; } $sth->finish; return($nextaccntno); } =item find_reserves ($status, $record) = &find_reserves($itemnumber); Looks up an item in the reserves. C<$itemnumber> is the itemnumber to look up. C<$status> is true iff the search was successful. C<$record> is a reference-to-hash describing the reserve. Its keys are the fields from the reserves table of the Koha database. =cut #' # FIXME - This API is bogus: just return the record, or undef if none # was found. # FIXME - There's also a &C4::Circulation::Returns::find_reserves, but # that one looks rather different. sub find_reserves { # Stolen from Returns.pm my ($itemno) = @_; my %env; my $dbh = C4::Context->dbh; my ($itemdata) = getiteminformation(\%env, $itemno,0); my $bibno = $dbh->quote($itemdata->{'biblionumber'}); my $bibitm = $dbh->quote($itemdata->{'biblioitemnumber'}); my $sth = $dbh->prepare("select * from reserves where ((found = 'W') or (found is null)) and biblionumber = ? and cancellationdate is NULL order by priority, reservedate"); $sth->execute($bibno); my $resfound = 0; my $resrec; my $lastrec; # print $query; # FIXME - I'm not really sure what's going on here, but since we # only want one result, wouldn't it be possible (and far more # efficient) to do something clever in SQL that only returns one # set of values? while (($resrec = $sth->fetchrow_hashref) && (not $resfound)) { # FIXME - Unlike Pascal, Perl allows you to exit loops # early. Take out the "&& (not $resfound)" and just # use "last" at the appropriate point in the loop. # (Oh, and just in passing: if you'd used "!" instead # of "not", you wouldn't have needed the parentheses.) $lastrec = $resrec; my $brn = $dbh->quote($resrec->{'borrowernumber'}); my $rdate = $dbh->quote($resrec->{'reservedate'}); my $bibno = $dbh->quote($resrec->{'biblionumber'}); if ($resrec->{'found'} eq "W") { if ($resrec->{'itemnumber'} eq $itemno) { $resfound = 1; } } else { # FIXME - Use 'elsif' to avoid unnecessary indentation. if ($resrec->{'constrainttype'} eq "a") { $resfound = 1; } else { my $consth = $dbh->prepare("select * from reserveconstraints where borrowernumber = ? and reservedate = ? and biblionumber = ? and biblioitemnumber = ?"); $consth->execute($brn,$rdate,$bibno,$bibitm); if (my $conrec = $consth->fetchrow_hashref) { if ($resrec->{'constrainttype'} eq "o") { $resfound = 1; } } $consth->finish; } } if ($resfound) { my $updsth = $dbh->prepare("update reserves set found = 'W', itemnumber = ? where borrowernumber = ? and reservedate = ? and biblionumber = ?"); $updsth->execute($itemno,$brn,$rdate,$bibno); $updsth->finish; # FIXME - "last;" here to break out of the loop early. } } $sth->finish; return ($resfound,$lastrec); } sub fixdate { my ($year, $month, $day) = @_; my $invalidduedate; my $date; if ($year && $month && $day){ if (($year eq 0 ) && ($month eq 0) && ($year eq 0)) { # $env{'datedue'}=''; } else { if (($year eq 0) || ($month eq 0) || ($year eq 0)) { $invalidduedate=1; } else { if (($day>30) && (($month==4) || ($month==6) || ($month==9) || ($month==11))) { $invalidduedate = 1; } elsif (($day > 29) && ($month == 2)) { $invalidduedate=1; } elsif (($month == 2) && ($day > 28) && (($year%4) && ((!($year%100) || ($year%400))))) { $invalidduedate=1; } else { $date="$year-$month-$day"; } } } } return ($date, $invalidduedate); } 1; __END__ =back =head1 AUTHOR Koha Developement team =cut