Bug 5670: [QA Followup] Don't force ISO dates.
[koha.git] / C4 / Overdues.pm
blob08a222913b503609f7058aa7d1b49546118b1cf7
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::Line;
38 use Koha::Account::Lines;
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 &NumberNotifyId
53 &AmountNotify
54 &UpdateFine
55 &GetFine
56 &get_chargeable_units
57 &CheckItemNotify
58 &GetOverduesForBranch
59 &RemoveNotifyLine
60 &AddNotifyLine
61 &GetOverdueMessageTransportTypes
62 &parse_overdues_letter
65 # subs to remove
66 push @EXPORT, qw(
67 &BorType
70 # check that an equivalent don't exist already before moving
72 # subs to move to Circulation.pm
73 push @EXPORT, qw(
74 &GetIssuesIteminfo
77 # &GetIssuingRules - delete.
78 # use C4::Circulation::GetIssuingRule instead.
80 # subs to move to Biblio.pm
81 push @EXPORT, qw(
82 &GetItems
86 =head1 NAME
88 C4::Circulation::Fines - Koha module dealing with fines
90 =head1 SYNOPSIS
92 use C4::Overdues;
94 =head1 DESCRIPTION
96 This module contains several functions for dealing with fines for
97 overdue items. It is primarily used by the 'misc/fines2.pl' script.
99 =head1 FUNCTIONS
101 =head2 Getoverdues
103 $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
105 Returns the list of all overdue books, with their itemtype.
107 C<$overdues> is a reference-to-array. Each element is a
108 reference-to-hash whose keys are the fields of the issues table in the
109 Koha database.
111 =cut
114 sub Getoverdues {
115 my $params = shift;
116 my $dbh = C4::Context->dbh;
117 my $statement;
118 if ( C4::Context->preference('item-level_itypes') ) {
119 $statement = "
120 SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost, items.replacementprice
121 FROM issues
122 LEFT JOIN items USING (itemnumber)
123 WHERE date_due < NOW()
125 } else {
126 $statement = "
127 SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost, replacementprice
128 FROM issues
129 LEFT JOIN items USING (itemnumber)
130 LEFT JOIN biblioitems USING (biblioitemnumber)
131 WHERE date_due < NOW()
135 my @bind_parameters;
136 if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
137 $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
138 push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
139 } elsif ( exists $params->{'minimumdays'} ) {
140 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
141 push @bind_parameters, $params->{'minimumdays'};
142 } elsif ( exists $params->{'maximumdays'} ) {
143 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
144 push @bind_parameters, $params->{'maximumdays'};
146 $statement .= 'ORDER BY borrowernumber';
147 my $sth = $dbh->prepare( $statement );
148 $sth->execute( @bind_parameters );
149 return $sth->fetchall_arrayref({});
153 =head2 checkoverdues
155 ($count, $overdueitems) = checkoverdues($borrowernumber);
157 Returns a count and a list of overdueitems for a given borrowernumber
159 =cut
161 sub checkoverdues {
162 my $borrowernumber = shift or return;
163 # don't select biblioitems.marcxml... too slow on large systems
164 my $sth = C4::Context->dbh->prepare(
165 "SELECT biblio.*, items.*, issues.*,
166 biblioitems.volume,
167 biblioitems.number,
168 biblioitems.itemtype,
169 biblioitems.isbn,
170 biblioitems.issn,
171 biblioitems.publicationyear,
172 biblioitems.publishercode,
173 biblioitems.volumedate,
174 biblioitems.volumedesc,
175 biblioitems.collectiontitle,
176 biblioitems.collectionissn,
177 biblioitems.collectionvolume,
178 biblioitems.editionstatement,
179 biblioitems.editionresponsibility,
180 biblioitems.illus,
181 biblioitems.pages,
182 biblioitems.notes,
183 biblioitems.size,
184 biblioitems.place,
185 biblioitems.lccn,
186 biblioitems.url,
187 biblioitems.cn_source,
188 biblioitems.cn_class,
189 biblioitems.cn_item,
190 biblioitems.cn_suffix,
191 biblioitems.cn_sort,
192 biblioitems.totalissues
193 FROM issues
194 LEFT JOIN items ON issues.itemnumber = items.itemnumber
195 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
196 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
197 WHERE issues.borrowernumber = ?
198 AND issues.date_due < NOW()"
200 $sth->execute($borrowernumber);
201 my $results = $sth->fetchall_arrayref({});
202 return ( scalar(@$results), $results); # returning the count and the results is silly
205 =head2 CalcFine
207 ($amount, $chargename, $units_minus_grace, $chargeable_units) = &CalcFine($item,
208 $categorycode, $branch,
209 $start_dt, $end_dt );
211 Calculates the fine for a book.
213 The issuingrules table in the Koha database is a fine matrix, listing
214 the penalties for each type of patron for each type of item and each branch (e.g., the
215 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
216 members might get a longer grace period between the first and second
217 reminders that a book is overdue).
220 C<$item> is an item object (hashref).
222 C<$categorycode> is the category code (string) of the patron who currently has
223 the book.
225 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
227 C<$start_date> & C<$end_date> are DateTime objects
228 defining the date range over which to determine the fine.
230 Fines scripts should just supply the date range over which to calculate the fine.
232 C<&CalcFine> returns four values:
234 C<$amount> is the fine owed by the patron (see above).
236 C<$chargename> is the chargename field from the applicable record in
237 the categoryitem table, whatever that is.
239 C<$units_minus_grace> is the number of chargeable units minus the grace period
241 C<$chargeable_units> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
242 minus any applicable grace period, or hours)
244 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
245 or "Final Notice". But CalcFine never defined any value.
247 =cut
249 sub CalcFine {
250 my ( $item, $bortype, $branchcode, $due_dt, $end_date ) = @_;
251 my $start_date = $due_dt->clone();
252 # get issuingrules (fines part will be used)
253 my $itemtype = $item->{itemtype} || $item->{itype};
254 my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
255 my $fine_unit = $data->{lengthunit};
256 $fine_unit ||= 'days';
258 my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
259 my $units_minus_grace = $chargeable_units - $data->{firstremind};
260 my $amount = 0;
261 if ( $data->{'chargeperiod'} && ( $units_minus_grace > 0 ) ) {
262 my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
263 my $charge_periods = $units / $data->{'chargeperiod'};
264 # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
265 # if chargeperiod_charge_at = 0, we charge at the end of each charge period
266 $charge_periods = $data->{'chargeperiod_charge_at'} == 1 ? ceil($charge_periods) : floor($charge_periods);
267 $amount = $charge_periods * $data->{'fine'};
268 } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
270 $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
271 $amount = $item->{replacementprice} if ( $data->{cap_fine_to_replacement_price} && $item->{replacementprice} && $amount > $item->{replacementprice} );
272 $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
273 return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
274 # FIXME: chargename is NEVER populated anywhere.
278 =head2 get_chargeable_units
280 get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
282 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
284 C<$unit> is 'days' or 'hours' (default is 'days').
286 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
288 C<$branchcode> is the branch whose calendar to use for finding holidays.
290 =cut
292 sub get_chargeable_units {
293 my ($unit, $date_due, $date_returned, $branchcode) = @_;
295 # If the due date is later than the return date
296 return 0 unless ( $date_returned > $date_due );
298 my $charge_units = 0;
299 my $charge_duration;
300 if ($unit eq 'hours') {
301 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
302 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
303 $charge_duration = $calendar->hours_between( $date_due, $date_returned );
304 } else {
305 $charge_duration = $date_returned->delta_ms( $date_due );
307 if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
308 return 1;
310 return $charge_duration->in_units('hours');
312 else { # days
313 if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
314 my $calendar = Koha::Calendar->new( branchcode => $branchcode );
315 $charge_duration = $calendar->days_between( $date_due, $date_returned );
316 } else {
317 $charge_duration = $date_returned->delta_days( $date_due );
319 return $charge_duration->in_units('days');
324 =head2 GetSpecialHolidays
326 &GetSpecialHolidays($date_dues,$itemnumber);
328 return number of special days between date of the day and date due
330 C<$date_dues> is the envisaged date of book return.
332 C<$itemnumber> is the book's item number.
334 =cut
336 sub GetSpecialHolidays {
337 my ( $date_dues, $itemnumber ) = @_;
339 # calcul the today date
340 my $today = join "-", &Today();
342 # return the holdingbranch
343 my $iteminfo = GetIssuesIteminfo($itemnumber);
345 # use sql request to find all date between date_due and today
346 my $dbh = C4::Context->dbh;
347 my $query =
348 qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
349 FROM `special_holidays`
350 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
351 AND DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
352 AND branchcode=?
354 my @result = GetWdayFromItemnumber($itemnumber);
355 my @result_date;
356 my $wday;
357 my $dateinsec;
358 my $sth = $dbh->prepare($query);
359 $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
360 ; # FIXME: just use NOW() in SQL instead of passing in $today
362 while ( my $special_date = $sth->fetchrow_hashref ) {
363 push( @result_date, $special_date );
366 my $specialdaycount = scalar(@result_date);
368 for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
369 $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
370 ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
371 localtime($dateinsec);
372 for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
373 if ( $wday == ( $result[$j]->{'weekday'} ) ) {
374 $specialdaycount--;
379 return $specialdaycount;
382 =head2 GetRepeatableHolidays
384 &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
386 return number of day closed between date of the day and date due
388 C<$date_dues> is the envisaged date of book return.
390 C<$itemnumber> is item number.
392 C<$difference> numbers of between day date of the day and date due
394 =cut
396 sub GetRepeatableHolidays {
397 my ( $date_dues, $itemnumber, $difference ) = @_;
398 my $dateinsec = UnixDate( $date_dues, "%o" );
399 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
400 localtime($dateinsec);
401 my @result = GetWdayFromItemnumber($itemnumber);
402 my @dayclosedcount;
403 my $j;
405 for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
406 my $k = $wday;
408 for ( $j = 0 ; $j < $difference ; $j++ ) {
409 if ( $result[$i]->{'weekday'} == $k ) {
410 push( @dayclosedcount, $k );
412 $k++;
413 ( $k = 0 ) if ( $k eq 7 );
416 return scalar(@dayclosedcount);
420 =head2 GetWayFromItemnumber
422 &Getwdayfromitemnumber($itemnumber);
424 return the different week day from repeatable_holidays table
426 C<$itemnumber> is item number.
428 =cut
430 sub GetWdayFromItemnumber {
431 my ($itemnumber) = @_;
432 my $iteminfo = GetIssuesIteminfo($itemnumber);
433 my @result;
434 my $query = qq|SELECT weekday
435 FROM repeatable_holidays
436 WHERE branchcode=?
438 my $sth = C4::Context->dbh->prepare($query);
440 $sth->execute( $iteminfo->{'branchcode'} );
441 while ( my $weekday = $sth->fetchrow_hashref ) {
442 push( @result, $weekday );
444 return @result;
448 =head2 GetIssuesIteminfo
450 &GetIssuesIteminfo($itemnumber);
452 return all data from issues about item
454 C<$itemnumber> is item number.
456 =cut
458 sub GetIssuesIteminfo {
459 my ($itemnumber) = @_;
460 my $dbh = C4::Context->dbh;
461 my $query = qq|SELECT *
462 FROM issues
463 WHERE itemnumber=?
465 my $sth = $dbh->prepare($query);
466 $sth->execute($itemnumber);
467 my ($issuesinfo) = $sth->fetchrow_hashref;
468 return $issuesinfo;
472 =head2 UpdateFine
474 &UpdateFine({ issue_id => $issue_id, itemnumber => $itemnumber, borrwernumber => $borrowernumber, amount => $amount, type => $type, $due => $date_due });
476 (Note: the following is mostly conjecture and guesswork.)
478 Updates the fine owed on an overdue book.
480 C<$itemnumber> is the book's item number.
482 C<$borrowernumber> is the borrower number of the patron who currently
483 has the book on loan.
485 C<$amount> is the current amount owed by the patron.
487 C<$type> will be used in the description of the fine.
489 C<$due> is the due date formatted to the currently specified date format
491 C<&UpdateFine> looks up the amount currently owed on the given item
492 and sets it to C<$amount>, creating, if necessary, a new entry in the
493 accountlines table of the Koha database.
495 =cut
498 # Question: Why should the caller have to
499 # specify both the item number and the borrower number? A book can't
500 # be on loan to two different people, so the item number should be
501 # sufficient.
503 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
505 sub UpdateFine {
506 my ($params) = @_;
508 my $issue_id = $params->{issue_id};
509 my $itemnum = $params->{itemnumber};
510 my $borrowernumber = $params->{borrowernumber};
511 my $amount = $params->{amount};
512 my $type = $params->{type};
513 my $due = $params->{due};
515 $debug and warn "UpdateFine({ itemnumber => $itemnum, borrowernumber => $borrowernumber, type => $type, due => $due, issue_id => $issue_id})";
517 unless ( $issue_id ) {
518 carp("No issue_id passed in!");
519 return;
522 my $dbh = C4::Context->dbh;
523 # FIXME - What exactly is this query supposed to do? It looks up an
524 # entry in accountlines that matches the given item and borrower
525 # numbers, where the description contains $due, and where the
526 # account type has one of several values, but what does this _mean_?
527 # Does it look up existing fines for this item?
528 # FIXME - What are these various account types? ("FU", "O", "F", "M")
529 # "L" is LOST item
530 # "A" is Account Management Fee
531 # "N" is New Card
532 # "M" is Sundry
533 # "O" is Overdue ??
534 # "F" is Fine ??
535 # "FU" is Fine UPDATE??
536 # "Pay" is Payment
537 # "REF" is Cash Refund
538 my $sth = $dbh->prepare(
539 "SELECT * FROM accountlines
540 WHERE borrowernumber=? AND
541 (( accounttype IN ('O','F','M') AND amountoutstanding<>0 ) OR
542 accounttype = 'FU' )"
544 $sth->execute( $borrowernumber );
545 my $data;
546 my $total_amount_other = 0.00;
547 my $due_qr = qr/$due/;
548 # Cycle through the fines and
549 # - find line that relates to the requested $itemnum
550 # - accumulate fines for other items
551 # so we can update $itemnum fine taking in account fine caps
552 while (my $rec = $sth->fetchrow_hashref) {
553 if ( $rec->{issue_id} == $issue_id && $rec->{accounttype} eq 'FU' ) {
554 if ($data) {
555 warn "Not a unique accountlines record for issue_id $issue_id";
556 #FIXME Should we still count this one in total_amount ??
558 else {
559 $data = $rec;
560 next;
563 $total_amount_other += $rec->{'amountoutstanding'};
566 if (my $maxfine = C4::Context->preference('MaxFine')) {
567 if ($total_amount_other + $amount > $maxfine) {
568 my $new_amount = $maxfine - $total_amount_other;
569 return if $new_amount <= 0.00;
570 warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
571 $amount = $new_amount;
575 if ( $data ) {
576 # we're updating an existing fine. Only modify if amount changed
577 # Note that in the current implementation, you cannot pay against an accruing fine
578 # (i.e. , of accounttype 'FU'). Doing so will break accrual.
579 if ( $data->{'amount'} != $amount ) {
580 my $accountline = Koha::Account::Lines->find( $data->{accountlines_id} );
581 my $diff = $amount - $data->{'amount'};
583 #3341: diff could be positive or negative!
584 my $out = $data->{'amountoutstanding'} + $diff;
586 $accountline->set(
588 date => dt_from_string(),
589 amount => $amount,
590 amountoutstanding => $out,
591 lastincrement => $diff,
592 accounttype => 'FU',
594 )->store();
596 } else {
597 if ( $amount ) { # Don't add new fines with an amount of 0
598 my $sth4 = $dbh->prepare(
599 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
601 $sth4->execute($itemnum);
602 my $title = $sth4->fetchrow;
604 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
606 my $desc = ( $type ? "$type " : '' ) . "$title $due"; # FIXEDME, avoid whitespace prefix on empty $type
608 my $accountline = Koha::Account::Line->new(
610 borrowernumber => $borrowernumber,
611 itemnumber => $itemnum,
612 date => dt_from_string(),
613 amount => $amount,
614 description => $desc,
615 accounttype => 'FU',
616 amountoutstanding => $amount,
617 lastincrement => $amount,
618 accountno => $nextaccntno,
619 issue_id => $issue_id,
621 )->store();
624 # logging action
625 &logaction(
626 "FINES",
627 $type,
628 $borrowernumber,
629 "due=".$due." amount=".$amount." itemnumber=".$itemnum
630 ) if C4::Context->preference("FinesLog");
633 =head2 BorType
635 $borrower = &BorType($borrowernumber);
637 Looks up a patron by borrower number.
639 C<$borrower> is a reference-to-hash whose keys are all of the fields
640 from the borrowers and categories tables of the Koha database. Thus,
641 C<$borrower> contains all information about both the borrower and
642 category he or she belongs to.
644 =cut
646 sub BorType {
647 my ($borrowernumber) = @_;
648 my $dbh = C4::Context->dbh;
649 my $sth = $dbh->prepare(
650 "SELECT * from borrowers
651 LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
652 WHERE borrowernumber=?"
654 $sth->execute($borrowernumber);
655 return $sth->fetchrow_hashref;
658 =head2 GetFine
660 $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
662 return the total of fine
664 C<$itemnum> is item number
666 C<$borrowernumber> is the borrowernumber
668 =cut
670 sub GetFine {
671 my ( $itemnum, $borrowernumber ) = @_;
672 my $dbh = C4::Context->dbh();
673 my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
674 where accounttype like 'F%'
675 AND amountoutstanding > 0 AND borrowernumber=?|;
676 my @query_param;
677 push @query_param, $borrowernumber;
678 if (defined $itemnum )
680 $query .= " AND itemnumber=?";
681 push @query_param, $itemnum;
683 my $sth = $dbh->prepare($query);
684 $sth->execute( @query_param );
685 my $fine = $sth->fetchrow_hashref();
686 if ($fine->{fineamount}) {
687 return $fine->{fineamount};
689 return 0;
692 =head2 NumberNotifyId
694 (@notify) = &NumberNotifyId($borrowernumber);
696 Returns amount for all file per borrowers
697 C<@notify> array contains all file per borrowers
699 C<$notify_id> contains the file number for the borrower number nad item number
701 =cut
703 sub NumberNotifyId{
704 my ($borrowernumber)=@_;
705 my $dbh = C4::Context->dbh;
706 my $query=qq| SELECT distinct(notify_id)
707 FROM accountlines
708 WHERE borrowernumber=?|;
709 my @notify;
710 my $sth = $dbh->prepare($query);
711 $sth->execute($borrowernumber);
712 while ( my ($numberofnotify) = $sth->fetchrow ) {
713 push( @notify, $numberofnotify );
715 return (@notify);
718 =head2 AmountNotify
720 ($totalnotify) = &AmountNotify($notifyid);
722 Returns amount for all file per borrowers
723 C<$notifyid> is the file number
725 C<$totalnotify> contains amount of a file
727 C<$notify_id> contains the file number for the borrower number and item number
729 =cut
731 sub AmountNotify{
732 my ($notifyid,$borrowernumber)=@_;
733 my $dbh = C4::Context->dbh;
734 my $query=qq| SELECT sum(amountoutstanding)
735 FROM accountlines
736 WHERE notify_id=? AND borrowernumber = ?|;
737 my $sth=$dbh->prepare($query);
738 $sth->execute($notifyid,$borrowernumber);
739 my $totalnotify=$sth->fetchrow;
740 $sth->finish;
741 return ($totalnotify);
744 =head2 GetItems
746 ($items) = &GetItems($itemnumber);
748 Returns the list of all delays from overduerules.
750 C<$items> is a reference-to-hash whose keys are all of the fields
751 from the items tables of the Koha database. Thus,
753 C<$itemnumber> contains the borrower categorycode
755 =cut
757 # FIXME: This is a bad function to have here.
758 # Shouldn't it be in C4::Items?
759 # Shouldn't it be called GetItem since you only get 1 row?
760 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
762 sub GetItems {
763 my $itemnumber = shift or return;
764 my $query = qq|SELECT *
765 FROM items
766 WHERE itemnumber=?|;
767 my $sth = C4::Context->dbh->prepare($query);
768 $sth->execute($itemnumber);
769 my ($items) = $sth->fetchrow_hashref;
770 return ($items);
773 =head2 GetBranchcodesWithOverdueRules
775 my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
777 returns a list of branch codes for branches with overdue rules defined.
779 =cut
781 sub GetBranchcodesWithOverdueRules {
782 my $dbh = C4::Context->dbh;
783 my $branchcodes = $dbh->selectcol_arrayref(q|
784 SELECT DISTINCT(branchcode)
785 FROM overduerules
786 WHERE delay1 IS NOT NULL
787 ORDER BY branchcode
789 if ( $branchcodes->[0] eq '' ) {
790 # If a default rule exists, all branches should be returned
791 return map { $_->branchcode } Koha::Libraries->search({}, { order_by => 'branchname' });
793 return @$branchcodes;
796 =head2 CheckItemNotify
798 Sql request to check if the document has alreday been notified
799 this function is not exported, only used with GetOverduesForBranch
801 =cut
803 sub CheckItemNotify {
804 my ($notify_id,$notify_level,$itemnumber) = @_;
805 my $dbh = C4::Context->dbh;
806 my $sth = $dbh->prepare("
807 SELECT COUNT(*)
808 FROM notifys
809 WHERE notify_id = ?
810 AND notify_level = ?
811 AND itemnumber = ? ");
812 $sth->execute($notify_id,$notify_level,$itemnumber);
813 my $notified = $sth->fetchrow;
814 return ($notified);
817 =head2 GetOverduesForBranch
819 Sql request for display all information for branchoverdues.pl
820 2 possibilities : with or without location .
821 display is filtered by branch
823 FIXME: This function should be renamed.
825 =cut
827 sub GetOverduesForBranch {
828 my ( $branch, $location) = @_;
829 my $itype_link = (C4::Context->preference('item-level_itypes')) ? " items.itype " : " biblioitems.itemtype ";
830 my $dbh = C4::Context->dbh;
831 my $select = "
832 SELECT
833 borrowers.cardnumber,
834 borrowers.borrowernumber,
835 borrowers.surname,
836 borrowers.firstname,
837 borrowers.phone,
838 borrowers.email,
839 biblio.title,
840 biblio.author,
841 biblio.biblionumber,
842 issues.date_due,
843 issues.returndate,
844 issues.branchcode,
845 branches.branchname,
846 items.barcode,
847 items.homebranch,
848 items.itemcallnumber,
849 items.location,
850 items.itemnumber,
851 itemtypes.description,
852 accountlines.notify_id,
853 accountlines.notify_level,
854 accountlines.amountoutstanding
855 FROM accountlines
856 LEFT JOIN issues ON issues.itemnumber = accountlines.itemnumber
857 AND issues.borrowernumber = accountlines.borrowernumber
858 LEFT JOIN borrowers ON borrowers.borrowernumber = accountlines.borrowernumber
859 LEFT JOIN items ON items.itemnumber = issues.itemnumber
860 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
861 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
862 LEFT JOIN itemtypes ON itemtypes.itemtype = $itype_link
863 LEFT JOIN branches ON branches.branchcode = issues.branchcode
864 WHERE (accountlines.amountoutstanding != '0.000000')
865 AND (accountlines.accounttype = 'FU' )
866 AND (issues.branchcode = ? )
867 AND (issues.date_due < NOW())
869 my @getoverdues;
870 my $i = 0;
871 my $sth;
872 if ($location) {
873 $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
874 $sth->execute($branch, $location);
875 } else {
876 $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
877 $sth->execute($branch);
879 while ( my $data = $sth->fetchrow_hashref ) {
880 #check if the document has already been notified
881 my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
882 if ($countnotify eq '0') {
883 $getoverdues[$i] = $data;
884 $i++;
887 return (@getoverdues);
891 =head2 AddNotifyLine
893 &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
895 Create a line into notify, if the method is phone, the notification_send_date is implemented to
897 =cut
899 sub AddNotifyLine {
900 my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
901 my $dbh = C4::Context->dbh;
902 if ( $method eq "phone" ) {
903 my $sth = $dbh->prepare(
904 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
905 VALUES (?,?,now(),now(),?,?,?)"
907 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
908 $notifyId );
910 else {
911 my $sth = $dbh->prepare(
912 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
913 VALUES (?,?,now(),?,?,?)"
915 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
916 $notifyId );
918 return 1;
921 =head2 RemoveNotifyLine
923 &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
925 Cancel a notification
927 =cut
929 sub RemoveNotifyLine {
930 my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
931 my $dbh = C4::Context->dbh;
932 my $sth = $dbh->prepare(
933 "DELETE FROM notifys
934 WHERE
935 borrowernumber=?
936 AND itemnumber=?
937 AND notify_date=?"
939 $sth->execute( $borrowernumber, $itemnumber, $notify_date );
940 return 1;
943 =head2 GetOverdueMessageTransportTypes
945 my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
947 return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
949 =cut
951 sub GetOverdueMessageTransportTypes {
952 my ( $branchcode, $categorycode, $letternumber ) = @_;
953 return unless $categorycode and $letternumber;
954 my $dbh = C4::Context->dbh;
955 my $sth = $dbh->prepare("
956 SELECT message_transport_type
957 FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
958 WHERE branchcode = ?
959 AND categorycode = ?
960 AND letternumber = ?
962 $sth->execute( $branchcode, $categorycode, $letternumber );
963 my @mtts;
964 while ( my $mtt = $sth->fetchrow ) {
965 push @mtts, $mtt;
968 # Put 'print' in first if exists
969 # It avoid to sent a print notice with an email or sms template is no email or sms is defined
970 @mtts = uniq( 'print', @mtts )
971 if grep {/^print$/} @mtts;
973 return \@mtts;
976 =head2 parse_overdues_letter
978 parses the letter template, replacing the placeholders with data
979 specific to this patron, biblio, or item for overdues
981 named parameters:
982 letter - required hashref
983 borrowernumber - required integer
984 substitute - optional hashref of other key/value pairs that should
985 be substituted in the letter content
987 returns the C<letter> hashref, with the content updated to reflect the
988 substituted keys and values.
990 =cut
992 sub parse_overdues_letter {
993 my $params = shift;
994 foreach my $required (qw( letter_code borrowernumber )) {
995 return unless ( exists $params->{$required} && $params->{$required} );
998 my $substitute = $params->{'substitute'} || {};
999 $substitute->{today} ||= output_pref( { dt => dt_from_string, dateonly => 1} );
1001 my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
1002 if ( my $p = $params->{'branchcode'} ) {
1003 $tables{'branches'} = $p;
1006 my $active_currency = Koha::Acquisition::Currencies->get_active;
1008 my $currency_format;
1009 $currency_format = $active_currency->currency if defined($active_currency);
1011 my @item_tables;
1012 if ( my $i = $params->{'items'} ) {
1013 my $item_format = '';
1014 foreach my $item (@$i) {
1015 my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
1016 if ( !$item_format and defined $params->{'letter'}->{'content'} ) {
1017 $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
1018 $item_format = $1;
1021 $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
1022 # if active currency isn't correct ISO code fallback to sprintf
1023 $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
1025 push @item_tables, {
1026 'biblio' => $item->{'biblionumber'},
1027 'biblioitems' => $item->{'biblionumber'},
1028 'items' => $item,
1029 'issues' => $item->{'itemnumber'},
1034 return C4::Letters::GetPreparedLetter (
1035 module => 'circulation',
1036 letter_code => $params->{'letter_code'},
1037 branchcode => $params->{'branchcode'},
1038 tables => \%tables,
1039 substitute => $substitute,
1040 repeat => { item => \@item_tables },
1041 message_transport_type => $params->{message_transport_type},
1046 __END__
1048 =head1 AUTHOR
1050 Koha Development Team <http://koha-community.org/>
1052 =cut