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