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