Bug 14402: (QA followup) Add notes to usage text about --fees
[koha.git] / C4 / Overdues.pm
blob0fc623260e8baca58c9d16b2706d1ddd151fc5f3
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;
30 use C4::Circulation;
31 use C4::Context;
32 use C4::Accounts;
33 use C4::Log; # logaction
34 use C4::Debug;
35 use C4::Budgets qw(GetCurrency);
37 use vars qw($VERSION @ISA @EXPORT);
39 BEGIN {
40 # set the version for version checking
41 $VERSION = 3.07.00.049;
42 require Exporter;
43 @ISA = qw(Exporter);
45 # subs to rename (and maybe merge some...)
46 push @EXPORT, qw(
47 &CalcFine
48 &Getoverdues
49 &checkoverdues
50 &NumberNotifyId
51 &AmountNotify
52 &UpdateFine
53 &GetFine
54 &get_chargeable_units
55 &CheckItemNotify
56 &GetOverduesForBranch
57 &RemoveNotifyLine
58 &AddNotifyLine
59 &GetOverdueMessageTransportTypes
60 &parse_overdues_letter
63 # subs to remove
64 push @EXPORT, qw(
65 &BorType
68 # check that an equivalent don't exist already before moving
70 # subs to move to Circulation.pm
71 push @EXPORT, qw(
72 &GetIssuesIteminfo
75 # &GetIssuingRules - delete.
76 # use C4::Circulation::GetIssuingRule instead.
78 # subs to move to Biblio.pm
79 push @EXPORT, qw(
80 &GetItems
84 =head1 NAME
86 C4::Circulation::Fines - Koha module dealing with fines
88 =head1 SYNOPSIS
90 use C4::Overdues;
92 =head1 DESCRIPTION
94 This module contains several functions for dealing with fines for
95 overdue items. It is primarily used by the 'misc/fines2.pl' script.
97 =head1 FUNCTIONS
99 =head2 Getoverdues
101 $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
103 Returns the list of all overdue books, with their itemtype.
105 C<$overdues> is a reference-to-array. Each element is a
106 reference-to-hash whose keys are the fields of the issues table in the
107 Koha database.
109 =cut
112 sub Getoverdues {
113 my $params = shift;
114 my $dbh = C4::Context->dbh;
115 my $statement;
116 if ( C4::Context->preference('item-level_itypes') ) {
117 $statement = "
118 SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost
119 FROM issues
120 LEFT JOIN items USING (itemnumber)
121 WHERE date_due < NOW()
123 } else {
124 $statement = "
125 SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost
126 FROM issues
127 LEFT JOIN items USING (itemnumber)
128 LEFT JOIN biblioitems USING (biblioitemnumber)
129 WHERE date_due < NOW()
133 my @bind_parameters;
134 if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
135 $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
136 push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
137 } elsif ( exists $params->{'minimumdays'} ) {
138 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
139 push @bind_parameters, $params->{'minimumdays'};
140 } elsif ( exists $params->{'maximumdays'} ) {
141 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
142 push @bind_parameters, $params->{'maximumdays'};
144 $statement .= 'ORDER BY borrowernumber';
145 my $sth = $dbh->prepare( $statement );
146 $sth->execute( @bind_parameters );
147 return $sth->fetchall_arrayref({});
151 =head2 checkoverdues
153 ($count, $overdueitems) = checkoverdues($borrowernumber);
155 Returns a count and a list of overdueitems for a given borrowernumber
157 =cut
159 sub checkoverdues {
160 my $borrowernumber = shift or return;
161 # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
162 my $sth = C4::Context->dbh->prepare(
163 "SELECT biblio.*, items.*, issues.*,
164 biblioitems.volume,
165 biblioitems.number,
166 biblioitems.itemtype,
167 biblioitems.isbn,
168 biblioitems.issn,
169 biblioitems.publicationyear,
170 biblioitems.publishercode,
171 biblioitems.volumedate,
172 biblioitems.volumedesc,
173 biblioitems.collectiontitle,
174 biblioitems.collectionissn,
175 biblioitems.collectionvolume,
176 biblioitems.editionstatement,
177 biblioitems.editionresponsibility,
178 biblioitems.illus,
179 biblioitems.pages,
180 biblioitems.notes,
181 biblioitems.size,
182 biblioitems.place,
183 biblioitems.lccn,
184 biblioitems.url,
185 biblioitems.cn_source,
186 biblioitems.cn_class,
187 biblioitems.cn_item,
188 biblioitems.cn_suffix,
189 biblioitems.cn_sort,
190 biblioitems.totalissues
191 FROM issues
192 LEFT JOIN items ON issues.itemnumber = items.itemnumber
193 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
194 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
195 WHERE issues.borrowernumber = ?
196 AND issues.date_due < NOW()"
198 # FIXME: SELECT * across 4 tables? do we really need the marc AND marcxml blobs??
199 $sth->execute($borrowernumber);
200 my $results = $sth->fetchall_arrayref({});
201 return ( scalar(@$results), $results); # returning the count and the results is silly
204 =head2 CalcFine
206 ($amount, $chargename, $daycounttotal) = &CalcFine($item,
207 $categorycode, $branch,
208 $start_dt, $end_dt );
210 Calculates the fine for a book.
212 The issuingrules table in the Koha database is a fine matrix, listing
213 the penalties for each type of patron for each type of item and each branch (e.g., the
214 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
215 members might get a longer grace period between the first and second
216 reminders that a book is overdue).
219 C<$item> is an item object (hashref).
221 C<$categorycode> is the category code (string) of the patron who currently has
222 the book.
224 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
226 C<$start_date> & C<$end_date> are DateTime objects
227 defining the date range over which to determine the fine.
229 Fines scripts should just supply the date range over which to calculate the fine.
231 C<&CalcFine> returns four values:
233 C<$amount> is the fine owed by the patron (see above).
235 C<$chargename> is the chargename field from the applicable record in
236 the categoryitem table, whatever that is.
238 C<$unitcount> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
239 minus any applicable grace period, or hours)
241 FIXME - What is chargename supposed to be ?
243 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
244 or "Final Notice". But CalcFine never defined any value.
246 =cut
248 sub CalcFine {
249 my ( $item, $bortype, $branchcode, $due_dt, $end_date ) = @_;
250 my $start_date = $due_dt->clone();
251 # get issuingrules (fines part will be used)
252 my $itemtype = $item->{itemtype} || $item->{itype};
253 my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
254 my $fine_unit = $data->{lengthunit};
255 $fine_unit ||= 'days';
257 my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
258 my $units_minus_grace = $chargeable_units - $data->{firstremind};
259 my $amount = 0;
260 if ( $data->{'chargeperiod'} && ( $units_minus_grace > 0 ) ) {
261 my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
262 my $charge_periods = $units / $data->{'chargeperiod'};
263 # If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
264 # if chargeperiod_charge_at = 0, we charge at the end of each charge period
265 $charge_periods = $data->{'chargeperiod_charge_at'} == 1 ? ceil($charge_periods) : floor($charge_periods);
266 $amount = $charge_periods * $data->{'fine'};
267 } # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
269 $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
270 $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
271 return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
272 # FIXME: chargename is NEVER populated anywhere.
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($itemnumber, $borrowernumber, $amount, $type, $description);
474 (Note: the following is mostly conjecture and guesswork.)
476 Updates the fine owed on an overdue book.
478 C<$itemnumber> is the book's item number.
480 C<$borrowernumber> is the borrower number of the patron who currently
481 has the book on loan.
483 C<$amount> is the current amount owed by the patron.
485 C<$type> will be used in the description of the fine.
487 C<$description> is a string that must be present in the description of
488 the fine. I think this is expected to be a date in DD/MM/YYYY format.
490 C<&UpdateFine> looks up the amount currently owed on the given item
491 and sets it to C<$amount>, creating, if necessary, a new entry in the
492 accountlines table of the Koha database.
494 =cut
497 # Question: Why should the caller have to
498 # specify both the item number and the borrower number? A book can't
499 # be on loan to two different people, so the item number should be
500 # sufficient.
502 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
504 sub UpdateFine {
505 my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
506 $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
507 my $dbh = C4::Context->dbh;
508 # FIXME - What exactly is this query supposed to do? It looks up an
509 # entry in accountlines that matches the given item and borrower
510 # numbers, where the description contains $due, and where the
511 # account type has one of several values, but what does this _mean_?
512 # Does it look up existing fines for this item?
513 # FIXME - What are these various account types? ("FU", "O", "F", "M")
514 # "L" is LOST item
515 # "A" is Account Management Fee
516 # "N" is New Card
517 # "M" is Sundry
518 # "O" is Overdue ??
519 # "F" is Fine ??
520 # "FU" is Fine UPDATE??
521 # "Pay" is Payment
522 # "REF" is Cash Refund
523 my $sth = $dbh->prepare(
524 "SELECT * FROM accountlines
525 WHERE borrowernumber=?
526 AND accounttype IN ('FU','O','F','M')"
528 $sth->execute( $borrowernumber );
529 my $data;
530 my $total_amount_other = 0.00;
531 my $due_qr = qr/$due/;
532 # Cycle through the fines and
533 # - find line that relates to the requested $itemnum
534 # - accumulate fines for other items
535 # so we can update $itemnum fine taking in account fine caps
536 while (my $rec = $sth->fetchrow_hashref) {
537 if ($rec->{itemnumber} == $itemnum && $rec->{description} =~ /$due_qr/) {
538 if ($data) {
539 warn "Not a unique accountlines record for item $itemnum borrower $borrowernumber";
540 } else {
541 $data = $rec;
542 next;
545 $total_amount_other += $rec->{'amountoutstanding'};
548 if (my $maxfine = C4::Context->preference('MaxFine')) {
549 if ($total_amount_other + $amount > $maxfine) {
550 my $new_amount = $maxfine - $total_amount_other;
551 return if $new_amount <= 0.00;
552 warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
553 $amount = $new_amount;
557 if ( $data ) {
559 # we're updating an existing fine. Only modify if amount changed
560 # Note that in the current implementation, you cannot pay against an accruing fine
561 # (i.e. , of accounttype 'FU'). Doing so will break accrual.
562 if ( $data->{'amount'} != $amount ) {
563 my $diff = $amount - $data->{'amount'};
564 #3341: diff could be positive or negative!
565 my $out = $data->{'amountoutstanding'} + $diff;
566 my $query = "
567 UPDATE accountlines
568 SET date=now(), amount=?, amountoutstanding=?,
569 lastincrement=?, accounttype='FU'
570 WHERE borrowernumber=?
571 AND itemnumber=?
572 AND accounttype IN ('FU','O')
573 AND description LIKE ?
574 LIMIT 1 ";
575 my $sth2 = $dbh->prepare($query);
576 # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
577 # LIMIT 1 added to prevent multiple affected lines
578 # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.
579 # But actually, we should just have a regular autoincrementing PK and forget accountline,
580 # including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
581 # FIXME: Why only 2 account types here?
582 $debug and print STDERR "UpdateFine query: $query\n" .
583 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
584 $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
585 } else {
586 # print "no update needed $data->{'amount'}"
588 } else {
589 if ( $amount ) { # Don't add new fines with an amount of 0
590 my $sth4 = $dbh->prepare(
591 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
593 $sth4->execute($itemnum);
594 my $title = $sth4->fetchrow;
596 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
598 my $desc = ( $type ? "$type " : '' ) . "$title $due"; # FIXEDME, avoid whitespace prefix on empty $type
600 my $query = "INSERT INTO accountlines
601 (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
602 VALUES (?,?,now(),?,?,'FU',?,?,?)";
603 my $sth2 = $dbh->prepare($query);
604 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
605 $sth2->execute( $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno );
608 # logging action
609 &logaction(
610 "FINES",
611 $type,
612 $borrowernumber,
613 "due=".$due." amount=".$amount." itemnumber=".$itemnum
614 ) if C4::Context->preference("FinesLog");
617 =head2 BorType
619 $borrower = &BorType($borrowernumber);
621 Looks up a patron by borrower number.
623 C<$borrower> is a reference-to-hash whose keys are all of the fields
624 from the borrowers and categories tables of the Koha database. Thus,
625 C<$borrower> contains all information about both the borrower and
626 category he or she belongs to.
628 =cut
630 sub BorType {
631 my ($borrowernumber) = @_;
632 my $dbh = C4::Context->dbh;
633 my $sth = $dbh->prepare(
634 "SELECT * from borrowers
635 LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
636 WHERE borrowernumber=?"
638 $sth->execute($borrowernumber);
639 return $sth->fetchrow_hashref;
642 =head2 GetFine
644 $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
646 return the total of fine
648 C<$itemnum> is item number
650 C<$borrowernumber> is the borrowernumber
652 =cut
654 sub GetFine {
655 my ( $itemnum, $borrowernumber ) = @_;
656 my $dbh = C4::Context->dbh();
657 my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
658 where accounttype like 'F%'
659 AND amountoutstanding > 0 AND borrowernumber=?|;
660 my @query_param;
661 push @query_param, $borrowernumber;
662 if (defined $itemnum )
664 $query .= " AND itemnumber=?";
665 push @query_param, $itemnum;
667 my $sth = $dbh->prepare($query);
668 $sth->execute( @query_param );
669 my $fine = $sth->fetchrow_hashref();
670 if ($fine->{fineamount}) {
671 return $fine->{fineamount};
673 return 0;
676 =head2 NumberNotifyId
678 (@notify) = &NumberNotifyId($borrowernumber);
680 Returns amount for all file per borrowers
681 C<@notify> array contains all file per borrowers
683 C<$notify_id> contains the file number for the borrower number nad item number
685 =cut
687 sub NumberNotifyId{
688 my ($borrowernumber)=@_;
689 my $dbh = C4::Context->dbh;
690 my $query=qq| SELECT distinct(notify_id)
691 FROM accountlines
692 WHERE borrowernumber=?|;
693 my @notify;
694 my $sth = $dbh->prepare($query);
695 $sth->execute($borrowernumber);
696 while ( my ($numberofnotify) = $sth->fetchrow ) {
697 push( @notify, $numberofnotify );
699 return (@notify);
702 =head2 AmountNotify
704 ($totalnotify) = &AmountNotify($notifyid);
706 Returns amount for all file per borrowers
707 C<$notifyid> is the file number
709 C<$totalnotify> contains amount of a file
711 C<$notify_id> contains the file number for the borrower number and item number
713 =cut
715 sub AmountNotify{
716 my ($notifyid,$borrowernumber)=@_;
717 my $dbh = C4::Context->dbh;
718 my $query=qq| SELECT sum(amountoutstanding)
719 FROM accountlines
720 WHERE notify_id=? AND borrowernumber = ?|;
721 my $sth=$dbh->prepare($query);
722 $sth->execute($notifyid,$borrowernumber);
723 my $totalnotify=$sth->fetchrow;
724 $sth->finish;
725 return ($totalnotify);
728 =head2 GetItems
730 ($items) = &GetItems($itemnumber);
732 Returns the list of all delays from overduerules.
734 C<$items> is a reference-to-hash whose keys are all of the fields
735 from the items tables of the Koha database. Thus,
737 C<$itemnumber> contains the borrower categorycode
739 =cut
741 # FIXME: This is a bad function to have here.
742 # Shouldn't it be in C4::Items?
743 # Shouldn't it be called GetItem since you only get 1 row?
744 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
746 sub GetItems {
747 my $itemnumber = shift or return;
748 my $query = qq|SELECT *
749 FROM items
750 WHERE itemnumber=?|;
751 my $sth = C4::Context->dbh->prepare($query);
752 $sth->execute($itemnumber);
753 my ($items) = $sth->fetchrow_hashref;
754 return ($items);
757 =head2 GetBranchcodesWithOverdueRules
759 my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
761 returns a list of branch codes for branches with overdue rules defined.
763 =cut
765 sub GetBranchcodesWithOverdueRules {
766 my $dbh = C4::Context->dbh;
767 my $branchcodes = $dbh->selectcol_arrayref(q|
768 SELECT DISTINCT(branchcode)
769 FROM overduerules
770 WHERE delay1 IS NOT NULL
771 ORDER BY branchcode
773 if ( $branchcodes->[0] eq '' ) {
774 # If a default rule exists, all branches should be returned
775 my $availbranches = C4::Branch::GetBranches();
776 return keys %$availbranches;
778 return @$branchcodes;
781 =head2 CheckItemNotify
783 Sql request to check if the document has alreday been notified
784 this function is not exported, only used with GetOverduesForBranch
786 =cut
788 sub CheckItemNotify {
789 my ($notify_id,$notify_level,$itemnumber) = @_;
790 my $dbh = C4::Context->dbh;
791 my $sth = $dbh->prepare("
792 SELECT COUNT(*)
793 FROM notifys
794 WHERE notify_id = ?
795 AND notify_level = ?
796 AND itemnumber = ? ");
797 $sth->execute($notify_id,$notify_level,$itemnumber);
798 my $notified = $sth->fetchrow;
799 return ($notified);
802 =head2 GetOverduesForBranch
804 Sql request for display all information for branchoverdues.pl
805 2 possibilities : with or without location .
806 display is filtered by branch
808 FIXME: This function should be renamed.
810 =cut
812 sub GetOverduesForBranch {
813 my ( $branch, $location) = @_;
814 my $itype_link = (C4::Context->preference('item-level_itypes')) ? " items.itype " : " biblioitems.itemtype ";
815 my $dbh = C4::Context->dbh;
816 my $select = "
817 SELECT
818 borrowers.cardnumber,
819 borrowers.borrowernumber,
820 borrowers.surname,
821 borrowers.firstname,
822 borrowers.phone,
823 borrowers.email,
824 biblio.title,
825 biblio.author,
826 biblio.biblionumber,
827 issues.date_due,
828 issues.returndate,
829 issues.branchcode,
830 branches.branchname,
831 items.barcode,
832 items.homebranch,
833 items.itemcallnumber,
834 items.location,
835 items.itemnumber,
836 itemtypes.description,
837 accountlines.notify_id,
838 accountlines.notify_level,
839 accountlines.amountoutstanding
840 FROM accountlines
841 LEFT JOIN issues ON issues.itemnumber = accountlines.itemnumber
842 AND issues.borrowernumber = accountlines.borrowernumber
843 LEFT JOIN borrowers ON borrowers.borrowernumber = accountlines.borrowernumber
844 LEFT JOIN items ON items.itemnumber = issues.itemnumber
845 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
846 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
847 LEFT JOIN itemtypes ON itemtypes.itemtype = $itype_link
848 LEFT JOIN branches ON branches.branchcode = issues.branchcode
849 WHERE (accountlines.amountoutstanding != '0.000000')
850 AND (accountlines.accounttype = 'FU' )
851 AND (issues.branchcode = ? )
852 AND (issues.date_due < NOW())
854 my @getoverdues;
855 my $i = 0;
856 my $sth;
857 if ($location) {
858 $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
859 $sth->execute($branch, $location);
860 } else {
861 $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
862 $sth->execute($branch);
864 while ( my $data = $sth->fetchrow_hashref ) {
865 #check if the document has already been notified
866 my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
867 if ($countnotify eq '0') {
868 $getoverdues[$i] = $data;
869 $i++;
872 return (@getoverdues);
876 =head2 AddNotifyLine
878 &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
880 Create a line into notify, if the method is phone, the notification_send_date is implemented to
882 =cut
884 sub AddNotifyLine {
885 my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
886 my $dbh = C4::Context->dbh;
887 if ( $method eq "phone" ) {
888 my $sth = $dbh->prepare(
889 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
890 VALUES (?,?,now(),now(),?,?,?)"
892 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
893 $notifyId );
895 else {
896 my $sth = $dbh->prepare(
897 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
898 VALUES (?,?,now(),?,?,?)"
900 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
901 $notifyId );
903 return 1;
906 =head2 RemoveNotifyLine
908 &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
910 Cancel a notification
912 =cut
914 sub RemoveNotifyLine {
915 my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
916 my $dbh = C4::Context->dbh;
917 my $sth = $dbh->prepare(
918 "DELETE FROM notifys
919 WHERE
920 borrowernumber=?
921 AND itemnumber=?
922 AND notify_date=?"
924 $sth->execute( $borrowernumber, $itemnumber, $notify_date );
925 return 1;
928 =head2 GetOverdueMessageTransportTypes
930 my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
932 return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
934 =cut
936 sub GetOverdueMessageTransportTypes {
937 my ( $branchcode, $categorycode, $letternumber ) = @_;
938 return unless $categorycode and $letternumber;
939 my $dbh = C4::Context->dbh;
940 my $sth = $dbh->prepare("
941 SELECT message_transport_type FROM overduerules_transport_types
942 WHERE branchcode = ? AND categorycode = ? AND letternumber = ?
944 $sth->execute( $branchcode, $categorycode, $letternumber );
945 my @mtts;
946 while ( my $mtt = $sth->fetchrow ) {
947 push @mtts, $mtt;
950 # Put 'print' in first if exists
951 # It avoid to sent a print notice with an email or sms template is no email or sms is defined
952 @mtts = uniq( 'print', @mtts )
953 if grep {/^print$/} @mtts;
955 return \@mtts;
958 =head2 parse_overdues_letter
960 parses the letter template, replacing the placeholders with data
961 specific to this patron, biblio, or item for overdues
963 named parameters:
964 letter - required hashref
965 borrowernumber - required integer
966 substitute - optional hashref of other key/value pairs that should
967 be substituted in the letter content
969 returns the C<letter> hashref, with the content updated to reflect the
970 substituted keys and values.
972 =cut
974 sub parse_overdues_letter {
975 my $params = shift;
976 foreach my $required (qw( letter_code borrowernumber )) {
977 return unless ( exists $params->{$required} && $params->{$required} );
980 my $substitute = $params->{'substitute'} || {};
981 $substitute->{today} ||= C4::Dates->new()->output("syspref");
983 my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
984 if ( my $p = $params->{'branchcode'} ) {
985 $tables{'branches'} = $p;
988 my $currencies = GetCurrency();
989 my $currency_format;
990 $currency_format = $currencies->{currency} if defined($currencies);
992 my @item_tables;
993 if ( my $i = $params->{'items'} ) {
994 my $item_format = '';
995 foreach my $item (@$i) {
996 my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
997 if ( !$item_format and defined $params->{'letter'}->{'content'} ) {
998 $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
999 $item_format = $1;
1002 $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
1003 # if active currency isn't correct ISO code fallback to sprintf
1004 $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
1006 push @item_tables, {
1007 'biblio' => $item->{'biblionumber'},
1008 'biblioitems' => $item->{'biblionumber'},
1009 'items' => $item,
1010 'issues' => $item->{'itemnumber'},
1015 return C4::Letters::GetPreparedLetter (
1016 module => 'circulation',
1017 letter_code => $params->{'letter_code'},
1018 branchcode => $params->{'branchcode'},
1019 tables => \%tables,
1020 substitute => $substitute,
1021 repeat => { item => \@item_tables },
1022 message_transport_type => $params->{message_transport_type},
1027 __END__
1029 =head1 AUTHOR
1031 Koha Development Team <http://koha-community.org/>
1033 =cut