Bug 24146: Corrections to UpdateFine logic concerning maxFine
[koha.git] / C4 / Overdues.pm
blobcd4f838cfd6af54b85ca4f535d99685f93c38495
1 package C4::Overdues;
4 # Copyright 2000-2002 Katipo Communications
5 # copyright 2010 BibLibre
7 # This file is part of Koha.
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.
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.
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>.
22 use Modern::Perl;
23 use Date::Calc qw/Today Date_to_Days/;
24 use Date::Manip qw/UnixDate/;
25 use List::MoreUtils qw( uniq );
26 use POSIX qw( floor ceil );
27 use Locale::Currency::Format 1.28;
28 use Carp;
30 use C4::Circulation;
31 use C4::Context;
32 use C4::Accounts;
33 use C4::Log; # logaction
34 use C4::Debug;
35 use Koha::DateUtils;
36 use Koha::Account::Lines;
37 use Koha::Account::Offsets;
38 use Koha::IssuingRules;
39 use Koha::Libraries;
41 use vars qw(@ISA @EXPORT);
43 BEGIN {
44 require Exporter;
45 @ISA = qw(Exporter);
47 # subs to rename (and maybe merge some...)
48 push @EXPORT, qw(
49 &CalcFine
50 &Getoverdues
51 &checkoverdues
52 &UpdateFine
53 &GetFine
54 &get_chargeable_units
55 &GetOverduesForBranch
56 &GetOverdueMessageTransportTypes
57 &parse_overdues_letter
60 # subs to remove
61 push @EXPORT, qw(
62 &BorType
65 # check that an equivalent don't exist already before moving
67 # subs to move to Circulation.pm
68 push @EXPORT, qw(
69 &GetIssuesIteminfo
73 =head1 NAME
75 C4::Circulation::Fines - Koha module dealing with fines
77 =head1 SYNOPSIS
79 use C4::Overdues;
81 =head1 DESCRIPTION
83 This module contains several functions for dealing with fines for
84 overdue items. It is primarily used by the 'misc/fines2.pl' script.
86 =head1 FUNCTIONS
88 =head2 Getoverdues
90 $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
92 Returns the list of all overdue books, with their itemtype.
94 C<$overdues> is a reference-to-array. Each element is a
95 reference-to-hash whose keys are the fields of the issues table in the
96 Koha database.
98 =cut
101 sub Getoverdues {
102 my $params = shift;
103 my $dbh = C4::Context->dbh;
104 my $statement;
105 if ( C4::Context->preference('item-level_itypes') ) {
106 $statement = "
107 SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost, items.replacementprice
108 FROM issues
109 LEFT JOIN items USING (itemnumber)
110 WHERE date_due < NOW()
112 } else {
113 $statement = "
114 SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost, replacementprice
115 FROM issues
116 LEFT JOIN items USING (itemnumber)
117 LEFT JOIN biblioitems USING (biblioitemnumber)
118 WHERE date_due < NOW()
122 my @bind_parameters;
123 if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
124 $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
125 push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
126 } elsif ( exists $params->{'minimumdays'} ) {
127 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
128 push @bind_parameters, $params->{'minimumdays'};
129 } elsif ( exists $params->{'maximumdays'} ) {
130 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
131 push @bind_parameters, $params->{'maximumdays'};
133 $statement .= 'ORDER BY borrowernumber';
134 my $sth = $dbh->prepare( $statement );
135 $sth->execute( @bind_parameters );
136 return $sth->fetchall_arrayref({});
140 =head2 checkoverdues
142 ($count, $overdueitems) = checkoverdues($borrowernumber);
144 Returns a count and a list of overdueitems for a given borrowernumber
146 =cut
148 sub checkoverdues {
149 my $borrowernumber = shift or return;
150 my $sth = C4::Context->dbh->prepare(
151 "SELECT biblio.*, items.*, issues.*,
152 biblioitems.volume,
153 biblioitems.number,
154 biblioitems.itemtype,
155 biblioitems.isbn,
156 biblioitems.issn,
157 biblioitems.publicationyear,
158 biblioitems.publishercode,
159 biblioitems.volumedate,
160 biblioitems.volumedesc,
161 biblioitems.collectiontitle,
162 biblioitems.collectionissn,
163 biblioitems.collectionvolume,
164 biblioitems.editionstatement,
165 biblioitems.editionresponsibility,
166 biblioitems.illus,
167 biblioitems.pages,
168 biblioitems.notes,
169 biblioitems.size,
170 biblioitems.place,
171 biblioitems.lccn,
172 biblioitems.url,
173 biblioitems.cn_source,
174 biblioitems.cn_class,
175 biblioitems.cn_item,
176 biblioitems.cn_suffix,
177 biblioitems.cn_sort,
178 biblioitems.totalissues
179 FROM issues
180 LEFT JOIN items ON issues.itemnumber = items.itemnumber
181 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
182 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
183 WHERE issues.borrowernumber = ?
184 AND issues.date_due < NOW()"
186 $sth->execute($borrowernumber);
187 my $results = $sth->fetchall_arrayref({});
188 return ( scalar(@$results), $results); # returning the count and the results is silly
191 =head2 CalcFine
193 ($amount, $units_minus_grace, $chargeable_units) = &CalcFine($item,
194 $categorycode, $branch,
195 $start_dt, $end_dt );
197 Calculates the fine for a book.
199 The issuingrules table in the Koha database is a fine matrix, listing
200 the penalties for each type of patron for each type of item and each branch (e.g., the
201 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
202 members might get a longer grace period between the first and second
203 reminders that a book is overdue).
206 C<$item> is an item object (hashref).
208 C<$categorycode> is the category code (string) of the patron who currently has
209 the book.
211 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
213 C<$start_date> & C<$end_date> are DateTime objects
214 defining the date range over which to determine the fine.
216 Fines scripts should just supply the date range over which to calculate the fine.
218 C<&CalcFine> returns three values:
220 C<$amount> is the fine owed by the patron (see above).
222 C<$units_minus_grace> is the number of chargeable units minus the grace period
224 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
225 minus any applicable grace period, or hours)
227 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
228 or "Final Notice". But CalcFine never defined any value.
230 =cut
232 sub CalcFine {
233 my ( $item, $bortype, $branchcode, $due_dt, $end_date ) = @_;
235 # Skip calculations if item is not overdue
236 return ( 0, 0, 0 ) unless (DateTime->compare( $due_dt, $end_date ) == -1);
238 my $start_date = $due_dt->clone();
239 # get issuingrules (fines part will be used)
240 my $itemtype = $item->{itemtype} || $item->{itype};
241 my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $bortype, itemtype => $itemtype, branchcode => $branchcode });
243 $itemtype = Koha::ItemTypes->find($itemtype);
245 return unless $issuing_rule; # If not rule exist, there is no fine
247 my $fine_unit = $issuing_rule->lengthunit || 'days';
249 my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
250 my $units_minus_grace = $chargeable_units - ($issuing_rule->firstremind || 0);
251 my $amount = 0;
252 if ( $issuing_rule->chargeperiod && ( $units_minus_grace > 0 ) ) {
253 my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
254 my $charge_periods = $units / $issuing_rule->chargeperiod;
255 # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
256 # if chargeperiod_charge_at = 0, we charge at the end of each charge period
257 $charge_periods = $issuing_rule->chargeperiod_charge_at == 1 ? ceil($charge_periods) : floor($charge_periods);
258 $amount = $charge_periods * $issuing_rule->fine;
259 } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
261 $amount = $issuing_rule->overduefinescap if $issuing_rule->overduefinescap && $amount > $issuing_rule->overduefinescap;
263 # This must be moved to Koha::Item (see also similar code in C4::Accounts::chargelostitem
264 $item->{replacementprice} ||= $itemtype->defaultreplacecost
265 if $itemtype
266 && ( ! defined $item->{replacementprice} || $item->{replacementprice} == 0 )
267 && C4::Context->preference("useDefaultReplacementCost");
269 $amount = $item->{replacementprice} if ( $issuing_rule->cap_fine_to_replacement_price && $item->{replacementprice} && $amount > $item->{replacementprice} );
271 $debug and warn sprintf("CalcFine returning (%s, %s, %s)", $amount, $units_minus_grace, $chargeable_units);
272 return ($amount, $units_minus_grace, $chargeable_units);
276 =head2 get_chargeable_units
278 get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
280 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
282 C<$unit> is 'days' or 'hours' (default is 'days').
284 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
286 C<$branchcode> is the branch whose calendar to use for finding holidays.
288 =cut
290 sub get_chargeable_units {
291 my ($unit, $date_due, $date_returned, $branchcode) = @_;
293 # If the due date is later than the return date
294 return 0 unless ( $date_returned > $date_due );
296 my $charge_units = 0;
297 my $charge_duration;
298 if ($unit eq 'hours') {
299 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
300 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
301 $charge_duration = $calendar->hours_between( $date_due, $date_returned );
302 } else {
303 $charge_duration = $date_returned->delta_ms( $date_due );
305 if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
306 return 1;
308 return $charge_duration->in_units('hours');
310 else { # days
311 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
312 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
313 $charge_duration = $calendar->days_between( $date_due, $date_returned );
314 } else {
315 $charge_duration = $date_returned->delta_days( $date_due );
317 return $charge_duration->in_units('days');
322 =head2 GetSpecialHolidays
324 &GetSpecialHolidays($date_dues,$itemnumber);
326 return number of special days between date of the day and date due
328 C<$date_dues> is the envisaged date of book return.
330 C<$itemnumber> is the book's item number.
332 =cut
334 sub GetSpecialHolidays {
335 my ( $date_dues, $itemnumber ) = @_;
337 # calcul the today date
338 my $today = join "-", &Today();
340 # return the holdingbranch
341 my $iteminfo = GetIssuesIteminfo($itemnumber);
343 # use sql request to find all date between date_due and today
344 my $dbh = C4::Context->dbh;
345 my $query =
346 qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
347 FROM `special_holidays`
348 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
349 AND DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
350 AND branchcode=?
352 my @result = GetWdayFromItemnumber($itemnumber);
353 my @result_date;
354 my $wday;
355 my $dateinsec;
356 my $sth = $dbh->prepare($query);
357 $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
358 ; # FIXME: just use NOW() in SQL instead of passing in $today
360 while ( my $special_date = $sth->fetchrow_hashref ) {
361 push( @result_date, $special_date );
364 my $specialdaycount = scalar(@result_date);
366 for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
367 $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
368 ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
369 localtime($dateinsec);
370 for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
371 if ( $wday == ( $result[$j]->{'weekday'} ) ) {
372 $specialdaycount--;
377 return $specialdaycount;
380 =head2 GetRepeatableHolidays
382 &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
384 return number of day closed between date of the day and date due
386 C<$date_dues> is the envisaged date of book return.
388 C<$itemnumber> is item number.
390 C<$difference> numbers of between day date of the day and date due
392 =cut
394 sub GetRepeatableHolidays {
395 my ( $date_dues, $itemnumber, $difference ) = @_;
396 my $dateinsec = UnixDate( $date_dues, "%o" );
397 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
398 localtime($dateinsec);
399 my @result = GetWdayFromItemnumber($itemnumber);
400 my @dayclosedcount;
401 my $j;
403 for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
404 my $k = $wday;
406 for ( $j = 0 ; $j < $difference ; $j++ ) {
407 if ( $result[$i]->{'weekday'} == $k ) {
408 push( @dayclosedcount, $k );
410 $k++;
411 ( $k = 0 ) if ( $k eq 7 );
414 return scalar(@dayclosedcount);
418 =head2 GetWayFromItemnumber
420 &Getwdayfromitemnumber($itemnumber);
422 return the different week day from repeatable_holidays table
424 C<$itemnumber> is item number.
426 =cut
428 sub GetWdayFromItemnumber {
429 my ($itemnumber) = @_;
430 my $iteminfo = GetIssuesIteminfo($itemnumber);
431 my @result;
432 my $query = qq|SELECT weekday
433 FROM repeatable_holidays
434 WHERE branchcode=?
436 my $sth = C4::Context->dbh->prepare($query);
438 $sth->execute( $iteminfo->{'branchcode'} );
439 while ( my $weekday = $sth->fetchrow_hashref ) {
440 push( @result, $weekday );
442 return @result;
446 =head2 GetIssuesIteminfo
448 &GetIssuesIteminfo($itemnumber);
450 return all data from issues about item
452 C<$itemnumber> is item number.
454 =cut
456 sub GetIssuesIteminfo {
457 my ($itemnumber) = @_;
458 my $dbh = C4::Context->dbh;
459 my $query = qq|SELECT *
460 FROM issues
461 WHERE itemnumber=?
463 my $sth = $dbh->prepare($query);
464 $sth->execute($itemnumber);
465 my ($issuesinfo) = $sth->fetchrow_hashref;
466 return $issuesinfo;
470 =head2 UpdateFine
472 &UpdateFine(
474 issue_id => $issue_id,
475 itemnumber => $itemnumber,
476 borrowernumber => $borrowernumber,
477 amount => $amount,
478 due => $date_due
482 (Note: the following is mostly conjecture and guesswork.)
484 Updates the fine owed on an overdue book.
486 C<$itemnumber> is the book's item number.
488 C<$borrowernumber> is the borrower number of the patron who currently
489 has the book on loan.
491 C<$amount> is the current amount owed by the patron.
493 C<$due> is the due date formatted to the currently specified date format
495 C<&UpdateFine> looks up the amount currently owed on the given item
496 and sets it to C<$amount>, creating, if necessary, a new entry in the
497 accountlines table of the Koha database.
499 =cut
502 # Question: Why should the caller have to
503 # specify both the item number and the borrower number? A book can't
504 # be on loan to two different people, so the item number should be
505 # sufficient.
507 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
509 sub UpdateFine {
510 my ($params) = @_;
512 my $issue_id = $params->{issue_id};
513 my $itemnum = $params->{itemnumber};
514 my $borrowernumber = $params->{borrowernumber};
515 my $amount = $params->{amount};
516 my $due = $params->{due};
518 $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, due => $due, issue_id => $issue_id})";
520 unless ( $issue_id ) {
521 carp("No issue_id passed in!");
522 return;
525 my $dbh = C4::Context->dbh;
526 my $overdues = Koha::Account::Lines->search(
528 borrowernumber => $borrowernumber,
529 debit_type_code => 'OVERDUE'
533 my $accountline;
534 my $total_amount_other = 0.00;
535 my $due_qr = qr/$due/;
536 # Cycle through the fines and
537 # - find line that relates to the requested $itemnum
538 # - accumulate fines for other items
539 # so we can update $itemnum fine taking in account fine caps
540 while (my $overdue = $overdues->next) {
541 if ( $overdue->issue_id == $issue_id && $overdue->status eq 'UNRETURNED' ) {
542 if ($accountline) {
543 $debug and warn "Not a unique accountlines record for issue_id $issue_id";
544 #FIXME Should we still count this one in total_amount ??
546 else {
547 $accountline = $overdue;
548 next;
551 $total_amount_other += $overdue->amountoutstanding;
554 if ( my $maxfine = C4::Context->preference('MaxFine') ) {
555 my $maxIncrease = $maxfine - $total_amount_other;
556 return if $maxIncrease <= 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;
564 elsif ( $amount > $maxIncrease ) {
565 $debug and warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $maxIncrease - MaxFine reached";
566 $amount = $maxIncrease;
570 if ( $accountline ) {
571 if ( $accountline->amount != $amount ) {
572 $accountline->adjust(
574 amount => $amount,
575 type => 'overdue_update',
576 interface => C4::Context->interface
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=?"
585 $sth4->execute($itemnum);
586 my $title = $sth4->fetchrow;
587 my $desc = "$title $due";
589 my $account = Koha::Account->new({ patron_id => $borrowernumber });
590 $accountline = $account->add_debit(
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,
607 =head2 BorType
609 $borrower = &BorType($borrowernumber);
611 Looks up a patron by borrower number.
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.
618 =cut
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=?"
628 $sth->execute($borrowernumber);
629 return $sth->fetchrow_hashref;
632 =head2 GetFine
634 $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
636 return the total of fine
638 C<$itemnum> is item number
640 C<$borrowernumber> is the borrowernumber
642 =cut
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 )
654 $query .= " AND itemnumber=?";
655 push @query_param, $itemnum;
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};
663 return 0;
666 =head2 GetBranchcodesWithOverdueRules
668 my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
670 returns a list of branch codes for branches with overdue rules defined.
672 =cut
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
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' });
686 return @$branchcodes;
689 =head2 GetOverduesForBranch
691 Sql request for display all information for branchoverdues.pl
692 2 possibilities : with or without location .
693 display is filtered by branch
695 FIXME: This function should be renamed.
697 =cut
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())
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 ) };
753 =head2 GetOverdueMessageTransportTypes
755 my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
757 return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
759 =cut
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 = ?
772 $sth->execute( $branchcode, $categorycode, $letternumber );
773 my @mtts;
774 while ( my $mtt = $sth->fetchrow ) {
775 push @mtts, $mtt;
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;
783 return \@mtts;
786 =head2 parse_overdues_letter
788 parses the letter template, replacing the placeholders with data
789 specific to this patron, biblio, or item for overdues
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
797 returns the C<letter> hashref, with the content updated to reflect the
798 substituted keys and values.
800 =cut
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} );
808 my $patron = Koha::Patrons->find( $params->{borrowernumber} );
810 my $substitute = $params->{'substitute'} || {};
812 my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
813 if ( my $p = $params->{'branchcode'} ) {
814 $tables{'branches'} = $p;
817 my $active_currency = Koha::Acquisition::Currencies->get_active;
819 my $currency_format;
820 $currency_format = $active_currency->currency if defined($active_currency);
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'};
830 push @item_tables, {
831 'biblio' => $item->{'biblionumber'},
832 'biblioitems' => $item->{'biblionumber'},
833 'items' => $item,
834 'issues' => $item->{'itemnumber'},
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 ],
848 substitute => $substitute,
849 repeat => { item => \@item_tables },
850 message_transport_type => $params->{message_transport_type},
855 __END__
857 =head1 AUTHOR
859 Koha Development Team <http://koha-community.org/>
861 =cut