Bug 30778: Remove ModAuthInBatch
[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
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 Modern::Perl;
23 use Date::Calc qw( Today );
24 use Date::Manip qw( UnixDate );
25 use List::MoreUtils qw( uniq );
26 use POSIX qw( ceil floor );
27 use Locale::Currency::Format 1.28 qw( currency_format FMT_SYMBOL );
28 use Carp qw( carp );
29
30 use C4::Context;
31 use C4::Accounts;
32 use Koha::Logger;
33 use Koha::Account::Lines;
34 use Koha::Account::Offsets;
35 use Koha::Libraries;
36 use Koha::Recalls;
37
38 our (@ISA, @EXPORT_OK);
39 BEGIN {
40     require Exporter;
41     @ISA = qw(Exporter);
42
43     # subs to rename (and maybe merge some...)
44     @EXPORT_OK = qw(
45       CalcFine
46       Getoverdues
47       checkoverdues
48       UpdateFine
49       GetFine
50       GetBranchcodesWithOverdueRules
51       get_chargeable_units
52       GetOverduesForBranch
53       GetOverdueMessageTransportTypes
54       parse_overdues_letter
55       GetIssuesIteminfo
56     );
57 }
58
59 =head1 NAME
60
61 C4::Circulation::Fines - Koha module dealing with fines
62
63 =head1 SYNOPSIS
64
65   use C4::Overdues;
66
67 =head1 DESCRIPTION
68
69 This module contains several functions for dealing with fines for
70 overdue items. It is primarily used by the 'misc/fines2.pl' script.
71
72 =head1 FUNCTIONS
73
74 =head2 Getoverdues
75
76   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
77
78 Returns the list of all overdue books, with their itemtype.
79
80 C<$overdues> is a reference-to-array. Each element is a
81 reference-to-hash whose keys are the fields of the issues table in the
82 Koha database.
83
84 =cut
85
86 #'
87 sub Getoverdues {
88     my $params = shift;
89     my $dbh = C4::Context->dbh;
90     my $statement;
91     if ( C4::Context->preference('item-level_itypes') ) {
92         $statement = "
93    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost, items.replacementprice, items.biblionumber
94      FROM issues 
95 LEFT JOIN items       USING (itemnumber)
96     WHERE date_due < NOW()
97 ";
98     } else {
99         $statement = "
100    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost, replacementprice, items.biblionumber
101      FROM issues 
102 LEFT JOIN items       USING (itemnumber)
103 LEFT JOIN biblioitems USING (biblioitemnumber)
104     WHERE date_due < NOW()
105 ";
106     }
107
108     my @bind_parameters;
109     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
110         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
111         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
112     } elsif ( exists $params->{'minimumdays'} ) {
113         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
114         push @bind_parameters, $params->{'minimumdays'};
115     } elsif ( exists $params->{'maximumdays'} ) {
116         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
117         push @bind_parameters, $params->{'maximumdays'};
118     }
119     $statement .= 'ORDER BY borrowernumber';
120     my $sth = $dbh->prepare( $statement );
121     $sth->execute( @bind_parameters );
122     return $sth->fetchall_arrayref({});
123 }
124
125
126 =head2 checkoverdues
127
128     ($count, $overdueitems) = checkoverdues($borrowernumber);
129
130 Returns a count and a list of overdueitems for a given borrowernumber
131
132 =cut
133
134 sub checkoverdues {
135     my $borrowernumber = shift or return;
136     my $sth = C4::Context->dbh->prepare(
137         "SELECT biblio.*, items.*, issues.*,
138                 biblioitems.volume,
139                 biblioitems.number,
140                 biblioitems.itemtype,
141                 biblioitems.isbn,
142                 biblioitems.issn,
143                 biblioitems.publicationyear,
144                 biblioitems.publishercode,
145                 biblioitems.volumedate,
146                 biblioitems.volumedesc,
147                 biblioitems.collectiontitle,
148                 biblioitems.collectionissn,
149                 biblioitems.collectionvolume,
150                 biblioitems.editionstatement,
151                 biblioitems.editionresponsibility,
152                 biblioitems.illus,
153                 biblioitems.pages,
154                 biblioitems.notes,
155                 biblioitems.size,
156                 biblioitems.place,
157                 biblioitems.lccn,
158                 biblioitems.url,
159                 biblioitems.cn_source,
160                 biblioitems.cn_class,
161                 biblioitems.cn_item,
162                 biblioitems.cn_suffix,
163                 biblioitems.cn_sort,
164                 biblioitems.totalissues
165          FROM issues
166          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
167          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
168          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
169             WHERE issues.borrowernumber  = ?
170             AND   issues.date_due < NOW()"
171     );
172     $sth->execute($borrowernumber);
173     my $results = $sth->fetchall_arrayref({});
174     return ( scalar(@$results), $results);  # returning the count and the results is silly
175 }
176
177 =head2 CalcFine
178
179     ($amount, $units_minus_grace, $chargeable_units) = &CalcFine($item,
180                                   $categorycode, $branch,
181                                   $start_dt, $end_dt );
182
183 Calculates the fine for a book.
184
185 The issuingrules table in the Koha database is a fine matrix, listing
186 the penalties for each type of patron for each type of item and each branch (e.g., the
187 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
188 members might get a longer grace period between the first and second
189 reminders that a book is overdue).
190
191
192 C<$item> is an item object (hashref).
193
194 C<$categorycode> is the category code (string) of the patron who currently has
195 the book.
196
197 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
198
199 C<$start_date> & C<$end_date> are DateTime objects
200 defining the date range over which to determine the fine.
201
202 Fines scripts should just supply the date range over which to calculate the fine.
203
204 C<&CalcFine> returns three values:
205
206 C<$amount> is the fine owed by the patron (see above).
207
208 C<$units_minus_grace> is the number of chargeable units minus the grace period
209
210 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
211 minus any applicable grace period, or hours)
212
213 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
214 or "Final Notice".  But CalcFine never defined any value.
215
216 =cut
217
218 sub CalcFine {
219     my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
220
221     # Skip calculations if item is not overdue
222     return ( 0, 0, 0 ) unless (DateTime->compare( $due_dt, $end_date ) == -1);
223
224     my $start_date = $due_dt->clone();
225     # get issuingrules (fines part will be used)
226     my $itemtype = $item->{itemtype} || $item->{itype};
227     my $issuing_rule = Koha::CirculationRules->get_effective_rules(
228         {
229             categorycode => $bortype,
230             itemtype     => $itemtype,
231             branchcode   => $branchcode,
232             rules => [
233                 'lengthunit',
234                 'firstremind',
235                 'chargeperiod',
236                 'chargeperiod_charge_at',
237                 'fine',
238                 'overduefinescap',
239                 'cap_fine_to_replacement_price',
240                 'recall_overdue_fine',
241             ]
242         }
243     );
244
245     $itemtype = Koha::ItemTypes->find($itemtype);
246
247     return unless $issuing_rule; # If not rule exist, there is no fine
248
249     my $fine_unit = $issuing_rule->{lengthunit} || 'days';
250
251     my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
252     my $units_minus_grace = $chargeable_units - ($issuing_rule->{firstremind} || 0);
253     my $amount = 0;
254     if ( $issuing_rule->{chargeperiod} && ( $units_minus_grace > 0 ) ) {
255         my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
256         my $charge_periods = $units / $issuing_rule->{chargeperiod};
257         # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
258         # if chargeperiod_charge_at = 0, we charge at the end of each charge period
259         $charge_periods = defined $issuing_rule->{chargeperiod_charge_at} && $issuing_rule->{chargeperiod_charge_at} == 1 ? ceil($charge_periods) : floor($charge_periods);
260
261         # check if item has been recalled. recall should have been marked Overdue by cronjob, so only look at overdue recalls
262         # only charge using recall_overdue_fine if there is an item-level recall for this particular item, OR a biblio-level recall
263         my @recalls = Koha::Recalls->search({ biblio_id => $item->{biblionumber}, status => 'overdue' })->as_list;
264         my $bib_level_recall = 0;
265         $bib_level_recall = 1 if scalar @recalls > 0;
266         foreach my $recall ( @recalls ) {
267             if ( $recall->item_level and $recall->item_id == $item->{itemnumber} and $issuing_rule->{recall_overdue_fine} ) {
268                 $bib_level_recall = 0;
269                 $amount = $charge_periods * $issuing_rule->{recall_overdue_fine};
270                 last;
271             }
272         }
273         if ( $bib_level_recall and $issuing_rule->{recall_overdue_fine} ) {
274             # biblio-level recall
275             $amount = $charge_periods * $issuing_rule->{recall_overdue_fine};
276         }
277         if ( scalar @recalls == 0 && $issuing_rule->{fine}) {
278             # no recall, use normal fine amount
279             $amount = $charge_periods * $issuing_rule->{fine};
280         }
281     } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
282
283     $amount = $issuing_rule->{overduefinescap} if $issuing_rule->{overduefinescap} && $amount > $issuing_rule->{overduefinescap};
284
285     # This must be moved to Koha::Item (see also similar code in C4::Accounts::chargelostitem
286     $item->{replacementprice} ||= $itemtype->defaultreplacecost
287       if $itemtype
288       && ( ! defined $item->{replacementprice} || $item->{replacementprice} == 0 )
289       && C4::Context->preference("useDefaultReplacementCost");
290
291     $amount = $item->{replacementprice} if ( $issuing_rule->{cap_fine_to_replacement_price} && $item->{replacementprice} && $amount > $item->{replacementprice} );
292
293     return ($amount, $units_minus_grace, $chargeable_units);
294 }
295
296
297 =head2 get_chargeable_units
298
299     get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
300
301 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
302
303 C<$unit> is 'days' or 'hours' (default is 'days').
304
305 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
306
307 C<$branchcode> is the branch whose calendar to use for finding holidays.
308
309 =cut
310
311 sub get_chargeable_units {
312     my ($unit, $date_due, $date_returned, $branchcode) = @_;
313
314     # If the due date is later than the return date
315     return 0 unless ( $date_returned > $date_due );
316
317     my $charge_units = 0;
318     my $charge_duration;
319     if ($unit eq 'hours') {
320         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
321             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
322             $charge_duration = $calendar->hours_between( $date_due, $date_returned );
323         } else {
324             $charge_duration = $date_returned->delta_ms( $date_due );
325         }
326         if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
327             return 1;
328         }
329         return $charge_duration->in_units('hours');
330     }
331     else { # days
332         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
333             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
334             $charge_duration = $calendar->days_between( $date_due, $date_returned );
335         } else {
336             $charge_duration = $date_returned->delta_days( $date_due );
337         }
338         return $charge_duration->in_units('days');
339     }
340 }
341
342
343 =head2 GetSpecialHolidays
344
345     &GetSpecialHolidays($date_dues,$itemnumber);
346
347 return number of special days  between date of the day and date due
348
349 C<$date_dues> is the envisaged date of book return.
350
351 C<$itemnumber> is the book's item number.
352
353 =cut
354
355 sub GetSpecialHolidays {
356     my ( $date_dues, $itemnumber ) = @_;
357
358     # calcul the today date
359     my $today = join "-", &Today();
360
361     # return the holdingbranch
362     my $iteminfo = GetIssuesIteminfo($itemnumber);
363
364     # use sql request to find all date between date_due and today
365     my $dbh = C4::Context->dbh;
366     my $query =
367       qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
368 FROM `special_holidays`
369 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
370 AND   DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
371 AND branchcode=?
372 |;
373     my @result = GetWdayFromItemnumber($itemnumber);
374     my @result_date;
375     my $wday;
376     my $dateinsec;
377     my $sth = $dbh->prepare($query);
378     $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
379       ;    # FIXME: just use NOW() in SQL instead of passing in $today
380
381     while ( my $special_date = $sth->fetchrow_hashref ) {
382         push( @result_date, $special_date );
383     }
384
385     my $specialdaycount = scalar(@result_date);
386
387     for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
388         $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
389         ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
390           localtime($dateinsec);
391         for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
392             if ( $wday == ( $result[$j]->{'weekday'} ) ) {
393                 $specialdaycount--;
394             }
395         }
396     }
397
398     return $specialdaycount;
399 }
400
401 =head2 GetRepeatableHolidays
402
403     &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
404
405 return number of day closed between date of the day and date due
406
407 C<$date_dues> is the envisaged date of book return.
408
409 C<$itemnumber> is item number.
410
411 C<$difference> numbers of between day date of the day and date due
412
413 =cut
414
415 sub GetRepeatableHolidays {
416     my ( $date_dues, $itemnumber, $difference ) = @_;
417     my $dateinsec = UnixDate( $date_dues, "%o" );
418     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
419       localtime($dateinsec);
420     my @result = GetWdayFromItemnumber($itemnumber);
421     my @dayclosedcount;
422     my $j;
423
424     for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
425         my $k = $wday;
426
427         for ( $j = 0 ; $j < $difference ; $j++ ) {
428             if ( $result[$i]->{'weekday'} == $k ) {
429                 push( @dayclosedcount, $k );
430             }
431             $k++;
432             ( $k = 0 ) if ( $k eq 7 );
433         }
434     }
435     return scalar(@dayclosedcount);
436 }
437
438
439 =head2 GetWayFromItemnumber
440
441     &Getwdayfromitemnumber($itemnumber);
442
443 return the different week day from repeatable_holidays table
444
445 C<$itemnumber> is  item number.
446
447 =cut
448
449 sub GetWdayFromItemnumber {
450     my ($itemnumber) = @_;
451     my $iteminfo = GetIssuesIteminfo($itemnumber);
452     my @result;
453     my $query = qq|SELECT weekday
454     FROM repeatable_holidays
455     WHERE branchcode=?
456 |;
457     my $sth = C4::Context->dbh->prepare($query);
458
459     $sth->execute( $iteminfo->{'branchcode'} );
460     while ( my $weekday = $sth->fetchrow_hashref ) {
461         push( @result, $weekday );
462     }
463     return @result;
464 }
465
466
467 =head2 GetIssuesIteminfo
468
469     &GetIssuesIteminfo($itemnumber);
470
471 return all data from issues about item
472
473 C<$itemnumber> is  item number.
474
475 =cut
476
477 sub GetIssuesIteminfo {
478     my ($itemnumber) = @_;
479     my $dbh          = C4::Context->dbh;
480     my $query        = qq|SELECT *
481     FROM issues
482     WHERE itemnumber=?
483     |;
484     my $sth = $dbh->prepare($query);
485     $sth->execute($itemnumber);
486     my ($issuesinfo) = $sth->fetchrow_hashref;
487     return $issuesinfo;
488 }
489
490
491 =head2 UpdateFine
492
493     &UpdateFine(
494         {
495             issue_id       => $issue_id,
496             itemnumber     => $itemnumber,
497             borrowernumber => $borrowernumber,
498             amount         => $amount,
499             due            => $date_due
500         }
501     );
502
503 (Note: the following is mostly conjecture and guesswork.)
504
505 Updates the fine owed on an overdue book.
506
507 C<$itemnumber> is the book's item number.
508
509 C<$borrowernumber> is the borrower number of the patron who currently
510 has the book on loan.
511
512 C<$amount> is the current amount owed by the patron.
513
514 C<$due> is the due date formatted to the currently specified date format
515
516 C<&UpdateFine> looks up the amount currently owed on the given item
517 and sets it to C<$amount>, creating, if necessary, a new entry in the
518 accountlines table of the Koha database.
519
520 =cut
521
522 #
523 # Question: Why should the caller have to
524 # specify both the item number and the borrower number? A book can't
525 # be on loan to two different people, so the item number should be
526 # sufficient.
527 #
528 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
529 #
530 sub UpdateFine {
531     my ($params) = @_;
532
533     my $issue_id       = $params->{issue_id};
534     my $itemnum        = $params->{itemnumber};
535     my $borrowernumber = $params->{borrowernumber};
536     my $amount         = $params->{amount};
537     my $due            = $params->{due} // q{};
538
539     unless ( $issue_id ) {
540         carp("No issue_id passed in!");
541         return;
542     }
543
544     my $dbh = C4::Context->dbh;
545     my $overdues = Koha::Account::Lines->search(
546         {
547             borrowernumber    => $borrowernumber,
548             debit_type_code   => 'OVERDUE'
549         }
550     );
551
552     my $accountline;
553     my $total_amount_other = 0.00;
554     my $due_qr = qr/$due/;
555     # Cycle through the fines and
556     # - find line that relates to the requested $itemnum
557     # - accumulate fines for other items
558     # so we can update $itemnum fine taking in account fine caps
559     while (my $overdue = $overdues->next) {
560         if ( defined $overdue->issue_id && $overdue->issue_id == $issue_id && $overdue->status eq 'UNRETURNED' ) {
561             if ($accountline) {
562                 Koha::Logger->get->debug("Not a unique accountlines record for issue_id $issue_id"); # FIXME Do we really need to log that?
563                 #FIXME Should we still count this one in total_amount ??
564             }
565             else {
566                 $accountline = $overdue;
567             }
568         }
569         $total_amount_other += $overdue->amountoutstanding;
570     }
571
572     if ( my $maxfine = C4::Context->preference('MaxFine') ) {
573         my $maxIncrease = $maxfine - $total_amount_other;
574         return if Koha::Number::Price->new($maxIncrease)->round <= 0.00;
575         if ($accountline) {
576             if ( ( $amount - $accountline->amount ) > $maxIncrease ) {
577                 my $new_amount = $accountline->amount + $maxIncrease;
578                 Koha::Logger->get->debug("Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached");
579                 $amount = $new_amount;
580             }
581         }
582         elsif ( $amount > $maxIncrease ) {
583             Koha::Logger->get->debug("Reducing fine for item $itemnum borrower $borrowernumber from $amount to $maxIncrease - MaxFine reached");
584             $amount = $maxIncrease;
585         }
586     }
587
588     if ( $accountline ) {
589         if ( Koha::Number::Price->new($accountline->amount)->round != Koha::Number::Price->new($amount)->round ) {
590             $accountline->adjust(
591                 {
592                     amount    => $amount,
593                     type      => 'overdue_update',
594                     interface => C4::Context->interface
595                 }
596             );
597         }
598     } else {
599         if ( $amount ) { # Don't add new fines with an amount of 0
600             my $sth4 = $dbh->prepare(
601                 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
602             );
603             $sth4->execute($itemnum);
604             my $title = $sth4->fetchrow;
605             my $desc = "$title $due";
606
607             my $account = Koha::Account->new({ patron_id => $borrowernumber });
608             $accountline = $account->add_debit(
609                 {
610                     amount      => $amount,
611                     description => $desc,
612                     note        => undef,
613                     user_id     => undef,
614                     interface   => C4::Context->interface,
615                     library_id  => undef, #FIXME: Should we grab the checkout or circ-control branch here perhaps?
616                     type        => 'OVERDUE',
617                     item_id     => $itemnum,
618                     issue_id    => $issue_id,
619                 }
620             );
621         }
622     }
623 }
624
625 =head2 GetFine
626
627     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
628
629 return the total of fine
630
631 C<$itemnum> is item number
632
633 C<$borrowernumber> is the borrowernumber
634
635 =cut 
636
637 sub GetFine {
638     my ( $itemnum, $borrowernumber ) = @_;
639     my $dbh   = C4::Context->dbh();
640     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
641     WHERE debit_type_code = 'OVERDUE'
642   AND amountoutstanding > 0 AND borrowernumber=?|;
643     my @query_param;
644     push @query_param, $borrowernumber;
645     if (defined $itemnum )
646     {
647         $query .= " AND itemnumber=?";
648         push @query_param, $itemnum;
649     }
650     my $sth = $dbh->prepare($query);
651     $sth->execute( @query_param );
652     my $fine = $sth->fetchrow_hashref();
653     if ($fine->{fineamount}) {
654         return $fine->{fineamount};
655     }
656     return 0;
657 }
658
659 =head2 GetBranchcodesWithOverdueRules
660
661     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
662
663 returns a list of branch codes for branches with overdue rules defined.
664
665 =cut
666
667 sub GetBranchcodesWithOverdueRules {
668     my $dbh               = C4::Context->dbh;
669     my $branchcodes = $dbh->selectcol_arrayref(q|
670         SELECT DISTINCT(branchcode)
671         FROM overduerules
672         WHERE delay1 IS NOT NULL
673         ORDER BY branchcode
674     |);
675     if ( $branchcodes->[0] eq '' ) {
676         # If a default rule exists, all branches should be returned
677         return Koha::Libraries->search({}, { order_by => 'branchname' })->get_column('branchcode');
678     }
679     return @$branchcodes;
680 }
681
682 =head2 GetOverduesForBranch
683
684 Sql request for display all information for branchoverdues.pl
685 2 possibilities : with or without location .
686 display is filtered by branch
687
688 FIXME: This function should be renamed.
689
690 =cut
691
692 sub GetOverduesForBranch {
693     my ( $branch, $location) = @_;
694         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
695     my $dbh = C4::Context->dbh;
696     my $select = "
697     SELECT
698             borrowers.cardnumber,
699             borrowers.borrowernumber,
700             borrowers.surname,
701             borrowers.firstname,
702             borrowers.phone,
703             borrowers.email,
704                biblio.title,
705                biblio.subtitle,
706                biblio.medium,
707                biblio.part_number,
708                biblio.part_name,
709                biblio.author,
710                biblio.biblionumber,
711                issues.date_due,
712                issues.returndate,
713                issues.branchcode,
714              branches.branchname,
715                 items.barcode,
716                 items.homebranch,
717                 items.itemcallnumber,
718                 items.location,
719                 items.itemnumber,
720             itemtypes.description,
721          accountlines.amountoutstanding
722     FROM  accountlines
723     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
724                           AND   issues.borrowernumber = accountlines.borrowernumber
725     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
726     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
727     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
728     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
729     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
730     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
731     WHERE (accountlines.amountoutstanding  != '0.000000')
732       AND (accountlines.debit_type_code     = 'OVERDUE' )
733       AND (accountlines.status              = 'UNRETURNED' )
734       AND (issues.branchcode =  ?   )
735       AND (issues.date_due  < NOW())
736     ";
737     if ($location) {
738         my $q = "$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname";
739         return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch, $location ) };
740     } else {
741         my $q = "$select ORDER BY borrowers.surname, borrowers.firstname";
742         return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch ) };
743     }
744 }
745
746 =head2 GetOverdueMessageTransportTypes
747
748     my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
749
750     return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
751
752 =cut
753
754 sub GetOverdueMessageTransportTypes {
755     my ( $branchcode, $categorycode, $letternumber ) = @_;
756     return unless $categorycode and $letternumber;
757     my $dbh = C4::Context->dbh;
758     my $sth = $dbh->prepare("
759         SELECT message_transport_type
760         FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
761         WHERE branchcode = ?
762           AND categorycode = ?
763           AND letternumber = ?
764     ");
765     $sth->execute( $branchcode, $categorycode, $letternumber );
766     my @mtts;
767     while ( my $mtt = $sth->fetchrow ) {
768         push @mtts, $mtt;
769     }
770
771     # Put 'print' in first if exists
772     # It avoid to sent a print notice with an email or sms template is no email or sms is defined
773     @mtts = uniq( 'print', @mtts )
774         if grep { $_ eq 'print' } @mtts;
775
776     return \@mtts;
777 }
778
779 =head2 parse_overdues_letter
780
781 parses the letter template, replacing the placeholders with data
782 specific to this patron, biblio, or item for overdues
783
784 named parameters:
785   letter - required hashref
786   borrowernumber - required integer
787   substitute - optional hashref of other key/value pairs that should
788     be substituted in the letter content
789
790 returns the C<letter> hashref, with the content updated to reflect the
791 substituted keys and values.
792
793 =cut
794
795 sub parse_overdues_letter {
796     my $params = shift;
797     foreach my $required (qw( letter_code borrowernumber )) {
798         return unless ( exists $params->{$required} && $params->{$required} );
799     }
800
801     my $patron = Koha::Patrons->find( $params->{borrowernumber} );
802
803     my $substitute = $params->{'substitute'} || {};
804
805     my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
806     if ( my $p = $params->{'branchcode'} ) {
807         $tables{'branches'} = $p;
808     }
809
810     my $active_currency = Koha::Acquisition::Currencies->get_active;
811
812     my $currency_format;
813     $currency_format = $active_currency->currency if defined($active_currency);
814
815     my @item_tables;
816     if ( my $i = $params->{'items'} ) {
817         foreach my $item (@$i) {
818             my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
819             $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
820             # if active currency isn't correct ISO code fallback to sprintf
821             $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
822
823             push @item_tables, {
824                 'biblio' => $item->{'biblionumber'},
825                 'biblioitems' => $item->{'biblionumber'},
826                 'items' => $item,
827                 'issues' => $item->{'itemnumber'},
828             };
829         }
830     }
831
832     return C4::Letters::GetPreparedLetter (
833         module => 'circulation',
834         letter_code => $params->{'letter_code'},
835         branchcode => $params->{'branchcode'},
836         lang => $patron->lang,
837         tables => \%tables,
838         loops => {
839             overdues => [ map { $_->{items}->{itemnumber} } @item_tables ],
840         },
841         substitute => $substitute,
842         repeat => { item => \@item_tables },
843         message_transport_type => $params->{message_transport_type},
844     );
845 }
846
847 1;
848 __END__
849
850 =head1 AUTHOR
851
852 Koha Development Team <http://koha-community.org/>
853
854 =cut