Bug 21295: Update selenium tests for admin bootstrap changes
[koha.git] / C4 / Overdues.pm
blobf5c5132c96a2d945e1c2a942e5c33994765b92b2
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 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;
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;
42 use vars qw(@ISA @EXPORT);
44 BEGIN {
45 require Exporter;
46 @ISA = qw(Exporter);
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
61 # subs to remove
62 push @EXPORT, qw(
63 &BorType
66 # check that an equivalent don't exist already before moving
68 # subs to move to Circulation.pm
69 push @EXPORT, qw(
70 &GetIssuesIteminfo
74 =head1 NAME
76 C4::Circulation::Fines - Koha module dealing with fines
78 =head1 SYNOPSIS
80 use C4::Overdues;
82 =head1 DESCRIPTION
84 This module contains several functions for dealing with fines for
85 overdue items. It is primarily used by the 'misc/fines2.pl' script.
87 =head1 FUNCTIONS
89 =head2 Getoverdues
91 $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
93 Returns the list of all overdue books, with their itemtype.
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.
99 =cut
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()
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()
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'};
134 $statement .= 'ORDER BY borrowernumber';
135 my $sth = $dbh->prepare( $statement );
136 $sth->execute( @bind_parameters );
137 return $sth->fetchall_arrayref({});
141 =head2 checkoverdues
143 ($count, $overdueitems) = checkoverdues($borrowernumber);
145 Returns a count and a list of overdueitems for a given borrowernumber
147 =cut
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()"
187 $sth->execute($borrowernumber);
188 my $results = $sth->fetchall_arrayref({});
189 return ( scalar(@$results), $results); # returning the count and the results is silly
192 =head2 CalcFine
194 ($amount, $chargename, $units_minus_grace, $chargeable_units) = &CalcFine($item,
195 $categorycode, $branch,
196 $start_dt, $end_dt );
198 Calculates the fine for a book.
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).
207 C<$item> is an item object (hashref).
209 C<$categorycode> is the category code (string) of the patron who currently has
210 the book.
212 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
214 C<$start_date> & C<$end_date> are DateTime objects
215 defining the date range over which to determine the fine.
217 Fines scripts should just supply the date range over which to calculate the fine.
219 C<&CalcFine> returns four values:
221 C<$amount> is the fine owed by the patron (see above).
223 C<$chargename> is the chargename field from the applicable record in
224 the categoryitem table, whatever that is.
226 C<$units_minus_grace> is the number of chargeable units minus the grace period
228 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
229 minus any applicable grace period, or hours)
231 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
232 or "Final Notice". But CalcFine never defined any value.
234 =cut
236 sub CalcFine {
237 my ( $item, $bortype, $branchcode, $due_dt, $end_date ) = @_;
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 return unless $issuing_rule; # If not rule exist, there is no fine
245 my $fine_unit = $issuing_rule->lengthunit || 'days';
247 my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
248 my $units_minus_grace = $chargeable_units - $issuing_rule->firstremind;
249 my $amount = 0;
250 if ( $issuing_rule->chargeperiod && ( $units_minus_grace > 0 ) ) {
251 my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
252 my $charge_periods = $units / $issuing_rule->chargeperiod;
253 # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
254 # if chargeperiod_charge_at = 0, we charge at the end of each charge period
255 $charge_periods = $issuing_rule->chargeperiod_charge_at == 1 ? ceil($charge_periods) : floor($charge_periods);
256 $amount = $charge_periods * $issuing_rule->fine;
257 } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
259 $amount = $issuing_rule->overduefinescap if $issuing_rule->overduefinescap && $amount > $issuing_rule->overduefinescap;
260 $amount = $item->{replacementprice} if ( $issuing_rule->cap_fine_to_replacement_price && $item->{replacementprice} && $amount > $item->{replacementprice} );
261 $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $issuing_rule->chargename, $units_minus_grace, $chargeable_units);
262 return ($amount, $issuing_rule->chargename, $units_minus_grace, $chargeable_units);
263 # FIXME: chargename is NEVER populated anywhere.
267 =head2 get_chargeable_units
269 get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
271 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
273 C<$unit> is 'days' or 'hours' (default is 'days').
275 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
277 C<$branchcode> is the branch whose calendar to use for finding holidays.
279 =cut
281 sub get_chargeable_units {
282 my ($unit, $date_due, $date_returned, $branchcode) = @_;
284 # If the due date is later than the return date
285 return 0 unless ( $date_returned > $date_due );
287 my $charge_units = 0;
288 my $charge_duration;
289 if ($unit eq 'hours') {
290 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
291 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
292 $charge_duration = $calendar->hours_between( $date_due, $date_returned );
293 } else {
294 $charge_duration = $date_returned->delta_ms( $date_due );
296 if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
297 return 1;
299 return $charge_duration->in_units('hours');
301 else { # days
302 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
303 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
304 $charge_duration = $calendar->days_between( $date_due, $date_returned );
305 } else {
306 $charge_duration = $date_returned->delta_days( $date_due );
308 return $charge_duration->in_units('days');
313 =head2 GetSpecialHolidays
315 &GetSpecialHolidays($date_dues,$itemnumber);
317 return number of special days between date of the day and date due
319 C<$date_dues> is the envisaged date of book return.
321 C<$itemnumber> is the book's item number.
323 =cut
325 sub GetSpecialHolidays {
326 my ( $date_dues, $itemnumber ) = @_;
328 # calcul the today date
329 my $today = join "-", &Today();
331 # return the holdingbranch
332 my $iteminfo = GetIssuesIteminfo($itemnumber);
334 # use sql request to find all date between date_due and today
335 my $dbh = C4::Context->dbh;
336 my $query =
337 qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
338 FROM `special_holidays`
339 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
340 AND DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
341 AND branchcode=?
343 my @result = GetWdayFromItemnumber($itemnumber);
344 my @result_date;
345 my $wday;
346 my $dateinsec;
347 my $sth = $dbh->prepare($query);
348 $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
349 ; # FIXME: just use NOW() in SQL instead of passing in $today
351 while ( my $special_date = $sth->fetchrow_hashref ) {
352 push( @result_date, $special_date );
355 my $specialdaycount = scalar(@result_date);
357 for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
358 $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
359 ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
360 localtime($dateinsec);
361 for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
362 if ( $wday == ( $result[$j]->{'weekday'} ) ) {
363 $specialdaycount--;
368 return $specialdaycount;
371 =head2 GetRepeatableHolidays
373 &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
375 return number of day closed between date of the day and date due
377 C<$date_dues> is the envisaged date of book return.
379 C<$itemnumber> is item number.
381 C<$difference> numbers of between day date of the day and date due
383 =cut
385 sub GetRepeatableHolidays {
386 my ( $date_dues, $itemnumber, $difference ) = @_;
387 my $dateinsec = UnixDate( $date_dues, "%o" );
388 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
389 localtime($dateinsec);
390 my @result = GetWdayFromItemnumber($itemnumber);
391 my @dayclosedcount;
392 my $j;
394 for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
395 my $k = $wday;
397 for ( $j = 0 ; $j < $difference ; $j++ ) {
398 if ( $result[$i]->{'weekday'} == $k ) {
399 push( @dayclosedcount, $k );
401 $k++;
402 ( $k = 0 ) if ( $k eq 7 );
405 return scalar(@dayclosedcount);
409 =head2 GetWayFromItemnumber
411 &Getwdayfromitemnumber($itemnumber);
413 return the different week day from repeatable_holidays table
415 C<$itemnumber> is item number.
417 =cut
419 sub GetWdayFromItemnumber {
420 my ($itemnumber) = @_;
421 my $iteminfo = GetIssuesIteminfo($itemnumber);
422 my @result;
423 my $query = qq|SELECT weekday
424 FROM repeatable_holidays
425 WHERE branchcode=?
427 my $sth = C4::Context->dbh->prepare($query);
429 $sth->execute( $iteminfo->{'branchcode'} );
430 while ( my $weekday = $sth->fetchrow_hashref ) {
431 push( @result, $weekday );
433 return @result;
437 =head2 GetIssuesIteminfo
439 &GetIssuesIteminfo($itemnumber);
441 return all data from issues about item
443 C<$itemnumber> is item number.
445 =cut
447 sub GetIssuesIteminfo {
448 my ($itemnumber) = @_;
449 my $dbh = C4::Context->dbh;
450 my $query = qq|SELECT *
451 FROM issues
452 WHERE itemnumber=?
454 my $sth = $dbh->prepare($query);
455 $sth->execute($itemnumber);
456 my ($issuesinfo) = $sth->fetchrow_hashref;
457 return $issuesinfo;
461 =head2 UpdateFine
463 &UpdateFine({ issue_id => $issue_id, itemnumber => $itemnumber, borrwernumber => $borrowernumber, amount => $amount, type => $type, $due => $date_due });
465 (Note: the following is mostly conjecture and guesswork.)
467 Updates the fine owed on an overdue book.
469 C<$itemnumber> is the book's item number.
471 C<$borrowernumber> is the borrower number of the patron who currently
472 has the book on loan.
474 C<$amount> is the current amount owed by the patron.
476 C<$type> will be used in the description of the fine.
478 C<$due> is the due date formatted to the currently specified date format
480 C<&UpdateFine> looks up the amount currently owed on the given item
481 and sets it to C<$amount>, creating, if necessary, a new entry in the
482 accountlines table of the Koha database.
484 =cut
487 # Question: Why should the caller have to
488 # specify both the item number and the borrower number? A book can't
489 # be on loan to two different people, so the item number should be
490 # sufficient.
492 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
494 sub UpdateFine {
495 my ($params) = @_;
497 my $issue_id = $params->{issue_id};
498 my $itemnum = $params->{itemnumber};
499 my $borrowernumber = $params->{borrowernumber};
500 my $amount = $params->{amount};
501 my $type = $params->{type};
502 my $due = $params->{due};
504 $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, type => $type, due => $due, issue_id => $issue_id})";
506 unless ( $issue_id ) {
507 carp("No issue_id passed in!");
508 return;
511 my $dbh = C4::Context->dbh;
512 # FIXME - What exactly is this query supposed to do? It looks up an
513 # entry in accountlines that matches the given item and borrower
514 # numbers, where the description contains $due, and where the
515 # account type has one of several values, but what does this _mean_?
516 # Does it look up existing fines for this item?
517 # FIXME - What are these various account types? ("FU", "O", "F", "M")
518 # "L" is LOST item
519 # "A" is Account Management Fee
520 # "N" is New Card
521 # "M" is Sundry
522 # "O" is Overdue ??
523 # "F" is Fine ??
524 # "FU" is Fine UPDATE??
525 # "Pay" is Payment
526 # "REF" is Cash Refund
527 my $sth = $dbh->prepare(
528 "SELECT * FROM accountlines
529 WHERE borrowernumber=? AND
530 (( accounttype IN ('O','F','M') AND amountoutstanding<>0 ) OR
531 accounttype = 'FU' )"
533 $sth->execute( $borrowernumber );
534 my $data;
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 $rec = $sth->fetchrow_hashref) {
542 if ( $rec->{issue_id} == $issue_id && $rec->{accounttype} eq 'FU' ) {
543 if ($data) {
544 warn "Not a unique accountlines record for issue_id $issue_id";
545 #FIXME Should we still count this one in total_amount ??
547 else {
548 $data = $rec;
549 next;
552 $total_amount_other += $rec->{'amountoutstanding'};
555 if (my $maxfine = C4::Context->preference('MaxFine')) {
556 if ($total_amount_other + $amount > $maxfine) {
557 my $new_amount = $maxfine - $total_amount_other;
558 return if $new_amount <= 0.00;
559 warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
560 $amount = $new_amount;
564 if ( $data ) {
565 # we're updating an existing fine. Only modify if amount changed
566 # Note that in the current implementation, you cannot pay against an accruing fine
567 # (i.e. , of accounttype 'FU'). Doing so will break accrual.
568 if ( $data->{'amount'} != $amount ) {
569 my $accountline = Koha::Account::Lines->find( $data->{accountlines_id} );
570 my $diff = $amount - $data->{'amount'};
572 #3341: diff could be positive or negative!
573 my $out = $data->{'amountoutstanding'} + $diff;
575 $accountline->set(
577 date => dt_from_string(),
578 amount => $amount,
579 amountoutstanding => $out,
580 lastincrement => $diff,
581 accounttype => 'FU',
583 )->store();
585 Koha::Account::Offset->new(
587 debit_id => $accountline->id,
588 type => 'Fine Update',
589 amount => $diff,
591 )->store();
593 } else {
594 if ( $amount ) { # Don't add new fines with an amount of 0
595 my $sth4 = $dbh->prepare(
596 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
598 $sth4->execute($itemnum);
599 my $title = $sth4->fetchrow;
601 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
603 my $desc = ( $type ? "$type " : '' ) . "$title $due"; # FIXEDME, avoid whitespace prefix on empty $type
605 my $accountline = Koha::Account::Line->new(
607 borrowernumber => $borrowernumber,
608 itemnumber => $itemnum,
609 date => dt_from_string(),
610 amount => $amount,
611 description => $desc,
612 accounttype => 'FU',
613 amountoutstanding => $amount,
614 lastincrement => $amount,
615 accountno => $nextaccntno,
616 issue_id => $issue_id,
618 )->store();
620 Koha::Account::Offset->new(
622 debit_id => $accountline->id,
623 type => 'Fine',
624 amount => $amount,
626 )->store();
629 # logging action
630 &logaction(
631 "FINES",
632 $type,
633 $borrowernumber,
634 "due=".$due." amount=".$amount." itemnumber=".$itemnum
635 ) if C4::Context->preference("FinesLog");
638 =head2 BorType
640 $borrower = &BorType($borrowernumber);
642 Looks up a patron by borrower number.
644 C<$borrower> is a reference-to-hash whose keys are all of the fields
645 from the borrowers and categories tables of the Koha database. Thus,
646 C<$borrower> contains all information about both the borrower and
647 category they belong to.
649 =cut
651 sub BorType {
652 my ($borrowernumber) = @_;
653 my $dbh = C4::Context->dbh;
654 my $sth = $dbh->prepare(
655 "SELECT * from borrowers
656 LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
657 WHERE borrowernumber=?"
659 $sth->execute($borrowernumber);
660 return $sth->fetchrow_hashref;
663 =head2 GetFine
665 $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
667 return the total of fine
669 C<$itemnum> is item number
671 C<$borrowernumber> is the borrowernumber
673 =cut
675 sub GetFine {
676 my ( $itemnum, $borrowernumber ) = @_;
677 my $dbh = C4::Context->dbh();
678 my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
679 where accounttype like 'F%'
680 AND amountoutstanding > 0 AND borrowernumber=?|;
681 my @query_param;
682 push @query_param, $borrowernumber;
683 if (defined $itemnum )
685 $query .= " AND itemnumber=?";
686 push @query_param, $itemnum;
688 my $sth = $dbh->prepare($query);
689 $sth->execute( @query_param );
690 my $fine = $sth->fetchrow_hashref();
691 if ($fine->{fineamount}) {
692 return $fine->{fineamount};
694 return 0;
697 =head2 GetBranchcodesWithOverdueRules
699 my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
701 returns a list of branch codes for branches with overdue rules defined.
703 =cut
705 sub GetBranchcodesWithOverdueRules {
706 my $dbh = C4::Context->dbh;
707 my $branchcodes = $dbh->selectcol_arrayref(q|
708 SELECT DISTINCT(branchcode)
709 FROM overduerules
710 WHERE delay1 IS NOT NULL
711 ORDER BY branchcode
713 if ( $branchcodes->[0] eq '' ) {
714 # If a default rule exists, all branches should be returned
715 return map { $_->branchcode } Koha::Libraries->search({}, { order_by => 'branchname' });
717 return @$branchcodes;
720 =head2 GetOverduesForBranch
722 Sql request for display all information for branchoverdues.pl
723 2 possibilities : with or without location .
724 display is filtered by branch
726 FIXME: This function should be renamed.
728 =cut
730 sub GetOverduesForBranch {
731 my ( $branch, $location) = @_;
732 my $itype_link = (C4::Context->preference('item-level_itypes')) ? " items.itype " : " biblioitems.itemtype ";
733 my $dbh = C4::Context->dbh;
734 my $select = "
735 SELECT
736 borrowers.cardnumber,
737 borrowers.borrowernumber,
738 borrowers.surname,
739 borrowers.firstname,
740 borrowers.phone,
741 borrowers.email,
742 biblio.title,
743 biblio.author,
744 biblio.biblionumber,
745 issues.date_due,
746 issues.returndate,
747 issues.branchcode,
748 branches.branchname,
749 items.barcode,
750 items.homebranch,
751 items.itemcallnumber,
752 items.location,
753 items.itemnumber,
754 itemtypes.description,
755 accountlines.amountoutstanding
756 FROM accountlines
757 LEFT JOIN issues ON issues.itemnumber = accountlines.itemnumber
758 AND issues.borrowernumber = accountlines.borrowernumber
759 LEFT JOIN borrowers ON borrowers.borrowernumber = accountlines.borrowernumber
760 LEFT JOIN items ON items.itemnumber = issues.itemnumber
761 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
762 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
763 LEFT JOIN itemtypes ON itemtypes.itemtype = $itype_link
764 LEFT JOIN branches ON branches.branchcode = issues.branchcode
765 WHERE (accountlines.amountoutstanding != '0.000000')
766 AND (accountlines.accounttype = 'FU' )
767 AND (issues.branchcode = ? )
768 AND (issues.date_due < NOW())
770 if ($location) {
771 my $q = "$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname";
772 return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch, $location ) };
773 } else {
774 my $q = "$select ORDER BY borrowers.surname, borrowers.firstname";
775 return @{ $dbh->selectall_arrayref($q, { Slice => {} }, $branch ) };
779 =head2 GetOverdueMessageTransportTypes
781 my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
783 return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
785 =cut
787 sub GetOverdueMessageTransportTypes {
788 my ( $branchcode, $categorycode, $letternumber ) = @_;
789 return unless $categorycode and $letternumber;
790 my $dbh = C4::Context->dbh;
791 my $sth = $dbh->prepare("
792 SELECT message_transport_type
793 FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
794 WHERE branchcode = ?
795 AND categorycode = ?
796 AND letternumber = ?
798 $sth->execute( $branchcode, $categorycode, $letternumber );
799 my @mtts;
800 while ( my $mtt = $sth->fetchrow ) {
801 push @mtts, $mtt;
804 # Put 'print' in first if exists
805 # It avoid to sent a print notice with an email or sms template is no email or sms is defined
806 @mtts = uniq( 'print', @mtts )
807 if grep {/^print$/} @mtts;
809 return \@mtts;
812 =head2 parse_overdues_letter
814 parses the letter template, replacing the placeholders with data
815 specific to this patron, biblio, or item for overdues
817 named parameters:
818 letter - required hashref
819 borrowernumber - required integer
820 substitute - optional hashref of other key/value pairs that should
821 be substituted in the letter content
823 returns the C<letter> hashref, with the content updated to reflect the
824 substituted keys and values.
826 =cut
828 sub parse_overdues_letter {
829 my $params = shift;
830 foreach my $required (qw( letter_code borrowernumber )) {
831 return unless ( exists $params->{$required} && $params->{$required} );
834 my $patron = Koha::Patrons->find( $params->{borrowernumber} );
836 my $substitute = $params->{'substitute'} || {};
838 my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
839 if ( my $p = $params->{'branchcode'} ) {
840 $tables{'branches'} = $p;
843 my $active_currency = Koha::Acquisition::Currencies->get_active;
845 my $currency_format;
846 $currency_format = $active_currency->currency if defined($active_currency);
848 my @item_tables;
849 if ( my $i = $params->{'items'} ) {
850 foreach my $item (@$i) {
851 my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
852 $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
853 # if active currency isn't correct ISO code fallback to sprintf
854 $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
856 push @item_tables, {
857 'biblio' => $item->{'biblionumber'},
858 'biblioitems' => $item->{'biblionumber'},
859 'items' => $item,
860 'issues' => $item->{'itemnumber'},
865 return C4::Letters::GetPreparedLetter (
866 module => 'circulation',
867 letter_code => $params->{'letter_code'},
868 branchcode => $params->{'branchcode'},
869 lang => $patron->lang,
870 tables => \%tables,
871 loops => {
872 overdues => [ map { $_->{items}->{itemnumber} } @item_tables ],
874 substitute => $substitute,
875 repeat => { item => \@item_tables },
876 message_transport_type => $params->{message_transport_type},
881 __END__
883 =head1 AUTHOR
885 Koha Development Team <http://koha-community.org/>
887 =cut