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
36 &recordpayment &makepayment &manualinvoice
37 &getnextacctno &reconcileaccount &getcharges &ModNote &getcredits
38 &getrefunds &chargelostitem
41 recordpayment_selectaccts
47 C4::Accounts - Functions for dealing with Koha accounts
55 The functions in this module deal with the monetary aspect of Koha,
56 including looking up and modifying the amount of money owed by a
63 &recordpayment($borrowernumber, $payment);
65 Record payment by a patron. C<$borrowernumber> is the patron's
66 borrower number. C<$payment> is a floating-point number, giving the
69 Amounts owed are paid off oldest first. That is, if the patron has a
70 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
71 of $1.50, then the oldest fine will be paid off in full, and $0.50
72 will be credited to the next one.
79 #here we update the account lines
80 my ( $borrowernumber, $data ) = @_;
81 my $dbh = C4
::Context
->dbh;
84 my $branch = C4
::Context
->userenv->{'branch'};
85 my $amountleft = $data;
88 my $nextaccntno = getnextacctno
($borrowernumber);
90 # get lines with outstanding amounts to offset
91 my $sth = $dbh->prepare(
92 "SELECT * FROM accountlines
93 WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
96 $sth->execute($borrowernumber);
99 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
100 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
102 $amountleft -= $accdata->{'amountoutstanding'};
105 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
108 my $thisacct = $accdata->{accountno
};
109 my $usth = $dbh->prepare(
110 "UPDATE accountlines SET amountoutstanding= ?
111 WHERE (borrowernumber = ?) AND (accountno=?)"
113 $usth->execute( $newamtos, $borrowernumber, $thisacct );
115 # $usth = $dbh->prepare(
116 # "INSERT INTO accountoffsets
117 # (borrowernumber, accountno, offsetaccount, offsetamount)
120 # $usth->execute( $borrowernumber, $accdata->{'accountno'},
121 # $nextaccntno, $newamtos );
126 my $usth = $dbh->prepare(
127 "INSERT INTO accountlines
128 (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding)
129 VALUES (?,?,now(),?,'Payment,thanks','Pay',?)"
131 $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft );
133 UpdateStats
( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
139 &makepayment($borrowernumber, $acctnumber, $amount, $branchcode);
141 Records the fact that a patron has paid off the entire amount he or
144 C<$borrowernumber> is the patron's borrower number. C<$acctnumber> is
145 the account that was credited. C<$amount> is the amount paid (this is
146 only used to record the payment. It is assumed to be equal to the
147 amount owed). C<$branchcode> is the code of the branch where payment
153 # FIXME - I'm not at all sure about the above, because I don't
154 # understand what the acct* tables in the Koha database are for.
157 #here we update both the accountoffsets and the account lines
158 #updated to check, if they are paying off a lost item, we return the item
159 # from their card, and put a note on the item record
160 my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
161 my $dbh = C4
::Context
->dbh;
163 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
166 my $nextaccntno = getnextacctno
($borrowernumber);
170 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno=?");
171 $sth->execute( $borrowernumber, $accountno );
172 my $data = $sth->fetchrow_hashref;
175 if($data->{'accounttype'} eq "Pay"){
179 SET amountoutstanding = 0, description = 'Payment,thanks'
180 WHERE borrowernumber = ?
184 $udp->execute($borrowernumber, $accountno );
190 SET amountoutstanding = 0
191 WHERE borrowernumber = ?
195 $udp->execute($borrowernumber, $accountno );
199 my $payment = 0 - $amount;
204 INTO accountlines (borrowernumber, accountno, date, amount, itemnumber, description, accounttype, amountoutstanding, manager_id)
205 VALUES ( ?, ?, now(), ?, ?, 'Payment,thanks', 'Pay', 0, ?)"
207 $ins->execute($borrowernumber, $nextaccntno, $payment, $data->{'itemnumber'}, $manager_id);
211 # FIXME - The second argument to &UpdateStats is supposed to be the
213 # UpdateStats is now being passed $accountno too. MTJ
214 UpdateStats
( $user, 'payment', $amount, '', '', '', $borrowernumber,
216 #from perldoc: for SELECT only #$sth->finish;
218 #check to see what accounttype
219 if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
220 C4
::Circulation
::ReturnLostItem
( $borrowernumber, $data->{'itemnumber'} );
226 $nextacct = &getnextacctno($borrowernumber);
228 Returns the next unused account number for the patron with the given
234 # FIXME - Okay, so what does the above actually _mean_?
235 sub getnextacctno
($) {
236 my ($borrowernumber) = shift or return undef;
237 my $sth = C4
::Context
->dbh->prepare(
238 "SELECT accountno+1 FROM accountlines
239 WHERE (borrowernumber = ?)
240 ORDER BY accountno DESC
243 $sth->execute($borrowernumber);
244 return ($sth->fetchrow || 1);
247 =head2 fixaccounts (removed)
249 &fixaccounts($borrowernumber, $accountnumber, $amount);
252 # FIXME - I don't understand what this function does.
254 my ( $borrowernumber, $accountno, $amount ) = @_;
255 my $dbh = C4::Context->dbh;
256 my $sth = $dbh->prepare(
257 "SELECT * FROM accountlines WHERE borrowernumber=?
260 $sth->execute( $borrowernumber, $accountno );
261 my $data = $sth->fetchrow_hashref;
263 # FIXME - Error-checking
264 my $diff = $amount - $data->{'amount'};
265 my $outstanding = $data->{'amountoutstanding'} + $diff;
270 SET amount = '$amount',
271 amountoutstanding = '$outstanding'
272 WHERE borrowernumber = $borrowernumber
273 AND accountno = $accountno
275 # FIXME: exceedingly bad form. Use prepare with placholders ("?") in query and execute args.
281 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
282 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
283 # a charge has been added
284 # FIXME : if no replacement price, borrower just doesn't get charged?
285 my $dbh = C4
::Context
->dbh();
286 my ($borrowernumber, $itemnumber, $amount, $description) = @_;
288 # first make sure the borrower hasn't already been charged for this item
289 my $sth1=$dbh->prepare("SELECT * from accountlines
290 WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
291 $sth1->execute($borrowernumber,$itemnumber);
292 my $existing_charge_hashref=$sth1->fetchrow_hashref();
295 unless ($existing_charge_hashref) {
296 # This item is on issue ... add replacement cost to the borrower's record and mark it returned
297 # Note that we add this to the account even if there's no replacement price, allowing some other
298 # process (or person) to update it, since we don't handle any defaults for replacement prices.
299 my $accountno = getnextacctno
($borrowernumber);
300 my $sth2=$dbh->prepare("INSERT INTO accountlines
301 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber)
302 VALUES (?,?,now(),?,?,'L',?,?)");
303 $sth2->execute($borrowernumber,$accountno,$amount,
304 $description,$amount,$itemnumber);
312 &manualinvoice($borrowernumber, $itemnumber, $description, $type,
315 C<$borrowernumber> is the patron's borrower number.
316 C<$description> is a description of the transaction.
317 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
319 C<$itemnumber> is the item involved, if pertinent; otherwise, it
320 should be the empty string.
325 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
328 # 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
331 # 'A' = Account Management fee
337 my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_;
339 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
340 my $dbh = C4
::Context
->dbh;
343 my $accountno = getnextacctno
($borrowernumber);
344 my $amountleft = $amount;
352 # my $amount2 = $amount * -1; # FIXME - $amount2 = -$amount
354 # fixcredit( $borrowernumber, $amount2, $itemnum, $type, $user );
356 if ( $type eq 'N' ) {
357 $desc .= " New Card";
359 if ( $type eq 'F' ) {
362 if ( $type eq 'A' ) {
363 $desc .= " Account Management fee";
365 if ( $type eq 'M' ) {
369 if ( $type eq 'L' && $desc eq '' ) {
371 $desc = " Lost Item";
373 # if ( $type eq 'REF' ) {
374 # $desc .= " Cash Refund";
375 # $amountleft = refund( '', $borrowernumber, $amount );
377 if ( ( $type eq 'L' )
381 or ( $type eq 'M' ) )
387 $desc .= ' ' . $itemnum;
388 my $sth = $dbh->prepare(
389 'INSERT INTO accountlines
390 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id, note, manager_id)
391 VALUES (?, ?, now(), ?,?, ?,?,?,?,?,?)');
392 $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid, $note, $manager_id) || return $sth->errstr;
394 my $sth=$dbh->prepare("INSERT INTO accountlines
395 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id, note, manager_id)
396 VALUES (?, ?, now(), ?, ?, ?, ?,?,?,?)"
398 $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
399 $amountleft, $notifyid, $note, $manager_id );
404 =head2 fixcredit #### DEPRECATED
406 $amountleft = &fixcredit($borrowernumber, $data, $barcode, $type, $user);
408 This function is only used internally, not exported.
412 # This function is deprecated in 3.0
416 #here we update both the accountoffsets and the account lines
417 my ( $borrowernumber, $data, $barcode, $type, $user ) = @_;
418 my $dbh = C4
::Context
->dbh;
421 my $amountleft = $data;
422 if ( $barcode ne '' ) {
423 my $item = GetBiblioFromItemNumber
( '', $barcode );
424 my $nextaccntno = getnextacctno
($borrowernumber);
425 my $query = "SELECT * FROM accountlines WHERE (borrowernumber=?
426 AND itemnumber=? AND amountoutstanding > 0)";
427 if ( $type eq 'CL' ) {
428 $query .= " AND (accounttype = 'L' OR accounttype = 'Rep')";
430 elsif ( $type eq 'CF' ) {
431 $query .= " AND (accounttype = 'F' OR accounttype = 'FU' OR
432 accounttype='Res' OR accounttype='Rent')";
434 elsif ( $type eq 'CB' ) {
435 $query .= " and accounttype='A'";
439 my $sth = $dbh->prepare($query);
440 $sth->execute( $borrowernumber, $item->{'itemnumber'} );
441 $accdata = $sth->fetchrow_hashref;
443 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
445 $amountleft -= $accdata->{'amountoutstanding'};
448 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
451 my $thisacct = $accdata->{accountno
};
452 my $usth = $dbh->prepare(
453 "UPDATE accountlines SET amountoutstanding= ?
454 WHERE (borrowernumber = ?) AND (accountno=?)"
456 $usth->execute( $newamtos, $borrowernumber, $thisacct );
458 $usth = $dbh->prepare(
459 "INSERT INTO accountoffsets
460 (borrowernumber, accountno, offsetaccount, offsetamount)
463 $usth->execute( $borrowernumber, $accdata->{'accountno'},
464 $nextaccntno, $newamtos );
469 my $nextaccntno = getnextacctno
($borrowernumber);
471 # get lines with outstanding amounts to offset
472 my $sth = $dbh->prepare(
473 "SELECT * FROM accountlines
474 WHERE (borrowernumber = ?) AND (amountoutstanding >0)
477 $sth->execute($borrowernumber);
480 # offset transactions
481 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
482 if ( $accdata->{'amountoutstanding'} < $amountleft ) {
484 $amountleft -= $accdata->{'amountoutstanding'};
487 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
490 my $thisacct = $accdata->{accountno
};
491 my $usth = $dbh->prepare(
492 "UPDATE accountlines SET amountoutstanding= ?
493 WHERE (borrowernumber = ?) AND (accountno=?)"
495 $usth->execute( $newamtos, $borrowernumber, $thisacct );
497 $usth = $dbh->prepare(
498 "INSERT INTO accountoffsets
499 (borrowernumber, accountno, offsetaccount, offsetamount)
502 $usth->execute( $borrowernumber, $accdata->{'accountno'},
503 $nextaccntno, $newamtos );
507 $type = "Credit " . $type;
508 UpdateStats
( $user, $type, $data, $user, '', '', $borrowernumber );
510 return ($amountleft);
516 #FIXME : DEPRECATED SUB
517 This subroutine tracks payments and/or credits against fines/charges
518 using the accountoffsets table, which is not used consistently in
519 Koha's fines management, and so is not used in 3.0
525 #here we update both the accountoffsets and the account lines
526 my ( $borrowernumber, $data ) = @_;
527 my $dbh = C4
::Context
->dbh;
530 my $amountleft = $data * -1;
533 my $nextaccntno = getnextacctno
($borrowernumber);
535 # get lines with outstanding amounts to offset
536 my $sth = $dbh->prepare(
537 "SELECT * FROM accountlines
538 WHERE (borrowernumber = ?) AND (amountoutstanding<0)
541 $sth->execute($borrowernumber);
544 # offset transactions
545 while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft < 0 ) ) {
546 if ( $accdata->{'amountoutstanding'} > $amountleft ) {
548 $amountleft -= $accdata->{'amountoutstanding'};
551 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
556 my $thisacct = $accdata->{accountno
};
557 my $usth = $dbh->prepare(
558 "UPDATE accountlines SET amountoutstanding= ?
559 WHERE (borrowernumber = ?) AND (accountno=?)"
561 $usth->execute( $newamtos, $borrowernumber, $thisacct );
563 $usth = $dbh->prepare(
564 "INSERT INTO accountoffsets
565 (borrowernumber, accountno, offsetaccount, offsetamount)
568 $usth->execute( $borrowernumber, $accdata->{'accountno'},
569 $nextaccntno, $newamtos );
573 return ($amountleft);
577 my ( $borrowerno, $timestamp, $accountno ) = @_;
578 my $dbh = C4
::Context
->dbh;
579 my $timestamp2 = $timestamp - 1;
581 my $sth = $dbh->prepare(
582 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?"
584 $sth->execute( $borrowerno, $accountno );
587 while ( my $data = $sth->fetchrow_hashref ) {
594 my ( $borrowernumber, $accountno, $note ) = @_;
595 my $dbh = C4
::Context
->dbh;
596 my $sth = $dbh->prepare('UPDATE accountlines SET note = ? WHERE borrowernumber = ? AND accountno = ?');
597 $sth->execute( $note, $borrowernumber, $accountno );
601 my ( $date, $date2 ) = @_;
602 my $dbh = C4
::Context
->dbh;
603 my $sth = $dbh->prepare(
604 "SELECT * FROM accountlines,borrowers
605 WHERE amount < 0 AND accounttype <> 'Pay' AND accountlines.borrowernumber = borrowers.borrowernumber
606 AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
609 $sth->execute( $date, $date2 );
611 while ( my $data = $sth->fetchrow_hashref ) {
612 $data->{'date'} = $data->{'timestamp'};
620 my ( $date, $date2 ) = @_;
621 my $dbh = C4
::Context
->dbh;
623 my $sth = $dbh->prepare(
624 "SELECT *,timestamp AS datetime
625 FROM accountlines,borrowers
626 WHERE (accounttype = 'REF'
627 AND accountlines.borrowernumber = borrowers.borrowernumber
628 AND date >=? AND date <?)"
631 $sth->execute( $date, $date2 );
634 while ( my $data = $sth->fetchrow_hashref ) {
642 my ( $borrowernumber, $accountno ) = @_;
643 my $dbh = C4
::Context
->dbh;
645 my $sth = $dbh->prepare('SELECT amountoutstanding FROM accountlines WHERE borrowernumber = ? AND accountno = ?');
646 $sth->execute( $borrowernumber, $accountno );
647 my $row = $sth->fetchrow_hashref();
648 my $amount_outstanding = $row->{'amountoutstanding'};
650 if ( $amount_outstanding <= 0 ) {
651 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = amount * -1, description = CONCAT( description, " Reversed -" ) WHERE borrowernumber = ? AND accountno = ?');
652 $sth->execute( $borrowernumber, $accountno );
654 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = 0, description = CONCAT( description, " Reversed -" ) WHERE borrowernumber = ? AND accountno = ?');
655 $sth->execute( $borrowernumber, $accountno );
659 =head2 recordpayment_selectaccts
661 recordpayment_selectaccts($borrowernumber, $payment,$accts);
663 Record payment by a patron. C<$borrowernumber> is the patron's
664 borrower number. C<$payment> is a floating-point number, giving the
665 amount that was paid. C<$accts> is an array ref to a list of
666 accountnos which the payment can be recorded against
668 Amounts owed are paid off oldest first. That is, if the patron has a
669 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
670 of $1.50, then the oldest fine will be paid off in full, and $0.50
671 will be credited to the next one.
675 sub recordpayment_selectaccts
{
676 my ( $borrowernumber, $amount, $accts ) = @_;
678 my $dbh = C4
::Context
->dbh;
681 my $branch = C4
::Context
->userenv->{branch
};
682 my $amountleft = $amount;
683 my $sql = 'SELECT * FROM accountlines WHERE (borrowernumber = ?) ' .
684 'AND (amountoutstanding<>0) ';
686 $sql .= ' AND accountno IN ( ' . join ',', @
{$accts};
689 $sql .= ' ORDER BY date';
691 my $nextaccntno = getnextacctno
($borrowernumber);
693 # get lines with outstanding amounts to offset
694 my $rows = $dbh->selectall_arrayref($sql, { Slice
=> {} }, $borrowernumber);
696 # offset transactions
697 my $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding= ? ' .
698 'WHERE (borrowernumber = ?) AND (accountno=?)');
699 for my $accdata ( @
{$rows} ) {
700 if ($amountleft == 0) {
703 if ( $accdata->{amountoutstanding
} < $amountleft ) {
705 $amountleft -= $accdata->{amountoutstanding
};
708 $newamtos = $accdata->{amountoutstanding
} - $amountleft;
711 my $thisacct = $accdata->{accountno
};
712 $sth->execute( $newamtos, $borrowernumber, $thisacct );
716 $sql = 'INSERT INTO accountlines ' .
717 '(borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding) ' .
718 q
|VALUES
(?
,?
,now
(),?
,'Payment,thanks','Pay',?
)|;
719 $dbh->do($sql,{},$borrowernumber, $nextaccntno, 0 - $amount, 0 - $amountleft );
720 UpdateStats
( $branch, 'payment', $amount, '', '', '', $borrowernumber, $nextaccntno );
724 # makepayment needs to be fixed to handle partials till then this separate subroutine
726 sub makepartialpayment
{
727 my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
729 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
730 if (!$amount || $amount < 0) {
733 my $dbh = C4
::Context
->dbh;
735 my $nextaccntno = getnextacctno
($borrowernumber);
738 my $data = $dbh->selectrow_hashref(
739 'SELECT * FROM accountlines WHERE borrowernumber=? AND accountno=?',undef,$borrowernumber,$accountno);
740 my $new_outstanding = $data->{amountoutstanding
} - $amount;
742 my $update = 'UPDATE accountlines SET amountoutstanding = ? WHERE borrowernumber = ? '
743 . ' AND accountno = ?';
744 $dbh->do( $update, undef, $new_outstanding, $borrowernumber, $accountno);
747 my $insert = 'INSERT INTO accountlines (borrowernumber, accountno, date, amount, '
748 . 'description, accounttype, amountoutstanding, itemnumber, manager_id) '
749 . ' VALUES (?, ?, now(), ?, ?, ?, 0, ?, ?)';
751 $dbh->do( $insert, undef, $borrowernumber, $nextaccntno, $amount,
752 "Payment, thanks - $user", 'Pay', $data->{'itemnumber'}, $manager_id);
754 UpdateStats
( $user, 'payment', $amount, '', '', '', $borrowernumber, $accountno );
761 END { } # module clean-up code here (global destructor)