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
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
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #use warnings; FIXME - Bug 2505
26 use C4
::Circulation
qw(ReturnLostItem);
28 use vars
qw($VERSION @ISA @EXPORT);
31 # set the version for version checking
48 &recordpayment_selectaccts
55 C4::Accounts - Functions for dealing with Koha accounts
63 The functions in this module deal with the monetary aspect of Koha,
64 including looking up and modifying the amount of money owed by a
71 &recordpayment($borrowernumber, $payment);
73 Record payment by a patron. C<$borrowernumber> is the patron's
74 borrower number. C<$payment> is a floating-point number, giving the
77 Amounts owed are paid off oldest first. That is, if the patron has a
78 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
79 of $1.50, then the oldest fine will be paid off in full, and $0.50
80 will be credited to the next one.
87 #here we update the account lines
88 my ( $borrowernumber, $data ) = @_;
89 my $dbh = C4
::Context
->dbh;
92 my $branch = C4
::Context
->userenv->{'branch'};
93 my $amountleft = $data;
96 my $nextaccntno = getnextacctno
($borrowernumber);
98 # get lines with outstanding amounts to offset
99 my $sth = $dbh->prepare(
100 "SELECT * FROM accountlines
101 WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
104 $sth->execute($borrowernumber);
106 # offset transactions
107 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
108 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
110 $amountleft -= $accdata->{'amountoutstanding'};
113 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
116 my $thisacct = $accdata->{accountno
};
117 my $usth = $dbh->prepare(
118 "UPDATE accountlines SET amountoutstanding= ?
119 WHERE (borrowernumber = ?) AND (accountno=?)"
121 $usth->execute( $newamtos, $borrowernumber, $thisacct );
123 # $usth = $dbh->prepare(
124 # "INSERT INTO accountoffsets
125 # (borrowernumber, accountno, offsetaccount, offsetamount)
128 # $usth->execute( $borrowernumber, $accdata->{'accountno'},
129 # $nextaccntno, $newamtos );
134 my $usth = $dbh->prepare(
135 "INSERT INTO accountlines
136 (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding)
137 VALUES (?,?,now(),?,'Payment,thanks','Pay',?)"
139 $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft );
141 UpdateStats
( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
147 &makepayment($borrowernumber, $acctnumber, $amount, $branchcode);
149 Records the fact that a patron has paid off the entire amount he or
152 C<$borrowernumber> is the patron's borrower number. C<$acctnumber> is
153 the account that was credited. C<$amount> is the amount paid (this is
154 only used to record the payment. It is assumed to be equal to the
155 amount owed). C<$branchcode> is the code of the branch where payment
161 # FIXME - I'm not at all sure about the above, because I don't
162 # understand what the acct* tables in the Koha database are for.
165 #here we update both the accountoffsets and the account lines
166 #updated to check, if they are paying off a lost item, we return the item
167 # from their card, and put a note on the item record
168 my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
169 my $dbh = C4
::Context
->dbh;
171 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
174 my $nextaccntno = getnextacctno
($borrowernumber);
178 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno=?");
179 $sth->execute( $borrowernumber, $accountno );
180 my $data = $sth->fetchrow_hashref;
183 if($data->{'accounttype'} eq "Pay"){
187 SET amountoutstanding = 0, description = 'Payment,thanks'
188 WHERE borrowernumber = ?
192 $udp->execute($borrowernumber, $accountno );
198 SET amountoutstanding = 0
199 WHERE borrowernumber = ?
203 $udp->execute($borrowernumber, $accountno );
207 my $payment = 0 - $amount;
212 INTO accountlines (borrowernumber, accountno, date, amount, itemnumber, description, accounttype, amountoutstanding, manager_id)
213 VALUES ( ?, ?, now(), ?, ?, 'Payment,thanks', 'Pay', 0, ?)"
215 $ins->execute($borrowernumber, $nextaccntno, $payment, $data->{'itemnumber'}, $manager_id);
219 # FIXME - The second argument to &UpdateStats is supposed to be the
221 # UpdateStats is now being passed $accountno too. MTJ
222 UpdateStats
( $user, 'payment', $amount, '', '', '', $borrowernumber,
224 #from perldoc: for SELECT only #$sth->finish;
226 #check to see what accounttype
227 if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
228 C4
::Circulation
::ReturnLostItem
( $borrowernumber, $data->{'itemnumber'} );
234 $nextacct = &getnextacctno($borrowernumber);
236 Returns the next unused account number for the patron with the given
242 # FIXME - Okay, so what does the above actually _mean_?
243 sub getnextacctno
($) {
244 my ($borrowernumber) = shift or return undef;
245 my $sth = C4
::Context
->dbh->prepare(
246 "SELECT accountno+1 FROM accountlines
247 WHERE (borrowernumber = ?)
248 ORDER BY accountno DESC
251 $sth->execute($borrowernumber);
252 return ($sth->fetchrow || 1);
255 =head2 fixaccounts (removed)
257 &fixaccounts($borrowernumber, $accountnumber, $amount);
260 # FIXME - I don't understand what this function does.
262 my ( $borrowernumber, $accountno, $amount ) = @_;
263 my $dbh = C4::Context->dbh;
264 my $sth = $dbh->prepare(
265 "SELECT * FROM accountlines WHERE borrowernumber=?
268 $sth->execute( $borrowernumber, $accountno );
269 my $data = $sth->fetchrow_hashref;
271 # FIXME - Error-checking
272 my $diff = $amount - $data->{'amount'};
273 my $outstanding = $data->{'amountoutstanding'} + $diff;
278 SET amount = '$amount',
279 amountoutstanding = '$outstanding'
280 WHERE borrowernumber = $borrowernumber
281 AND accountno = $accountno
283 # FIXME: exceedingly bad form. Use prepare with placholders ("?") in query and execute args.
289 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
290 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
291 # a charge has been added
292 # FIXME : if no replacement price, borrower just doesn't get charged?
293 my $dbh = C4
::Context
->dbh();
294 my ($borrowernumber, $itemnumber, $amount, $description) = @_;
296 # first make sure the borrower hasn't already been charged for this item
297 my $sth1=$dbh->prepare("SELECT * from accountlines
298 WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
299 $sth1->execute($borrowernumber,$itemnumber);
300 my $existing_charge_hashref=$sth1->fetchrow_hashref();
303 unless ($existing_charge_hashref) {
304 # This item is on issue ... add replacement cost to the borrower's record and mark it returned
305 # Note that we add this to the account even if there's no replacement price, allowing some other
306 # process (or person) to update it, since we don't handle any defaults for replacement prices.
307 my $accountno = getnextacctno
($borrowernumber);
308 my $sth2=$dbh->prepare("INSERT INTO accountlines
309 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber)
310 VALUES (?,?,now(),?,?,'L',?,?)");
311 $sth2->execute($borrowernumber,$accountno,$amount,
312 $description,$amount,$itemnumber);
320 &manualinvoice($borrowernumber, $itemnumber, $description, $type,
323 C<$borrowernumber> is the patron's borrower number.
324 C<$description> is a description of the transaction.
325 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
327 C<$itemnumber> is the item involved, if pertinent; otherwise, it
328 should be the empty string.
333 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
336 # 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
339 # 'A' = Account Management fee
345 my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_;
347 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
348 my $dbh = C4
::Context
->dbh;
351 my $accountno = getnextacctno
($borrowernumber);
352 my $amountleft = $amount;
360 # my $amount2 = $amount * -1; # FIXME - $amount2 = -$amount
362 # fixcredit( $borrowernumber, $amount2, $itemnum, $type, $user );
364 if ( $type eq 'N' ) {
365 $desc .= " New Card";
367 if ( $type eq 'F' ) {
370 if ( $type eq 'A' ) {
371 $desc .= " Account Management fee";
373 if ( $type eq 'M' ) {
377 if ( $type eq 'L' && $desc eq '' ) {
379 $desc = " Lost Item";
381 # if ( $type eq 'REF' ) {
382 # $desc .= " Cash Refund";
383 # $amountleft = refund( '', $borrowernumber, $amount );
385 if ( ( $type eq 'L' )
389 or ( $type eq 'M' ) )
395 $desc .= ' ' . $itemnum;
396 my $sth = $dbh->prepare(
397 'INSERT INTO accountlines
398 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id, note, manager_id)
399 VALUES (?, ?, now(), ?,?, ?,?,?,?,?,?)');
400 $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid, $note, $manager_id) || return $sth->errstr;
402 my $sth=$dbh->prepare("INSERT INTO accountlines
403 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id, note, manager_id)
404 VALUES (?, ?, now(), ?, ?, ?, ?,?,?,?)"
406 $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
407 $amountleft, $notifyid, $note, $manager_id );
412 =head2 fixcredit #### DEPRECATED
414 $amountleft = &fixcredit($borrowernumber, $data, $barcode, $type, $user);
416 This function is only used internally, not exported.
420 # This function is deprecated in 3.0
424 #here we update both the accountoffsets and the account lines
425 my ( $borrowernumber, $data, $barcode, $type, $user ) = @_;
426 my $dbh = C4
::Context
->dbh;
429 my $amountleft = $data;
430 if ( $barcode ne '' ) {
431 my $item = GetBiblioFromItemNumber
( '', $barcode );
432 my $nextaccntno = getnextacctno
($borrowernumber);
433 my $query = "SELECT * FROM accountlines WHERE (borrowernumber=?
434 AND itemnumber=? AND amountoutstanding > 0)";
435 if ( $type eq 'CL' ) {
436 $query .= " AND (accounttype = 'L' OR accounttype = 'Rep')";
438 elsif ( $type eq 'CF' ) {
439 $query .= " AND (accounttype = 'F' OR accounttype = 'FU' OR
440 accounttype='Res' OR accounttype='Rent')";
442 elsif ( $type eq 'CB' ) {
443 $query .= " and accounttype='A'";
447 my $sth = $dbh->prepare($query);
448 $sth->execute( $borrowernumber, $item->{'itemnumber'} );
449 $accdata = $sth->fetchrow_hashref;
451 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
453 $amountleft -= $accdata->{'amountoutstanding'};
456 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
459 my $thisacct = $accdata->{accountno
};
460 my $usth = $dbh->prepare(
461 "UPDATE accountlines SET amountoutstanding= ?
462 WHERE (borrowernumber = ?) AND (accountno=?)"
464 $usth->execute( $newamtos, $borrowernumber, $thisacct );
466 $usth = $dbh->prepare(
467 "INSERT INTO accountoffsets
468 (borrowernumber, accountno, offsetaccount, offsetamount)
471 $usth->execute( $borrowernumber, $accdata->{'accountno'},
472 $nextaccntno, $newamtos );
477 my $nextaccntno = getnextacctno
($borrowernumber);
479 # get lines with outstanding amounts to offset
480 my $sth = $dbh->prepare(
481 "SELECT * FROM accountlines
482 WHERE (borrowernumber = ?) AND (amountoutstanding >0)
485 $sth->execute($borrowernumber);
488 # offset transactions
489 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
490 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
492 $amountleft -= $accdata->{'amountoutstanding'};
495 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
498 my $thisacct = $accdata->{accountno
};
499 my $usth = $dbh->prepare(
500 "UPDATE accountlines SET amountoutstanding= ?
501 WHERE (borrowernumber = ?) AND (accountno=?)"
503 $usth->execute( $newamtos, $borrowernumber, $thisacct );
505 $usth = $dbh->prepare(
506 "INSERT INTO accountoffsets
507 (borrowernumber, accountno, offsetaccount, offsetamount)
510 $usth->execute( $borrowernumber, $accdata->{'accountno'},
511 $nextaccntno, $newamtos );
515 $type = "Credit " . $type;
516 UpdateStats
( $user, $type, $data, $user, '', '', $borrowernumber );
518 return ($amountleft);
524 #FIXME : DEPRECATED SUB
525 This subroutine tracks payments and/or credits against fines/charges
526 using the accountoffsets table, which is not used consistently in
527 Koha's fines management, and so is not used in 3.0
533 #here we update both the accountoffsets and the account lines
534 my ( $borrowernumber, $data ) = @_;
535 my $dbh = C4
::Context
->dbh;
538 my $amountleft = $data * -1;
541 my $nextaccntno = getnextacctno
($borrowernumber);
543 # get lines with outstanding amounts to offset
544 my $sth = $dbh->prepare(
545 "SELECT * FROM accountlines
546 WHERE (borrowernumber = ?) AND (amountoutstanding<0)
549 $sth->execute($borrowernumber);
552 # offset transactions
553 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft < 0 ) ) {
554 if ( $accdata->{'amountoutstanding'} > $amountleft ) {
556 $amountleft -= $accdata->{'amountoutstanding'};
559 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
564 my $thisacct = $accdata->{accountno
};
565 my $usth = $dbh->prepare(
566 "UPDATE accountlines SET amountoutstanding= ?
567 WHERE (borrowernumber = ?) AND (accountno=?)"
569 $usth->execute( $newamtos, $borrowernumber, $thisacct );
571 $usth = $dbh->prepare(
572 "INSERT INTO accountoffsets
573 (borrowernumber, accountno, offsetaccount, offsetamount)
576 $usth->execute( $borrowernumber, $accdata->{'accountno'},
577 $nextaccntno, $newamtos );
581 return ($amountleft);
585 my ( $borrowerno, $timestamp, $accountno ) = @_;
586 my $dbh = C4
::Context
->dbh;
587 my $timestamp2 = $timestamp - 1;
589 my $sth = $dbh->prepare(
590 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?"
592 $sth->execute( $borrowerno, $accountno );
595 while ( my $data = $sth->fetchrow_hashref ) {
602 my ( $borrowernumber, $accountno, $note ) = @_;
603 my $dbh = C4
::Context
->dbh;
604 my $sth = $dbh->prepare('UPDATE accountlines SET note = ? WHERE borrowernumber = ? AND accountno = ?');
605 $sth->execute( $note, $borrowernumber, $accountno );
609 my ( $date, $date2 ) = @_;
610 my $dbh = C4
::Context
->dbh;
611 my $sth = $dbh->prepare(
612 "SELECT * FROM accountlines,borrowers
613 WHERE amount < 0 AND accounttype <> 'Pay' AND accountlines.borrowernumber = borrowers.borrowernumber
614 AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
617 $sth->execute( $date, $date2 );
619 while ( my $data = $sth->fetchrow_hashref ) {
620 $data->{'date'} = $data->{'timestamp'};
628 my ( $date, $date2 ) = @_;
629 my $dbh = C4
::Context
->dbh;
631 my $sth = $dbh->prepare(
632 "SELECT *,timestamp AS datetime
633 FROM accountlines,borrowers
634 WHERE (accounttype = 'REF'
635 AND accountlines.borrowernumber = borrowers.borrowernumber
636 AND date >=? AND date <?)"
639 $sth->execute( $date, $date2 );
642 while ( my $data = $sth->fetchrow_hashref ) {
650 my ( $borrowernumber, $accountno ) = @_;
651 my $dbh = C4
::Context
->dbh;
653 my $sth = $dbh->prepare('SELECT amountoutstanding FROM accountlines WHERE borrowernumber = ? AND accountno = ?');
654 $sth->execute( $borrowernumber, $accountno );
655 my $row = $sth->fetchrow_hashref();
656 my $amount_outstanding = $row->{'amountoutstanding'};
658 if ( $amount_outstanding <= 0 ) {
659 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = amount * -1, description = CONCAT( description, " Reversed -" ) WHERE borrowernumber = ? AND accountno = ?');
660 $sth->execute( $borrowernumber, $accountno );
662 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = 0, description = CONCAT( description, " Reversed -" ) WHERE borrowernumber = ? AND accountno = ?');
663 $sth->execute( $borrowernumber, $accountno );
667 =head2 recordpayment_selectaccts
669 recordpayment_selectaccts($borrowernumber, $payment,$accts);
671 Record payment by a patron. C<$borrowernumber> is the patron's
672 borrower number. C<$payment> is a floating-point number, giving the
673 amount that was paid. C<$accts> is an array ref to a list of
674 accountnos which the payment can be recorded against
676 Amounts owed are paid off oldest first. That is, if the patron has a
677 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
678 of $1.50, then the oldest fine will be paid off in full, and $0.50
679 will be credited to the next one.
683 sub recordpayment_selectaccts
{
684 my ( $borrowernumber, $amount, $accts ) = @_;
686 my $dbh = C4
::Context
->dbh;
689 my $branch = C4
::Context
->userenv->{branch
};
690 my $amountleft = $amount;
691 my $sql = 'SELECT * FROM accountlines WHERE (borrowernumber = ?) ' .
692 'AND (amountoutstanding<>0) ';
694 $sql .= ' AND accountno IN ( ' . join ',', @
{$accts};
697 $sql .= ' ORDER BY date';
699 my $nextaccntno = getnextacctno
($borrowernumber);
701 # get lines with outstanding amounts to offset
702 my $rows = $dbh->selectall_arrayref($sql, { Slice
=> {} }, $borrowernumber);
704 # offset transactions
705 my $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding= ? ' .
706 'WHERE (borrowernumber = ?) AND (accountno=?)');
707 for my $accdata ( @
{$rows} ) {
708 if ($amountleft == 0) {
711 if ( $accdata->{amountoutstanding
} < $amountleft ) {
713 $amountleft -= $accdata->{amountoutstanding
};
716 $newamtos = $accdata->{amountoutstanding
} - $amountleft;
719 my $thisacct = $accdata->{accountno
};
720 $sth->execute( $newamtos, $borrowernumber, $thisacct );
724 $sql = 'INSERT INTO accountlines ' .
725 '(borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding) ' .
726 q
|VALUES
(?
,?
,now
(),?
,'Payment,thanks','Pay',?
)|;
727 $dbh->do($sql,{},$borrowernumber, $nextaccntno, 0 - $amount, 0 - $amountleft );
728 UpdateStats
( $branch, 'payment', $amount, '', '', '', $borrowernumber, $nextaccntno );
732 # makepayment needs to be fixed to handle partials till then this separate subroutine
734 sub makepartialpayment
{
735 my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
737 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
738 if (!$amount || $amount < 0) {
741 my $dbh = C4
::Context
->dbh;
743 my $nextaccntno = getnextacctno
($borrowernumber);
746 my $data = $dbh->selectrow_hashref(
747 'SELECT * FROM accountlines WHERE borrowernumber=? AND accountno=?',undef,$borrowernumber,$accountno);
748 my $new_outstanding = $data->{amountoutstanding
} - $amount;
750 my $update = 'UPDATE accountlines SET amountoutstanding = ? WHERE borrowernumber = ? '
751 . ' AND accountno = ?';
752 $dbh->do( $update, undef, $new_outstanding, $borrowernumber, $accountno);
755 my $insert = 'INSERT INTO accountlines (borrowernumber, accountno, date, amount, '
756 . 'description, accounttype, amountoutstanding, itemnumber, manager_id) '
757 . ' VALUES (?, ?, now(), ?, ?, ?, 0, ?, ?)';
759 $dbh->do( $insert, undef, $borrowernumber, $nextaccntno, $amount,
760 "Payment, thanks - $user", 'Pay', $data->{'itemnumber'}, $manager_id);
762 UpdateStats
( $user, 'payment', $amount, '', '', '', $borrowernumber, $accountno );
769 WriteOff( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch );
771 Write off a fine for a patron.
772 C<$borrowernumber> is the patron's borrower number.
773 C<$accountnum> is the accountnumber of the fee to write off.
774 C<$itemnum> is the itemnumber of of item whose fine is being written off.
775 C<$accounttype> is the account type of the fine being written off.
776 C<$amount> is a floating-point number, giving the amount that is being written off.
777 C<$branch> is the branchcode of the library where the writeoff occurred.
782 my ( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch ) = @_;
783 $branch ||= C4
::Context
->userenv->{branch
};
785 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
787 # if no item is attached to fine, make sure to store it as a NULL
791 my $dbh = C4
::Context
->dbh();
794 UPDATE accountlines SET amountoutstanding = 0
795 WHERE accountno = ? AND borrowernumber = ?
797 $sth = $dbh->prepare( $query );
798 $sth->execute( $accountnum, $borrowernumber );
801 INSERT INTO accountlines
802 ( borrowernumber, accountno, itemnumber, date, amount, description, accounttype, manager_id )
803 VALUES ( ?, ?, ?, NOW(), ?, 'Writeoff', 'W', ? )
805 $sth = $dbh->prepare( $query );
806 my $acct = getnextacctno
($borrowernumber);
807 $sth->execute( $borrowernumber, $acct, $itemnum, $amount, $manager_id );
809 UpdateStats
( $branch, 'writeoff', $amount, q{}, q{}, q{}, $borrowernumber );
813 END { } # module clean-up code here (global destructor)