Bug 8739 - Partial Fine Payments Saving Amount Paid Incorrectly
[koha.git] / C4 / Accounts.pm
1 package C4::Accounts;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
15 #
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.
19
20
21 use strict;
22 #use warnings; FIXME - Bug 2505
23 use C4::Context;
24 use C4::Stats;
25 use C4::Members;
26 use C4::Circulation qw(ReturnLostItem);
27
28 use vars qw($VERSION @ISA @EXPORT);
29
30 BEGIN {
31         # set the version for version checking
32     $VERSION = 3.07.00.049;
33         require Exporter;
34         @ISA    = qw(Exporter);
35         @EXPORT = qw(
36                 &recordpayment
37                 &makepayment
38                 &manualinvoice
39                 &getnextacctno
40                 &reconcileaccount
41                 &getcharges
42                 &ModNote
43                 &getcredits
44                 &getrefunds
45                 &chargelostitem
46                 &ReversePayment
47                 &makepartialpayment
48                 &recordpayment_selectaccts
49                 &WriteOffFee
50         );
51 }
52
53 =head1 NAME
54
55 C4::Accounts - Functions for dealing with Koha accounts
56
57 =head1 SYNOPSIS
58
59 use C4::Accounts;
60
61 =head1 DESCRIPTION
62
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
65 patron.
66
67 =head1 FUNCTIONS
68
69 =head2 recordpayment
70
71   &recordpayment($borrowernumber, $payment);
72
73 Record payment by a patron. C<$borrowernumber> is the patron's
74 borrower number. C<$payment> is a floating-point number, giving the
75 amount that was paid.
76
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.
81
82 =cut
83
84 #'
85 sub recordpayment {
86
87     #here we update the account lines
88     my ( $borrowernumber, $data ) = @_;
89     my $dbh        = C4::Context->dbh;
90     my $newamtos   = 0;
91     my $accdata    = "";
92     my $branch     = C4::Context->userenv->{'branch'};
93     my $amountleft = $data;
94     my $manager_id = 0;
95     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
96
97     # begin transaction
98     my $nextaccntno = getnextacctno($borrowernumber);
99
100     # get lines with outstanding amounts to offset
101     my $sth = $dbh->prepare(
102         "SELECT * FROM accountlines
103   WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
104   ORDER BY date"
105     );
106     $sth->execute($borrowernumber);
107
108     # offset transactions
109     while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
110         if ( $accdata->{'amountoutstanding'} < $amountleft ) {
111             $newamtos = 0;
112             $amountleft -= $accdata->{'amountoutstanding'};
113         }
114         else {
115             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
116             $amountleft = 0;
117         }
118         my $thisacct = $accdata->{accountlines_id};
119         my $usth     = $dbh->prepare(
120             "UPDATE accountlines SET amountoutstanding= ?
121      WHERE (accountlines_id = ?)"
122         );
123         $usth->execute( $newamtos, $thisacct );
124         $usth->finish;
125 #        $usth = $dbh->prepare(
126 #            "INSERT INTO accountoffsets
127 #     (borrowernumber, accountno, offsetaccount,  offsetamount)
128 #     VALUES (?,?,?,?)"
129 #        );
130 #        $usth->execute( $borrowernumber, $accdata->{'accountno'},
131 #            $nextaccntno, $newamtos );
132         $usth->finish;
133     }
134
135     # create new line
136     my $usth = $dbh->prepare(
137         "INSERT INTO accountlines
138   (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding,manager_id)
139   VALUES (?,?,now(),?,'Payment,thanks','Pay',?,?)"
140     );
141     $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft, $manager_id );
142     $usth->finish;
143     UpdateStats( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
144     $sth->finish;
145 }
146
147 =head2 makepayment
148
149   &makepayment($accountlines_id, $borrowernumber, $acctnumber, $amount, $branchcode);
150
151 Records the fact that a patron has paid off the entire amount he or
152 she owes.
153
154 C<$borrowernumber> is the patron's borrower number. C<$acctnumber> is
155 the account that was credited. C<$amount> is the amount paid (this is
156 only used to record the payment. It is assumed to be equal to the
157 amount owed). C<$branchcode> is the code of the branch where payment
158 was made.
159
160 =cut
161
162 #'
163 # FIXME - I'm not at all sure about the above, because I don't
164 # understand what the acct* tables in the Koha database are for.
165 sub makepayment {
166
167     #here we update both the accountoffsets and the account lines
168     #updated to check, if they are paying off a lost item, we return the item
169     # from their card, and put a note on the item record
170     my ( $accountlines_id, $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
171     my $dbh = C4::Context->dbh;
172     my $manager_id = 0;
173     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
174
175     # begin transaction
176     my $nextaccntno = getnextacctno($borrowernumber);
177     my $newamtos    = 0;
178     my $sth         = $dbh->prepare("SELECT * FROM accountlines WHERE accountlines_id=?");
179     $sth->execute( $accountlines_id );
180     my $data = $sth->fetchrow_hashref;
181     $sth->finish;
182
183     if($data->{'accounttype'} eq "Pay"){
184         my $udp =               
185             $dbh->prepare(
186                 "UPDATE accountlines
187                     SET amountoutstanding = 0, description = 'Payment,thanks'
188                     WHERE accountlines_id = ?
189                 "
190             );
191         $udp->execute($accountlines_id);
192         $udp->finish;
193     }else{
194         my $udp =               
195             $dbh->prepare(
196                 "UPDATE accountlines
197                     SET amountoutstanding = 0
198                     WHERE accountlines_id = ?
199                 "
200             );
201         $udp->execute($accountlines_id);
202         $udp->finish;
203
204          # create new line
205         my $payment = 0 - $amount;
206         
207         my $ins = 
208             $dbh->prepare( 
209                 "INSERT 
210                     INTO accountlines (borrowernumber, accountno, date, amount, itemnumber, description, accounttype, amountoutstanding, manager_id)
211                     VALUES ( ?, ?, now(), ?, ?, 'Payment,thanks', 'Pay', 0, ?)"
212             );
213         $ins->execute($borrowernumber, $nextaccntno, $payment, $data->{'itemnumber'}, $manager_id);
214         $ins->finish;
215     }
216
217     # FIXME - The second argument to &UpdateStats is supposed to be the
218     # branch code.
219     # UpdateStats is now being passed $accountno too. MTJ
220     UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber,
221         $accountno );
222     #from perldoc: for SELECT only #$sth->finish;
223
224     #check to see what accounttype
225     if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
226         C4::Circulation::ReturnLostItem( $borrowernumber, $data->{'itemnumber'} );
227     }
228     my $sthr = $dbh->prepare("SELECT max(accountlines_id) AS lastinsertid FROM accountlines");
229     $sthr->execute();
230     my $datalastinsertid = $sthr->fetchrow_hashref;
231     $sthr->finish;
232     return $datalastinsertid->{'lastinsertid'};
233 }
234
235 =head2 getnextacctno
236
237   $nextacct = &getnextacctno($borrowernumber);
238
239 Returns the next unused account number for the patron with the given
240 borrower number.
241
242 =cut
243
244 #'
245 # FIXME - Okay, so what does the above actually _mean_?
246 sub getnextacctno ($) {
247     my ($borrowernumber) = shift or return undef;
248     my $sth = C4::Context->dbh->prepare(
249         "SELECT accountno+1 FROM accountlines
250          WHERE    (borrowernumber = ?)
251          ORDER BY accountno DESC
252                  LIMIT 1"
253     );
254     $sth->execute($borrowernumber);
255     return ($sth->fetchrow || 1);
256 }
257
258 =head2 fixaccounts (removed)
259
260   &fixaccounts($accountlines_id, $borrowernumber, $accountnumber, $amount);
261
262 #'
263 # FIXME - I don't understand what this function does.
264 sub fixaccounts {
265     my ( $accountlines_id, $borrowernumber, $accountno, $amount ) = @_;
266     my $dbh = C4::Context->dbh;
267     my $sth = $dbh->prepare(
268         "SELECT * FROM accountlines WHERE accountlines_id=?"
269     );
270     $sth->execute( $accountlines_id );
271     my $data = $sth->fetchrow_hashref;
272
273     # FIXME - Error-checking
274     my $diff        = $amount - $data->{'amount'};
275     my $outstanding = $data->{'amountoutstanding'} + $diff;
276     $sth->finish;
277
278     $dbh->do(<<EOT);
279         UPDATE  accountlines
280         SET     amount = '$amount',
281                 amountoutstanding = '$outstanding'
282         WHERE   accountlines_id = $accountlines_id
283 EOT
284         # FIXME: exceedingly bad form.  Use prepare with placholders ("?") in query and execute args.
285 }
286
287 =cut
288
289 sub chargelostitem{
290 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
291 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
292 # a charge has been added
293 # FIXME : if no replacement price, borrower just doesn't get charged?
294     my $dbh = C4::Context->dbh();
295     my ($borrowernumber, $itemnumber, $amount, $description) = @_;
296
297     # first make sure the borrower hasn't already been charged for this item
298     my $sth1=$dbh->prepare("SELECT * from accountlines
299     WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
300     $sth1->execute($borrowernumber,$itemnumber);
301     my $existing_charge_hashref=$sth1->fetchrow_hashref();
302
303     # OK, they haven't
304     unless ($existing_charge_hashref) {
305         my $manager_id = 0;
306         $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
307         # This item is on issue ... add replacement cost to the borrower's record and mark it returned
308         #  Note that we add this to the account even if there's no replacement price, allowing some other
309         #  process (or person) to update it, since we don't handle any defaults for replacement prices.
310         my $accountno = getnextacctno($borrowernumber);
311         my $sth2=$dbh->prepare("INSERT INTO accountlines
312         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber,manager_id)
313         VALUES (?,?,now(),?,?,'L',?,?,?)");
314         $sth2->execute($borrowernumber,$accountno,$amount,
315         $description,$amount,$itemnumber,$manager_id);
316         $sth2->finish;
317     # FIXME: Log this ?
318     }
319 }
320
321 =head2 manualinvoice
322
323   &manualinvoice($borrowernumber, $itemnumber, $description, $type,
324                  $amount, $note);
325
326 C<$borrowernumber> is the patron's borrower number.
327 C<$description> is a description of the transaction.
328 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
329 or C<REF>.
330 C<$itemnumber> is the item involved, if pertinent; otherwise, it
331 should be the empty string.
332
333 =cut
334
335 #'
336 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
337 # are :  
338 #               'C' = CREDIT
339 #               'FOR' = FORGIVEN  (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
340 #               'N' = New Card fee
341 #               'F' = Fine
342 #               'A' = Account Management fee
343 #               'M' = Sundry
344 #               'L' = Lost Item
345 #
346
347 sub manualinvoice {
348     my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_;
349     my $manager_id = 0;
350     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
351     my $dbh      = C4::Context->dbh;
352     my $notifyid = 0;
353     my $insert;
354     my $accountno  = getnextacctno($borrowernumber);
355     my $amountleft = $amount;
356
357 #    if (   $type eq 'CS'
358 #        || $type eq 'CB'
359 #        || $type eq 'CW'
360 #        || $type eq 'CF'
361 #        || $type eq 'CL' )
362 #    {
363 #        my $amount2 = $amount * -1;    # FIXME - $amount2 = -$amount
364 #        $amountleft =
365 #          fixcredit( $borrowernumber, $amount2, $itemnum, $type, $user );
366 #    }
367     if ( $type eq 'N' ) {
368         $desc .= " New Card";
369     }
370     if ( $type eq 'F' ) {
371         $desc .= " Fine";
372     }
373     if ( $type eq 'A' ) {
374         $desc .= " Account Management fee";
375     }
376     if ( $type eq 'M' ) {
377         $desc .= " Sundry";
378     }
379
380     if ( $type eq 'L' && $desc eq '' ) {
381
382         $desc = " Lost Item";
383     }
384 #    if ( $type eq 'REF' ) {
385 #        $desc .= " Cash Refund";
386 #        $amountleft = refund( '', $borrowernumber, $amount );
387 #    }
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' ) )
393     {
394         $notifyid = 1;
395     }
396
397     if ( $itemnum ) {
398         $desc .= ' ' . $itemnum;
399         my $sth = $dbh->prepare(
400             'INSERT INTO  accountlines
401                         (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id, note, manager_id)
402         VALUES (?, ?, now(), ?,?, ?,?,?,?,?,?)');
403      $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid, $note, $manager_id) || return $sth->errstr;
404   } else {
405     my $sth=$dbh->prepare("INSERT INTO  accountlines
406             (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id, note, manager_id)
407             VALUES (?, ?, now(), ?, ?, ?, ?,?,?,?)"
408         );
409         $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
410             $amountleft, $notifyid, $note, $manager_id );
411     }
412     return 0;
413 }
414
415 =head2 fixcredit #### DEPRECATED
416
417  $amountleft = &fixcredit($borrowernumber, $data, $barcode, $type, $user);
418
419  This function is only used internally, not exported.
420
421 =cut
422
423 # This function is deprecated in 3.0
424
425 sub fixcredit {
426
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')";
440         }
441         elsif ( $type eq 'CF' ) {
442             $query .= " AND (accounttype = 'F' OR accounttype = 'FU' OR
443       accounttype='Res' OR accounttype='Rent')";
444         }
445         elsif ( $type eq 'CB' ) {
446             $query .= " and accounttype='A'";
447         }
448
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'};
457         }
458         else {
459             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
460             $amountleft = 0;
461         }
462         my $thisacct = $accdata->{accountlines_id};
463         my $usth     = $dbh->prepare(
464             "UPDATE accountlines SET amountoutstanding= ?
465      WHERE (accountlines_id = ?)"
466         );
467         $usth->execute( $newamtos, $thisacct );
468         $usth->finish;
469         $usth = $dbh->prepare(
470             "INSERT INTO accountoffsets
471      (borrowernumber, accountno, offsetaccount,  offsetamount)
472      VALUES (?,?,?,?)"
473         );
474         $usth->execute( $borrowernumber, $accdata->{'accountno'},
475             $nextaccntno, $newamtos );
476         $usth->finish;
477     }
478
479     # begin transaction
480     my $nextaccntno = getnextacctno($borrowernumber);
481
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"
487     );
488     $sth->execute($borrowernumber);
489
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'};
496         }
497         else {
498             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
499             $amountleft = 0;
500         }
501         my $thisacct = $accdata->{accountlines_id};
502         my $usth     = $dbh->prepare(
503             "UPDATE accountlines SET amountoutstanding= ?
504      WHERE (accountlines_id = ?)"
505         );
506         $usth->execute( $newamtos, $thisacct );
507         $usth->finish;
508         $usth = $dbh->prepare(
509             "INSERT INTO accountoffsets
510      (borrowernumber, accountno, offsetaccount,  offsetamount)
511      VALUE (?,?,?,?)"
512         );
513         $usth->execute( $borrowernumber, $accdata->{'accountno'},
514             $nextaccntno, $newamtos );
515         $usth->finish;
516     }
517     $sth->finish;
518     $type = "Credit " . $type;
519     UpdateStats( $user, $type, $data, $user, '', '', $borrowernumber );
520     $amountleft *= -1;
521     return ($amountleft);
522
523 }
524
525 =head2 refund
526
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 
531
532 =cut 
533
534 sub refund {
535
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;
542
543     # begin transaction
544     my $nextaccntno = getnextacctno($borrowernumber);
545
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"
551     );
552     $sth->execute($borrowernumber);
553
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'};
560         }
561         else {
562             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
563             $amountleft = 0;
564         }
565
566         #     print $amountleft;
567         my $thisacct = $accdata->{accountlines_id};
568         my $usth     = $dbh->prepare(
569             "UPDATE accountlines SET amountoutstanding= ?
570      WHERE (accountlines_id = ?)"
571         );
572         $usth->execute( $newamtos, $thisacct );
573         $usth->finish;
574         $usth = $dbh->prepare(
575             "INSERT INTO accountoffsets
576      (borrowernumber, accountno, offsetaccount,  offsetamount)
577      VALUES (?,?,?,?)"
578         );
579         $usth->execute( $borrowernumber, $accdata->{'accountno'},
580             $nextaccntno, $newamtos );
581         $usth->finish;
582     }
583     $sth->finish;
584     return ($amountleft);
585 }
586
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 = ?"
594           );
595         $sth->execute( $borrowerno, $accountno );
596         
597     my @results;
598     while ( my $data = $sth->fetchrow_hashref ) {
599                 push @results,$data;
600         }
601     return (@results);
602 }
603
604 sub ModNote {
605     my ( $accountlines_id, $note ) = @_;
606     my $dbh = C4::Context->dbh;
607     my $sth = $dbh->prepare('UPDATE accountlines SET note = ? WHERE accountlines_id = ?');
608     $sth->execute( $note, $accountlines_id );
609 }
610
611 sub getcredits {
612         my ( $date, $date2 ) = @_;
613         my $dbh = C4::Context->dbh;
614         my $sth = $dbh->prepare(
615                                 "SELECT * FROM accountlines,borrowers
616       WHERE amount < 0 AND accounttype <> 'Pay' AND accountlines.borrowernumber = borrowers.borrowernumber
617           AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
618       );  
619
620     $sth->execute( $date, $date2 );                                                                                                              
621     my @results;          
622     while ( my $data = $sth->fetchrow_hashref ) {
623                 $data->{'date'} = $data->{'timestamp'};
624                 push @results,$data;
625         }
626     return (@results);
627
628
629
630 sub getrefunds {
631         my ( $date, $date2 ) = @_;
632         my $dbh = C4::Context->dbh;
633         
634         my $sth = $dbh->prepare(
635                                 "SELECT *,timestamp AS datetime                                                                                      
636                   FROM accountlines,borrowers
637                   WHERE (accounttype = 'REF'
638                                           AND accountlines.borrowernumber = borrowers.borrowernumber
639                                                           AND date  >=?  AND date  <?)"
640     );
641
642     $sth->execute( $date, $date2 );
643
644     my @results;
645     while ( my $data = $sth->fetchrow_hashref ) {
646                 push @results,$data;
647                 
648         }
649     return (@results);
650 }
651
652 sub ReversePayment {
653     my ( $accountlines_id ) = @_;
654     my $dbh = C4::Context->dbh;
655
656     my $sth = $dbh->prepare('SELECT amountoutstanding FROM accountlines WHERE accountlines_id = ?');
657     $sth->execute( $accountlines_id );
658     my $row = $sth->fetchrow_hashref();
659     my $amount_outstanding = $row->{'amountoutstanding'};
660
661     if ( $amount_outstanding <= 0 ) {
662         $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = amount * -1, description = CONCAT( description, " Reversed -" ) WHERE accountlines_id = ?');
663         $sth->execute( $accountlines_id );
664     } else {
665         $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = 0, description = CONCAT( description, " Reversed -" ) WHERE accountlines_id = ?');
666         $sth->execute( $accountlines_id );
667     }
668 }
669
670 =head2 recordpayment_selectaccts
671
672   recordpayment_selectaccts($borrowernumber, $payment,$accts);
673
674 Record payment by a patron. C<$borrowernumber> is the patron's
675 borrower number. C<$payment> is a floating-point number, giving the
676 amount that was paid. C<$accts> is an array ref to a list of
677 accountnos which the payment can be recorded against
678
679 Amounts owed are paid off oldest first. That is, if the patron has a
680 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
681 of $1.50, then the oldest fine will be paid off in full, and $0.50
682 will be credited to the next one.
683
684 =cut
685
686 sub recordpayment_selectaccts {
687     my ( $borrowernumber, $amount, $accts ) = @_;
688
689     my $dbh        = C4::Context->dbh;
690     my $newamtos   = 0;
691     my $accdata    = q{};
692     my $branch     = C4::Context->userenv->{branch};
693     my $amountleft = $amount;
694     my $manager_id = 0;
695     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
696     my $sql = 'SELECT * FROM accountlines WHERE (borrowernumber = ?) ' .
697     'AND (amountoutstanding<>0) ';
698     if (@{$accts} ) {
699         $sql .= ' AND accountno IN ( ' .  join ',', @{$accts};
700         $sql .= ' ) ';
701     }
702     $sql .= ' ORDER BY date';
703     # begin transaction
704     my $nextaccntno = getnextacctno($borrowernumber);
705
706     # get lines with outstanding amounts to offset
707     my $rows = $dbh->selectall_arrayref($sql, { Slice => {} }, $borrowernumber);
708
709     # offset transactions
710     my $sth     = $dbh->prepare('UPDATE accountlines SET amountoutstanding= ? ' .
711         'WHERE accountlines_id=?');
712     for my $accdata ( @{$rows} ) {
713         if ($amountleft == 0) {
714             last;
715         }
716         if ( $accdata->{amountoutstanding} < $amountleft ) {
717             $newamtos = 0;
718             $amountleft -= $accdata->{amountoutstanding};
719         }
720         else {
721             $newamtos   = $accdata->{amountoutstanding} - $amountleft;
722             $amountleft = 0;
723         }
724         my $thisacct = $accdata->{accountlines_id};
725         $sth->execute( $newamtos, $thisacct );
726     }
727
728     # create new line
729     $sql = 'INSERT INTO accountlines ' .
730     '(borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding,manager_id) ' .
731     q|VALUES (?,?,now(),?,'Payment,thanks','Pay',?,?)|;
732     $dbh->do($sql,{},$borrowernumber, $nextaccntno, 0 - $amount, 0 - $amountleft, $manager_id );
733     UpdateStats( $branch, 'payment', $amount, '', '', '', $borrowernumber, $nextaccntno );
734     return;
735 }
736
737 # makepayment needs to be fixed to handle partials till then this separate subroutine
738 # fills in
739 sub makepartialpayment {
740     my ( $accountlines_id, $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
741     my $manager_id = 0;
742     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
743     if (!$amount || $amount < 0) {
744         return;
745     }
746     my $dbh = C4::Context->dbh;
747
748     my $nextaccntno = getnextacctno($borrowernumber);
749     my $newamtos    = 0;
750
751     my $data = $dbh->selectrow_hashref(
752         'SELECT * FROM accountlines WHERE  accountlines_id=?',undef,$accountlines_id);
753     my $new_outstanding = $data->{amountoutstanding} - $amount;
754
755     my $update = 'UPDATE  accountlines SET amountoutstanding = ?  WHERE   accountlines_id = ? ';
756     $dbh->do( $update, undef, $new_outstanding, $accountlines_id);
757
758     # create new line
759     my $insert = 'INSERT INTO accountlines (borrowernumber, accountno, date, amount, '
760     .  'description, accounttype, amountoutstanding, itemnumber, manager_id) '
761     . ' VALUES (?, ?, now(), ?, ?, ?, 0, ?, ?)';
762
763     $dbh->do(  $insert, undef, $borrowernumber, $nextaccntno, 0 - $amount,
764         "Payment, thanks - $user", 'Pay', $data->{'itemnumber'}, $manager_id);
765
766     UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber, $accountno );
767
768     return;
769 }
770
771 =head2 WriteOff
772
773   WriteOff( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch );
774
775 Write off a fine for a patron.
776 C<$borrowernumber> is the patron's borrower number.
777 C<$accountnum> is the accountnumber of the fee to write off.
778 C<$itemnum> is the itemnumber of of item whose fine is being written off.
779 C<$accounttype> is the account type of the fine being written off.
780 C<$amount> is a floating-point number, giving the amount that is being written off.
781 C<$branch> is the branchcode of the library where the writeoff occurred.
782
783 =cut
784
785 sub WriteOffFee {
786     my ( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch ) = @_;
787     $branch ||= C4::Context->userenv->{branch};
788     my $manager_id = 0;
789     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
790
791     # if no item is attached to fine, make sure to store it as a NULL
792     $itemnum ||= undef;
793
794     my ( $sth, $query );
795     my $dbh = C4::Context->dbh();
796
797     $query = "
798         UPDATE accountlines SET amountoutstanding = 0
799         WHERE accountno = ? AND borrowernumber = ?
800     ";
801     $sth = $dbh->prepare( $query );
802     $sth->execute( $accountnum, $borrowernumber );
803
804     $query ="
805         INSERT INTO accountlines
806         ( borrowernumber, accountno, itemnumber, date, amount, description, accounttype, manager_id )
807         VALUES ( ?, ?, ?, NOW(), ?, 'Writeoff', 'W', ? )
808     ";
809     $sth = $dbh->prepare( $query );
810     my $acct = getnextacctno($borrowernumber);
811     $sth->execute( $borrowernumber, $acct, $itemnum, $amount, $manager_id );
812
813     UpdateStats( $branch, 'writeoff', $amount, q{}, q{}, q{}, $borrowernumber );
814
815 }
816
817 END { }    # module clean-up code here (global destructor)
818
819 1;
820 __END__
821
822 =head1 SEE ALSO
823
824 DBI(3)
825
826 =cut
827