b315511a826da317e80d316d781bf787d3427ab1
[koha-ffzg.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
10 # under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # Koha is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with Koha; if not, see <http://www.gnu.org/licenses>.
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 use POSIX qw( floor ceil );
28 use Locale::Currency::Format 1.28;
29 use Carp;
30
31 use C4::Circulation;
32 use C4::Context;
33 use C4::Accounts;
34 use C4::Log; # logaction
35 use C4::Debug;
36 use Koha::DateUtils;
37 use Koha::Account::Lines;
38 use Koha::Account::Offsets;
39 use Koha::IssuingRules;
40 use Koha::Libraries;
41
42 use vars qw(@ISA @EXPORT);
43
44 BEGIN {
45     require Exporter;
46     @ISA = qw(Exporter);
47
48     # subs to rename (and maybe merge some...)
49     push @EXPORT, qw(
50       &CalcFine
51       &Getoverdues
52       &checkoverdues
53       &NumberNotifyId
54       &AmountNotify
55       &UpdateFine
56       &GetFine
57       &get_chargeable_units
58       &GetOverduesForBranch
59       &GetOverdueMessageTransportTypes
60       &parse_overdues_letter
61     );
62
63     # subs to remove
64     push @EXPORT, qw(
65       &BorType
66     );
67
68     # check that an equivalent don't exist already before moving
69
70     # subs to move to Circulation.pm
71     push @EXPORT, qw(
72       &GetIssuesIteminfo
73     );
74
75     # subs to move to Biblio.pm
76     push @EXPORT, qw(
77       &GetItems
78     );
79 }
80
81 =head1 NAME
82
83 C4::Circulation::Fines - Koha module dealing with fines
84
85 =head1 SYNOPSIS
86
87   use C4::Overdues;
88
89 =head1 DESCRIPTION
90
91 This module contains several functions for dealing with fines for
92 overdue items. It is primarily used by the 'misc/fines2.pl' script.
93
94 =head1 FUNCTIONS
95
96 =head2 Getoverdues
97
98   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
99
100 Returns the list of all overdue books, with their itemtype.
101
102 C<$overdues> is a reference-to-array. Each element is a
103 reference-to-hash whose keys are the fields of the issues table in the
104 Koha database.
105
106 =cut
107
108 #'
109 sub Getoverdues {
110     my $params = shift;
111     my $dbh = C4::Context->dbh;
112     my $statement;
113     if ( C4::Context->preference('item-level_itypes') ) {
114         $statement = "
115    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost, items.replacementprice
116      FROM issues 
117 LEFT JOIN items       USING (itemnumber)
118     WHERE date_due < NOW()
119 ";
120     } else {
121         $statement = "
122    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost, replacementprice
123      FROM issues 
124 LEFT JOIN items       USING (itemnumber)
125 LEFT JOIN biblioitems USING (biblioitemnumber)
126     WHERE date_due < NOW()
127 ";
128     }
129
130     my @bind_parameters;
131     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
132         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
133         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
134     } elsif ( exists $params->{'minimumdays'} ) {
135         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
136         push @bind_parameters, $params->{'minimumdays'};
137     } elsif ( exists $params->{'maximumdays'} ) {
138         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
139         push @bind_parameters, $params->{'maximumdays'};
140     }
141     $statement .= 'ORDER BY borrowernumber';
142     my $sth = $dbh->prepare( $statement );
143     $sth->execute( @bind_parameters );
144     return $sth->fetchall_arrayref({});
145 }
146
147
148 =head2 checkoverdues
149
150     ($count, $overdueitems) = checkoverdues($borrowernumber);
151
152 Returns a count and a list of overdueitems for a given borrowernumber
153
154 =cut
155
156 sub checkoverdues {
157     my $borrowernumber = shift or return;
158     my $sth = C4::Context->dbh->prepare(
159         "SELECT biblio.*, items.*, issues.*,
160                 biblioitems.volume,
161                 biblioitems.number,
162                 biblioitems.itemtype,
163                 biblioitems.isbn,
164                 biblioitems.issn,
165                 biblioitems.publicationyear,
166                 biblioitems.publishercode,
167                 biblioitems.volumedate,
168                 biblioitems.volumedesc,
169                 biblioitems.collectiontitle,
170                 biblioitems.collectionissn,
171                 biblioitems.collectionvolume,
172                 biblioitems.editionstatement,
173                 biblioitems.editionresponsibility,
174                 biblioitems.illus,
175                 biblioitems.pages,
176                 biblioitems.notes,
177                 biblioitems.size,
178                 biblioitems.place,
179                 biblioitems.lccn,
180                 biblioitems.url,
181                 biblioitems.cn_source,
182                 biblioitems.cn_class,
183                 biblioitems.cn_item,
184                 biblioitems.cn_suffix,
185                 biblioitems.cn_sort,
186                 biblioitems.totalissues
187          FROM issues
188          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
189          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
190          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
191             WHERE issues.borrowernumber  = ?
192             AND   issues.date_due < NOW()"
193     );
194     $sth->execute($borrowernumber);
195     my $results = $sth->fetchall_arrayref({});
196     return ( scalar(@$results), $results);  # returning the count and the results is silly
197 }
198
199 =head2 CalcFine
200
201     ($amount, $chargename,  $units_minus_grace, $chargeable_units) = &CalcFine($item,
202                                   $categorycode, $branch,
203                                   $start_dt, $end_dt );
204
205 Calculates the fine for a book.
206
207 The issuingrules table in the Koha database is a fine matrix, listing
208 the penalties for each type of patron for each type of item and each branch (e.g., the
209 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
210 members might get a longer grace period between the first and second
211 reminders that a book is overdue).
212
213
214 C<$item> is an item object (hashref).
215
216 C<$categorycode> is the category code (string) of the patron who currently has
217 the book.
218
219 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
220
221 C<$start_date> & C<$end_date> are DateTime objects
222 defining the date range over which to determine the fine.
223
224 Fines scripts should just supply the date range over which to calculate the fine.
225
226 C<&CalcFine> returns four values:
227
228 C<$amount> is the fine owed by the patron (see above).
229
230 C<$chargename> is the chargename field from the applicable record in
231 the categoryitem table, whatever that is.
232
233 C<$units_minus_grace> is the number of chargeable units minus the grace period
234
235 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
236 minus any applicable grace period, or hours)
237
238 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
239 or "Final Notice".  But CalcFine never defined any value.
240
241 =cut
242
243 sub CalcFine {
244     my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
245     my $start_date = $due_dt->clone();
246     # get issuingrules (fines part will be used)
247     my $itemtype = $item->{itemtype} || $item->{itype};
248     my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $bortype, itemtype => $itemtype, branchcode => $branchcode });
249
250     return unless $issuing_rule; # If not rule exist, there is no fine
251
252     my $fine_unit = $issuing_rule->lengthunit || 'days';
253
254     my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
255     my $units_minus_grace = $chargeable_units - $issuing_rule->firstremind;
256     my $amount = 0;
257     if ( $issuing_rule->chargeperiod && ( $units_minus_grace > 0 ) ) {
258         my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
259         my $charge_periods = $units / $issuing_rule->chargeperiod;
260         # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
261         # if chargeperiod_charge_at = 0, we charge at the end of each charge period
262         $charge_periods = $issuing_rule->chargeperiod_charge_at == 1 ? ceil($charge_periods) : floor($charge_periods);
263         $amount = $charge_periods * $issuing_rule->fine;
264     } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
265
266     $amount = $issuing_rule->overduefinescap if $issuing_rule->overduefinescap && $amount > $issuing_rule->overduefinescap;
267     $amount = $item->{replacementprice} if ( $issuing_rule->cap_fine_to_replacement_price && $item->{replacementprice} && $amount > $item->{replacementprice} );
268     $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $issuing_rule->chargename, $units_minus_grace, $chargeable_units);
269     return ($amount, $issuing_rule->chargename, $units_minus_grace, $chargeable_units);
270     # FIXME: chargename is NEVER populated anywhere.
271 }
272
273
274 =head2 get_chargeable_units
275
276     get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
277
278 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
279
280 C<$unit> is 'days' or 'hours' (default is 'days').
281
282 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
283
284 C<$branchcode> is the branch whose calendar to use for finding holidays.
285
286 =cut
287
288 sub get_chargeable_units {
289     my ($unit, $date_due, $date_returned, $branchcode) = @_;
290
291     # If the due date is later than the return date
292     return 0 unless ( $date_returned > $date_due );
293
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( $date_due, $date_returned );
300         } else {
301             $charge_duration = $date_returned->delta_ms( $date_due );
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( $date_due, $date_returned );
312         } else {
313             $charge_duration = $date_returned->delta_days( $date_due );
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({ issue_id => $issue_id, itemnumber => $itemnumber, borrwernumber => $borrowernumber, amount => $amount, type => $type, $due => $date_due });
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<$due> is the due date formatted to the currently specified date format
486
487 C<&UpdateFine> looks up the amount currently owed on the given item
488 and sets it to C<$amount>, creating, if necessary, a new entry in the
489 accountlines table of the Koha database.
490
491 =cut
492
493 #
494 # Question: Why should the caller have to
495 # specify both the item number and the borrower number? A book can't
496 # be on loan to two different people, so the item number should be
497 # sufficient.
498 #
499 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
500 #
501 sub UpdateFine {
502     my ($params) = @_;
503
504     my $issue_id       = $params->{issue_id};
505     my $itemnum        = $params->{itemnumber};
506     my $borrowernumber = $params->{borrowernumber};
507     my $amount         = $params->{amount};
508     my $type           = $params->{type};
509     my $due            = $params->{due};
510
511     $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, type => $type, due => $due, issue_id => $issue_id})";
512
513     unless ( $issue_id ) {
514         carp("No issue_id passed in!");
515         return;
516     }
517
518     my $dbh = C4::Context->dbh;
519     # FIXME - What exactly is this query supposed to do? It looks up an
520     # entry in accountlines that matches the given item and borrower
521     # numbers, where the description contains $due, and where the
522     # account type has one of several values, but what does this _mean_?
523     # Does it look up existing fines for this item?
524     # FIXME - What are these various account types? ("FU", "O", "F", "M")
525     #   "L"   is LOST item
526     #   "A"   is Account Management Fee
527     #   "N"   is New Card
528     #   "M"   is Sundry
529     #   "O"   is Overdue ??
530     #   "F"   is Fine ??
531     #   "FU"  is Fine UPDATE??
532     #   "Pay" is Payment
533     #   "REF" is Cash Refund
534     my $sth = $dbh->prepare(
535         "SELECT * FROM accountlines
536         WHERE borrowernumber=? AND
537         (( accounttype IN ('O','F','M') AND amountoutstanding<>0 ) OR
538            accounttype = 'FU' )"
539     );
540     $sth->execute( $borrowernumber );
541     my $data;
542     my $total_amount_other = 0.00;
543     my $due_qr = qr/$due/;
544     # Cycle through the fines and
545     # - find line that relates to the requested $itemnum
546     # - accumulate fines for other items
547     # so we can update $itemnum fine taking in account fine caps
548     while (my $rec = $sth->fetchrow_hashref) {
549         if ( $rec->{issue_id} == $issue_id && $rec->{accounttype} eq 'FU' ) {
550             if ($data) {
551                 warn "Not a unique accountlines record for issue_id $issue_id";
552                 #FIXME Should we still count this one in total_amount ??
553             }
554             else {
555                 $data = $rec;
556                 next;
557             }
558         }
559         $total_amount_other += $rec->{'amountoutstanding'};
560     }
561
562     if (my $maxfine = C4::Context->preference('MaxFine')) {
563         if ($total_amount_other + $amount > $maxfine) {
564             my $new_amount = $maxfine - $total_amount_other;
565             return if $new_amount <= 0.00;
566             warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
567             $amount = $new_amount;
568         }
569     }
570
571     if ( $data ) {
572         # we're updating an existing fine.  Only modify if amount changed
573         # Note that in the current implementation, you cannot pay against an accruing fine
574         # (i.e. , of accounttype 'FU').  Doing so will break accrual.
575         if ( $data->{'amount'} != $amount ) {
576             my $accountline = Koha::Account::Lines->find( $data->{accountlines_id} );
577             my $diff = $amount - $data->{'amount'};
578
579             #3341: diff could be positive or negative!
580             my $out   = $data->{'amountoutstanding'} + $diff;
581
582             $accountline->set(
583                 {
584                     date          => dt_from_string(),
585                     amount        => $amount,
586                     amountoutstanding   => $out,
587                     lastincrement => $diff,
588                     accounttype   => 'FU',
589                 }
590             )->store();
591
592             Koha::Account::Offset->new(
593                 {
594                     debit_id => $accountline->id,
595                     type     => 'Fine Update',
596                     amount   => $diff,
597                 }
598             )->store();
599         }
600     } else {
601         if ( $amount ) { # Don't add new fines with an amount of 0
602             my $sth4 = $dbh->prepare(
603                 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
604             );
605             $sth4->execute($itemnum);
606             my $title = $sth4->fetchrow;
607
608             my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
609
610             my $desc = ( $type ? "$type " : '' ) . "$title $due";    # FIXEDME, avoid whitespace prefix on empty $type
611
612             my $accountline = Koha::Account::Line->new(
613                 {
614                     borrowernumber    => $borrowernumber,
615                     itemnumber        => $itemnum,
616                     date              => dt_from_string(),
617                     amount            => $amount,
618                     description       => $desc,
619                     accounttype       => 'FU',
620                     amountoutstanding => $amount,
621                     lastincrement     => $amount,
622                     accountno         => $nextaccntno,
623                     issue_id          => $issue_id,
624                 }
625             )->store();
626
627             Koha::Account::Offset->new(
628                 {
629                     debit_id => $accountline->id,
630                     type     => 'Fine',
631                     amount   => $amount,
632                 }
633             )->store();
634         }
635     }
636     # logging action
637     &logaction(
638         "FINES",
639         $type,
640         $borrowernumber,
641         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
642         ) if C4::Context->preference("FinesLog");
643 }
644
645 =head2 BorType
646
647     $borrower = &BorType($borrowernumber);
648
649 Looks up a patron by borrower number.
650
651 C<$borrower> is a reference-to-hash whose keys are all of the fields
652 from the borrowers and categories tables of the Koha database. Thus,
653 C<$borrower> contains all information about both the borrower and
654 category they belong to.
655
656 =cut
657
658 sub BorType {
659     my ($borrowernumber) = @_;
660     my $dbh              = C4::Context->dbh;
661     my $sth              = $dbh->prepare(
662         "SELECT * from borrowers
663       LEFT JOIN categories ON borrowers.categorycode=categories.categorycode 
664       WHERE borrowernumber=?"
665     );
666     $sth->execute($borrowernumber);
667     return $sth->fetchrow_hashref;
668 }
669
670 =head2 GetFine
671
672     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
673
674 return the total of fine
675
676 C<$itemnum> is item number
677
678 C<$borrowernumber> is the borrowernumber
679
680 =cut 
681
682 sub GetFine {
683     my ( $itemnum, $borrowernumber ) = @_;
684     my $dbh   = C4::Context->dbh();
685     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
686     where accounttype like 'F%'
687   AND amountoutstanding > 0 AND borrowernumber=?|;
688     my @query_param;
689     push @query_param, $borrowernumber;
690     if (defined $itemnum )
691     {
692         $query .= " AND itemnumber=?";
693         push @query_param, $itemnum;
694     }
695     my $sth = $dbh->prepare($query);
696     $sth->execute( @query_param );
697     my $fine = $sth->fetchrow_hashref();
698     if ($fine->{fineamount}) {
699         return $fine->{fineamount};
700     }
701     return 0;
702 }
703
704 =head2 NumberNotifyId
705
706     (@notify) = &NumberNotifyId($borrowernumber);
707
708 Returns amount for all file per borrowers
709 C<@notify> array contains all file per borrowers
710
711 C<$notify_id> contains the file number for the borrower number nad item number
712
713 =cut
714
715 sub NumberNotifyId{
716     my ($borrowernumber)=@_;
717     my $dbh = C4::Context->dbh;
718     my $query=qq|    SELECT distinct(notify_id)
719             FROM accountlines
720             WHERE borrowernumber=?|;
721     my @notify;
722     my $sth = $dbh->prepare($query);
723     $sth->execute($borrowernumber);
724     while ( my ($numberofnotify) = $sth->fetchrow ) {
725         push( @notify, $numberofnotify );
726     }
727     return (@notify);
728 }
729
730 =head2 AmountNotify
731
732     ($totalnotify) = &AmountNotify($notifyid);
733
734 Returns amount for all file per borrowers
735 C<$notifyid> is the file number
736
737 C<$totalnotify> contains amount of a file
738
739 C<$notify_id> contains the file number for the borrower number and item number
740
741 =cut
742
743 sub AmountNotify{
744     my ($notifyid,$borrowernumber)=@_;
745     my $dbh = C4::Context->dbh;
746     my $query=qq|    SELECT sum(amountoutstanding)
747             FROM accountlines
748             WHERE notify_id=? AND borrowernumber = ?|;
749     my $sth=$dbh->prepare($query);
750         $sth->execute($notifyid,$borrowernumber);
751         my $totalnotify=$sth->fetchrow;
752     $sth->finish;
753     return ($totalnotify);
754 }
755
756 =head2 GetItems
757
758     ($items) = &GetItems($itemnumber);
759
760 Returns the list of all delays from overduerules.
761
762 C<$items> is a reference-to-hash whose keys are all of the fields
763 from the items tables of the Koha database. Thus,
764
765 C<$itemnumber> contains the borrower categorycode
766
767 =cut
768
769 # FIXME: This is a bad function to have here.
770 # Shouldn't it be in C4::Items?
771 # Shouldn't it be called GetItem since you only get 1 row?
772 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
773
774 sub GetItems {
775     my $itemnumber = shift or return;
776     my $query = qq|SELECT *
777              FROM items
778               WHERE itemnumber=?|;
779     my $sth = C4::Context->dbh->prepare($query);
780     $sth->execute($itemnumber);
781     my ($items) = $sth->fetchrow_hashref;
782     return ($items);
783 }
784
785 =head2 GetBranchcodesWithOverdueRules
786
787     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
788
789 returns a list of branch codes for branches with overdue rules defined.
790
791 =cut
792
793 sub GetBranchcodesWithOverdueRules {
794     my $dbh               = C4::Context->dbh;
795     my $branchcodes = $dbh->selectcol_arrayref(q|
796         SELECT DISTINCT(branchcode)
797         FROM overduerules
798         WHERE delay1 IS NOT NULL
799         ORDER BY branchcode
800     |);
801     if ( $branchcodes->[0] eq '' ) {
802         # If a default rule exists, all branches should be returned
803         return map { $_->branchcode } Koha::Libraries->search({}, { order_by => 'branchname' });
804     }
805     return @$branchcodes;
806 }
807
808 =head2 GetOverduesForBranch
809
810 Sql request for display all information for branchoverdues.pl
811 2 possibilities : with or without location .
812 display is filtered by branch
813
814 FIXME: This function should be renamed.
815
816 =cut
817
818 sub GetOverduesForBranch {
819     my ( $branch, $location) = @_;
820         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
821     my $dbh = C4::Context->dbh;
822     my $select = "
823     SELECT
824             borrowers.cardnumber,
825             borrowers.borrowernumber,
826             borrowers.surname,
827             borrowers.firstname,
828             borrowers.phone,
829             borrowers.email,
830                biblio.title,
831                biblio.author,
832                biblio.biblionumber,
833                issues.date_due,
834                issues.returndate,
835                issues.branchcode,
836              branches.branchname,
837                 items.barcode,
838                 items.homebranch,
839                 items.itemcallnumber,
840                 items.location,
841                 items.itemnumber,
842             itemtypes.description,
843          accountlines.notify_id,
844          accountlines.notify_level,
845          accountlines.amountoutstanding
846     FROM  accountlines
847     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
848                           AND   issues.borrowernumber = accountlines.borrowernumber
849     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
850     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
851     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
852     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
853     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
854     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
855     WHERE (accountlines.amountoutstanding  != '0.000000')
856       AND (accountlines.accounttype         = 'FU'      )
857       AND (issues.branchcode =  ?   )
858       AND (issues.date_due  < NOW())
859     ";
860     if ($location) {
861         my $q = "$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname";
862         return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch, $location ) };
863     } else {
864         my $q = "$select ORDER BY borrowers.surname, borrowers.firstname";
865         return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch ) };
866     }
867 }
868
869 =head2 GetOverdueMessageTransportTypes
870
871     my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
872
873     return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
874
875 =cut
876
877 sub GetOverdueMessageTransportTypes {
878     my ( $branchcode, $categorycode, $letternumber ) = @_;
879     return unless $categorycode and $letternumber;
880     my $dbh = C4::Context->dbh;
881     my $sth = $dbh->prepare("
882         SELECT message_transport_type
883         FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
884         WHERE branchcode = ?
885           AND categorycode = ?
886           AND letternumber = ?
887     ");
888     $sth->execute( $branchcode, $categorycode, $letternumber );
889     my @mtts;
890     while ( my $mtt = $sth->fetchrow ) {
891         push @mtts, $mtt;
892     }
893
894     # Put 'print' in first if exists
895     # It avoid to sent a print notice with an email or sms template is no email or sms is defined
896     @mtts = uniq( 'print', @mtts )
897         if grep {/^print$/} @mtts;
898
899     return \@mtts;
900 }
901
902 =head2 parse_overdues_letter
903
904 parses the letter template, replacing the placeholders with data
905 specific to this patron, biblio, or item for overdues
906
907 named parameters:
908   letter - required hashref
909   borrowernumber - required integer
910   substitute - optional hashref of other key/value pairs that should
911     be substituted in the letter content
912
913 returns the C<letter> hashref, with the content updated to reflect the
914 substituted keys and values.
915
916 =cut
917
918 sub parse_overdues_letter {
919     my $params = shift;
920     foreach my $required (qw( letter_code borrowernumber )) {
921         return unless ( exists $params->{$required} && $params->{$required} );
922     }
923
924     my $patron = Koha::Patrons->find( $params->{borrowernumber} );
925
926     my $substitute = $params->{'substitute'} || {};
927
928     my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
929     if ( my $p = $params->{'branchcode'} ) {
930         $tables{'branches'} = $p;
931     }
932
933     my $active_currency = Koha::Acquisition::Currencies->get_active;
934
935     my $currency_format;
936     $currency_format = $active_currency->currency if defined($active_currency);
937
938     my @item_tables;
939     if ( my $i = $params->{'items'} ) {
940         foreach my $item (@$i) {
941             my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
942             $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
943             # if active currency isn't correct ISO code fallback to sprintf
944             $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
945
946             push @item_tables, {
947                 'biblio' => $item->{'biblionumber'},
948                 'biblioitems' => $item->{'biblionumber'},
949                 'items' => $item,
950                 'issues' => $item->{'itemnumber'},
951             };
952         }
953     }
954
955     return C4::Letters::GetPreparedLetter (
956         module => 'circulation',
957         letter_code => $params->{'letter_code'},
958         branchcode => $params->{'branchcode'},
959         lang => $patron->lang,
960         tables => \%tables,
961         loops => {
962             overdues => [ map { $_->{items}->{itemnumber} } @item_tables ],
963         },
964         substitute => $substitute,
965         repeat => { item => \@item_tables },
966         message_transport_type => $params->{message_transport_type},
967     );
968 }
969
970 1;
971 __END__
972
973 =head1 AUTHOR
974
975 Koha Development Team <http://koha-community.org/>
976
977 =cut