Bug 7864: Reintroduce list of subscribers to a serial alert message
[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.03;
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
95     # begin transaction
96     my $nextaccntno = getnextacctno($borrowernumber);
97
98     # get lines with outstanding amounts to offset
99     my $sth = $dbh->prepare(
100         "SELECT * FROM accountlines
101   WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
102   ORDER BY date"
103     );
104     $sth->execute($borrowernumber);
105
106     # offset transactions
107     while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
108         if ( $accdata->{'amountoutstanding'} < $amountleft ) {
109             $newamtos = 0;
110             $amountleft -= $accdata->{'amountoutstanding'};
111         }
112         else {
113             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
114             $amountleft = 0;
115         }
116         my $thisacct = $accdata->{accountno};
117         my $usth     = $dbh->prepare(
118             "UPDATE accountlines SET amountoutstanding= ?
119      WHERE (borrowernumber = ?) AND (accountno=?)"
120         );
121         $usth->execute( $newamtos, $borrowernumber, $thisacct );
122         $usth->finish;
123 #        $usth = $dbh->prepare(
124 #            "INSERT INTO accountoffsets
125 #     (borrowernumber, accountno, offsetaccount,  offsetamount)
126 #     VALUES (?,?,?,?)"
127 #        );
128 #        $usth->execute( $borrowernumber, $accdata->{'accountno'},
129 #            $nextaccntno, $newamtos );
130         $usth->finish;
131     }
132
133     # create new line
134     my $usth = $dbh->prepare(
135         "INSERT INTO accountlines
136   (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding)
137   VALUES (?,?,now(),?,'Payment,thanks','Pay',?)"
138     );
139     $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft );
140     $usth->finish;
141     UpdateStats( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
142     $sth->finish;
143 }
144
145 =head2 makepayment
146
147   &makepayment($borrowernumber, $acctnumber, $amount, $branchcode);
148
149 Records the fact that a patron has paid off the entire amount he or
150 she owes.
151
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
156 was made.
157
158 =cut
159
160 #'
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.
163 sub makepayment {
164
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;
170     my $manager_id = 0;
171     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
172
173     # begin transaction
174     my $nextaccntno = getnextacctno($borrowernumber);
175     my $newamtos    = 0;
176     my $sth =
177       $dbh->prepare(
178         "SELECT * FROM accountlines WHERE  borrowernumber=? AND accountno=?");
179     $sth->execute( $borrowernumber, $accountno );
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 borrowernumber = ?
189                     AND accountno = ?
190                 "
191             );
192         $udp->execute($borrowernumber, $accountno );
193         $udp->finish;
194     }else{
195         my $udp =               
196             $dbh->prepare(
197                 "UPDATE accountlines
198                     SET amountoutstanding = 0
199                     WHERE borrowernumber = ?
200                     AND accountno = ?
201                 "
202             );
203         $udp->execute($borrowernumber, $accountno );
204         $udp->finish;
205
206          # create new line
207         my $payment = 0 - $amount;
208         
209         my $ins = 
210             $dbh->prepare( 
211                 "INSERT 
212                     INTO accountlines (borrowernumber, accountno, date, amount, itemnumber, description, accounttype, amountoutstanding, manager_id)
213                     VALUES ( ?, ?, now(), ?, ?, 'Payment,thanks', 'Pay', 0, ?)"
214             );
215         $ins->execute($borrowernumber, $nextaccntno, $payment, $data->{'itemnumber'}, $manager_id);
216         $ins->finish;
217     }
218
219     # FIXME - The second argument to &UpdateStats is supposed to be the
220     # branch code.
221     # UpdateStats is now being passed $accountno too. MTJ
222     UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber,
223         $accountno );
224     #from perldoc: for SELECT only #$sth->finish;
225
226     #check to see what accounttype
227     if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
228         C4::Circulation::ReturnLostItem( $borrowernumber, $data->{'itemnumber'} );
229     }
230 }
231
232 =head2 getnextacctno
233
234   $nextacct = &getnextacctno($borrowernumber);
235
236 Returns the next unused account number for the patron with the given
237 borrower number.
238
239 =cut
240
241 #'
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
249                  LIMIT 1"
250     );
251     $sth->execute($borrowernumber);
252     return ($sth->fetchrow || 1);
253 }
254
255 =head2 fixaccounts (removed)
256
257   &fixaccounts($borrowernumber, $accountnumber, $amount);
258
259 #'
260 # FIXME - I don't understand what this function does.
261 sub fixaccounts {
262     my ( $borrowernumber, $accountno, $amount ) = @_;
263     my $dbh = C4::Context->dbh;
264     my $sth = $dbh->prepare(
265         "SELECT * FROM accountlines WHERE borrowernumber=?
266      AND accountno=?"
267     );
268     $sth->execute( $borrowernumber, $accountno );
269     my $data = $sth->fetchrow_hashref;
270
271     # FIXME - Error-checking
272     my $diff        = $amount - $data->{'amount'};
273     my $outstanding = $data->{'amountoutstanding'} + $diff;
274     $sth->finish;
275
276     $dbh->do(<<EOT);
277         UPDATE  accountlines
278         SET     amount = '$amount',
279                 amountoutstanding = '$outstanding'
280         WHERE   borrowernumber = $borrowernumber
281           AND   accountno = $accountno
282 EOT
283         # FIXME: exceedingly bad form.  Use prepare with placholders ("?") in query and execute args.
284 }
285
286 =cut
287
288 sub chargelostitem{
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) = @_;
295
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();
301
302     # OK, they haven't
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);
313         $sth2->finish;
314     # FIXME: Log this ?
315     }
316 }
317
318 =head2 manualinvoice
319
320   &manualinvoice($borrowernumber, $itemnumber, $description, $type,
321                  $amount, $note);
322
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>,
326 or C<REF>.
327 C<$itemnumber> is the item involved, if pertinent; otherwise, it
328 should be the empty string.
329
330 =cut
331
332 #'
333 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
334 # are :  
335 #               'C' = CREDIT
336 #               'FOR' = FORGIVEN  (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
337 #               'N' = New Card fee
338 #               'F' = Fine
339 #               'A' = Account Management fee
340 #               'M' = Sundry
341 #               'L' = Lost Item
342 #
343
344 sub manualinvoice {
345     my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_;
346     my $manager_id = 0;
347     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
348     my $dbh      = C4::Context->dbh;
349     my $notifyid = 0;
350     my $insert;
351     my $accountno  = getnextacctno($borrowernumber);
352     my $amountleft = $amount;
353
354 #    if (   $type eq 'CS'
355 #        || $type eq 'CB'
356 #        || $type eq 'CW'
357 #        || $type eq 'CF'
358 #        || $type eq 'CL' )
359 #    {
360 #        my $amount2 = $amount * -1;    # FIXME - $amount2 = -$amount
361 #        $amountleft =
362 #          fixcredit( $borrowernumber, $amount2, $itemnum, $type, $user );
363 #    }
364     if ( $type eq 'N' ) {
365         $desc .= " New Card";
366     }
367     if ( $type eq 'F' ) {
368         $desc .= " Fine";
369     }
370     if ( $type eq 'A' ) {
371         $desc .= " Account Management fee";
372     }
373     if ( $type eq 'M' ) {
374         $desc .= " Sundry";
375     }
376
377     if ( $type eq 'L' && $desc eq '' ) {
378
379         $desc = " Lost Item";
380     }
381 #    if ( $type eq 'REF' ) {
382 #        $desc .= " Cash Refund";
383 #        $amountleft = refund( '', $borrowernumber, $amount );
384 #    }
385     if (   ( $type eq 'L' )
386         or ( $type eq 'F' )
387         or ( $type eq 'A' )
388         or ( $type eq 'N' )
389         or ( $type eq 'M' ) )
390     {
391         $notifyid = 1;
392     }
393
394     if ( $itemnum ) {
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;
401   } else {
402     my $sth=$dbh->prepare("INSERT INTO  accountlines
403             (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id, note, manager_id)
404             VALUES (?, ?, now(), ?, ?, ?, ?,?,?,?)"
405         );
406         $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
407             $amountleft, $notifyid, $note, $manager_id );
408     }
409     return 0;
410 }
411
412 =head2 fixcredit #### DEPRECATED
413
414  $amountleft = &fixcredit($borrowernumber, $data, $barcode, $type, $user);
415
416  This function is only used internally, not exported.
417
418 =cut
419
420 # This function is deprecated in 3.0
421
422 sub fixcredit {
423
424     #here we update both the accountoffsets and the account lines
425     my ( $borrowernumber, $data, $barcode, $type, $user ) = @_;
426     my $dbh        = C4::Context->dbh;
427     my $newamtos   = 0;
428     my $accdata    = "";
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')";
437         }
438         elsif ( $type eq 'CF' ) {
439             $query .= " AND (accounttype = 'F' OR accounttype = 'FU' OR
440       accounttype='Res' OR accounttype='Rent')";
441         }
442         elsif ( $type eq 'CB' ) {
443             $query .= " and accounttype='A'";
444         }
445
446         #    print $query;
447         my $sth = $dbh->prepare($query);
448         $sth->execute( $borrowernumber, $item->{'itemnumber'} );
449         $accdata = $sth->fetchrow_hashref;
450         $sth->finish;
451         if ( $accdata->{'amountoutstanding'} < $amountleft ) {
452             $newamtos = 0;
453             $amountleft -= $accdata->{'amountoutstanding'};
454         }
455         else {
456             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
457             $amountleft = 0;
458         }
459         my $thisacct = $accdata->{accountno};
460         my $usth     = $dbh->prepare(
461             "UPDATE accountlines SET amountoutstanding= ?
462      WHERE (borrowernumber = ?) AND (accountno=?)"
463         );
464         $usth->execute( $newamtos, $borrowernumber, $thisacct );
465         $usth->finish;
466         $usth = $dbh->prepare(
467             "INSERT INTO accountoffsets
468      (borrowernumber, accountno, offsetaccount,  offsetamount)
469      VALUES (?,?,?,?)"
470         );
471         $usth->execute( $borrowernumber, $accdata->{'accountno'},
472             $nextaccntno, $newamtos );
473         $usth->finish;
474     }
475
476     # begin transaction
477     my $nextaccntno = getnextacctno($borrowernumber);
478
479     # get lines with outstanding amounts to offset
480     my $sth = $dbh->prepare(
481         "SELECT * FROM accountlines
482   WHERE (borrowernumber = ?) AND (amountoutstanding >0)
483   ORDER BY date"
484     );
485     $sth->execute($borrowernumber);
486
487     #  print $query;
488     # offset transactions
489     while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
490         if ( $accdata->{'amountoutstanding'} < $amountleft ) {
491             $newamtos = 0;
492             $amountleft -= $accdata->{'amountoutstanding'};
493         }
494         else {
495             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
496             $amountleft = 0;
497         }
498         my $thisacct = $accdata->{accountno};
499         my $usth     = $dbh->prepare(
500             "UPDATE accountlines SET amountoutstanding= ?
501      WHERE (borrowernumber = ?) AND (accountno=?)"
502         );
503         $usth->execute( $newamtos, $borrowernumber, $thisacct );
504         $usth->finish;
505         $usth = $dbh->prepare(
506             "INSERT INTO accountoffsets
507      (borrowernumber, accountno, offsetaccount,  offsetamount)
508      VALUE (?,?,?,?)"
509         );
510         $usth->execute( $borrowernumber, $accdata->{'accountno'},
511             $nextaccntno, $newamtos );
512         $usth->finish;
513     }
514     $sth->finish;
515     $type = "Credit " . $type;
516     UpdateStats( $user, $type, $data, $user, '', '', $borrowernumber );
517     $amountleft *= -1;
518     return ($amountleft);
519
520 }
521
522 =head2 refund
523
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 
528
529 =cut 
530
531 sub refund {
532
533     #here we update both the accountoffsets and the account lines
534     my ( $borrowernumber, $data ) = @_;
535     my $dbh        = C4::Context->dbh;
536     my $newamtos   = 0;
537     my $accdata    = "";
538     my $amountleft = $data * -1;
539
540     # begin transaction
541     my $nextaccntno = getnextacctno($borrowernumber);
542
543     # get lines with outstanding amounts to offset
544     my $sth = $dbh->prepare(
545         "SELECT * FROM accountlines
546   WHERE (borrowernumber = ?) AND (amountoutstanding<0)
547   ORDER BY date"
548     );
549     $sth->execute($borrowernumber);
550
551     #  print $amountleft;
552     # offset transactions
553     while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft < 0 ) ) {
554         if ( $accdata->{'amountoutstanding'} > $amountleft ) {
555             $newamtos = 0;
556             $amountleft -= $accdata->{'amountoutstanding'};
557         }
558         else {
559             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
560             $amountleft = 0;
561         }
562
563         #     print $amountleft;
564         my $thisacct = $accdata->{accountno};
565         my $usth     = $dbh->prepare(
566             "UPDATE accountlines SET amountoutstanding= ?
567      WHERE (borrowernumber = ?) AND (accountno=?)"
568         );
569         $usth->execute( $newamtos, $borrowernumber, $thisacct );
570         $usth->finish;
571         $usth = $dbh->prepare(
572             "INSERT INTO accountoffsets
573      (borrowernumber, accountno, offsetaccount,  offsetamount)
574      VALUES (?,?,?,?)"
575         );
576         $usth->execute( $borrowernumber, $accdata->{'accountno'},
577             $nextaccntno, $newamtos );
578         $usth->finish;
579     }
580     $sth->finish;
581     return ($amountleft);
582 }
583
584 sub getcharges {
585         my ( $borrowerno, $timestamp, $accountno ) = @_;
586         my $dbh        = C4::Context->dbh;
587         my $timestamp2 = $timestamp - 1;
588         my $query      = "";
589         my $sth = $dbh->prepare(
590                         "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?"
591           );
592         $sth->execute( $borrowerno, $accountno );
593         
594     my @results;
595     while ( my $data = $sth->fetchrow_hashref ) {
596                 push @results,$data;
597         }
598     return (@results);
599 }
600
601 sub ModNote {
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 );
606 }
607
608 sub getcredits {
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(?)"
615       );  
616
617     $sth->execute( $date, $date2 );                                                                                                              
618     my @results;          
619     while ( my $data = $sth->fetchrow_hashref ) {
620                 $data->{'date'} = $data->{'timestamp'};
621                 push @results,$data;
622         }
623     return (@results);
624
625
626
627 sub getrefunds {
628         my ( $date, $date2 ) = @_;
629         my $dbh = C4::Context->dbh;
630         
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  <?)"
637     );
638
639     $sth->execute( $date, $date2 );
640
641     my @results;
642     while ( my $data = $sth->fetchrow_hashref ) {
643                 push @results,$data;
644                 
645         }
646     return (@results);
647 }
648
649 sub ReversePayment {
650   my ( $borrowernumber, $accountno ) = @_;
651   my $dbh = C4::Context->dbh;
652   
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'};
657   
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 );
661   } else {
662     $sth = $dbh->prepare('UPDATE accountlines SET amountoutstanding = 0, description = CONCAT( description, " Reversed -" ) WHERE borrowernumber = ? AND accountno = ?');
663     $sth->execute( $borrowernumber, $accountno );
664   }
665 }
666
667 =head2 recordpayment_selectaccts
668
669   recordpayment_selectaccts($borrowernumber, $payment,$accts);
670
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
675
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.
680
681 =cut
682
683 sub recordpayment_selectaccts {
684     my ( $borrowernumber, $amount, $accts ) = @_;
685
686     my $dbh        = C4::Context->dbh;
687     my $newamtos   = 0;
688     my $accdata    = q{};
689     my $branch     = C4::Context->userenv->{branch};
690     my $amountleft = $amount;
691     my $sql = 'SELECT * FROM accountlines WHERE (borrowernumber = ?) ' .
692     'AND (amountoutstanding<>0) ';
693     if (@{$accts} ) {
694         $sql .= ' AND accountno IN ( ' .  join ',', @{$accts};
695         $sql .= ' ) ';
696     }
697     $sql .= ' ORDER BY date';
698     # begin transaction
699     my $nextaccntno = getnextacctno($borrowernumber);
700
701     # get lines with outstanding amounts to offset
702     my $rows = $dbh->selectall_arrayref($sql, { Slice => {} }, $borrowernumber);
703
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) {
709             last;
710         }
711         if ( $accdata->{amountoutstanding} < $amountleft ) {
712             $newamtos = 0;
713             $amountleft -= $accdata->{amountoutstanding};
714         }
715         else {
716             $newamtos   = $accdata->{amountoutstanding} - $amountleft;
717             $amountleft = 0;
718         }
719         my $thisacct = $accdata->{accountno};
720         $sth->execute( $newamtos, $borrowernumber, $thisacct );
721     }
722
723     # create new line
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 );
729     return;
730 }
731
732 # makepayment needs to be fixed to handle partials till then this separate subroutine
733 # fills in
734 sub makepartialpayment {
735     my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
736     my $manager_id = 0;
737     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
738     if (!$amount || $amount < 0) {
739         return;
740     }
741     my $dbh = C4::Context->dbh;
742
743     my $nextaccntno = getnextacctno($borrowernumber);
744     my $newamtos    = 0;
745
746     my $data = $dbh->selectrow_hashref(
747         'SELECT * FROM accountlines WHERE  borrowernumber=? AND accountno=?',undef,$borrowernumber,$accountno);
748     my $new_outstanding = $data->{amountoutstanding} - $amount;
749
750     my $update = 'UPDATE  accountlines SET amountoutstanding = ?  WHERE   borrowernumber = ? '
751     . ' AND   accountno = ?';
752     $dbh->do( $update, undef, $new_outstanding, $borrowernumber, $accountno);
753
754     # create new line
755     my $insert = 'INSERT INTO accountlines (borrowernumber, accountno, date, amount, '
756     .  'description, accounttype, amountoutstanding, itemnumber, manager_id) '
757     . ' VALUES (?, ?, now(), ?, ?, ?, 0, ?, ?)';
758
759     $dbh->do(  $insert, undef, $borrowernumber, $nextaccntno, $amount,
760         "Payment, thanks - $user", 'Pay', $data->{'itemnumber'}, $manager_id);
761
762     UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber, $accountno );
763
764     return;
765 }
766
767 =head2 WriteOff
768
769   WriteOff( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch );
770
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.
778
779 =cut
780
781 sub WriteOffFee {
782     my ( $borrowernumber, $accountnum, $itemnum, $accounttype, $amount, $branch ) = @_;
783     $branch ||= C4::Context->userenv->{branch};
784     my $manager_id = 0;
785     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
786
787     # if no item is attached to fine, make sure to store it as a NULL
788     $itemnum ||= undef;
789
790     my ( $sth, $query );
791     my $dbh = C4::Context->dbh();
792
793     $query = "
794         UPDATE accountlines SET amountoutstanding = 0
795         WHERE accountno = ? AND borrowernumber = ?
796     ";
797     $sth = $dbh->prepare( $query );
798     $sth->execute( $accountnum, $borrowernumber );
799
800     $query ="
801         INSERT INTO accountlines
802         ( borrowernumber, accountno, itemnumber, date, amount, description, accounttype, manager_id )
803         VALUES ( ?, ?, ?, NOW(), ?, 'Writeoff', 'W', ? )
804     ";
805     $sth = $dbh->prepare( $query );
806     my $acct = getnextacctno($borrowernumber);
807     $sth->execute( $borrowernumber, $acct, $itemnum, $amount, $manager_id );
808
809     UpdateStats( $branch, 'writeoff', $amount, q{}, q{}, q{}, $borrowernumber );
810
811 }
812
813 END { }    # module clean-up code here (global destructor)
814
815 1;
816 __END__
817
818 =head1 SEE ALSO
819
820 DBI(3)
821
822 =cut
823