Bug 10402: Move contacts to separate table
[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 List::MoreUtils qw( uniq );
27
28 use C4::Circulation;
29 use C4::Context;
30 use C4::Accounts;
31 use C4::Log; # logaction
32 use C4::Debug;
33
34 use vars qw($VERSION @ISA @EXPORT);
35
36 BEGIN {
37         # set the version for version checking
38     $VERSION = 3.07.00.049;
39         require Exporter;
40         @ISA    = qw(Exporter);
41         # subs to rename (and maybe merge some...)
42         push @EXPORT, qw(
43         &CalcFine
44         &Getoverdues
45         &checkoverdues
46         &NumberNotifyId
47         &AmountNotify
48         &UpdateFine
49         &GetFine
50         
51         &CheckItemNotify
52         &GetOverduesForBranch
53         &RemoveNotifyLine
54         &AddNotifyLine
55         &GetOverdueMessageTransportTypes
56         );
57         # subs to remove
58         push @EXPORT, qw(
59         &BorType
60         );
61
62         # check that an equivalent don't exist already before moving
63
64         # subs to move to Circulation.pm
65         push @EXPORT, qw(
66         &GetIssuesIteminfo
67         );
68
69      # &GetIssuingRules - delete.
70    # use C4::Circulation::GetIssuingRule instead.
71
72         # subs to move to Biblio.pm
73         push @EXPORT, qw(
74         &GetItems
75         );
76 }
77
78 =head1 NAME
79
80 C4::Circulation::Fines - Koha module dealing with fines
81
82 =head1 SYNOPSIS
83
84   use C4::Overdues;
85
86 =head1 DESCRIPTION
87
88 This module contains several functions for dealing with fines for
89 overdue items. It is primarily used by the 'misc/fines2.pl' script.
90
91 =head1 FUNCTIONS
92
93 =head2 Getoverdues
94
95   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
96
97 Returns the list of all overdue books, with their itemtype.
98
99 C<$overdues> is a reference-to-array. Each element is a
100 reference-to-hash whose keys are the fields of the issues table in the
101 Koha database.
102
103 =cut
104
105 #'
106 sub Getoverdues {
107     my $params = shift;
108     my $dbh = C4::Context->dbh;
109     my $statement;
110     if ( C4::Context->preference('item-level_itypes') ) {
111         $statement = "
112    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
113      FROM issues 
114 LEFT JOIN items       USING (itemnumber)
115     WHERE date_due < NOW()
116 ";
117     } else {
118         $statement = "
119    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode
120      FROM issues 
121 LEFT JOIN items       USING (itemnumber)
122 LEFT JOIN biblioitems USING (biblioitemnumber)
123     WHERE date_due < NOW()
124 ";
125     }
126
127     my @bind_parameters;
128     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
129         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
130         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
131     } elsif ( exists $params->{'minimumdays'} ) {
132         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
133         push @bind_parameters, $params->{'minimumdays'};
134     } elsif ( exists $params->{'maximumdays'} ) {
135         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
136         push @bind_parameters, $params->{'maximumdays'};
137     }
138     $statement .= 'ORDER BY borrowernumber';
139     my $sth = $dbh->prepare( $statement );
140     $sth->execute( @bind_parameters );
141     return $sth->fetchall_arrayref({});
142 }
143
144
145 =head2 checkoverdues
146
147     ($count, $overdueitems) = checkoverdues($borrowernumber);
148
149 Returns a count and a list of overdueitems for a given borrowernumber
150
151 =cut
152
153 sub checkoverdues {
154     my $borrowernumber = shift or return;
155     # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
156     my $sth = C4::Context->dbh->prepare(
157         "SELECT biblio.*, items.*, issues.*,
158                 biblioitems.volume,
159                 biblioitems.number,
160                 biblioitems.itemtype,
161                 biblioitems.isbn,
162                 biblioitems.issn,
163                 biblioitems.publicationyear,
164                 biblioitems.publishercode,
165                 biblioitems.volumedate,
166                 biblioitems.volumedesc,
167                 biblioitems.collectiontitle,
168                 biblioitems.collectionissn,
169                 biblioitems.collectionvolume,
170                 biblioitems.editionstatement,
171                 biblioitems.editionresponsibility,
172                 biblioitems.illus,
173                 biblioitems.pages,
174                 biblioitems.notes,
175                 biblioitems.size,
176                 biblioitems.place,
177                 biblioitems.lccn,
178                 biblioitems.url,
179                 biblioitems.cn_source,
180                 biblioitems.cn_class,
181                 biblioitems.cn_item,
182                 biblioitems.cn_suffix,
183                 biblioitems.cn_sort,
184                 biblioitems.totalissues
185          FROM issues
186          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
187          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
188          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
189             WHERE issues.borrowernumber  = ?
190             AND   issues.date_due < NOW()"
191     );
192     # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
193     $sth->execute($borrowernumber);
194     my $results = $sth->fetchall_arrayref({});
195     return ( scalar(@$results), $results);  # returning the count and the results is silly
196 }
197
198 =head2 CalcFine
199
200     ($amount, $chargename,  $daycounttotal) = &CalcFine($item,
201                                   $categorycode, $branch,
202                                   $start_dt, $end_dt );
203
204 Calculates the fine for a book.
205
206 The issuingrules table in the Koha database is a fine matrix, listing
207 the penalties for each type of patron for each type of item and each branch (e.g., the
208 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
209 members might get a longer grace period between the first and second
210 reminders that a book is overdue).
211
212
213 C<$item> is an item object (hashref).
214
215 C<$categorycode> is the category code (string) of the patron who currently has
216 the book.
217
218 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
219
220 C<$start_date> & C<$end_date> are DateTime objects
221 defining the date range over which to determine the fine.
222
223 Fines scripts should just supply the date range over which to calculate the fine.
224
225 C<&CalcFine> returns four values:
226
227 C<$amount> is the fine owed by the patron (see above).
228
229 C<$chargename> is the chargename field from the applicable record in
230 the categoryitem table, whatever that is.
231
232 C<$unitcount> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
233 minus any applicable grace period, or hours)
234
235 FIXME - What is chargename supposed to be ?
236
237 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
238 or "Final Notice".  But CalcFine never defined any value.
239
240 =cut
241
242 sub CalcFine {
243     my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
244     my $start_date = $due_dt->clone();
245     # get issuingrules (fines part will be used)
246     my $itemtype = $item->{itemtype} || $item->{itype};
247     my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
248     my $fine_unit = $data->{lengthunit};
249     $fine_unit ||= 'days';
250
251     my $chargeable_units = _get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
252     my $units_minus_grace = $chargeable_units - $data->{firstremind};
253     my $amount = 0;
254     if ($data->{'chargeperiod'}  && ($units_minus_grace > 0)  ) {
255         if ( C4::Context->preference('FinesIncludeGracePeriod') ) {
256             $amount = int($chargeable_units / $data->{'chargeperiod'}) * $data->{'fine'};# TODO fine calc should be in cents
257         } else {
258             $amount = int($units_minus_grace / $data->{'chargeperiod'}) * $data->{'fine'};
259         }
260     } else {
261         # a zero (or null) chargeperiod or negative units_minus_grace value means no charge.
262     }
263     $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
264     $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
265     return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
266     # FIXME: chargename is NEVER populated anywhere.
267 }
268
269
270 =head2 _get_chargeable_units
271
272     _get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
273
274 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
275
276 C<$unit> is 'days' or 'hours' (default is 'days').
277
278 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
279
280 C<$branchcode> is the branch whose calendar to use for finding holidays.
281
282 =cut
283
284 sub _get_chargeable_units {
285     my ($unit, $date_due, $date_returned, $branchcode) = @_;
286
287     # If the due date is later than the return date
288     return 0 unless ( $date_returned > $date_due );
289
290     my $charge_units = 0;
291     my $charge_duration;
292     if ($unit eq 'hours') {
293         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
294             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
295             $charge_duration = $calendar->hours_between( $date_due, $date_returned );
296         } else {
297             $charge_duration = $date_returned->delta_ms( $date_due );
298         }
299         if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
300             return 1;
301         }
302         return $charge_duration->in_units('hours');
303     }
304     else { # days
305         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
306             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
307             $charge_duration = $calendar->days_between( $date_due, $date_returned );
308         } else {
309             $charge_duration = $date_returned->delta_days( $date_due );
310         }
311         return $charge_duration->in_units('days');
312     }
313 }
314
315
316 =head2 GetSpecialHolidays
317
318     &GetSpecialHolidays($date_dues,$itemnumber);
319
320 return number of special days  between date of the day and date due
321
322 C<$date_dues> is the envisaged date of book return.
323
324 C<$itemnumber> is the book's item number.
325
326 =cut
327
328 sub GetSpecialHolidays {
329     my ( $date_dues, $itemnumber ) = @_;
330
331     # calcul the today date
332     my $today = join "-", &Today();
333
334     # return the holdingbranch
335     my $iteminfo = GetIssuesIteminfo($itemnumber);
336
337     # use sql request to find all date between date_due and today
338     my $dbh = C4::Context->dbh;
339     my $query =
340       qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
341 FROM `special_holidays`
342 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
343 AND   DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
344 AND branchcode=?
345 |;
346     my @result = GetWdayFromItemnumber($itemnumber);
347     my @result_date;
348     my $wday;
349     my $dateinsec;
350     my $sth = $dbh->prepare($query);
351     $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
352       ;    # FIXME: just use NOW() in SQL instead of passing in $today
353
354     while ( my $special_date = $sth->fetchrow_hashref ) {
355         push( @result_date, $special_date );
356     }
357
358     my $specialdaycount = scalar(@result_date);
359
360     for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
361         $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
362         ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
363           localtime($dateinsec);
364         for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
365             if ( $wday == ( $result[$j]->{'weekday'} ) ) {
366                 $specialdaycount--;
367             }
368         }
369     }
370
371     return $specialdaycount;
372 }
373
374 =head2 GetRepeatableHolidays
375
376     &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
377
378 return number of day closed between date of the day and date due
379
380 C<$date_dues> is the envisaged date of book return.
381
382 C<$itemnumber> is item number.
383
384 C<$difference> numbers of between day date of the day and date due
385
386 =cut
387
388 sub GetRepeatableHolidays {
389     my ( $date_dues, $itemnumber, $difference ) = @_;
390     my $dateinsec = UnixDate( $date_dues, "%o" );
391     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
392       localtime($dateinsec);
393     my @result = GetWdayFromItemnumber($itemnumber);
394     my @dayclosedcount;
395     my $j;
396
397     for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
398         my $k = $wday;
399
400         for ( $j = 0 ; $j < $difference ; $j++ ) {
401             if ( $result[$i]->{'weekday'} == $k ) {
402                 push( @dayclosedcount, $k );
403             }
404             $k++;
405             ( $k = 0 ) if ( $k eq 7 );
406         }
407     }
408     return scalar(@dayclosedcount);
409 }
410
411
412 =head2 GetWayFromItemnumber
413
414     &Getwdayfromitemnumber($itemnumber);
415
416 return the different week day from repeatable_holidays table
417
418 C<$itemnumber> is  item number.
419
420 =cut
421
422 sub GetWdayFromItemnumber {
423     my ($itemnumber) = @_;
424     my $iteminfo = GetIssuesIteminfo($itemnumber);
425     my @result;
426     my $query = qq|SELECT weekday
427     FROM repeatable_holidays
428     WHERE branchcode=?
429 |;
430     my $sth = C4::Context->dbh->prepare($query);
431
432     $sth->execute( $iteminfo->{'branchcode'} );
433     while ( my $weekday = $sth->fetchrow_hashref ) {
434         push( @result, $weekday );
435     }
436     return @result;
437 }
438
439
440 =head2 GetIssuesIteminfo
441
442     &GetIssuesIteminfo($itemnumber);
443
444 return all data from issues about item
445
446 C<$itemnumber> is  item number.
447
448 =cut
449
450 sub GetIssuesIteminfo {
451     my ($itemnumber) = @_;
452     my $dbh          = C4::Context->dbh;
453     my $query        = qq|SELECT *
454     FROM issues
455     WHERE itemnumber=?
456     |;
457     my $sth = $dbh->prepare($query);
458     $sth->execute($itemnumber);
459     my ($issuesinfo) = $sth->fetchrow_hashref;
460     return $issuesinfo;
461 }
462
463
464 =head2 UpdateFine
465
466     &UpdateFine($itemnumber, $borrowernumber, $amount, $type, $description);
467
468 (Note: the following is mostly conjecture and guesswork.)
469
470 Updates the fine owed on an overdue book.
471
472 C<$itemnumber> is the book's item number.
473
474 C<$borrowernumber> is the borrower number of the patron who currently
475 has the book on loan.
476
477 C<$amount> is the current amount owed by the patron.
478
479 C<$type> will be used in the description of the fine.
480
481 C<$description> is a string that must be present in the description of
482 the fine. I think this is expected to be a date in DD/MM/YYYY format.
483
484 C<&UpdateFine> looks up the amount currently owed on the given item
485 and sets it to C<$amount>, creating, if necessary, a new entry in the
486 accountlines table of the Koha database.
487
488 =cut
489
490 #
491 # Question: Why should the caller have to
492 # specify both the item number and the borrower number? A book can't
493 # be on loan to two different people, so the item number should be
494 # sufficient.
495 #
496 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
497 #
498 sub UpdateFine {
499     my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
500         $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
501     my $dbh = C4::Context->dbh;
502     # FIXME - What exactly is this query supposed to do? It looks up an
503     # entry in accountlines that matches the given item and borrower
504     # numbers, where the description contains $due, and where the
505     # account type has one of several values, but what does this _mean_?
506     # Does it look up existing fines for this item?
507     # FIXME - What are these various account types? ("FU", "O", "F", "M")
508         #       "L"   is LOST item
509         #   "A"   is Account Management Fee
510         #   "N"   is New Card
511         #   "M"   is Sundry
512         #   "O"   is Overdue ??
513         #   "F"   is Fine ??
514         #   "FU"  is Fine UPDATE??
515         #       "Pay" is Payment
516         #   "REF" is Cash Refund
517     my $sth = $dbh->prepare(
518         "SELECT * FROM accountlines
519         WHERE borrowernumber=?
520         AND   accounttype IN ('FU','O','F','M')"
521     );
522     $sth->execute( $borrowernumber );
523     my $data;
524     my $total_amount_other = 0.00;
525     my $due_qr = qr/$due/;
526     # Cycle through the fines and
527     # - find line that relates to the requested $itemnum
528     # - accumulate fines for other items
529     # so we can update $itemnum fine taking in account fine caps
530     while (my $rec = $sth->fetchrow_hashref) {
531         if ($rec->{itemnumber} == $itemnum && $rec->{description} =~ /$due_qr/) {
532             if ($data) {
533                 warn "Not a unique accountlines record for item $itemnum borrower $borrowernumber";
534             } else {
535                 $data = $rec;
536                 next;
537             }
538         }
539         $total_amount_other += $rec->{'amountoutstanding'};
540     }
541
542     if (my $maxfine = C4::Context->preference('MaxFine')) {
543         if ($total_amount_other + $amount > $maxfine) {
544             my $new_amount = $maxfine - $total_amount_other;
545             return if $new_amount <= 0.00;
546             warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
547             $amount = $new_amount;
548         }
549     }
550
551     if ( $data ) {
552
553                 # we're updating an existing fine.  Only modify if amount changed
554         # Note that in the current implementation, you cannot pay against an accruing fine
555         # (i.e. , of accounttype 'FU').  Doing so will break accrual.
556         if ( $data->{'amount'} != $amount ) {
557             my $diff = $amount - $data->{'amount'};
558             #3341: diff could be positive or negative!
559             my $out  = $data->{'amountoutstanding'} + $diff;
560             my $query = "
561                 UPDATE accountlines
562                                 SET date=now(), amount=?, amountoutstanding=?,
563                                         lastincrement=?, accounttype='FU'
564                                 WHERE borrowernumber=?
565                                 AND   itemnumber=?
566                                 AND   accounttype IN ('FU','O')
567                                 AND   description LIKE ?
568                                 LIMIT 1 ";
569             my $sth2 = $dbh->prepare($query);
570                         # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
571                         #               LIMIT 1 added to prevent multiple affected lines
572                         # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.  
573                         #               But actually, we should just have a regular autoincrementing PK and forget accountline,
574                         #               including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
575                         # FIXME: Why only 2 account types here?
576                         $debug and print STDERR "UpdateFine query: $query\n" .
577                                 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
578             $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
579         } else {
580             #      print "no update needed $data->{'amount'}"
581         }
582     } else {
583         my $sth4 = $dbh->prepare(
584             "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
585         );
586         $sth4->execute($itemnum);
587         my $title = $sth4->fetchrow;
588
589 #         #   print "not in account";
590 #         my $sth3 = $dbh->prepare("Select max(accountno) from accountlines");
591 #         $sth3->execute;
592
593 #         # FIXME - Make $accountno a scalar.
594 #         my @accountno = $sth3->fetchrow_array;
595 #         $sth3->finish;
596 #         $accountno[0]++;
597 # begin transaction
598                 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
599                 my $desc = ($type ? "$type " : '') . "$title $due";     # FIXEDME, avoid whitespace prefix on empty $type
600                 my $query = "INSERT INTO accountlines
601                     (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
602                             VALUES (?,?,now(),?,?,'FU',?,?,?)";
603                 my $sth2 = $dbh->prepare($query);
604                 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
605         $sth2->execute($borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno);
606     }
607     # logging action
608     &logaction(
609         "FINES",
610         $type,
611         $borrowernumber,
612         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
613         ) if C4::Context->preference("FinesLog");
614 }
615
616 =head2 BorType
617
618     $borrower = &BorType($borrowernumber);
619
620 Looks up a patron by borrower number.
621
622 C<$borrower> is a reference-to-hash whose keys are all of the fields
623 from the borrowers and categories tables of the Koha database. Thus,
624 C<$borrower> contains all information about both the borrower and
625 category he or she belongs to.
626
627 =cut
628
629 sub BorType {
630     my ($borrowernumber) = @_;
631     my $dbh              = C4::Context->dbh;
632     my $sth              = $dbh->prepare(
633         "SELECT * from borrowers
634       LEFT JOIN categories ON borrowers.categorycode=categories.categorycode 
635       WHERE borrowernumber=?"
636     );
637     $sth->execute($borrowernumber);
638     return $sth->fetchrow_hashref;
639 }
640
641 =head2 GetFine
642
643     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
644
645 return the total of fine
646
647 C<$itemnum> is item number
648
649 C<$borrowernumber> is the borrowernumber
650
651 =cut 
652
653 sub GetFine {
654     my ( $itemnum, $borrowernumber ) = @_;
655     my $dbh   = C4::Context->dbh();
656     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
657     where accounttype like 'F%'
658   AND amountoutstanding > 0 AND itemnumber = ? AND borrowernumber=?|;
659     my $sth = $dbh->prepare($query);
660     $sth->execute( $itemnum, $borrowernumber );
661     my $fine = $sth->fetchrow_hashref();
662     if ($fine->{fineamount}) {
663         return $fine->{fineamount};
664     }
665     return 0;
666 }
667
668 =head2 NumberNotifyId
669
670     (@notify) = &NumberNotifyId($borrowernumber);
671
672 Returns amount for all file per borrowers
673 C<@notify> array contains all file per borrowers
674
675 C<$notify_id> contains the file number for the borrower number nad item number
676
677 =cut
678
679 sub NumberNotifyId{
680     my ($borrowernumber)=@_;
681     my $dbh = C4::Context->dbh;
682     my $query=qq|    SELECT distinct(notify_id)
683             FROM accountlines
684             WHERE borrowernumber=?|;
685     my @notify;
686     my $sth = $dbh->prepare($query);
687     $sth->execute($borrowernumber);
688     while ( my ($numberofnotify) = $sth->fetchrow ) {
689         push( @notify, $numberofnotify );
690     }
691     return (@notify);
692 }
693
694 =head2 AmountNotify
695
696     ($totalnotify) = &AmountNotify($notifyid);
697
698 Returns amount for all file per borrowers
699 C<$notifyid> is the file number
700
701 C<$totalnotify> contains amount of a file
702
703 C<$notify_id> contains the file number for the borrower number and item number
704
705 =cut
706
707 sub AmountNotify{
708     my ($notifyid,$borrowernumber)=@_;
709     my $dbh = C4::Context->dbh;
710     my $query=qq|    SELECT sum(amountoutstanding)
711             FROM accountlines
712             WHERE notify_id=? AND borrowernumber = ?|;
713     my $sth=$dbh->prepare($query);
714         $sth->execute($notifyid,$borrowernumber);
715         my $totalnotify=$sth->fetchrow;
716     $sth->finish;
717     return ($totalnotify);
718 }
719
720 =head2 GetItems
721
722     ($items) = &GetItems($itemnumber);
723
724 Returns the list of all delays from overduerules.
725
726 C<$items> is a reference-to-hash whose keys are all of the fields
727 from the items tables of the Koha database. Thus,
728
729 C<$itemnumber> contains the borrower categorycode
730
731 =cut
732
733 # FIXME: This is a bad function to have here.
734 # Shouldn't it be in C4::Items?
735 # Shouldn't it be called GetItem since you only get 1 row?
736 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
737
738 sub GetItems {
739     my $itemnumber = shift or return;
740     my $query = qq|SELECT *
741              FROM items
742               WHERE itemnumber=?|;
743     my $sth = C4::Context->dbh->prepare($query);
744     $sth->execute($itemnumber);
745     my ($items) = $sth->fetchrow_hashref;
746     return ($items);
747 }
748
749 =head2 GetBranchcodesWithOverdueRules
750
751     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
752
753 returns a list of branch codes for branches with overdue rules defined.
754
755 =cut
756
757 sub GetBranchcodesWithOverdueRules {
758     my $dbh               = C4::Context->dbh;
759     my $branchcodes = $dbh->selectcol_arrayref(q|
760         SELECT DISTINCT(branchcode)
761         FROM overduerules
762         WHERE delay1 IS NOT NULL
763         ORDER BY branchcode
764     |);
765     if ( $branchcodes->[0] eq '' ) {
766         # If a default rule exists, all branches should be returned
767         my $availbranches = C4::Branch::GetBranches();
768         return keys %$availbranches;
769     }
770     return @$branchcodes;
771 }
772
773 =head2 CheckItemNotify
774
775 Sql request to check if the document has alreday been notified
776 this function is not exported, only used with GetOverduesForBranch
777
778 =cut
779
780 sub CheckItemNotify {
781     my ($notify_id,$notify_level,$itemnumber) = @_;
782     my $dbh = C4::Context->dbh;
783     my $sth = $dbh->prepare("
784     SELECT COUNT(*)
785      FROM notifys
786     WHERE notify_id    = ?
787      AND  notify_level = ? 
788      AND  itemnumber   = ? ");
789     $sth->execute($notify_id,$notify_level,$itemnumber);
790     my $notified = $sth->fetchrow;
791     return ($notified);
792 }
793
794 =head2 GetOverduesForBranch
795
796 Sql request for display all information for branchoverdues.pl
797 2 possibilities : with or without location .
798 display is filtered by branch
799
800 FIXME: This function should be renamed.
801
802 =cut
803
804 sub GetOverduesForBranch {
805     my ( $branch, $location) = @_;
806         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
807     my $dbh = C4::Context->dbh;
808     my $select = "
809     SELECT
810             borrowers.cardnumber,
811             borrowers.borrowernumber,
812             borrowers.surname,
813             borrowers.firstname,
814             borrowers.phone,
815             borrowers.email,
816                biblio.title,
817                biblio.author,
818                biblio.biblionumber,
819                issues.date_due,
820                issues.returndate,
821                issues.branchcode,
822              branches.branchname,
823                 items.barcode,
824                 items.homebranch,
825                 items.itemcallnumber,
826                 items.location,
827                 items.itemnumber,
828             itemtypes.description,
829          accountlines.notify_id,
830          accountlines.notify_level,
831          accountlines.amountoutstanding
832     FROM  accountlines
833     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
834                           AND   issues.borrowernumber = accountlines.borrowernumber
835     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
836     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
837     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
838     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
839     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
840     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
841     WHERE (accountlines.amountoutstanding  != '0.000000')
842       AND (accountlines.accounttype         = 'FU'      )
843       AND (issues.branchcode =  ?   )
844       AND (issues.date_due  < NOW())
845     ";
846     my @getoverdues;
847     my $i = 0;
848     my $sth;
849     if ($location) {
850         $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
851         $sth->execute($branch, $location);
852     } else {
853         $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
854         $sth->execute($branch);
855     }
856     while ( my $data = $sth->fetchrow_hashref ) {
857     #check if the document has already been notified
858         my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
859         if ($countnotify eq '0') {
860             $getoverdues[$i] = $data;
861             $i++;
862         }
863     }
864     return (@getoverdues);
865 }
866
867
868 =head2 AddNotifyLine
869
870     &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
871
872 Create a line into notify, if the method is phone, the notification_send_date is implemented to
873
874 =cut
875
876 sub AddNotifyLine {
877     my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
878     my $dbh = C4::Context->dbh;
879     if ( $method eq "phone" ) {
880         my $sth = $dbh->prepare(
881             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
882         VALUES (?,?,now(),now(),?,?,?)"
883         );
884         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
885             $notifyId );
886     }
887     else {
888         my $sth = $dbh->prepare(
889             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
890         VALUES (?,?,now(),?,?,?)"
891         );
892         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
893             $notifyId );
894     }
895     return 1;
896 }
897
898 =head2 RemoveNotifyLine
899
900     &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
901
902 Cancel a notification
903
904 =cut
905
906 sub RemoveNotifyLine {
907     my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
908     my $dbh = C4::Context->dbh;
909     my $sth = $dbh->prepare(
910         "DELETE FROM notifys 
911             WHERE
912             borrowernumber=?
913             AND itemnumber=?
914             AND notify_date=?"
915     );
916     $sth->execute( $borrowernumber, $itemnumber, $notify_date );
917     return 1;
918 }
919
920 =head2 GetOverdueMessageTransportTypes
921
922     my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
923
924     return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
925
926 =cut
927
928 sub GetOverdueMessageTransportTypes {
929     my ( $branchcode, $categorycode, $letternumber ) = @_;
930     return unless $categorycode and $letternumber;
931     my $dbh = C4::Context->dbh;
932     my $sth = $dbh->prepare("
933         SELECT message_transport_type FROM overduerules_transport_types
934         WHERE branchcode = ? AND categorycode = ? AND letternumber = ?
935     ");
936     $sth->execute( $branchcode, $categorycode, $letternumber );
937     my @mtts;
938     while ( my $mtt = $sth->fetchrow ) {
939         push @mtts, $mtt;
940     }
941
942     # Put 'print' in first if exists
943     # It avoid to sent a print notice with an email or sms template is no email or sms is defined
944     @mtts = uniq( 'print', @mtts )
945         if grep {/^print$/} @mtts;
946
947     return \@mtts;
948 }
949
950 1;
951 __END__
952
953 =head1 AUTHOR
954
955 Koha Development Team <http://koha-community.org/>
956
957 =cut