bug 2958: fix search results navigation for CCL, CQL, and PQF queries
[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 with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20
21 use strict;
22 use C4::Context;
23 use C4::Stats;
24 use C4::Members;
25 use C4::Items;
26 use C4::Circulation qw(MarkIssueReturned);
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 &makepayment &manualinvoice
37                 &getnextacctno &reconcileaccount &getcharges &getcredits
38                 &getrefunds &chargelostitem
39         ); # removed &fixaccounts
40 }
41
42 =head1 NAME
43
44 C4::Accounts - Functions for dealing with Koha accounts
45
46 =head1 SYNOPSIS
47
48 use C4::Accounts;
49
50 =head1 DESCRIPTION
51
52 The functions in this module deal with the monetary aspect of Koha,
53 including looking up and modifying the amount of money owed by a
54 patron.
55
56 =head1 FUNCTIONS
57
58 =head2 recordpayment
59
60   &recordpayment($borrowernumber, $payment);
61
62 Record payment by a patron. C<$borrowernumber> is the patron's
63 borrower number. C<$payment> is a floating-point number, giving the
64 amount that was paid. 
65
66 Amounts owed are paid off oldest first. That is, if the patron has a
67 $1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a payment
68 of $1.50, then the oldest fine will be paid off in full, and $0.50
69 will be credited to the next one.
70
71 =cut
72
73 #'
74 sub recordpayment {
75
76     #here we update the account lines
77     my ( $borrowernumber, $data ) = @_;
78     my $dbh        = C4::Context->dbh;
79     my $newamtos   = 0;
80     my $accdata    = "";
81     my $branch     = C4::Context->userenv->{'branch'};
82     my $amountleft = $data;
83
84     # begin transaction
85     my $nextaccntno = getnextacctno($borrowernumber);
86
87     # get lines with outstanding amounts to offset
88     my $sth = $dbh->prepare(
89         "SELECT * FROM accountlines
90   WHERE (borrowernumber = ?) AND (amountoutstanding<>0)
91   ORDER BY date"
92     );
93     $sth->execute($borrowernumber);
94
95     # offset transactions
96     while ( ( $accdata = $sth->fetchrow_hashref ) and ( $amountleft > 0 ) ) {
97         if ( $accdata->{'amountoutstanding'} < $amountleft ) {
98             $newamtos = 0;
99             $amountleft -= $accdata->{'amountoutstanding'};
100         }
101         else {
102             $newamtos   = $accdata->{'amountoutstanding'} - $amountleft;
103             $amountleft = 0;
104         }
105         my $thisacct = $accdata->{accountno};
106         my $usth     = $dbh->prepare(
107             "UPDATE accountlines SET amountoutstanding= ?
108      WHERE (borrowernumber = ?) AND (accountno=?)"
109         );
110         $usth->execute( $newamtos, $borrowernumber, $thisacct );
111         $usth->finish;
112 #        $usth = $dbh->prepare(
113 #            "INSERT INTO accountoffsets
114 #     (borrowernumber, accountno, offsetaccount,  offsetamount)
115 #     VALUES (?,?,?,?)"
116 #        );
117 #        $usth->execute( $borrowernumber, $accdata->{'accountno'},
118 #            $nextaccntno, $newamtos );
119         $usth->finish;
120     }
121
122     # create new line
123     my $usth = $dbh->prepare(
124         "INSERT INTO accountlines
125   (borrowernumber, accountno,date,amount,description,accounttype,amountoutstanding)
126   VALUES (?,?,now(),?,'Payment,thanks','Pay',?)"
127     );
128     $usth->execute( $borrowernumber, $nextaccntno, 0 - $data, 0 - $amountleft );
129     $usth->finish;
130     UpdateStats( $branch, 'payment', $data, '', '', '', $borrowernumber, $nextaccntno );
131     $sth->finish;
132 }
133
134 =head2 makepayment
135
136   &makepayment($borrowernumber, $acctnumber, $amount, $branchcode);
137
138 Records the fact that a patron has paid off the entire amount he or
139 she owes.
140
141 C<$borrowernumber> is the patron's borrower number. C<$acctnumber> is
142 the account that was credited. C<$amount> is the amount paid (this is
143 only used to record the payment. It is assumed to be equal to the
144 amount owed). C<$branchcode> is the code of the branch where payment
145 was made.
146
147 =cut
148
149 #'
150 # FIXME - I'm not at all sure about the above, because I don't
151 # understand what the acct* tables in the Koha database are for.
152 sub makepayment {
153
154     #here we update both the accountoffsets and the account lines
155     #updated to check, if they are paying off a lost item, we return the item
156     # from their card, and put a note on the item record
157     my ( $borrowernumber, $accountno, $amount, $user, $branch ) = @_;
158     my $dbh = C4::Context->dbh;
159
160     # begin transaction
161     my $nextaccntno = getnextacctno($borrowernumber);
162     my $newamtos    = 0;
163     my $sth =
164       $dbh->prepare(
165         "SELECT * FROM accountlines WHERE  borrowernumber=? AND accountno=?");
166     $sth->execute( $borrowernumber, $accountno );
167     my $data = $sth->fetchrow_hashref;
168     $sth->finish;
169
170     $dbh->do(
171         "UPDATE  accountlines
172         SET     amountoutstanding = 0
173         WHERE   borrowernumber = $borrowernumber
174           AND   accountno = $accountno
175         "
176     );
177
178     #  print $updquery;
179 #    $dbh->do( "
180 #        INSERT INTO     accountoffsets
181 #                        (borrowernumber, accountno, offsetaccount,
182 #                         offsetamount)
183 #        VALUES          ($borrowernumber, $accountno, $nextaccntno, $newamtos)
184 #        " );
185
186     # create new line
187     my $payment = 0 - $amount;
188     $dbh->do( "
189         INSERT INTO     accountlines
190                         (borrowernumber, accountno, date, amount,
191                          description, accounttype, amountoutstanding)
192         VALUES          ($borrowernumber, $nextaccntno, now(), $payment,
193                         'Payment,thanks - $user', 'Pay', 0)
194         " );
195
196     # FIXME - The second argument to &UpdateStats is supposed to be the
197     # branch code.
198     # UpdateStats is now being passed $accountno too. MTJ
199     UpdateStats( $user, 'payment', $amount, '', '', '', $borrowernumber,
200         $accountno );
201     $sth->finish;
202
203     #check to see what accounttype
204     if ( $data->{'accounttype'} eq 'Rep' || $data->{'accounttype'} eq 'L' ) {
205         returnlost( $borrowernumber, $data->{'itemnumber'} );
206     }
207 }
208
209 =head2 getnextacctno
210
211   $nextacct = &getnextacctno($borrowernumber);
212
213 Returns the next unused account number for the patron with the given
214 borrower number.
215
216 =cut
217
218 #'
219 # FIXME - Okay, so what does the above actually _mean_?
220 sub getnextacctno ($) {
221     my ($borrowernumber) = shift or return undef;
222     my $sth = C4::Context->dbh->prepare(
223         "SELECT accountno+1 FROM accountlines
224          WHERE    (borrowernumber = ?)
225          ORDER BY accountno DESC
226                  LIMIT 1"
227     );
228     $sth->execute($borrowernumber);
229     return ($sth->fetchrow || 1);
230 }
231
232 =head2 fixaccounts (removed)
233
234   &fixaccounts($borrowernumber, $accountnumber, $amount);
235
236 #'
237 # FIXME - I don't understand what this function does.
238 sub fixaccounts {
239     my ( $borrowernumber, $accountno, $amount ) = @_;
240     my $dbh = C4::Context->dbh;
241     my $sth = $dbh->prepare(
242         "SELECT * FROM accountlines WHERE borrowernumber=?
243      AND accountno=?"
244     );
245     $sth->execute( $borrowernumber, $accountno );
246     my $data = $sth->fetchrow_hashref;
247
248     # FIXME - Error-checking
249     my $diff        = $amount - $data->{'amount'};
250     my $outstanding = $data->{'amountoutstanding'} + $diff;
251     $sth->finish;
252
253     $dbh->do(<<EOT);
254         UPDATE  accountlines
255         SET     amount = '$amount',
256                 amountoutstanding = '$outstanding'
257         WHERE   borrowernumber = $borrowernumber
258           AND   accountno = $accountno
259 EOT
260         # FIXME: exceedingly bad form.  Use prepare with placholders ("?") in query and execute args.
261 }
262
263 =cut
264
265 sub returnlost{
266     my ( $borrowernumber, $itemnum ) = @_;
267     C4::Circulation::MarkIssueReturned( $borrowernumber, $itemnum );
268     my $borrower = C4::Members::GetMember( $borrowernumber, 'borrowernumber' );
269     my @datearr = localtime(time);
270     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
271     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
272     ModItem({ paidfor =>  "Paid for by $bor $date" }, undef, $itemnum);
273 }
274
275
276 sub chargelostitem{
277 # http://wiki.koha.org/doku.php?id=en:development:kohastatuses
278 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
279 # FIXME: itemlost should be set to 3 after payment is made, should be a warning to the interface that
280 # a charge has been added
281 # FIXME : if no replacement price, borrower just doesn't get charged?
282    
283     my $dbh = C4::Context->dbh();
284     my ($itemnumber) = @_;
285     my $sth=$dbh->prepare("SELECT * FROM issues, items WHERE issues.itemnumber=items.itemnumber and  issues.itemnumber=?");
286     $sth->execute($itemnumber);
287     my $issues=$sth->fetchrow_hashref();
288
289     # if a borrower lost the item, add a replacement cost to the their record
290     if ( $issues->{borrowernumber} ){
291
292         # first make sure the borrower hasn't already been charged for this item
293         my $sth1=$dbh->prepare("SELECT * from accountlines
294         WHERE borrowernumber=? AND itemnumber=? and accounttype='L'");
295         $sth1->execute($issues->{'borrowernumber'},$itemnumber);
296         my $existing_charge_hashref=$sth1->fetchrow_hashref();
297
298         # OK, they haven't
299         unless ($existing_charge_hashref) {
300             # This item is on issue ... add replacement cost to the borrower's record and mark it returned
301             #  Note that we add this to the account even if there's no replacement price, allowing some other
302             #  process (or person) to update it, since we don't handle any defaults for replacement prices.
303             my $accountno = getnextacctno($issues->{'borrowernumber'});
304             my $sth2=$dbh->prepare("INSERT INTO accountlines
305             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber)
306             VALUES (?,?,now(),?,?,'L',?,?)");
307             $sth2->execute($issues->{'borrowernumber'},$accountno,$issues->{'replacementprice'},
308             "Lost Item $issues->{'title'} $issues->{'barcode'}",
309             $issues->{'replacementprice'},$itemnumber);
310             $sth2->finish;
311         # FIXME: Log this ?
312         }
313         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
314         warn " $issues->{'borrowernumber'}  /  $itemnumber ";
315         C4::Circulation::MarkIssueReturned($issues->{borrowernumber},$itemnumber);
316         #  Shouldn't MarkIssueReturned do this?
317         ModItem({ onloan => undef }, undef, $itemnumber);
318     }
319     $sth->finish;
320 }
321
322 =head2 manualinvoice
323
324   &manualinvoice($borrowernumber, $itemnumber, $description, $type,
325                  $amount, $user);
326
327 C<$borrowernumber> is the patron's borrower number.
328 C<$description> is a description of the transaction.
329 C<$type> may be one of C<CS>, C<CB>, C<CW>, C<CF>, C<CL>, C<N>, C<L>,
330 or C<REF>.
331 C<$itemnumber> is the item involved, if pertinent; otherwise, it
332 should be the empty string.
333
334 =cut
335
336 #'
337 # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function
338 # are :  
339 #               'C' = CREDIT
340 #               'FOR' = FORGIVEN  (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere)
341 #               'N' = New Card fee
342 #               'F' = Fine
343 #               'A' = Account Management fee
344 #               'M' = Sundry
345 #               'L' = Lost Item
346 #
347
348 sub manualinvoice {
349     my ( $borrowernumber, $itemnum, $desc, $type, $amount, $user ) = @_;
350     my $dbh      = C4::Context->dbh;
351     my $notifyid = 0;
352     my $insert;
353     $itemnum =~ s/ //g;
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 ne '' ) {
398         $desc .= " " . $itemnum;
399         my $sth = $dbh->prepare(
400             "INSERT INTO  accountlines
401                         (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding, itemnumber,notify_id)
402         VALUES (?, ?, now(), ?,?, ?,?,?,?)");
403      $sth->execute($borrowernumber, $accountno, $amount, $desc, $type, $amountleft, $itemnum,$notifyid) || return $sth->errstr;
404   } else {
405     my $sth=$dbh->prepare("INSERT INTO  accountlines
406             (borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding,notify_id)
407             VALUES (?, ?, now(), ?, ?, ?, ?,?)"
408         );
409         $sth->execute( $borrowernumber, $accountno, $amount, $desc, $type,
410             $amountleft, $notifyid );
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->{accountno};
463         my $usth     = $dbh->prepare(
464             "UPDATE accountlines SET amountoutstanding= ?
465      WHERE (borrowernumber = ?) AND (accountno=?)"
466         );
467         $usth->execute( $newamtos, $borrowernumber, $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->{accountno};
502         my $usth     = $dbh->prepare(
503             "UPDATE accountlines SET amountoutstanding= ?
504      WHERE (borrowernumber = ?) AND (accountno=?)"
505         );
506         $usth->execute( $newamtos, $borrowernumber, $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->{accountno};
568         my $usth     = $dbh->prepare(
569             "UPDATE accountlines SET amountoutstanding= ?
570      WHERE (borrowernumber = ?) AND (accountno=?)"
571         );
572         $usth->execute( $newamtos, $borrowernumber, $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
605 sub getcredits {
606         my ( $date, $date2 ) = @_;
607         my $dbh = C4::Context->dbh;
608         my $sth = $dbh->prepare(
609                                 "SELECT * FROM accountlines,borrowers
610       WHERE amount < 0 AND accounttype <> 'Pay' AND accountlines.borrowernumber = borrowers.borrowernumber
611           AND timestamp >=TIMESTAMP(?) AND timestamp < TIMESTAMP(?)"
612       );  
613
614     $sth->execute( $date, $date2 );                                                                                                              
615     my @results;          
616     while ( my $data = $sth->fetchrow_hashref ) {
617                 $data->{'date'} = $data->{'timestamp'};
618                 push @results,$data;
619         }
620     return (@results);
621
622
623
624 sub getrefunds {
625         my ( $date, $date2 ) = @_;
626         my $dbh = C4::Context->dbh;
627         
628         my $sth = $dbh->prepare(
629                                 "SELECT *,timestamp AS datetime                                                                                      
630                   FROM accountlines,borrowers
631                   WHERE (accounttype = 'REF'
632                                           AND accountlines.borrowernumber = borrowers.borrowernumber
633                                                           AND date  >=?  AND date  <?)"
634     );
635
636     $sth->execute( $date, $date2 );
637
638     my @results;
639     while ( my $data = $sth->fetchrow_hashref ) {
640                 push @results,$data;
641                 
642         }
643     return (@results);
644 }
645 END { }    # module clean-up code here (global destructor)
646
647 1;
648 __END__
649
650 =head1 SEE ALSO
651
652 DBI(3)
653
654 =cut
655