Remove ambiguity from SQL statement for holds queue.
[koha.git] / C4 / Accounts.pm
blobc43741bf49c9d3f5a4253670762f38ecaa297184
1 package C4::Accounts;
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
21 use strict;
22 use C4::Context;
23 use C4::Stats;
24 use C4::Members;
25 use C4::Items;
26 use C4::Circulation qw(MarkIssueReturned);
28 use vars qw($VERSION @ISA @EXPORT);
30 BEGIN {
31 # set the version for version checking
32 $VERSION = 3.03;
33 require Exporter;
34 @ISA = qw(Exporter);
35 @EXPORT = qw(
36 &recordpayment &makepayment &manualinvoice
37 &getnextacctno &reconcileaccount &getcharges &getcredits
38 &getrefunds &chargelostitem
39 ); # removed &fixaccounts
42 =head1 NAME
44 C4::Accounts - Functions for dealing with Koha accounts
46 =head1 SYNOPSIS
48 use C4::Accounts;
50 =head1 DESCRIPTION
52 The functions in this module deal with the monetary aspect of Koha,
53 including looking up and modifying the amount of money owed by a
54 patron.
56 =head1 FUNCTIONS
58 =head2 recordpayment
60 &recordpayment($borrowernumber, $payment);
62 Record payment by a patron. C<$borrowernumber> is the patron's
63 borrower number. C<$payment> is a floating-point number, giving the
64 amount that was paid.
66 Amounts owed are paid off oldest first. That is, if the patron has a
67 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
68 of $1.50, then the oldest fine will be paid off in full, and $0.50
69 will be credited to the next one.
71 =cut
74 sub recordpayment {
76 #here we update the account lines
77 my ( $borrowernumber, $data ) = @_;
78 my $dbh = C4::Context->dbh;
79 my $newamtos = 0;
80 my $accdata = "";
81 my $branch = C4::Context->userenv->{'branch'};
82 my $amountleft = $data;
84 # begin transaction
85 my $nextaccntno = getnextacctno($borrowernumber);
87 # get lines with outstanding amounts to offset
88 my $sth = $dbh->prepare(
89 "SELECT * FROM accountlines
90 WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
91 ORDER BY date"
93 $sth->execute($borrowernumber);
95 # offset transactions
96 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
97 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
98 $newamtos = 0;
99 $amountleft -= $accdata->{'amountoutstanding'};
101 else {
102 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
103 $amountleft = 0;
105 my $thisacct = $accdata->{accountno};
106 my $usth = $dbh->prepare(
107 "UPDATE accountlines SET amountoutstanding= ?
108 WHERE (borrowernumber = ?) AND (accountno=?)"
110 $usth->execute( $newamtos, $borrowernumber, $thisacct );
111 $usth->finish;
112 # $usth = $dbh->prepare(
113 # "INSERT INTO accountoffsets
114 # (borrowernumber, accountno, offsetaccount, offsetamount)
115 # VALUES (?,?,?,?)"
116 # );
117 # $usth->execute( $borrowernumber, $accdata->{'accountno'},
118 # $nextaccntno, $newamtos );
119 $usth->finish;
122 # create new line
123 my $usth = $dbh->prepare(
124 "INSERT INTO accountlines
125 (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding)
126 VALUES (?,?,now(),?,'Payment,thanks','Pay',?)"
128 $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft );
129 $usth->finish;
130 UpdateStats( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
131 $sth->finish;
134 =head2 makepayment
136 &makepayment($borrowernumber, $acctnumber, $amount, $branchcode);
138 Records the fact that a patron has paid off the entire amount he or
139 she owes.
141 C<$borrowernumber> is the patron's borrower number. C<$acctnumber> is
142 the account that was credited. C<$amount> is the amount paid (this is
143 only used to record the payment. It is assumed to be equal to the
144 amount owed). C<$branchcode> is the code of the branch where payment
145 was made.
147 =cut
150 # FIXME - I'm not at all sure about the above, because I don't
151 # understand what the acct* tables in the Koha database are for.
152 sub makepayment {
154 #here we update both the accountoffsets and the account lines
155 #updated to check, if they are paying off a lost item, we return the item
156 # from their card, and put a note on the item record
157 my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
158 my $dbh = C4::Context->dbh;
160 # begin transaction
161 my $nextaccntno = getnextacctno($borrowernumber);
162 my $newamtos = 0;
163 my $sth =
164 $dbh->prepare(
165 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno=?");
166 $sth->execute( $borrowernumber, $accountno );
167 my $data = $sth->fetchrow_hashref;
168 $sth->finish;
170 $dbh->do(
171 "UPDATE accountlines
172 SET amountoutstanding = 0
173 WHERE borrowernumber = $borrowernumber
174 AND accountno = $accountno
178 # print $updquery;
179 # $dbh->do( "
180 # INSERT INTO accountoffsets
181 # (borrowernumber, accountno, offsetaccount,
182 # offsetamount)
183 # VALUES ($borrowernumber, $accountno, $nextaccntno, $newamtos)
184 # " );
186 # create new line
187 my $payment = 0 - $amount;
188 $dbh->do( "
189 INSERT INTO accountlines
190 (borrowernumber, accountno, date, amount,
191 description, accounttype, amountoutstanding)
192 VALUES ($borrowernumber, $nextaccntno, now(), $payment,
193 'Payment,thanks - $user', 'Pay', 0)
194 " );
196 # FIXME - The second argument to &UpdateStats is supposed to be the
197 # branch code.
198 # UpdateStats is now being passed $accountno too. MTJ
199 UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber,
200 $accountno );
201 $sth->finish;
203 #check to see what accounttype
204 if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
205 returnlost( $borrowernumber, $data->{'itemnumber'} );
209 =head2 getnextacctno
211 $nextacct = &getnextacctno($borrowernumber);
213 Returns the next unused account number for the patron with the given
214 borrower number.
216 =cut
219 # FIXME - Okay, so what does the above actually _mean_?
220 sub getnextacctno ($) {
221 my ($borrowernumber) = shift or return undef;
222 my $sth = C4::Context->dbh->prepare(
223 "SELECT accountno+1 FROM accountlines
224 WHERE (borrowernumber = ?)
225 ORDER BY accountno DESC
226 LIMIT 1"
228 $sth->execute($borrowernumber);
229 return ($sth->fetchrow || 1);
232 =head2 fixaccounts (removed)
234 &fixaccounts($borrowernumber, $accountnumber, $amount);
237 # FIXME - I don't understand what this function does.
238 sub fixaccounts {
239 my ( $borrowernumber, $accountno, $amount ) = @_;
240 my $dbh = C4::Context->dbh;
241 my $sth = $dbh->prepare(
242 "SELECT * FROM accountlines WHERE borrowernumber=?
243 AND accountno=?"
245 $sth->execute( $borrowernumber, $accountno );
246 my $data = $sth->fetchrow_hashref;
248 # FIXME - Error-checking
249 my $diff = $amount - $data->{'amount'};
250 my $outstanding = $data->{'amountoutstanding'} + $diff;
251 $sth->finish;
253 $dbh->do(<<EOT);
254 UPDATE accountlines
255 SET amount = '$amount',
256 amountoutstanding = '$outstanding'
257 WHERE borrowernumber = $borrowernumber
258 AND accountno = $accountno
260 # FIXME: exceedingly bad form. Use prepare with placholders ("?") in query and execute args.
263 =cut
265 sub returnlost{
266 my ( $borrowernumber, $itemnum ) = @_;
267 C4::Circulation::MarkIssueReturned( $borrowernumber, $itemnum );
268 my $borrower = C4::Members::GetMember( $borrowernumber, 'borrowernumber' );
269 my @datearr = localtime(time);
270 my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
271 my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
272 ModItem({ paidfor => "Paid for by $bor $date" }, undef, $itemnum);
276 sub chargelostitem{
277 # http://wiki.koha.org/doku.php?id=en:development:kohastatuses
278 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
279 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
280 # a charge has been added
281 # FIXME : if no replacement price, borrower just doesn't get charged?
283 my $dbh = C4::Context->dbh();
284 my ($itemnumber) = @_;
285 my $sth=$dbh->prepare("SELECT * FROM issues, items WHERE issues.itemnumber=items.itemnumber and issues.itemnumber=?");
286 $sth->execute($itemnumber);
287 my $issues=$sth->fetchrow_hashref();
289 # if a borrower lost the item, add a replacement cost to the their record
290 if ( $issues->{borrowernumber} ){
292 # first make sure the borrower hasn't already been charged for this item
293 my $sth1=$dbh->prepare("SELECT * from accountlines
294 WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
295 $sth1->execute($issues->{'borrowernumber'},$itemnumber);
296 my $existing_charge_hashref=$sth1->fetchrow_hashref();
298 # OK, they haven't
299 unless ($existing_charge_hashref) {
300 # This item is on issue ... add replacement cost to the borrower's record and mark it returned
301 # Note that we add this to the account even if there's no replacement price, allowing some other
302 # process (or person) to update it, since we don't handle any defaults for replacement prices.
303 my $accountno = getnextacctno($issues->{'borrowernumber'});
304 my $sth2=$dbh->prepare("INSERT INTO accountlines
305 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber)
306 VALUES (?,?,now(),?,?,'L',?,?)");
307 $sth2->execute($issues->{'borrowernumber'},$accountno,$issues->{'replacementprice'},
308 "Lost Item $issues->{'title'} $issues->{'barcode'}",
309 $issues->{'replacementprice'},$itemnumber);
310 $sth2->finish;
311 # FIXME: Log this ?
313 #FIXME : Should probably have a way to distinguish this from an item that really was returned.
314 warn " $issues->{'borrowernumber'} / $itemnumber ";
315 C4::Circulation::MarkIssueReturned($issues->{borrowernumber},$itemnumber);
316 # Shouldn't MarkIssueReturned do this?
317 ModItem({ onloan => undef }, undef, $itemnumber);
319 $sth->finish;
322 =head2 manualinvoice
324 &manualinvoice($borrowernumber, $itemnumber, $description, $type,
325 $amount, $user);
327 C<$borrowernumber> is the patron's borrower number.
328 C<$description> is a description of the transaction.
329 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
330 or C<REF>.
331 C<$itemnumber> is the item involved, if pertinent; otherwise, it
332 should be the empty string.
334 =cut
337 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
338 # are :
339 # 'C' = CREDIT
340 # 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
341 # 'N' = New Card fee
342 # 'F' = Fine
343 # 'A' = Account Management fee
344 # 'M' = Sundry
345 # 'L' = Lost Item
348 sub manualinvoice {
349 my ( $borrowernumber, $itemnum, $desc, $type, $amount, $user ) = @_;
350 my $dbh = C4::Context->dbh;
351 my $notifyid = 0;
352 my $insert;
353 $itemnum =~ s/ //g;
354 my $accountno = getnextacctno($borrowernumber);
355 my $amountleft = $amount;
357 # if ( $type eq 'CS'
358 # || $type eq 'CB'
359 # || $type eq 'CW'
360 # || $type eq 'CF'
361 # || $type eq 'CL' )
363 # my $amount2 = $amount * -1; # FIXME - $amount2 = -$amount
364 # $amountleft =
365 # fixcredit( $borrowernumber, $amount2, $itemnum, $type, $user );
367 if ( $type eq 'N' ) {
368 $desc .= " New Card";
370 if ( $type eq 'F' ) {
371 $desc .= " Fine";
373 if ( $type eq 'A' ) {
374 $desc .= " Account Management fee";
376 if ( $type eq 'M' ) {
377 $desc .= " Sundry";
380 if ( $type eq 'L' && $desc eq '' ) {
382 $desc = " Lost Item";
384 # if ( $type eq 'REF' ) {
385 # $desc .= " Cash Refund";
386 # $amountleft = refund( '', $borrowernumber, $amount );
388 if ( ( $type eq 'L' )
389 or ( $type eq 'F' )
390 or ( $type eq 'A' )
391 or ( $type eq 'N' )
392 or ( $type eq 'M' ) )
394 $notifyid = 1;
397 if ( $itemnum ne '' ) {
398 $desc .= " " . $itemnum;
399 my $sth = $dbh->prepare(
400 "INSERT INTO accountlines
401 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id)
402 VALUES (?, ?, now(), ?,?, ?,?,?,?)");
403 $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid) || return $sth->errstr;
404 } else {
405 my $sth=$dbh->prepare("INSERT INTO accountlines
406 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id)
407 VALUES (?, ?, now(), ?, ?, ?, ?,?)"
409 $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
410 $amountleft, $notifyid );
412 return 0;
415 =head2 fixcredit #### DEPRECATED
417 $amountleft = &fixcredit($borrowernumber, $data, $barcode, $type, $user);
419 This function is only used internally, not exported.
421 =cut
423 # This function is deprecated in 3.0
425 sub fixcredit {
427 #here we update both the accountoffsets and the account lines
428 my ( $borrowernumber, $data, $barcode, $type, $user ) = @_;
429 my $dbh = C4::Context->dbh;
430 my $newamtos = 0;
431 my $accdata = "";
432 my $amountleft = $data;
433 if ( $barcode ne '' ) {
434 my $item = GetBiblioFromItemNumber( '', $barcode );
435 my $nextaccntno = getnextacctno($borrowernumber);
436 my $query = "SELECT * FROM accountlines WHERE (borrowernumber=?
437 AND itemnumber=? AND amountoutstanding > 0)";
438 if ( $type eq 'CL' ) {
439 $query .= " AND (accounttype = 'L' OR accounttype = 'Rep')";
441 elsif ( $type eq 'CF' ) {
442 $query .= " AND (accounttype = 'F' OR accounttype = 'FU' OR
443 accounttype='Res' OR accounttype='Rent')";
445 elsif ( $type eq 'CB' ) {
446 $query .= " and accounttype='A'";
449 # print $query;
450 my $sth = $dbh->prepare($query);
451 $sth->execute( $borrowernumber, $item->{'itemnumber'} );
452 $accdata = $sth->fetchrow_hashref;
453 $sth->finish;
454 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
455 $newamtos = 0;
456 $amountleft -= $accdata->{'amountoutstanding'};
458 else {
459 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
460 $amountleft = 0;
462 my $thisacct = $accdata->{accountno};
463 my $usth = $dbh->prepare(
464 "UPDATE accountlines SET amountoutstanding= ?
465 WHERE (borrowernumber = ?) AND (accountno=?)"
467 $usth->execute( $newamtos, $borrowernumber, $thisacct );
468 $usth->finish;
469 $usth = $dbh->prepare(
470 "INSERT INTO accountoffsets
471 (borrowernumber, accountno, offsetaccount, offsetamount)
472 VALUES (?,?,?,?)"
474 $usth->execute( $borrowernumber, $accdata->{'accountno'},
475 $nextaccntno, $newamtos );
476 $usth->finish;
479 # begin transaction
480 my $nextaccntno = getnextacctno($borrowernumber);
482 # get lines with outstanding amounts to offset
483 my $sth = $dbh->prepare(
484 "SELECT * FROM accountlines
485 WHERE (borrowernumber = ?) AND (amountoutstanding >0)
486 ORDER BY date"
488 $sth->execute($borrowernumber);
490 # print $query;
491 # offset transactions
492 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
493 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
494 $newamtos = 0;
495 $amountleft -= $accdata->{'amountoutstanding'};
497 else {
498 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
499 $amountleft = 0;
501 my $thisacct = $accdata->{accountno};
502 my $usth = $dbh->prepare(
503 "UPDATE accountlines SET amountoutstanding= ?
504 WHERE (borrowernumber = ?) AND (accountno=?)"
506 $usth->execute( $newamtos, $borrowernumber, $thisacct );
507 $usth->finish;
508 $usth = $dbh->prepare(
509 "INSERT INTO accountoffsets
510 (borrowernumber, accountno, offsetaccount, offsetamount)
511 VALUE (?,?,?,?)"
513 $usth->execute( $borrowernumber, $accdata->{'accountno'},
514 $nextaccntno, $newamtos );
515 $usth->finish;
517 $sth->finish;
518 $type = "Credit " . $type;
519 UpdateStats( $user, $type, $data, $user, '', '', $borrowernumber );
520 $amountleft *= -1;
521 return ($amountleft);
525 =head2 refund
527 #FIXME : DEPRECATED SUB
528 This subroutine tracks payments and/or credits against fines/charges
529 using the accountoffsets table, which is not used consistently in
530 Koha's fines management, and so is not used in 3.0
532 =cut
534 sub refund {
536 #here we update both the accountoffsets and the account lines
537 my ( $borrowernumber, $data ) = @_;
538 my $dbh = C4::Context->dbh;
539 my $newamtos = 0;
540 my $accdata = "";
541 my $amountleft = $data * -1;
543 # begin transaction
544 my $nextaccntno = getnextacctno($borrowernumber);
546 # get lines with outstanding amounts to offset
547 my $sth = $dbh->prepare(
548 "SELECT * FROM accountlines
549 WHERE (borrowernumber = ?) AND (amountoutstanding<0)
550 ORDER BY date"
552 $sth->execute($borrowernumber);
554 # print $amountleft;
555 # offset transactions
556 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft < 0 ) ) {
557 if ( $accdata->{'amountoutstanding'} > $amountleft ) {
558 $newamtos = 0;
559 $amountleft -= $accdata->{'amountoutstanding'};
561 else {
562 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
563 $amountleft = 0;
566 # print $amountleft;
567 my $thisacct = $accdata->{accountno};
568 my $usth = $dbh->prepare(
569 "UPDATE accountlines SET amountoutstanding= ?
570 WHERE (borrowernumber = ?) AND (accountno=?)"
572 $usth->execute( $newamtos, $borrowernumber, $thisacct );
573 $usth->finish;
574 $usth = $dbh->prepare(
575 "INSERT INTO accountoffsets
576 (borrowernumber, accountno, offsetaccount, offsetamount)
577 VALUES (?,?,?,?)"
579 $usth->execute( $borrowernumber, $accdata->{'accountno'},
580 $nextaccntno, $newamtos );
581 $usth->finish;
583 $sth->finish;
584 return ($amountleft);
587 sub getcharges {
588 my ( $borrowerno, $timestamp, $accountno ) = @_;
589 my $dbh = C4::Context->dbh;
590 my $timestamp2 = $timestamp - 1;
591 my $query = "";
592 my $sth = $dbh->prepare(
593 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?"
595 $sth->execute( $borrowerno, $accountno );
597 my @results;
598 while ( my $data = $sth->fetchrow_hashref ) {
599 push @results,$data;
601 return (@results);
605 sub getcredits {
606 my ( $date, $date2 ) = @_;
607 my $dbh = C4::Context->dbh;
608 my $sth = $dbh->prepare(
609 "SELECT * FROM accountlines,borrowers
610 WHERE amount < 0 AND accounttype <> 'Pay' AND accountlines.borrowernumber = borrowers.borrowernumber
611 AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
614 $sth->execute( $date, $date2 );
615 my @results;
616 while ( my $data = $sth->fetchrow_hashref ) {
617 $data->{'date'} = $data->{'timestamp'};
618 push @results,$data;
620 return (@results);
624 sub getrefunds {
625 my ( $date, $date2 ) = @_;
626 my $dbh = C4::Context->dbh;
628 my $sth = $dbh->prepare(
629 "SELECT *,timestamp AS datetime
630 FROM accountlines,borrowers
631 WHERE (accounttype = 'REF'
632 AND accountlines.borrowernumber = borrowers.borrowernumber
633 AND date >=? AND date <?)"
636 $sth->execute( $date, $date2 );
638 my @results;
639 while ( my $data = $sth->fetchrow_hashref ) {
640 push @results,$data;
643 return (@results);
645 END { } # module clean-up code here (global destructor)
648 __END__
650 =head1 SEE ALSO
652 DBI(3)
654 =cut