Bug 9016: (follow-up) fix tab characters and POD.
[koha.git] / C4 / Overdues.pm
1 package C4::Overdues;
2
3
4 # Copyright 2000-2002 Katipo Communications
5 # copyright 2010 BibLibre
6 #
7 # This file is part of Koha.
8 #
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
13 #
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
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 C4::Circulation;
27 use C4::Context;
28 use C4::Accounts;
29 use C4::Log; # logaction
30 use C4::Debug;
31
32 use vars qw($VERSION @ISA @EXPORT);
33
34 BEGIN {
35         # set the version for version checking
36     $VERSION = 3.07.00.049;
37         require Exporter;
38         @ISA    = qw(Exporter);
39         # subs to rename (and maybe merge some...)
40         push @EXPORT, qw(
41         &CalcFine
42         &Getoverdues
43         &checkoverdues
44         &NumberNotifyId
45         &AmountNotify
46         &UpdateFine
47         &GetFine
48         
49         &CheckItemNotify
50         &GetOverduesForBranch
51         &RemoveNotifyLine
52         &AddNotifyLine
53         &GetOverdueMessageTransportTypes
54         );
55         # subs to remove
56         push @EXPORT, qw(
57         &BorType
58         );
59
60         # check that an equivalent don't exist already before moving
61
62         # subs to move to Circulation.pm
63         push @EXPORT, qw(
64         &GetIssuesIteminfo
65         );
66
67      # &GetIssuingRules - delete.
68    # use C4::Circulation::GetIssuingRule instead.
69
70         # subs to move to Biblio.pm
71         push @EXPORT, qw(
72         &GetItems
73         );
74 }
75
76 =head1 NAME
77
78 C4::Circulation::Fines - Koha module dealing with fines
79
80 =head1 SYNOPSIS
81
82   use C4::Overdues;
83
84 =head1 DESCRIPTION
85
86 This module contains several functions for dealing with fines for
87 overdue items. It is primarily used by the 'misc/fines2.pl' script.
88
89 =head1 FUNCTIONS
90
91 =head2 Getoverdues
92
93   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
94
95 Returns the list of all overdue books, with their itemtype.
96
97 C<$overdues> is a reference-to-array. Each element is a
98 reference-to-hash whose keys are the fields of the issues table in the
99 Koha database.
100
101 =cut
102
103 #'
104 sub Getoverdues {
105     my $params = shift;
106     my $dbh = C4::Context->dbh;
107     my $statement;
108     if ( C4::Context->preference('item-level_itypes') ) {
109         $statement = "
110    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
111      FROM issues 
112 LEFT JOIN items       USING (itemnumber)
113     WHERE date_due < NOW()
114 ";
115     } else {
116         $statement = "
117    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode
118      FROM issues 
119 LEFT JOIN items       USING (itemnumber)
120 LEFT JOIN biblioitems USING (biblioitemnumber)
121     WHERE date_due < NOW()
122 ";
123     }
124
125     my @bind_parameters;
126     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
127         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
128         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
129     } elsif ( exists $params->{'minimumdays'} ) {
130         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
131         push @bind_parameters, $params->{'minimumdays'};
132     } elsif ( exists $params->{'maximumdays'} ) {
133         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
134         push @bind_parameters, $params->{'maximumdays'};
135     }
136     $statement .= 'ORDER BY borrowernumber';
137     my $sth = $dbh->prepare( $statement );
138     $sth->execute( @bind_parameters );
139     return $sth->fetchall_arrayref({});
140 }
141
142
143 =head2 checkoverdues
144
145     ($count, $overdueitems) = checkoverdues($borrowernumber);
146
147 Returns a count and a list of overdueitems for a given borrowernumber
148
149 =cut
150
151 sub checkoverdues {
152     my $borrowernumber = shift or return;
153     # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
154     my $sth = C4::Context->dbh->prepare(
155         "SELECT biblio.*, items.*, issues.*,
156                 biblioitems.volume,
157                 biblioitems.number,
158                 biblioitems.itemtype,
159                 biblioitems.isbn,
160                 biblioitems.issn,
161                 biblioitems.publicationyear,
162                 biblioitems.publishercode,
163                 biblioitems.volumedate,
164                 biblioitems.volumedesc,
165                 biblioitems.collectiontitle,
166                 biblioitems.collectionissn,
167                 biblioitems.collectionvolume,
168                 biblioitems.editionstatement,
169                 biblioitems.editionresponsibility,
170                 biblioitems.illus,
171                 biblioitems.pages,
172                 biblioitems.notes,
173                 biblioitems.size,
174                 biblioitems.place,
175                 biblioitems.lccn,
176                 biblioitems.url,
177                 biblioitems.cn_source,
178                 biblioitems.cn_class,
179                 biblioitems.cn_item,
180                 biblioitems.cn_suffix,
181                 biblioitems.cn_sort,
182                 biblioitems.totalissues
183          FROM issues
184          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
185          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
186          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
187             WHERE issues.borrowernumber  = ?
188             AND   issues.date_due < NOW()"
189     );
190     # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
191     $sth->execute($borrowernumber);
192     my $results = $sth->fetchall_arrayref({});
193     return ( scalar(@$results), $results);  # returning the count and the results is silly
194 }
195
196 =head2 CalcFine
197
198     ($amount, $chargename,  $daycounttotal) = &CalcFine($item,
199                                   $categorycode, $branch,
200                                   $start_dt, $end_dt );
201
202 Calculates the fine for a book.
203
204 The issuingrules table in the Koha database is a fine matrix, listing
205 the penalties for each type of patron for each type of item and each branch (e.g., the
206 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
207 members might get a longer grace period between the first and second
208 reminders that a book is overdue).
209
210
211 C<$item> is an item object (hashref).
212
213 C<$categorycode> is the category code (string) of the patron who currently has
214 the book.
215
216 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
217
218 C<$start_date> & C<$end_date> are DateTime objects
219 defining the date range over which to determine the fine.
220
221 Fines scripts should just supply the date range over which to calculate the fine.
222
223 C<&CalcFine> returns four values:
224
225 C<$amount> is the fine owed by the patron (see above).
226
227 C<$chargename> is the chargename field from the applicable record in
228 the categoryitem table, whatever that is.
229
230 C<$unitcount> is the number of chargeable units (days between start and end dates, Calendar adjusted where needed,
231 minus any applicable grace period, or hours)
232
233 FIXME - What is chargename supposed to be ?
234
235 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
236 or "Final Notice".  But CalcFine never defined any value.
237
238 =cut
239
240 sub CalcFine {
241     my ( $item, $bortype, $branchcode, $due_dt, $end_date  ) = @_;
242     my $start_date = $due_dt->clone();
243     # get issuingrules (fines part will be used)
244     my $itemtype = $item->{itemtype} || $item->{itype};
245     my $data = C4::Circulation::GetIssuingRule($bortype, $itemtype, $branchcode);
246     my $fine_unit = $data->{lengthunit};
247     $fine_unit ||= 'days';
248
249     my $chargeable_units = _get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
250     my $units_minus_grace = $chargeable_units - $data->{firstremind};
251     my $amount = 0;
252     if ($data->{'chargeperiod'}  && ($units_minus_grace > 0)  ) {
253         if ( C4::Context->preference('FinesIncludeGracePeriod') ) {
254             $amount = int($chargeable_units / $data->{'chargeperiod'}) * $data->{'fine'};# TODO fine calc should be in cents
255         } else {
256             $amount = int($units_minus_grace / $data->{'chargeperiod'}) * $data->{'fine'};
257         }
258     } else {
259         # a zero (or null) chargeperiod or negative units_minus_grace value means no charge.
260     }
261     $amount = $data->{overduefinescap} if $data->{overduefinescap} && $amount > $data->{overduefinescap};
262     $debug and warn sprintf("CalcFine returning (%s, %s, %s, %s)", $amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
263     return ($amount, $data->{'chargename'}, $units_minus_grace, $chargeable_units);
264     # FIXME: chargename is NEVER populated anywhere.
265 }
266
267
268 =head2 _get_chargeable_units
269
270     _get_chargeable_units($unit, $start_date_ $end_date, $branchcode);
271
272 return integer value of units between C<$start_date> and C<$end_date>, factoring in holidays for C<$branchcode>.
273
274 C<$unit> is 'days' or 'hours' (default is 'days').
275
276 C<$start_date> and C<$end_date> are the two DateTimes to get the number of units between.
277
278 C<$branchcode> is the branch whose calendar to use for finding holidays.
279
280 =cut
281
282 sub _get_chargeable_units {
283     my ($unit, $dt1, $dt2, $branchcode) = @_;
284     my $charge_units = 0;
285     my $charge_duration;
286     if ($unit eq 'hours') {
287         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
288             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
289             $charge_duration = $calendar->hours_between( $dt1, $dt2 );
290         } else {
291             $charge_duration = $dt2->delta_ms( $dt1 );
292         }
293         if($charge_duration->in_units('hours') == 0 && $charge_duration->in_units('seconds') > 0){
294             return 1;
295         }
296         return $charge_duration->in_units('hours');
297     }
298     else { # days
299         if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
300             my $calendar = Koha::Calendar->new( branchcode => $branchcode );
301             $charge_duration = $calendar->days_between( $dt1, $dt2 );
302         } else {
303             $charge_duration = $dt2->delta_days( $dt1 );
304         }
305         return $charge_duration->in_units('days');
306     }
307 }
308
309
310 =head2 GetSpecialHolidays
311
312     &GetSpecialHolidays($date_dues,$itemnumber);
313
314 return number of special days  between date of the day and date due
315
316 C<$date_dues> is the envisaged date of book return.
317
318 C<$itemnumber> is the book's item number.
319
320 =cut
321
322 sub GetSpecialHolidays {
323     my ( $date_dues, $itemnumber ) = @_;
324
325     # calcul the today date
326     my $today = join "-", &Today();
327
328     # return the holdingbranch
329     my $iteminfo = GetIssuesIteminfo($itemnumber);
330
331     # use sql request to find all date between date_due and today
332     my $dbh = C4::Context->dbh;
333     my $query =
334       qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
335 FROM `special_holidays`
336 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
337 AND   DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
338 AND branchcode=?
339 |;
340     my @result = GetWdayFromItemnumber($itemnumber);
341     my @result_date;
342     my $wday;
343     my $dateinsec;
344     my $sth = $dbh->prepare($query);
345     $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
346       ;    # FIXME: just use NOW() in SQL instead of passing in $today
347
348     while ( my $special_date = $sth->fetchrow_hashref ) {
349         push( @result_date, $special_date );
350     }
351
352     my $specialdaycount = scalar(@result_date);
353
354     for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
355         $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
356         ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
357           localtime($dateinsec);
358         for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
359             if ( $wday == ( $result[$j]->{'weekday'} ) ) {
360                 $specialdaycount--;
361             }
362         }
363     }
364
365     return $specialdaycount;
366 }
367
368 =head2 GetRepeatableHolidays
369
370     &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
371
372 return number of day closed between date of the day and date due
373
374 C<$date_dues> is the envisaged date of book return.
375
376 C<$itemnumber> is item number.
377
378 C<$difference> numbers of between day date of the day and date due
379
380 =cut
381
382 sub GetRepeatableHolidays {
383     my ( $date_dues, $itemnumber, $difference ) = @_;
384     my $dateinsec = UnixDate( $date_dues, "%o" );
385     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
386       localtime($dateinsec);
387     my @result = GetWdayFromItemnumber($itemnumber);
388     my @dayclosedcount;
389     my $j;
390
391     for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
392         my $k = $wday;
393
394         for ( $j = 0 ; $j < $difference ; $j++ ) {
395             if ( $result[$i]->{'weekday'} == $k ) {
396                 push( @dayclosedcount, $k );
397             }
398             $k++;
399             ( $k = 0 ) if ( $k eq 7 );
400         }
401     }
402     return scalar(@dayclosedcount);
403 }
404
405
406 =head2 GetWayFromItemnumber
407
408     &Getwdayfromitemnumber($itemnumber);
409
410 return the different week day from repeatable_holidays table
411
412 C<$itemnumber> is  item number.
413
414 =cut
415
416 sub GetWdayFromItemnumber {
417     my ($itemnumber) = @_;
418     my $iteminfo = GetIssuesIteminfo($itemnumber);
419     my @result;
420     my $query = qq|SELECT weekday
421     FROM repeatable_holidays
422     WHERE branchcode=?
423 |;
424     my $sth = C4::Context->dbh->prepare($query);
425
426     $sth->execute( $iteminfo->{'branchcode'} );
427     while ( my $weekday = $sth->fetchrow_hashref ) {
428         push( @result, $weekday );
429     }
430     return @result;
431 }
432
433
434 =head2 GetIssuesIteminfo
435
436     &GetIssuesIteminfo($itemnumber);
437
438 return all data from issues about item
439
440 C<$itemnumber> is  item number.
441
442 =cut
443
444 sub GetIssuesIteminfo {
445     my ($itemnumber) = @_;
446     my $dbh          = C4::Context->dbh;
447     my $query        = qq|SELECT *
448     FROM issues
449     WHERE itemnumber=?
450     |;
451     my $sth = $dbh->prepare($query);
452     $sth->execute($itemnumber);
453     my ($issuesinfo) = $sth->fetchrow_hashref;
454     return $issuesinfo;
455 }
456
457
458 =head2 UpdateFine
459
460     &UpdateFine($itemnumber, $borrowernumber, $amount, $type, $description);
461
462 (Note: the following is mostly conjecture and guesswork.)
463
464 Updates the fine owed on an overdue book.
465
466 C<$itemnumber> is the book's item number.
467
468 C<$borrowernumber> is the borrower number of the patron who currently
469 has the book on loan.
470
471 C<$amount> is the current amount owed by the patron.
472
473 C<$type> will be used in the description of the fine.
474
475 C<$description> is a string that must be present in the description of
476 the fine. I think this is expected to be a date in DD/MM/YYYY format.
477
478 C<&UpdateFine> looks up the amount currently owed on the given item
479 and sets it to C<$amount>, creating, if necessary, a new entry in the
480 accountlines table of the Koha database.
481
482 =cut
483
484 #
485 # Question: Why should the caller have to
486 # specify both the item number and the borrower number? A book can't
487 # be on loan to two different people, so the item number should be
488 # sufficient.
489 #
490 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
491 #
492 sub UpdateFine {
493     my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
494         $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
495     my $dbh = C4::Context->dbh;
496     # FIXME - What exactly is this query supposed to do? It looks up an
497     # entry in accountlines that matches the given item and borrower
498     # numbers, where the description contains $due, and where the
499     # account type has one of several values, but what does this _mean_?
500     # Does it look up existing fines for this item?
501     # FIXME - What are these various account types? ("FU", "O", "F", "M")
502         #       "L"   is LOST item
503         #   "A"   is Account Management Fee
504         #   "N"   is New Card
505         #   "M"   is Sundry
506         #   "O"   is Overdue ??
507         #   "F"   is Fine ??
508         #   "FU"  is Fine UPDATE??
509         #       "Pay" is Payment
510         #   "REF" is Cash Refund
511     my $sth = $dbh->prepare(
512         "SELECT * FROM accountlines
513         WHERE borrowernumber=?
514         AND   accounttype IN ('FU','O','F','M')"
515     );
516     $sth->execute( $borrowernumber );
517     my $data;
518     my $total_amount_other = 0.00;
519     my $due_qr = qr/$due/;
520     # Cycle through the fines and
521     # - find line that relates to the requested $itemnum
522     # - accumulate fines for other items
523     # so we can update $itemnum fine taking in account fine caps
524     while (my $rec = $sth->fetchrow_hashref) {
525         if ($rec->{itemnumber} == $itemnum && $rec->{description} =~ /$due_qr/) {
526             if ($data) {
527                 warn "Not a unique accountlines record for item $itemnum borrower $borrowernumber";
528             } else {
529                 $data = $rec;
530                 next;
531             }
532         }
533         $total_amount_other += $rec->{'amountoutstanding'};
534     }
535
536     if (my $maxfine = C4::Context->preference('MaxFine')) {
537         if ($total_amount_other + $amount > $maxfine) {
538             my $new_amount = $maxfine - $total_amount_other;
539             return if $new_amount <= 0.00;
540             warn "Reducing fine for item $itemnum borrower $borrowernumber from $amount to $new_amount - MaxFine reached";
541             $amount = $new_amount;
542         }
543     }
544
545     if ( $data ) {
546
547                 # we're updating an existing fine.  Only modify if amount changed
548         # Note that in the current implementation, you cannot pay against an accruing fine
549         # (i.e. , of accounttype 'FU').  Doing so will break accrual.
550         if ( $data->{'amount'} != $amount ) {
551             my $diff = $amount - $data->{'amount'};
552             #3341: diff could be positive or negative!
553             my $out  = $data->{'amountoutstanding'} + $diff;
554             my $query = "
555                 UPDATE accountlines
556                                 SET date=now(), amount=?, amountoutstanding=?,
557                                         lastincrement=?, accounttype='FU'
558                                 WHERE borrowernumber=?
559                                 AND   itemnumber=?
560                                 AND   accounttype IN ('FU','O')
561                                 AND   description LIKE ?
562                                 LIMIT 1 ";
563             my $sth2 = $dbh->prepare($query);
564                         # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
565                         #               LIMIT 1 added to prevent multiple affected lines
566                         # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.  
567                         #               But actually, we should just have a regular autoincrementing PK and forget accountline,
568                         #               including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
569                         # FIXME: Why only 2 account types here?
570                         $debug and print STDERR "UpdateFine query: $query\n" .
571                                 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
572             $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
573         } else {
574             #      print "no update needed $data->{'amount'}"
575         }
576     } else {
577         my $sth4 = $dbh->prepare(
578             "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
579         );
580         $sth4->execute($itemnum);
581         my $title = $sth4->fetchrow;
582
583 #         #   print "not in account";
584 #         my $sth3 = $dbh->prepare("Select max(accountno) from accountlines");
585 #         $sth3->execute;
586
587 #         # FIXME - Make $accountno a scalar.
588 #         my @accountno = $sth3->fetchrow_array;
589 #         $sth3->finish;
590 #         $accountno[0]++;
591 # begin transaction
592                 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
593                 my $desc = ($type ? "$type " : '') . "$title $due";     # FIXEDME, avoid whitespace prefix on empty $type
594                 my $query = "INSERT INTO accountlines
595                     (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
596                             VALUES (?,?,now(),?,?,'FU',?,?,?)";
597                 my $sth2 = $dbh->prepare($query);
598                 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
599         $sth2->execute($borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno);
600     }
601     # logging action
602     &logaction(
603         "FINES",
604         $type,
605         $borrowernumber,
606         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
607         ) if C4::Context->preference("FinesLog");
608 }
609
610 =head2 BorType
611
612     $borrower = &BorType($borrowernumber);
613
614 Looks up a patron by borrower number.
615
616 C<$borrower> is a reference-to-hash whose keys are all of the fields
617 from the borrowers and categories tables of the Koha database. Thus,
618 C<$borrower> contains all information about both the borrower and
619 category he or she belongs to.
620
621 =cut
622
623 sub BorType {
624     my ($borrowernumber) = @_;
625     my $dbh              = C4::Context->dbh;
626     my $sth              = $dbh->prepare(
627         "SELECT * from borrowers
628       LEFT JOIN categories ON borrowers.categorycode=categories.categorycode 
629       WHERE borrowernumber=?"
630     );
631     $sth->execute($borrowernumber);
632     return $sth->fetchrow_hashref;
633 }
634
635 =head2 GetFine
636
637     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
638
639 return the total of fine
640
641 C<$itemnum> is item number
642
643 C<$borrowernumber> is the borrowernumber
644
645 =cut 
646
647 sub GetFine {
648     my ( $itemnum, $borrowernumber ) = @_;
649     my $dbh   = C4::Context->dbh();
650     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
651     where accounttype like 'F%'
652   AND amountoutstanding > 0 AND itemnumber = ? AND borrowernumber=?|;
653     my $sth = $dbh->prepare($query);
654     $sth->execute( $itemnum, $borrowernumber );
655     my $fine = $sth->fetchrow_hashref();
656     if ($fine->{fineamount}) {
657         return $fine->{fineamount};
658     }
659     return 0;
660 }
661
662 =head2 NumberNotifyId
663
664     (@notify) = &NumberNotifyId($borrowernumber);
665
666 Returns amount for all file per borrowers
667 C<@notify> array contains all file per borrowers
668
669 C<$notify_id> contains the file number for the borrower number nad item number
670
671 =cut
672
673 sub NumberNotifyId{
674     my ($borrowernumber)=@_;
675     my $dbh = C4::Context->dbh;
676     my $query=qq|    SELECT distinct(notify_id)
677             FROM accountlines
678             WHERE borrowernumber=?|;
679     my @notify;
680     my $sth = $dbh->prepare($query);
681     $sth->execute($borrowernumber);
682     while ( my ($numberofnotify) = $sth->fetchrow ) {
683         push( @notify, $numberofnotify );
684     }
685     return (@notify);
686 }
687
688 =head2 AmountNotify
689
690     ($totalnotify) = &AmountNotify($notifyid);
691
692 Returns amount for all file per borrowers
693 C<$notifyid> is the file number
694
695 C<$totalnotify> contains amount of a file
696
697 C<$notify_id> contains the file number for the borrower number and item number
698
699 =cut
700
701 sub AmountNotify{
702     my ($notifyid,$borrowernumber)=@_;
703     my $dbh = C4::Context->dbh;
704     my $query=qq|    SELECT sum(amountoutstanding)
705             FROM accountlines
706             WHERE notify_id=? AND borrowernumber = ?|;
707     my $sth=$dbh->prepare($query);
708         $sth->execute($notifyid,$borrowernumber);
709         my $totalnotify=$sth->fetchrow;
710     $sth->finish;
711     return ($totalnotify);
712 }
713
714 =head2 GetItems
715
716     ($items) = &GetItems($itemnumber);
717
718 Returns the list of all delays from overduerules.
719
720 C<$items> is a reference-to-hash whose keys are all of the fields
721 from the items tables of the Koha database. Thus,
722
723 C<$itemnumber> contains the borrower categorycode
724
725 =cut
726
727 # FIXME: This is a bad function to have here.
728 # Shouldn't it be in C4::Items?
729 # Shouldn't it be called GetItem since you only get 1 row?
730 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
731
732 sub GetItems {
733     my $itemnumber = shift or return;
734     my $query = qq|SELECT *
735              FROM items
736               WHERE itemnumber=?|;
737     my $sth = C4::Context->dbh->prepare($query);
738     $sth->execute($itemnumber);
739     my ($items) = $sth->fetchrow_hashref;
740     return ($items);
741 }
742
743 =head2 GetBranchcodesWithOverdueRules
744
745     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
746
747 returns a list of branch codes for branches with overdue rules defined.
748
749 =cut
750
751 sub GetBranchcodesWithOverdueRules {
752     my $dbh               = C4::Context->dbh;
753     my $rqoverduebranches = $dbh->prepare("SELECT DISTINCT branchcode FROM overduerules WHERE delay1 IS NOT NULL AND branchcode <> '' ORDER BY branchcode");
754     $rqoverduebranches->execute;
755     my @branches = map { shift @$_ } @{ $rqoverduebranches->fetchall_arrayref };
756     if (!$branches[0]) {
757        my $availbranches = C4::Branch::GetBranches();
758        @branches = keys %$availbranches;
759     }
760     return @branches;
761 }
762
763 =head2 CheckItemNotify
764
765 Sql request to check if the document has alreday been notified
766 this function is not exported, only used with GetOverduesForBranch
767
768 =cut
769
770 sub CheckItemNotify {
771     my ($notify_id,$notify_level,$itemnumber) = @_;
772     my $dbh = C4::Context->dbh;
773     my $sth = $dbh->prepare("
774     SELECT COUNT(*)
775      FROM notifys
776     WHERE notify_id    = ?
777      AND  notify_level = ? 
778      AND  itemnumber   = ? ");
779     $sth->execute($notify_id,$notify_level,$itemnumber);
780     my $notified = $sth->fetchrow;
781     return ($notified);
782 }
783
784 =head2 GetOverduesForBranch
785
786 Sql request for display all information for branchoverdues.pl
787 2 possibilities : with or without location .
788 display is filtered by branch
789
790 FIXME: This function should be renamed.
791
792 =cut
793
794 sub GetOverduesForBranch {
795     my ( $branch, $location) = @_;
796         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
797     my $dbh = C4::Context->dbh;
798     my $select = "
799     SELECT
800             borrowers.cardnumber,
801             borrowers.borrowernumber,
802             borrowers.surname,
803             borrowers.firstname,
804             borrowers.phone,
805             borrowers.email,
806                biblio.title,
807                biblio.author,
808                biblio.biblionumber,
809                issues.date_due,
810                issues.returndate,
811                issues.branchcode,
812              branches.branchname,
813                 items.barcode,
814                 items.homebranch,
815                 items.itemcallnumber,
816                 items.location,
817                 items.itemnumber,
818             itemtypes.description,
819          accountlines.notify_id,
820          accountlines.notify_level,
821          accountlines.amountoutstanding
822     FROM  accountlines
823     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
824                           AND   issues.borrowernumber = accountlines.borrowernumber
825     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
826     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
827     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
828     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
829     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
830     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
831     WHERE (accountlines.amountoutstanding  != '0.000000')
832       AND (accountlines.accounttype         = 'FU'      )
833       AND (issues.branchcode =  ?   )
834       AND (issues.date_due  < NOW())
835     ";
836     my @getoverdues;
837     my $i = 0;
838     my $sth;
839     if ($location) {
840         $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
841         $sth->execute($branch, $location);
842     } else {
843         $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
844         $sth->execute($branch);
845     }
846     while ( my $data = $sth->fetchrow_hashref ) {
847     #check if the document has already been notified
848         my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
849         if ($countnotify eq '0') {
850             $getoverdues[$i] = $data;
851             $i++;
852         }
853     }
854     return (@getoverdues);
855 }
856
857
858 =head2 AddNotifyLine
859
860     &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
861
862 Create a line into notify, if the method is phone, the notification_send_date is implemented to
863
864 =cut
865
866 sub AddNotifyLine {
867     my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
868     my $dbh = C4::Context->dbh;
869     if ( $method eq "phone" ) {
870         my $sth = $dbh->prepare(
871             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
872         VALUES (?,?,now(),now(),?,?,?)"
873         );
874         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
875             $notifyId );
876     }
877     else {
878         my $sth = $dbh->prepare(
879             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
880         VALUES (?,?,now(),?,?,?)"
881         );
882         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
883             $notifyId );
884     }
885     return 1;
886 }
887
888 =head2 RemoveNotifyLine
889
890     &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
891
892 Cancel a notification
893
894 =cut
895
896 sub RemoveNotifyLine {
897     my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
898     my $dbh = C4::Context->dbh;
899     my $sth = $dbh->prepare(
900         "DELETE FROM notifys 
901             WHERE
902             borrowernumber=?
903             AND itemnumber=?
904             AND notify_date=?"
905     );
906     $sth->execute( $borrowernumber, $itemnumber, $notify_date );
907     return 1;
908 }
909
910 =head2 GetOverdueMessageTransportTypes
911
912     my $message_transport_types = GetOverdueMessageTransportTypes( $branchcode, $categorycode, $letternumber);
913
914     return a arrayref with all message_transport_type for given branchcode, categorycode and letternumber(1,2 or 3)
915
916 =cut
917
918 sub GetOverdueMessageTransportTypes {
919     my ( $branchcode, $categorycode, $letternumber ) = @_;
920     return unless $categorycode and $letternumber;
921     my $dbh = C4::Context->dbh;
922     my $sth = $dbh->prepare("
923         SELECT message_transport_type FROM overduerules_transport_types
924         WHERE branchcode = ? AND categorycode = ? AND letternumber = ?
925     ");
926     $sth->execute( $branchcode, $categorycode, $letternumber );
927     my @mtts;
928     while ( my $mtt = $sth->fetchrow ) {
929         push @mtts, $mtt;
930     }
931     return \@mtts;
932 }
933
934 1;
935 __END__
936
937 =head1 AUTHOR
938
939 Koha Development Team <http://koha-community.org/>
940
941 =cut