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
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 #use warnings; FIXME - Bug 2505
26 use C4
::Circulation
qw(ReturnLostItem);
27 use C4
::Log
qw(logaction);
30 use Data
::Dumper
qw(Dumper);
32 use vars
qw(@ISA @EXPORT);
47 &purge_zero_balance_fees
53 C4::Accounts - Functions for dealing with Koha accounts
61 The functions in this module deal with the monetary aspect of Koha,
62 including looking up and modifying the amount of money owed by a
69 $nextacct = &getnextacctno($borrowernumber);
71 Returns the next unused account number for the patron with the given
77 # FIXME - Okay, so what does the above actually _mean_?
79 my ($borrowernumber) = shift or return;
80 my $sth = C4
::Context
->dbh->prepare(
81 "SELECT accountno+1 FROM accountlines
82 WHERE (borrowernumber = ?)
83 ORDER BY accountno DESC
86 $sth->execute($borrowernumber);
87 return ($sth->fetchrow || 1);
90 =head2 fixaccounts (removed)
92 &fixaccounts($accountlines_id, $borrowernumber, $accountnumber, $amount);
95 # FIXME - I don't understand what this function does.
97 my ( $accountlines_id, $borrowernumber, $accountno, $amount ) = @_;
98 my $dbh = C4::Context->dbh;
99 my $sth = $dbh->prepare(
100 "SELECT * FROM accountlines WHERE accountlines_id=?"
102 $sth->execute( $accountlines_id );
103 my $data = $sth->fetchrow_hashref;
105 # FIXME - Error-checking
106 my $diff = $amount - $data->{'amount'};
107 my $outstanding = $data->{'amountoutstanding'} + $diff;
112 SET amount = '$amount',
113 amountoutstanding = '$outstanding'
114 WHERE accountlines_id = $accountlines_id
116 # FIXME: exceedingly bad form. Use prepare with placholders ("?") in query and execute args.
122 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
123 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
124 # a charge has been added
125 # FIXME : if no replacement price, borrower just doesn't get charged?
126 my $dbh = C4
::Context
->dbh();
127 my ($borrowernumber, $itemnumber, $amount, $description) = @_;
129 # first make sure the borrower hasn't already been charged for this item
130 my $sth1=$dbh->prepare("SELECT * from accountlines
131 WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
132 $sth1->execute($borrowernumber,$itemnumber);
133 my $existing_charge_hashref=$sth1->fetchrow_hashref();
136 unless ($existing_charge_hashref) {
138 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
139 # This item is on issue ... add replacement cost to the borrower's record and mark it returned
140 # Note that we add this to the account even if there's no replacement price, allowing some other
141 # process (or person) to update it, since we don't handle any defaults for replacement prices.
142 my $accountno = getnextacctno
($borrowernumber);
143 my $sth2=$dbh->prepare("INSERT INTO accountlines
144 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber,manager_id)
145 VALUES (?,?,now(),?,?,'L',?,?,?)");
146 $sth2->execute($borrowernumber,$accountno,$amount,
147 $description,$amount,$itemnumber,$manager_id);
149 if ( C4
::Context
->preference("FinesLog") ) {
150 logaction
("FINES", 'CREATE', $borrowernumber, Dumper
({
151 action
=> 'create_fee',
152 borrowernumber
=> $borrowernumber,
153 accountno
=> $accountno,
155 amountoutstanding
=> $amount,
156 description
=> $description,
158 itemnumber
=> $itemnumber,
159 manager_id
=> $manager_id,
168 &manualinvoice($borrowernumber, $itemnumber, $description, $type,
171 C<$borrowernumber> is the patron's borrower number.
172 C<$description> is a description of the transaction.
173 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
175 C<$itemnumber> is the item involved, if pertinent; otherwise, it
176 should be the empty string.
181 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
184 # 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
187 # 'A' = Account Management fee
193 my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_;
195 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
196 my $dbh = C4
::Context
->dbh;
199 my $accountno = getnextacctno
($borrowernumber);
200 my $amountleft = $amount;
202 if ( ( $type eq 'L' )
206 or ( $type eq 'M' ) )
212 $desc .= ' ' . $itemnum;
213 my $sth = $dbh->prepare(
214 'INSERT INTO accountlines
215 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id, note, manager_id)
216 VALUES (?, ?, now(), ?,?, ?,?,?,?,?,?)');
217 $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid, $note, $manager_id) || return $sth->errstr;
219 my $sth=$dbh->prepare("INSERT INTO accountlines
220 (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id, note, manager_id)
221 VALUES (?, ?, now(), ?, ?, ?, ?,?,?,?)"
223 $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
224 $amountleft, $notifyid, $note, $manager_id );
227 if ( C4
::Context
->preference("FinesLog") ) {
228 logaction
("FINES", 'CREATE',$borrowernumber,Dumper
({
229 action
=> 'create_fee',
230 borrowernumber
=> $borrowernumber,
231 accountno
=> $accountno,
233 description
=> $desc,
234 accounttype
=> $type,
235 amountoutstanding
=> $amountleft,
236 notify_id
=> $notifyid,
238 itemnumber
=> $itemnum,
239 manager_id
=> $manager_id,
247 my ( $borrowerno, $timestamp, $accountno ) = @_;
248 my $dbh = C4
::Context
->dbh;
249 my $timestamp2 = $timestamp - 1;
251 my $sth = $dbh->prepare(
252 "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?"
254 $sth->execute( $borrowerno, $accountno );
257 while ( my $data = $sth->fetchrow_hashref ) {
264 my ( $accountlines_id, $note ) = @_;
265 my $dbh = C4
::Context
->dbh;
266 my $sth = $dbh->prepare('UPDATE accountlines SET note = ? WHERE accountlines_id = ?');
267 $sth->execute( $note, $accountlines_id );
271 my ( $date, $date2 ) = @_;
272 my $dbh = C4
::Context
->dbh;
273 my $sth = $dbh->prepare(
274 "SELECT * FROM accountlines,borrowers
275 WHERE amount < 0 AND accounttype not like 'Pay%' AND accountlines.borrowernumber = borrowers.borrowernumber
276 AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
279 $sth->execute( $date, $date2 );
281 while ( my $data = $sth->fetchrow_hashref ) {
282 $data->{'date'} = $data->{'timestamp'};
290 my ( $date, $date2 ) = @_;
291 my $dbh = C4
::Context
->dbh;
293 my $sth = $dbh->prepare(
294 "SELECT *,timestamp AS datetime
295 FROM accountlines,borrowers
296 WHERE (accounttype = 'REF'
297 AND accountlines.borrowernumber = borrowers.borrowernumber
298 AND date >=? AND date <?)"
301 $sth->execute( $date, $date2 );
304 while ( my $data = $sth->fetchrow_hashref ) {
312 my ( $accountlines_id ) = @_;
313 my $dbh = C4
::Context
->dbh;
315 my $sth = $dbh->prepare('SELECT * FROM accountlines WHERE accountlines_id = ?');
316 $sth->execute( $accountlines_id );
317 my $row = $sth->fetchrow_hashref();
318 my $amount_outstanding = $row->{'amountoutstanding'};
320 if ( $amount_outstanding <= 0 ) {
321 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = amount * -1, description = CONCAT( description, " Reversed -" ) WHERE accountlines_id = ?');
322 $sth->execute( $accountlines_id );
324 $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = 0, description = CONCAT( description, " Reversed -" ) WHERE accountlines_id = ?');
325 $sth->execute( $accountlines_id );
328 if ( C4
::Context
->preference("FinesLog") ) {
330 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
332 if ( $amount_outstanding <= 0 ) {
333 $row->{'amountoutstanding'} *= -1;
335 $row->{'amountoutstanding'} = '0';
337 $row->{'description'} .= ' Reversed -';
338 logaction
("FINES", 'MODIFY', $row->{'borrowernumber'}, Dumper
({
339 action
=> 'reverse_fee_payment',
340 borrowernumber
=> $row->{'borrowernumber'},
341 old_amountoutstanding
=> $row->{'amountoutstanding'},
342 new_amountoutstanding
=> 0 - $amount_outstanding,,
343 accountlines_id
=> $row->{'accountlines_id'},
344 accountno
=> $row->{'accountno'},
345 manager_id
=> $manager_id,
354 WriteOffFee( $borrowernumber, $accountline_id, $itemnum, $accounttype, $amount, $branch, $payment_note );
356 Write off a fine for a patron.
357 C<$borrowernumber> is the patron's borrower number.
358 C<$accountline_id> is the accountline_id of the fee to write off.
359 C<$itemnum> is the itemnumber of of item whose fine is being written off.
360 C<$accounttype> is the account type of the fine being written off.
361 C<$amount> is a floating-point number, giving the amount that is being written off.
362 C<$branch> is the branchcode of the library where the writeoff occurred.
363 C<$payment_note> is the note to attach to this payment
368 my ( $borrowernumber, $accountlines_id, $itemnum, $accounttype, $amount, $branch, $payment_note ) = @_;
369 $payment_note //= "";
370 $branch ||= C4
::Context
->userenv->{branch
};
372 $manager_id = C4
::Context
->userenv->{'number'} if C4
::Context
->userenv;
374 # if no item is attached to fine, make sure to store it as a NULL
378 my $dbh = C4
::Context
->dbh();
381 UPDATE accountlines SET amountoutstanding = 0
382 WHERE accountlines_id = ? AND borrowernumber = ?
384 $sth = $dbh->prepare( $query );
385 $sth->execute( $accountlines_id, $borrowernumber );
387 if ( C4
::Context
->preference("FinesLog") ) {
388 logaction
("FINES", 'MODIFY', $borrowernumber, Dumper
({
389 action
=> 'fee_writeoff',
390 borrowernumber
=> $borrowernumber,
391 accountlines_id
=> $accountlines_id,
392 manager_id
=> $manager_id,
397 INSERT INTO accountlines
398 ( borrowernumber, accountno, itemnumber, date, amount, description, accounttype, manager_id, note )
399 VALUES ( ?, ?, ?, NOW(), ?, 'Writeoff', 'W', ?, ? )
401 $sth = $dbh->prepare( $query );
402 my $acct = getnextacctno
($borrowernumber);
403 $sth->execute( $borrowernumber, $acct, $itemnum, $amount, $manager_id, $payment_note );
405 if ( C4
::Context
->preference("FinesLog") ) {
406 logaction
("FINES", 'CREATE',$borrowernumber,Dumper
({
407 action
=> 'create_writeoff',
408 borrowernumber
=> $borrowernumber,
410 amount
=> 0 - $amount,
412 itemnumber
=> $itemnum,
413 accountlines_paid
=> [ $accountlines_id ],
414 manager_id
=> $manager_id,
422 borrowernumber
=> $borrowernumber}
427 =head2 purge_zero_balance_fees
429 purge_zero_balance_fees( $days );
431 Delete accountlines entries where amountoutstanding is 0 or NULL which are more than a given number of days old.
433 B<$days> -- Zero balance fees older than B<$days> days old will be deleted.
435 B<Warning:> Because fines and payments are not linked in accountlines, it is
436 possible for a fine to be deleted without the accompanying payment,
437 or vise versa. This won't affect the account balance, but might be
442 sub purge_zero_balance_fees
{
446 my $dbh = C4
::Context
->dbh;
447 my $sth = $dbh->prepare(
449 DELETE FROM accountlines
450 WHERE date < date_sub(curdate(), INTERVAL ? DAY)
451 AND ( amountoutstanding = 0 or amountoutstanding IS NULL );
454 $sth->execute($days) or die $dbh->errstr;
457 END { } # module clean-up code here (global destructor)