Bug 10030 - MaxFines checks against amount, not amount outstanding
[koha.git] / C4 / Overdues.pm
1 package C4::Overdues;
2
3
4 # Copyright 2000-2002 Katipo Communications
5 # copyright 2010 BibLibre
6 #
7 # This file is part of Koha.
8 #
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
13 #
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 use strict;
23 #use warnings; FIXME - Bug 2505
24 use Date::Calc qw/Today Date_to_Days/;
25 use Date::Manip qw/UnixDate/;
26 use C4::Circulation;
27 use C4::Context;
28 use C4::Accounts;
29 use C4::Log; # logaction
30 use C4::Debug;
31
32 use vars qw($VERSION @ISA @EXPORT);
33
34 BEGIN {
35         # set the version for version checking
36     $VERSION = 3.07.00.049;
37         require Exporter;
38         @ISA    = qw(Exporter);
39         # subs to rename (and maybe merge some...)
40         push @EXPORT, qw(
41         &CalcFine
42         &Getoverdues
43         &checkoverdues
44         &CheckAccountLineLevelInfo
45         &CheckAccountLineItemInfo
46         &CheckExistantNotifyid
47         &GetNextIdNotify
48         &GetNotifyId
49         &NumberNotifyId
50         &AmountNotify
51         &UpdateAccountLines
52         &UpdateFine
53         &GetOverdueDelays
54         &GetOverduerules
55         &GetFine
56         &CreateItemAccountLine
57         &ReplacementCost2
58         
59         &CheckItemNotify
60         &GetOverduesForBranch
61         &RemoveNotifyLine
62         &AddNotifyLine
63         );
64         # subs to remove
65         push @EXPORT, qw(
66         &BorType
67         );
68
69         # check that an equivalent don't exist already before moving
70
71         # subs to move to Circulation.pm
72         push @EXPORT, qw(
73         &GetIssuesIteminfo
74         );
75         # subs to move to Members.pm
76         push @EXPORT, qw(
77         &CheckBorrowerDebarred
78         );
79         # subs to move to Biblio.pm
80         push @EXPORT, qw(
81         &GetItems
82         &ReplacementCost
83         );
84 }
85
86 =head1 NAME
87
88 C4::Circulation::Fines - Koha module dealing with fines
89
90 =head1 SYNOPSIS
91
92   use C4::Overdues;
93
94 =head1 DESCRIPTION
95
96 This module contains several functions for dealing with fines for
97 overdue items. It is primarily used by the 'misc/fines2.pl' script.
98
99 =head1 FUNCTIONS
100
101 =head2 Getoverdues
102
103   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
104
105 Returns the list of all overdue books, with their itemtype.
106
107 C<$overdues> is a reference-to-array. Each element is a
108 reference-to-hash whose keys are the fields of the issues table in the
109 Koha database.
110
111 =cut
112
113 #'
114 sub Getoverdues {
115     my $params = shift;
116     my $dbh = C4::Context->dbh;
117     my $statement;
118     if ( C4::Context->preference('item-level_itypes') ) {
119         $statement = "
120    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
121      FROM issues 
122 LEFT JOIN items       USING (itemnumber)
123     WHERE date_due < NOW()
124 ";
125     } else {
126         $statement = "
127    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode
128      FROM issues 
129 LEFT JOIN items       USING (itemnumber)
130 LEFT JOIN biblioitems USING (biblioitemnumber)
131     WHERE date_due < NOW()
132 ";
133     }
134
135     my @bind_parameters;
136     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
137         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
138         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
139     } elsif ( exists $params->{'minimumdays'} ) {
140         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
141         push @bind_parameters, $params->{'minimumdays'};
142     } elsif ( exists $params->{'maximumdays'} ) {
143         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
144         push @bind_parameters, $params->{'maximumdays'};
145     }
146     $statement .= 'ORDER BY borrowernumber';
147     my $sth = $dbh->prepare( $statement );
148     $sth->execute( @bind_parameters );
149     return $sth->fetchall_arrayref({});
150 }
151
152
153 =head2 checkoverdues
154
155     ($count, $overdueitems) = checkoverdues($borrowernumber);
156
157 Returns a count and a list of overdueitems for a given borrowernumber
158
159 =cut
160
161 sub checkoverdues {
162     my $borrowernumber = shift or return;
163     # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
164     my $sth = C4::Context->dbh->prepare(
165         "SELECT biblio.*, items.*, issues.*,
166                 biblioitems.volume,
167                 biblioitems.number,
168                 biblioitems.itemtype,
169                 biblioitems.isbn,
170                 biblioitems.issn,
171                 biblioitems.publicationyear,
172                 biblioitems.publishercode,
173                 biblioitems.volumedate,
174                 biblioitems.volumedesc,
175                 biblioitems.collectiontitle,
176                 biblioitems.collectionissn,
177                 biblioitems.collectionvolume,
178                 biblioitems.editionstatement,
179                 biblioitems.editionresponsibility,
180                 biblioitems.illus,
181                 biblioitems.pages,
182                 biblioitems.notes,
183                 biblioitems.size,
184                 biblioitems.place,
185                 biblioitems.lccn,
186                 biblioitems.url,
187                 biblioitems.cn_source,
188                 biblioitems.cn_class,
189                 biblioitems.cn_item,
190                 biblioitems.cn_suffix,
191                 biblioitems.cn_sort,
192                 biblioitems.totalissues
193          FROM issues
194          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
195          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
196          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
197             WHERE issues.borrowernumber  = ?
198             AND   issues.date_due < NOW()"
199     );
200     # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
201     $sth->execute($borrowernumber);
202     my $results = $sth->fetchall_arrayref({});
203     return ( scalar(@$results), $results);  # returning the count and the results is silly
204 }
205
206 =head2 CalcFine
207
208     ($amount, $chargename,  $daycounttotal) = &CalcFine($item,
209                                   $categorycode, $branch,
210                                   $start_dt, $end_dt );
211
212 Calculates the fine for a book.
213
214 The issuingrules table in the Koha database is a fine matrix, listing
215 the penalties for each type of patron for each type of item and each branch (e.g., the
216 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
217 members might get a longer grace period between the first and second
218 reminders that a book is overdue).
219
220
221 C<$item> is an item object (hashref).
222
223 C<$categorycode> is the category code (string) of the patron who currently has
224 the book.
225
226 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
227
228 C<$start_date> & C<$end_date> are DateTime objects
229 defining the date range over which to determine the fine.
230
231 Fines scripts should just supply the date range over which to calculate the fine.
232
233 C<&CalcFine> returns four values:
234
235 C<$amount> is the fine owed by the patron (see above).
236
237 C<$chargename> is the chargename field from the applicable record in
238 the categoryitem table, whatever that is.
239
240 C<$unitcount> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
241 minus any applicable grace period, or hours)
242
243 FIXME - What is chargename supposed to be ?
244
245 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
246 or "Final Notice".  But CalcFine never defined any value.
247
248 =cut
249
250 sub CalcFine {
251     my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
252     my $start_date = $due_dt->clone();
253     # get issuingrules (fines part will be used)
254     my $itemtype = $item->{itemtype} || $item->{itype};
255     my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
256     my $fine_unit = $data->{lengthunit};
257     $fine_unit ||= 'days';
258
259     my $chargeable_units = _get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
260     my $units_minus_grace = $chargeable_units - $data->{firstremind};
261     my $amount = 0;
262     if ($data->{'chargeperiod'}  && ($units_minus_grace > 0)  ) {
263         if ( C4::Context->preference('FinesIncludeGracePeriod') ) {
264             $amount = int($chargeable_units / $data->{'chargeperiod'}) * $data->{'fine'};# TODO fine calc should be in cents
265         } else {
266             $amount = int($units_minus_grace / $data->{'chargeperiod'}) * $data->{'fine'};
267         }
268     } else {
269         # a zero (or null) chargeperiod or negative units_minus_grace value means no charge.
270     }
271     $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
272     $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
273     return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
274     # FIXME: chargename is NEVER populated anywhere.
275 }
276
277
278 =head2 _get_chargeable_units
279
280     _get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
281
282 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
283
284 C<$unit> is 'days' or 'hours' (default is 'days').
285
286 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
287
288 C<$branchcode> is the branch whose calendar to use for finding holidays.
289
290 =cut
291
292 sub _get_chargeable_units {
293     my ($unit, $dt1, $dt2, $branchcode) = @_;
294     my $charge_units = 0;
295     my $charge_duration;
296     if ($unit eq 'hours') {
297         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
298             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
299             $charge_duration = $calendar->hours_between( $dt1, $dt2 );
300         } else {
301             $charge_duration = $dt2->delta_ms( $dt1 );
302         }
303         if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
304             return 1;
305         }
306         return $charge_duration->in_units('hours');
307     }
308     else { # days
309         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
310             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
311             $charge_duration = $calendar->days_between( $dt1, $dt2 );
312         } else {
313             $charge_duration = $dt2->delta_days( $dt1 );
314         }
315         return $charge_duration->in_units('days');
316     }
317 }
318
319
320 =head2 GetSpecialHolidays
321
322     &GetSpecialHolidays($date_dues,$itemnumber);
323
324 return number of special days  between date of the day and date due
325
326 C<$date_dues> is the envisaged date of book return.
327
328 C<$itemnumber> is the book's item number.
329
330 =cut
331
332 sub GetSpecialHolidays {
333     my ( $date_dues, $itemnumber ) = @_;
334
335     # calcul the today date
336     my $today = join "-", &Today();
337
338     # return the holdingbranch
339     my $iteminfo = GetIssuesIteminfo($itemnumber);
340
341     # use sql request to find all date between date_due and today
342     my $dbh = C4::Context->dbh;
343     my $query =
344       qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
345 FROM `special_holidays`
346 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
347 AND   DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
348 AND branchcode=?
349 |;
350     my @result = GetWdayFromItemnumber($itemnumber);
351     my @result_date;
352     my $wday;
353     my $dateinsec;
354     my $sth = $dbh->prepare($query);
355     $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
356       ;    # FIXME: just use NOW() in SQL instead of passing in $today
357
358     while ( my $special_date = $sth->fetchrow_hashref ) {
359         push( @result_date, $special_date );
360     }
361
362     my $specialdaycount = scalar(@result_date);
363
364     for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
365         $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
366         ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
367           localtime($dateinsec);
368         for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
369             if ( $wday == ( $result[$j]->{'weekday'} ) ) {
370                 $specialdaycount--;
371             }
372         }
373     }
374
375     return $specialdaycount;
376 }
377
378 =head2 GetRepeatableHolidays
379
380     &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
381
382 return number of day closed between date of the day and date due
383
384 C<$date_dues> is the envisaged date of book return.
385
386 C<$itemnumber> is item number.
387
388 C<$difference> numbers of between day date of the day and date due
389
390 =cut
391
392 sub GetRepeatableHolidays {
393     my ( $date_dues, $itemnumber, $difference ) = @_;
394     my $dateinsec = UnixDate( $date_dues, "%o" );
395     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
396       localtime($dateinsec);
397     my @result = GetWdayFromItemnumber($itemnumber);
398     my @dayclosedcount;
399     my $j;
400
401     for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
402         my $k = $wday;
403
404         for ( $j = 0 ; $j < $difference ; $j++ ) {
405             if ( $result[$i]->{'weekday'} == $k ) {
406                 push( @dayclosedcount, $k );
407             }
408             $k++;
409             ( $k = 0 ) if ( $k eq 7 );
410         }
411     }
412     return scalar(@dayclosedcount);
413 }
414
415
416 =head2 GetWayFromItemnumber
417
418     &Getwdayfromitemnumber($itemnumber);
419
420 return the different week day from repeatable_holidays table
421
422 C<$itemnumber> is  item number.
423
424 =cut
425
426 sub GetWdayFromItemnumber {
427     my ($itemnumber) = @_;
428     my $iteminfo = GetIssuesIteminfo($itemnumber);
429     my @result;
430     my $query = qq|SELECT weekday
431     FROM repeatable_holidays
432     WHERE branchcode=?
433 |;
434     my $sth = C4::Context->dbh->prepare($query);
435
436     $sth->execute( $iteminfo->{'branchcode'} );
437     while ( my $weekday = $sth->fetchrow_hashref ) {
438         push( @result, $weekday );
439     }
440     return @result;
441 }
442
443
444 =head2 GetIssuesIteminfo
445
446     &GetIssuesIteminfo($itemnumber);
447
448 return all data from issues about item
449
450 C<$itemnumber> is  item number.
451
452 =cut
453
454 sub GetIssuesIteminfo {
455     my ($itemnumber) = @_;
456     my $dbh          = C4::Context->dbh;
457     my $query        = qq|SELECT *
458     FROM issues
459     WHERE itemnumber=?
460     |;
461     my $sth = $dbh->prepare($query);
462     $sth->execute($itemnumber);
463     my ($issuesinfo) = $sth->fetchrow_hashref;
464     return $issuesinfo;
465 }
466
467
468 =head2 UpdateFine
469
470     &UpdateFine($itemnumber, $borrowernumber, $amount, $type, $description);
471
472 (Note: the following is mostly conjecture and guesswork.)
473
474 Updates the fine owed on an overdue book.
475
476 C<$itemnumber> is the book's item number.
477
478 C<$borrowernumber> is the borrower number of the patron who currently
479 has the book on loan.
480
481 C<$amount> is the current amount owed by the patron.
482
483 C<$type> will be used in the description of the fine.
484
485 C<$description> is a string that must be present in the description of
486 the fine. I think this is expected to be a date in DD/MM/YYYY format.
487
488 C<&UpdateFine> looks up the amount currently owed on the given item
489 and sets it to C<$amount>, creating, if necessary, a new entry in the
490 accountlines table of the Koha database.
491
492 =cut
493
494 #
495 # Question: Why should the caller have to
496 # specify both the item number and the borrower number? A book can't
497 # be on loan to two different people, so the item number should be
498 # sufficient.
499 #
500 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
501 #
502 sub UpdateFine {
503     my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
504         $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
505     my $dbh = C4::Context->dbh;
506     # FIXME - What exactly is this query supposed to do? It looks up an
507     # entry in accountlines that matches the given item and borrower
508     # numbers, where the description contains $due, and where the
509     # account type has one of several values, but what does this _mean_?
510     # Does it look up existing fines for this item?
511     # FIXME - What are these various account types? ("FU", "O", "F", "M")
512         #       "L"   is LOST item
513         #   "A"   is Account Management Fee
514         #   "N"   is New Card
515         #   "M"   is Sundry
516         #   "O"   is Overdue ??
517         #   "F"   is Fine ??
518         #   "FU"  is Fine UPDATE??
519         #       "Pay" is Payment
520         #   "REF" is Cash Refund
521     my $sth = $dbh->prepare(
522         "SELECT * FROM accountlines
523         WHERE borrowernumber=?
524         AND   accounttype IN ('FU','O','F','M')"
525     );
526     $sth->execute( $borrowernumber );
527     my $data;
528     my $total_amount_other = 0.00;
529     my $due_qr = qr/$due/;
530     # Cycle through the fines and
531     # - find line that relates to the requested $itemnum
532     # - accumulate fines for other items
533     # so we can update $itemnum fine taking in account fine caps
534     while (my $rec = $sth->fetchrow_hashref) {
535         if ($rec->{itemnumber} == $itemnum && $rec->{description} =~ /$due_qr/) {
536             if ($data) {
537                 warn "Not a unique accountlines record for item $itemnum borrower $borrowernumber";
538             } else {
539                 $data = $rec;
540                 next;
541             }
542         }
543         $total_amount_other += $rec->{'amountoutstanding'};
544     }
545
546     if (my $maxfine = C4::Context->preference('MaxFine')) {
547         if ($total_amount_other + $amount > $maxfine) {
548             my $new_amount = $maxfine - $total_amount_other;
549             warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
550             return if $new_amount <= 0.00;
551
552             $amount = $new_amount;
553         }
554     }
555
556     if ( $data ) {
557
558                 # we're updating an existing fine.  Only modify if amount changed
559         # Note that in the current implementation, you cannot pay against an accruing fine
560         # (i.e. , of accounttype 'FU').  Doing so will break accrual.
561         if ( $data->{'amount'} != $amount ) {
562             my $diff = $amount - $data->{'amount'};
563             #3341: diff could be positive or negative!
564             my $out  = $data->{'amountoutstanding'} + $diff;
565             my $query = "
566                 UPDATE accountlines
567                                 SET date=now(), amount=?, amountoutstanding=?,
568                                         lastincrement=?, accounttype='FU'
569                                 WHERE borrowernumber=?
570                                 AND   itemnumber=?
571                                 AND   accounttype IN ('FU','O')
572                                 AND   description LIKE ?
573                                 LIMIT 1 ";
574             my $sth2 = $dbh->prepare($query);
575                         # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
576                         #               LIMIT 1 added to prevent multiple affected lines
577                         # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.  
578                         #               But actually, we should just have a regular autoincrementing PK and forget accountline,
579                         #               including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
580                         # FIXME: Why only 2 account types here?
581                         $debug and print STDERR "UpdateFine query: $query\n" .
582                                 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
583             $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
584         } else {
585             #      print "no update needed $data->{'amount'}"
586         }
587     } else {
588         my $sth4 = $dbh->prepare(
589             "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
590         );
591         $sth4->execute($itemnum);
592         my $title = $sth4->fetchrow;
593
594 #         #   print "not in account";
595 #         my $sth3 = $dbh->prepare("Select max(accountno) from accountlines");
596 #         $sth3->execute;
597
598 #         # FIXME - Make $accountno a scalar.
599 #         my @accountno = $sth3->fetchrow_array;
600 #         $sth3->finish;
601 #         $accountno[0]++;
602 # begin transaction
603                 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
604                 my $desc = ($type ? "$type " : '') . "$title $due";     # FIXEDME, avoid whitespace prefix on empty $type
605                 my $query = "INSERT INTO accountlines
606                     (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
607                             VALUES (?,?,now(),?,?,'FU',?,?,?)";
608                 my $sth2 = $dbh->prepare($query);
609                 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
610         $sth2->execute($borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno);
611     }
612     # logging action
613     &logaction(
614         "FINES",
615         $type,
616         $borrowernumber,
617         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
618         ) if C4::Context->preference("FinesLog");
619 }
620
621 =head2 BorType
622
623     $borrower = &BorType($borrowernumber);
624
625 Looks up a patron by borrower number.
626
627 C<$borrower> is a reference-to-hash whose keys are all of the fields
628 from the borrowers and categories tables of the Koha database. Thus,
629 C<$borrower> contains all information about both the borrower and
630 category he or she belongs to.
631
632 =cut
633
634 #'
635 sub BorType {
636     my ($borrowernumber) = @_;
637     my $dbh              = C4::Context->dbh;
638     my $sth              = $dbh->prepare(
639         "SELECT * from borrowers
640       LEFT JOIN categories ON borrowers.categorycode=categories.categorycode 
641       WHERE borrowernumber=?"
642     );
643     $sth->execute($borrowernumber);
644     return $sth->fetchrow_hashref;
645 }
646
647 =head2 ReplacementCost
648
649     $cost = &ReplacementCost($itemnumber);
650
651 Returns the replacement cost of the item with the given item number.
652
653 =cut
654
655 #'
656 sub ReplacementCost {
657     my ($itemnum) = @_;
658     my $dbh       = C4::Context->dbh;
659     my $sth       =
660       $dbh->prepare("Select replacementprice from items where itemnumber=?");
661     $sth->execute($itemnum);
662
663     # FIXME - Use fetchrow_array or a slice.
664     my $data = $sth->fetchrow_hashref;
665     return ( $data->{'replacementprice'} );
666 }
667
668 =head2 GetFine
669
670     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
671
672 return the total of fine
673
674 C<$itemnum> is item number
675
676 C<$borrowernumber> is the borrowernumber
677
678 =cut 
679
680
681 sub GetFine {
682     my ( $itemnum, $borrowernumber ) = @_;
683     my $dbh   = C4::Context->dbh();
684     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
685     where accounttype like 'F%'
686   AND amountoutstanding > 0 AND itemnumber = ? AND borrowernumber=?|;
687     my $sth = $dbh->prepare($query);
688     $sth->execute( $itemnum, $borrowernumber );
689     my $fine = $sth->fetchrow_hashref();
690     if ($fine->{fineamount}) {
691         return $fine->{fineamount};
692     }
693     return 0;
694 }
695
696 sub ReplacementCost2 {
697     my ( $itemnum, $borrowernumber ) = @_;
698     my $dbh   = C4::Context->dbh();
699     my $query = "SELECT amountoutstanding
700          FROM accountlines
701              WHERE accounttype like 'L'
702          AND amountoutstanding > 0
703          AND itemnumber = ?
704          AND borrowernumber= ?";
705     my $sth = $dbh->prepare($query);
706     $sth->execute( $itemnum, $borrowernumber );
707     my $data = $sth->fetchrow_hashref();
708     return ( $data->{'amountoutstanding'} );
709 }
710
711
712 =head2 GetNextIdNotify
713
714     ($result) = &GetNextIdNotify($reference);
715
716 Returns the new file number
717
718 C<$result> contains the next file number
719
720 C<$reference> contains the beggining of file number
721
722 =cut
723
724 sub GetNextIdNotify {
725     my ($reference) = @_;
726     my $query = qq|SELECT max(notify_id)
727          FROM accountlines
728          WHERE notify_id  like \"$reference%\"
729          |;
730
731     # AND borrowernumber=?|;
732     my $dbh = C4::Context->dbh;
733     my $sth = $dbh->prepare($query);
734     $sth->execute();
735     my $result = $sth->fetchrow;
736     my $count;
737     if ( $result eq '' ) {
738         ( $result = $reference . "01" );
739     }
740     else {
741         $count = substr( $result, 6 ) + 1;
742
743         if ( $count < 10 ) {
744             ( $count = "0" . $count );
745         }
746         $result = $reference . $count;
747     }
748     return $result;
749 }
750
751 =head2 NumberNotifyId
752
753     (@notify) = &NumberNotifyId($borrowernumber);
754
755 Returns amount for all file per borrowers
756 C<@notify> array contains all file per borrowers
757
758 C<$notify_id> contains the file number for the borrower number nad item number
759
760 =cut
761
762 sub NumberNotifyId{
763     my ($borrowernumber)=@_;
764     my $dbh = C4::Context->dbh;
765     my $query=qq|    SELECT distinct(notify_id)
766             FROM accountlines
767             WHERE borrowernumber=?|;
768     my @notify;
769     my $sth = $dbh->prepare($query);
770     $sth->execute($borrowernumber);
771     while ( my ($numberofnotify) = $sth->fetchrow ) {
772         push( @notify, $numberofnotify );
773     }
774     return (@notify);
775 }
776
777 =head2 AmountNotify
778
779     ($totalnotify) = &AmountNotify($notifyid);
780
781 Returns amount for all file per borrowers
782 C<$notifyid> is the file number
783
784 C<$totalnotify> contains amount of a file
785
786 C<$notify_id> contains the file number for the borrower number and item number
787
788 =cut
789
790 sub AmountNotify{
791     my ($notifyid,$borrowernumber)=@_;
792     my $dbh = C4::Context->dbh;
793     my $query=qq|    SELECT sum(amountoutstanding)
794             FROM accountlines
795             WHERE notify_id=? AND borrowernumber = ?|;
796     my $sth=$dbh->prepare($query);
797         $sth->execute($notifyid,$borrowernumber);
798         my $totalnotify=$sth->fetchrow;
799     $sth->finish;
800     return ($totalnotify);
801 }
802
803
804 =head2 GetNotifyId
805
806     ($notify_id) = &GetNotifyId($borrowernumber,$itemnumber);
807
808 Returns the file number per borrower and itemnumber
809
810 C<$borrowernumber> is a reference-to-hash whose keys are all of the fields
811 from the items tables of the Koha database. Thus,
812
813 C<$itemnumber> contains the borrower categorycode
814
815 C<$notify_id> contains the file number for the borrower number nad item number
816
817 =cut
818
819 sub GetNotifyId {
820     my ( $borrowernumber, $itemnumber ) = @_;
821     my $query = qq|SELECT notify_id
822            FROM accountlines
823            WHERE borrowernumber=?
824           AND itemnumber=?
825            AND (accounttype='FU' or accounttype='O')|;
826     my $dbh = C4::Context->dbh;
827     my $sth = $dbh->prepare($query);
828     $sth->execute( $borrowernumber, $itemnumber );
829     my ($notify_id) = $sth->fetchrow;
830     $sth->finish;
831     return ($notify_id);
832 }
833
834 =head2 CreateItemAccountLine
835
836     () = &CreateItemAccountLine($borrowernumber, $itemnumber, $date, $amount,
837                                $description, $accounttype, $amountoutstanding, 
838                                $timestamp, $notify_id, $level);
839
840 update the account lines with file number or with file level
841
842 C<$items> is a reference-to-hash whose keys are all of the fields
843 from the items tables of the Koha database. Thus,
844
845 C<$itemnumber> contains the item number
846
847 C<$borrowernumber> contains the borrower number
848
849 C<$date> contains the date of the day
850
851 C<$amount> contains item price
852
853 C<$description> contains the descritpion of accounttype 
854
855 C<$accounttype> contains the account type
856
857 C<$amountoutstanding> contains the $amountoutstanding 
858
859 C<$timestamp> contains the timestamp with time and the date of the day
860
861 C<$notify_id> contains the file number
862
863 C<$level> contains the file level
864
865 =cut
866
867 sub CreateItemAccountLine {
868     my (
869         $borrowernumber, $itemnumber,  $date,              $amount,
870         $description,    $accounttype, $amountoutstanding, $timestamp,
871         $notify_id,      $level
872     ) = @_;
873     my $dbh         = C4::Context->dbh;
874     my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
875     my $query       = "INSERT into accountlines
876          (borrowernumber,accountno,itemnumber,date,amount,description,accounttype,amountoutstanding,timestamp,notify_id,notify_level)
877           VALUES
878              (?,?,?,?,?,?,?,?,?,?,?)";
879
880     my $sth = $dbh->prepare($query);
881     $sth->execute(
882         $borrowernumber, $nextaccntno,       $itemnumber,
883         $date,           $amount,            $description,
884         $accounttype,    $amountoutstanding, $timestamp,
885         $notify_id,      $level
886     );
887 }
888
889 =head2 UpdateAccountLines
890
891     () = &UpdateAccountLines($notify_id,$notify_level,$borrowernumber,$itemnumber);
892
893 update the account lines with file number or with file level
894
895 C<$items> is a reference-to-hash whose keys are all of the fields
896 from the items tables of the Koha database. Thus,
897
898 C<$itemnumber> contains the item number
899
900 C<$notify_id> contains the file number
901
902 C<$notify_level> contains the file level
903
904 C<$borrowernumber> contains the borrowernumber
905
906 =cut
907
908 sub UpdateAccountLines {
909     my ( $notify_id, $notify_level, $borrowernumber, $itemnumber ) = @_;
910     my $query;
911     if ( $notify_id eq '' ) {
912         $query = qq|UPDATE accountlines
913     SET  notify_level=?
914     WHERE borrowernumber=? AND itemnumber=?
915     AND (accounttype='FU' or accounttype='O')|;
916     } else {
917         $query = qq|UPDATE accountlines
918      SET notify_id=?, notify_level=?
919    WHERE borrowernumber=?
920     AND itemnumber=?
921     AND (accounttype='FU' or accounttype='O')|;
922     }
923
924     my $sth = C4::Context->dbh->prepare($query);
925     if ( $notify_id eq '' ) {
926         $sth->execute( $notify_level, $borrowernumber, $itemnumber );
927     } else {
928         $sth->execute( $notify_id, $notify_level, $borrowernumber, $itemnumber );
929     }
930 }
931
932 =head2 GetItems
933
934     ($items) = &GetItems($itemnumber);
935
936 Returns the list of all delays from overduerules.
937
938 C<$items> is a reference-to-hash whose keys are all of the fields
939 from the items tables of the Koha database. Thus,
940
941 C<$itemnumber> contains the borrower categorycode
942
943 =cut
944
945 # FIXME: This is a bad function to have here.
946 # Shouldn't it be in C4::Items?
947 # Shouldn't it be called GetItem since you only get 1 row?
948 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
949
950 sub GetItems {
951     my $itemnumber = shift or return;
952     my $query = qq|SELECT *
953              FROM items
954               WHERE itemnumber=?|;
955     my $sth = C4::Context->dbh->prepare($query);
956     $sth->execute($itemnumber);
957     my ($items) = $sth->fetchrow_hashref;
958     return ($items);
959 }
960
961 =head2 GetOverdueDelays
962
963     (@delays) = &GetOverdueDelays($categorycode);
964
965 Returns the list of all delays from overduerules.
966
967 C<@delays> it's an array contains the three delays from overduerules table
968
969 C<$categorycode> contains the borrower categorycode
970
971 =cut
972
973 sub GetOverdueDelays {
974     my ($category) = @_;
975     my $query      = qq|SELECT delay1,delay2,delay3
976                 FROM overduerules
977                 WHERE categorycode=?|;
978     my $sth = C4::Context->dbh->prepare($query);
979     $sth->execute($category);
980     my (@delays) = $sth->fetchrow_array;
981     return (@delays);
982 }
983
984 =head2 GetBranchcodesWithOverdueRules
985
986     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
987
988 returns a list of branch codes for branches with overdue rules defined.
989
990 =cut
991
992 sub GetBranchcodesWithOverdueRules {
993     my $dbh               = C4::Context->dbh;
994     my $rqoverduebranches = $dbh->prepare("SELECT DISTINCT branchcode FROM overduerules WHERE delay1 IS NOT NULL AND branchcode <> '' ORDER BY branchcode");
995     $rqoverduebranches->execute;
996     my @branches = map { shift @$_ } @{ $rqoverduebranches->fetchall_arrayref };
997     if (!$branches[0]) {
998        my $availbranches = C4::Branch::GetBranches();
999        @branches = keys %$availbranches;
1000     }
1001     return @branches;
1002 }
1003
1004 =head2 CheckAccountLineLevelInfo
1005
1006     ($exist) = &CheckAccountLineLevelInfo($borrowernumber,$itemnumber,$accounttype,notify_level);
1007
1008 Check and Returns the list of all overdue books.
1009
1010 C<$exist> contains number of line in accounlines
1011 with the same .biblionumber,itemnumber,accounttype,and notify_level
1012
1013 C<$borrowernumber> contains the borrower number
1014
1015 C<$itemnumber> contains item number
1016
1017 C<$accounttype> contains account type
1018
1019 C<$notify_level> contains the accountline level 
1020
1021
1022 =cut
1023
1024 sub CheckAccountLineLevelInfo {
1025     my ( $borrowernumber, $itemnumber, $level ) = @_;
1026     my $dbh   = C4::Context->dbh;
1027     my $query = qq|SELECT count(*)
1028             FROM accountlines
1029             WHERE borrowernumber =?
1030             AND itemnumber = ?
1031             AND notify_level=?|;
1032     my $sth = $dbh->prepare($query);
1033     $sth->execute( $borrowernumber, $itemnumber, $level );
1034     my ($exist) = $sth->fetchrow;
1035     return ($exist);
1036 }
1037
1038 =head2 GetOverduerules
1039
1040     ($overduerules) = &GetOverduerules($categorycode);
1041
1042 Returns the value of borrowers (debarred or not) with notify level
1043
1044 C<$overduerules> return value of debbraed field in overduerules table
1045
1046 C<$category> contains the borrower categorycode
1047
1048 C<$notify_level> contains the notify level
1049
1050 =cut
1051
1052 sub GetOverduerules {
1053     my ( $category, $notify_level ) = @_;
1054     my $dbh   = C4::Context->dbh;
1055     my $query = qq|SELECT debarred$notify_level
1056                      FROM overduerules
1057                     WHERE categorycode=?|;
1058     my $sth = $dbh->prepare($query);
1059     $sth->execute($category);
1060     my ($overduerules) = $sth->fetchrow;
1061     return ($overduerules);
1062 }
1063
1064
1065 =head2 CheckBorrowerDebarred
1066
1067     ($debarredstatus) = &CheckBorrowerDebarred($borrowernumber);
1068
1069 Check if the borrowers is already debarred
1070
1071 C<$debarredstatus> return 0 for not debarred and return 1 for debarred
1072
1073 C<$borrowernumber> contains the borrower number
1074
1075 =cut
1076
1077 # FIXME: Shouldn't this be in C4::Members?
1078 sub CheckBorrowerDebarred {
1079     my ($borrowernumber) = @_;
1080     my $dbh   = C4::Context->dbh;
1081     my $query = qq|
1082         SELECT debarred
1083         FROM borrowers
1084         WHERE borrowernumber=?
1085         AND debarred > NOW()
1086     |;
1087     my $sth = $dbh->prepare($query);
1088     $sth->execute($borrowernumber);
1089     my $debarredstatus = $sth->fetchrow;
1090     return $debarredstatus;
1091 }
1092
1093
1094 =head2 CheckExistantNotifyid
1095
1096     ($exist) = &CheckExistantNotifyid($borrowernumber,$itemnumber,$accounttype,$notify_id);
1097
1098 Check and Returns the notify id if exist else return 0.
1099
1100 C<$exist> contains a notify_id 
1101
1102 C<$borrowernumber> contains the borrower number
1103
1104 C<$date_due> contains the date of item return 
1105
1106
1107 =cut
1108
1109 sub CheckExistantNotifyid {
1110     my ( $borrowernumber, $date_due ) = @_;
1111     my $dbh   = C4::Context->dbh;
1112     my $query = qq|SELECT notify_id FROM accountlines
1113              LEFT JOIN issues ON issues.itemnumber= accountlines.itemnumber
1114              WHERE accountlines.borrowernumber =?
1115               AND date_due = ?|;
1116     my $sth = $dbh->prepare($query);
1117     $sth->execute( $borrowernumber, $date_due );
1118     return $sth->fetchrow || 0;
1119 }
1120
1121 =head2 CheckAccountLineItemInfo
1122
1123     ($exist) = &CheckAccountLineItemInfo($borrowernumber,$itemnumber,$accounttype,$notify_id);
1124
1125 Check and Returns the list of all overdue items from the same file number(notify_id).
1126
1127 C<$exist> contains number of line in accounlines
1128 with the same .biblionumber,itemnumber,accounttype,notify_id
1129
1130 C<$borrowernumber> contains the borrower number
1131
1132 C<$itemnumber> contains item number
1133
1134 C<$accounttype> contains account type
1135
1136 C<$notify_id> contains the file number 
1137
1138 =cut
1139
1140 sub CheckAccountLineItemInfo {
1141     my ( $borrowernumber, $itemnumber, $accounttype, $notify_id ) = @_;
1142     my $dbh   = C4::Context->dbh;
1143     my $query = qq|SELECT count(*) FROM accountlines
1144              WHERE borrowernumber =?
1145              AND itemnumber = ?
1146               AND accounttype= ?
1147             AND notify_id = ?|;
1148     my $sth = $dbh->prepare($query);
1149     $sth->execute( $borrowernumber, $itemnumber, $accounttype, $notify_id );
1150     my ($exist) = $sth->fetchrow;
1151     return ($exist);
1152 }
1153
1154 =head2 CheckItemNotify
1155
1156 Sql request to check if the document has alreday been notified
1157 this function is not exported, only used with GetOverduesForBranch
1158
1159 =cut
1160
1161 sub CheckItemNotify {
1162     my ($notify_id,$notify_level,$itemnumber) = @_;
1163     my $dbh = C4::Context->dbh;
1164     my $sth = $dbh->prepare("
1165     SELECT COUNT(*)
1166      FROM notifys
1167     WHERE notify_id    = ?
1168      AND  notify_level = ? 
1169      AND  itemnumber   = ? ");
1170     $sth->execute($notify_id,$notify_level,$itemnumber);
1171     my $notified = $sth->fetchrow;
1172     return ($notified);
1173 }
1174
1175 =head2 GetOverduesForBranch
1176
1177 Sql request for display all information for branchoverdues.pl
1178 2 possibilities : with or without location .
1179 display is filtered by branch
1180
1181 FIXME: This function should be renamed.
1182
1183 =cut
1184
1185 sub GetOverduesForBranch {
1186     my ( $branch, $location) = @_;
1187         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
1188     my $dbh = C4::Context->dbh;
1189     my $select = "
1190     SELECT
1191             borrowers.borrowernumber,
1192             borrowers.surname,
1193             borrowers.firstname,
1194             borrowers.phone,
1195             borrowers.email,
1196                biblio.title,
1197                biblio.author,
1198                biblio.biblionumber,
1199                issues.date_due,
1200                issues.returndate,
1201                issues.branchcode,
1202              branches.branchname,
1203                 items.barcode,
1204                 items.homebranch,
1205                 items.itemcallnumber,
1206                 items.location,
1207                 items.itemnumber,
1208             itemtypes.description,
1209          accountlines.notify_id,
1210          accountlines.notify_level,
1211          accountlines.amountoutstanding
1212     FROM  accountlines
1213     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
1214                           AND   issues.borrowernumber = accountlines.borrowernumber
1215     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
1216     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
1217     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
1218     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1219     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
1220     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
1221     WHERE (accountlines.amountoutstanding  != '0.000000')
1222       AND (accountlines.accounttype         = 'FU'      )
1223       AND (issues.branchcode =  ?   )
1224       AND (issues.date_due  < NOW())
1225     ";
1226     my @getoverdues;
1227     my $i = 0;
1228     my $sth;
1229     if ($location) {
1230         $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
1231         $sth->execute($branch, $location);
1232     } else {
1233         $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
1234         $sth->execute($branch);
1235     }
1236     while ( my $data = $sth->fetchrow_hashref ) {
1237     #check if the document has already been notified
1238         my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
1239         if ($countnotify eq '0') {
1240             $getoverdues[$i] = $data;
1241             $i++;
1242         }
1243     }
1244     return (@getoverdues);
1245 }
1246
1247
1248 =head2 AddNotifyLine
1249
1250     &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
1251
1252 Create a line into notify, if the method is phone, the notification_send_date is implemented to
1253
1254 =cut
1255
1256 sub AddNotifyLine {
1257     my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
1258     my $dbh = C4::Context->dbh;
1259     if ( $method eq "phone" ) {
1260         my $sth = $dbh->prepare(
1261             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
1262         VALUES (?,?,now(),now(),?,?,?)"
1263         );
1264         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1265             $notifyId );
1266     }
1267     else {
1268         my $sth = $dbh->prepare(
1269             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
1270         VALUES (?,?,now(),?,?,?)"
1271         );
1272         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1273             $notifyId );
1274     }
1275     return 1;
1276 }
1277
1278 =head2 RemoveNotifyLine
1279
1280     &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
1281
1282 Cancel a notification
1283
1284 =cut
1285
1286 sub RemoveNotifyLine {
1287     my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
1288     my $dbh = C4::Context->dbh;
1289     my $sth = $dbh->prepare(
1290         "DELETE FROM notifys 
1291             WHERE
1292             borrowernumber=?
1293             AND itemnumber=?
1294             AND notify_date=?"
1295     );
1296     $sth->execute( $borrowernumber, $itemnumber, $notify_date );
1297     return 1;
1298 }
1299
1300 1;
1301 __END__
1302
1303 =head1 AUTHOR
1304
1305 Koha Development Team <http://koha-community.org/>
1306
1307 =cut