Bug 13177: Help page for Rotating Collections
[koha.git] / C4 / Overdues.pm
blob37711badecb3da25398d32fd4b06396341996656
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);
36 use Koha::DateUtils;
38 use vars qw($VERSION @ISA @EXPORT);
40 BEGIN {
41 # set the version for version checking
42 $VERSION = 3.07.00.049;
43 require Exporter;
44 @ISA = qw(Exporter);
46 # subs to rename (and maybe merge some...)
47 push @EXPORT, qw(
48 &CalcFine
49 &Getoverdues
50 &checkoverdues
51 &NumberNotifyId
52 &AmountNotify
53 &UpdateFine
54 &GetFine
55 &get_chargeable_units
56 &CheckItemNotify
57 &GetOverduesForBranch
58 &RemoveNotifyLine
59 &AddNotifyLine
60 &GetOverdueMessageTransportTypes
61 &parse_overdues_letter
64 # subs to remove
65 push @EXPORT, qw(
66 &BorType
69 # check that an equivalent don't exist already before moving
71 # subs to move to Circulation.pm
72 push @EXPORT, qw(
73 &GetIssuesIteminfo
76 # &GetIssuingRules - delete.
77 # use C4::Circulation::GetIssuingRule instead.
79 # subs to move to Biblio.pm
80 push @EXPORT, qw(
81 &GetItems
85 =head1 NAME
87 C4::Circulation::Fines - Koha module dealing with fines
89 =head1 SYNOPSIS
91 use C4::Overdues;
93 =head1 DESCRIPTION
95 This module contains several functions for dealing with fines for
96 overdue items. It is primarily used by the 'misc/fines2.pl' script.
98 =head1 FUNCTIONS
100 =head2 Getoverdues
102 $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
104 Returns the list of all overdue books, with their itemtype.
106 C<$overdues> is a reference-to-array. Each element is a
107 reference-to-hash whose keys are the fields of the issues table in the
108 Koha database.
110 =cut
113 sub Getoverdues {
114 my $params = shift;
115 my $dbh = C4::Context->dbh;
116 my $statement;
117 if ( C4::Context->preference('item-level_itypes') ) {
118 $statement = "
119 SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode, items.itemlost, items.replacementprice
120 FROM issues
121 LEFT JOIN items USING (itemnumber)
122 WHERE date_due < NOW()
124 } else {
125 $statement = "
126 SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode, items.itemlost, replacementprice
127 FROM issues
128 LEFT JOIN items USING (itemnumber)
129 LEFT JOIN biblioitems USING (biblioitemnumber)
130 WHERE date_due < NOW()
134 my @bind_parameters;
135 if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
136 $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
137 push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
138 } elsif ( exists $params->{'minimumdays'} ) {
139 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
140 push @bind_parameters, $params->{'minimumdays'};
141 } elsif ( exists $params->{'maximumdays'} ) {
142 $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
143 push @bind_parameters, $params->{'maximumdays'};
145 $statement .= 'ORDER BY borrowernumber';
146 my $sth = $dbh->prepare( $statement );
147 $sth->execute( @bind_parameters );
148 return $sth->fetchall_arrayref({});
152 =head2 checkoverdues
154 ($count, $overdueitems) = checkoverdues($borrowernumber);
156 Returns a count and a list of overdueitems for a given borrowernumber
158 =cut
160 sub checkoverdues {
161 my $borrowernumber = shift or return;
162 # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
163 my $sth = C4::Context->dbh->prepare(
164 "SELECT biblio.*, items.*, issues.*,
165 biblioitems.volume,
166 biblioitems.number,
167 biblioitems.itemtype,
168 biblioitems.isbn,
169 biblioitems.issn,
170 biblioitems.publicationyear,
171 biblioitems.publishercode,
172 biblioitems.volumedate,
173 biblioitems.volumedesc,
174 biblioitems.collectiontitle,
175 biblioitems.collectionissn,
176 biblioitems.collectionvolume,
177 biblioitems.editionstatement,
178 biblioitems.editionresponsibility,
179 biblioitems.illus,
180 biblioitems.pages,
181 biblioitems.notes,
182 biblioitems.size,
183 biblioitems.place,
184 biblioitems.lccn,
185 biblioitems.url,
186 biblioitems.cn_source,
187 biblioitems.cn_class,
188 biblioitems.cn_item,
189 biblioitems.cn_suffix,
190 biblioitems.cn_sort,
191 biblioitems.totalissues
192 FROM issues
193 LEFT JOIN items ON issues.itemnumber = items.itemnumber
194 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
195 LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
196 WHERE issues.borrowernumber = ?
197 AND issues.date_due < NOW()"
199 # FIXME: SELECT * across 4 tables? do we really need the marc AND marcxml blobs??
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($itemnumber, $borrowernumber, $amount, $type, $description);
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<$description> is a string that must be present in the description of
490 the fine. I think this is expected to be a date in DD/MM/YYYY format.
492 C<&UpdateFine> looks up the amount currently owed on the given item
493 and sets it to C<$amount>, creating, if necessary, a new entry in the
494 accountlines table of the Koha database.
496 =cut
499 # Question: Why should the caller have to
500 # specify both the item number and the borrower number? A book can't
501 # be on loan to two different people, so the item number should be
502 # sufficient.
504 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
506 sub UpdateFine {
507 my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
508 $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
509 my $dbh = C4::Context->dbh;
510 # FIXME - What exactly is this query supposed to do? It looks up an
511 # entry in accountlines that matches the given item and borrower
512 # numbers, where the description contains $due, and where the
513 # account type has one of several values, but what does this _mean_?
514 # Does it look up existing fines for this item?
515 # FIXME - What are these various account types? ("FU", "O", "F", "M")
516 # "L" is LOST item
517 # "A" is Account Management Fee
518 # "N" is New Card
519 # "M" is Sundry
520 # "O" is Overdue ??
521 # "F" is Fine ??
522 # "FU" is Fine UPDATE??
523 # "Pay" is Payment
524 # "REF" is Cash Refund
525 my $sth = $dbh->prepare(
526 "SELECT * FROM accountlines
527 WHERE borrowernumber=?
528 AND accounttype IN ('FU','O','F','M')"
530 $sth->execute( $borrowernumber );
531 my $data;
532 my $total_amount_other = 0.00;
533 my $due_qr = qr/$due/;
534 # Cycle through the fines and
535 # - find line that relates to the requested $itemnum
536 # - accumulate fines for other items
537 # so we can update $itemnum fine taking in account fine caps
538 while (my $rec = $sth->fetchrow_hashref) {
539 if ($rec->{itemnumber} == $itemnum && $rec->{description} =~ /$due_qr/) {
540 if ($data) {
541 warn "Not a unique accountlines record for item $itemnum borrower $borrowernumber";
542 } else {
543 $data = $rec;
544 next;
547 $total_amount_other += $rec->{'amountoutstanding'};
550 if (my $maxfine = C4::Context->preference('MaxFine')) {
551 if ($total_amount_other + $amount > $maxfine) {
552 my $new_amount = $maxfine - $total_amount_other;
553 return if $new_amount <= 0.00;
554 warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
555 $amount = $new_amount;
559 if ( $data ) {
561 # we're updating an existing fine. Only modify if amount changed
562 # Note that in the current implementation, you cannot pay against an accruing fine
563 # (i.e. , of accounttype 'FU'). Doing so will break accrual.
564 if ( $data->{'amount'} != $amount ) {
565 my $diff = $amount - $data->{'amount'};
566 #3341: diff could be positive or negative!
567 my $out = $data->{'amountoutstanding'} + $diff;
568 my $query = "
569 UPDATE accountlines
570 SET date=now(), amount=?, amountoutstanding=?,
571 lastincrement=?, accounttype='FU'
572 WHERE borrowernumber=?
573 AND itemnumber=?
574 AND accounttype IN ('FU','O')
575 AND description LIKE ?
576 LIMIT 1 ";
577 my $sth2 = $dbh->prepare($query);
578 # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
579 # LIMIT 1 added to prevent multiple affected lines
580 # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.
581 # But actually, we should just have a regular autoincrementing PK and forget accountline,
582 # including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
583 # FIXME: Why only 2 account types here?
584 $debug and print STDERR "UpdateFine query: $query\n" .
585 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
586 $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
587 } else {
588 # print "no update needed $data->{'amount'}"
590 } else {
591 if ( $amount ) { # Don't add new fines with an amount of 0
592 my $sth4 = $dbh->prepare(
593 "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
595 $sth4->execute($itemnum);
596 my $title = $sth4->fetchrow;
598 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
600 my $desc = ( $type ? "$type " : '' ) . "$title $due"; # FIXEDME, avoid whitespace prefix on empty $type
602 my $query = "INSERT INTO accountlines
603 (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
604 VALUES (?,?,now(),?,?,'FU',?,?,?)";
605 my $sth2 = $dbh->prepare($query);
606 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
607 $sth2->execute( $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno );
610 # logging action
611 &logaction(
612 "FINES",
613 $type,
614 $borrowernumber,
615 "due=".$due." amount=".$amount." itemnumber=".$itemnum
616 ) if C4::Context->preference("FinesLog");
619 =head2 BorType
621 $borrower = &BorType($borrowernumber);
623 Looks up a patron by borrower number.
625 C<$borrower> is a reference-to-hash whose keys are all of the fields
626 from the borrowers and categories tables of the Koha database. Thus,
627 C<$borrower> contains all information about both the borrower and
628 category he or she belongs to.
630 =cut
632 sub BorType {
633 my ($borrowernumber) = @_;
634 my $dbh = C4::Context->dbh;
635 my $sth = $dbh->prepare(
636 "SELECT * from borrowers
637 LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
638 WHERE borrowernumber=?"
640 $sth->execute($borrowernumber);
641 return $sth->fetchrow_hashref;
644 =head2 GetFine
646 $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
648 return the total of fine
650 C<$itemnum> is item number
652 C<$borrowernumber> is the borrowernumber
654 =cut
656 sub GetFine {
657 my ( $itemnum, $borrowernumber ) = @_;
658 my $dbh = C4::Context->dbh();
659 my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
660 where accounttype like 'F%'
661 AND amountoutstanding > 0 AND borrowernumber=?|;
662 my @query_param;
663 push @query_param, $borrowernumber;
664 if (defined $itemnum )
666 $query .= " AND itemnumber=?";
667 push @query_param, $itemnum;
669 my $sth = $dbh->prepare($query);
670 $sth->execute( @query_param );
671 my $fine = $sth->fetchrow_hashref();
672 if ($fine->{fineamount}) {
673 return $fine->{fineamount};
675 return 0;
678 =head2 NumberNotifyId
680 (@notify) = &NumberNotifyId($borrowernumber);
682 Returns amount for all file per borrowers
683 C<@notify> array contains all file per borrowers
685 C<$notify_id> contains the file number for the borrower number nad item number
687 =cut
689 sub NumberNotifyId{
690 my ($borrowernumber)=@_;
691 my $dbh = C4::Context->dbh;
692 my $query=qq| SELECT distinct(notify_id)
693 FROM accountlines
694 WHERE borrowernumber=?|;
695 my @notify;
696 my $sth = $dbh->prepare($query);
697 $sth->execute($borrowernumber);
698 while ( my ($numberofnotify) = $sth->fetchrow ) {
699 push( @notify, $numberofnotify );
701 return (@notify);
704 =head2 AmountNotify
706 ($totalnotify) = &AmountNotify($notifyid);
708 Returns amount for all file per borrowers
709 C<$notifyid> is the file number
711 C<$totalnotify> contains amount of a file
713 C<$notify_id> contains the file number for the borrower number and item number
715 =cut
717 sub AmountNotify{
718 my ($notifyid,$borrowernumber)=@_;
719 my $dbh = C4::Context->dbh;
720 my $query=qq| SELECT sum(amountoutstanding)
721 FROM accountlines
722 WHERE notify_id=? AND borrowernumber = ?|;
723 my $sth=$dbh->prepare($query);
724 $sth->execute($notifyid,$borrowernumber);
725 my $totalnotify=$sth->fetchrow;
726 $sth->finish;
727 return ($totalnotify);
730 =head2 GetItems
732 ($items) = &GetItems($itemnumber);
734 Returns the list of all delays from overduerules.
736 C<$items> is a reference-to-hash whose keys are all of the fields
737 from the items tables of the Koha database. Thus,
739 C<$itemnumber> contains the borrower categorycode
741 =cut
743 # FIXME: This is a bad function to have here.
744 # Shouldn't it be in C4::Items?
745 # Shouldn't it be called GetItem since you only get 1 row?
746 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
748 sub GetItems {
749 my $itemnumber = shift or return;
750 my $query = qq|SELECT *
751 FROM items
752 WHERE itemnumber=?|;
753 my $sth = C4::Context->dbh->prepare($query);
754 $sth->execute($itemnumber);
755 my ($items) = $sth->fetchrow_hashref;
756 return ($items);
759 =head2 GetBranchcodesWithOverdueRules
761 my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
763 returns a list of branch codes for branches with overdue rules defined.
765 =cut
767 sub GetBranchcodesWithOverdueRules {
768 my $dbh = C4::Context->dbh;
769 my $branchcodes = $dbh->selectcol_arrayref(q|
770 SELECT DISTINCT(branchcode)
771 FROM overduerules
772 WHERE delay1 IS NOT NULL
773 ORDER BY branchcode
775 if ( $branchcodes->[0] eq '' ) {
776 # If a default rule exists, all branches should be returned
777 my $availbranches = C4::Branch::GetBranches();
778 return keys %$availbranches;
780 return @$branchcodes;
783 =head2 CheckItemNotify
785 Sql request to check if the document has alreday been notified
786 this function is not exported, only used with GetOverduesForBranch
788 =cut
790 sub CheckItemNotify {
791 my ($notify_id,$notify_level,$itemnumber) = @_;
792 my $dbh = C4::Context->dbh;
793 my $sth = $dbh->prepare("
794 SELECT COUNT(*)
795 FROM notifys
796 WHERE notify_id = ?
797 AND notify_level = ?
798 AND itemnumber = ? ");
799 $sth->execute($notify_id,$notify_level,$itemnumber);
800 my $notified = $sth->fetchrow;
801 return ($notified);
804 =head2 GetOverduesForBranch
806 Sql request for display all information for branchoverdues.pl
807 2 possibilities : with or without location .
808 display is filtered by branch
810 FIXME: This function should be renamed.
812 =cut
814 sub GetOverduesForBranch {
815 my ( $branch, $location) = @_;
816 my $itype_link = (C4::Context->preference('item-level_itypes')) ? " items.itype " : " biblioitems.itemtype ";
817 my $dbh = C4::Context->dbh;
818 my $select = "
819 SELECT
820 borrowers.cardnumber,
821 borrowers.borrowernumber,
822 borrowers.surname,
823 borrowers.firstname,
824 borrowers.phone,
825 borrowers.email,
826 biblio.title,
827 biblio.author,
828 biblio.biblionumber,
829 issues.date_due,
830 issues.returndate,
831 issues.branchcode,
832 branches.branchname,
833 items.barcode,
834 items.homebranch,
835 items.itemcallnumber,
836 items.location,
837 items.itemnumber,
838 itemtypes.description,
839 accountlines.notify_id,
840 accountlines.notify_level,
841 accountlines.amountoutstanding
842 FROM accountlines
843 LEFT JOIN issues ON issues.itemnumber = accountlines.itemnumber
844 AND issues.borrowernumber = accountlines.borrowernumber
845 LEFT JOIN borrowers ON borrowers.borrowernumber = accountlines.borrowernumber
846 LEFT JOIN items ON items.itemnumber = issues.itemnumber
847 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
848 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
849 LEFT JOIN itemtypes ON itemtypes.itemtype = $itype_link
850 LEFT JOIN branches ON branches.branchcode = issues.branchcode
851 WHERE (accountlines.amountoutstanding != '0.000000')
852 AND (accountlines.accounttype = 'FU' )
853 AND (issues.branchcode = ? )
854 AND (issues.date_due < NOW())
856 my @getoverdues;
857 my $i = 0;
858 my $sth;
859 if ($location) {
860 $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
861 $sth->execute($branch, $location);
862 } else {
863 $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
864 $sth->execute($branch);
866 while ( my $data = $sth->fetchrow_hashref ) {
867 #check if the document has already been notified
868 my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
869 if ($countnotify eq '0') {
870 $getoverdues[$i] = $data;
871 $i++;
874 return (@getoverdues);
878 =head2 AddNotifyLine
880 &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
882 Create a line into notify, if the method is phone, the notification_send_date is implemented to
884 =cut
886 sub AddNotifyLine {
887 my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
888 my $dbh = C4::Context->dbh;
889 if ( $method eq "phone" ) {
890 my $sth = $dbh->prepare(
891 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
892 VALUES (?,?,now(),now(),?,?,?)"
894 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
895 $notifyId );
897 else {
898 my $sth = $dbh->prepare(
899 "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
900 VALUES (?,?,now(),?,?,?)"
902 $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
903 $notifyId );
905 return 1;
908 =head2 RemoveNotifyLine
910 &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
912 Cancel a notification
914 =cut
916 sub RemoveNotifyLine {
917 my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
918 my $dbh = C4::Context->dbh;
919 my $sth = $dbh->prepare(
920 "DELETE FROM notifys
921 WHERE
922 borrowernumber=?
923 AND itemnumber=?
924 AND notify_date=?"
926 $sth->execute( $borrowernumber, $itemnumber, $notify_date );
927 return 1;
930 =head2 GetOverdueMessageTransportTypes
932 my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
934 return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
936 =cut
938 sub GetOverdueMessageTransportTypes {
939 my ( $branchcode, $categorycode, $letternumber ) = @_;
940 return unless $categorycode and $letternumber;
941 my $dbh = C4::Context->dbh;
942 my $sth = $dbh->prepare("
943 SELECT message_transport_type
944 FROM overduerules odr LEFT JOIN overduerules_transport_types ott USING (overduerules_id)
945 WHERE branchcode = ?
946 AND categorycode = ?
947 AND letternumber = ?
949 $sth->execute( $branchcode, $categorycode, $letternumber );
950 my @mtts;
951 while ( my $mtt = $sth->fetchrow ) {
952 push @mtts, $mtt;
955 # Put 'print' in first if exists
956 # It avoid to sent a print notice with an email or sms template is no email or sms is defined
957 @mtts = uniq( 'print', @mtts )
958 if grep {/^print$/} @mtts;
960 return \@mtts;
963 =head2 parse_overdues_letter
965 parses the letter template, replacing the placeholders with data
966 specific to this patron, biblio, or item for overdues
968 named parameters:
969 letter - required hashref
970 borrowernumber - required integer
971 substitute - optional hashref of other key/value pairs that should
972 be substituted in the letter content
974 returns the C<letter> hashref, with the content updated to reflect the
975 substituted keys and values.
977 =cut
979 sub parse_overdues_letter {
980 my $params = shift;
981 foreach my $required (qw( letter_code borrowernumber )) {
982 return unless ( exists $params->{$required} && $params->{$required} );
985 my $substitute = $params->{'substitute'} || {};
986 $substitute->{today} ||= output_pref( { dt => dt_from_string, dateonly => 1} );
988 my %tables = ( 'borrowers' => $params->{'borrowernumber'} );
989 if ( my $p = $params->{'branchcode'} ) {
990 $tables{'branches'} = $p;
993 my $currencies = GetCurrency();
994 my $currency_format;
995 $currency_format = $currencies->{currency} if defined($currencies);
997 my @item_tables;
998 if ( my $i = $params->{'items'} ) {
999 my $item_format = '';
1000 foreach my $item (@$i) {
1001 my $fine = GetFine($item->{'itemnumber'}, $params->{'borrowernumber'});
1002 if ( !$item_format and defined $params->{'letter'}->{'content'} ) {
1003 $params->{'letter'}->{'content'} =~ m/(<item>.*<\/item>)/;
1004 $item_format = $1;
1007 $item->{'fine'} = currency_format($currency_format, "$fine", FMT_SYMBOL);
1008 # if active currency isn't correct ISO code fallback to sprintf
1009 $item->{'fine'} = sprintf('%.2f', $fine) unless $item->{'fine'};
1011 push @item_tables, {
1012 'biblio' => $item->{'biblionumber'},
1013 'biblioitems' => $item->{'biblionumber'},
1014 'items' => $item,
1015 'issues' => $item->{'itemnumber'},
1020 return C4::Letters::GetPreparedLetter (
1021 module => 'circulation',
1022 letter_code => $params->{'letter_code'},
1023 branchcode => $params->{'branchcode'},
1024 tables => \%tables,
1025 substitute => $substitute,
1026 repeat => { item => \@item_tables },
1027 message_transport_type => $params->{message_transport_type},
1032 __END__
1034 =head1 AUTHOR
1036 Koha Development Team <http://koha-community.org/>
1038 =cut