Bug 5549 : Let Timestamp do the right thing if passed a DateTime
[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.01;
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         &CheckAccountLineLevelInfo
45         &CheckAccountLineItemInfo
46         &CheckExistantNotifyid
47         &GetNextIdNotify
48         &GetNotifyId
49         &NumberNotifyId
50         &AmountNotify
51         &UpdateAccountLines
52         &UpdateFine
53         &GetOverdueDelays
54         &GetOverduerules
55         &GetFine
56         &CreateItemAccountLine
57         &ReplacementCost2
58         
59         &CheckItemNotify
60         &GetOverduesForBranch
61         &RemoveNotifyLine
62         &AddNotifyLine
63         );
64         # subs to remove
65         push @EXPORT, qw(
66         &BorType
67         );
68
69         # check that an equivalent don't exist already before moving
70
71         # subs to move to Circulation.pm
72         push @EXPORT, qw(
73         &GetIssuesIteminfo
74         );
75     #
76         # &GetIssuingRules - delete.
77         # use C4::Circulation::GetIssuingRule instead.
78         
79         # subs to move to Members.pm
80         push @EXPORT, qw(
81         &CheckBorrowerDebarred
82         );
83         # subs to move to Biblio.pm
84         push @EXPORT, qw(
85         &GetItems
86         &ReplacementCost
87         );
88 }
89
90 =head1 NAME
91
92 C4::Circulation::Fines - Koha module dealing with fines
93
94 =head1 SYNOPSIS
95
96   use C4::Overdues;
97
98 =head1 DESCRIPTION
99
100 This module contains several functions for dealing with fines for
101 overdue items. It is primarily used by the 'misc/fines2.pl' script.
102
103 =head1 FUNCTIONS
104
105 =head2 Getoverdues
106
107   $overdues = Getoverdues( { minimumdays => 1, maximumdays => 30 } );
108
109 Returns the list of all overdue books, with their itemtype.
110
111 C<$overdues> is a reference-to-array. Each element is a
112 reference-to-hash whose keys are the fields of the issues table in the
113 Koha database.
114
115 =cut
116
117 #'
118 sub Getoverdues {
119     my $params = shift;
120     my $dbh = C4::Context->dbh;
121     my $statement;
122     if ( C4::Context->preference('item-level_itypes') ) {
123         $statement = "
124    SELECT issues.*, items.itype as itemtype, items.homebranch, items.barcode
125      FROM issues 
126 LEFT JOIN items       USING (itemnumber)
127     WHERE date_due < CURDATE() 
128 ";
129     } else {
130         $statement = "
131    SELECT issues.*, biblioitems.itemtype, items.itype, items.homebranch, items.barcode
132      FROM issues 
133 LEFT JOIN items       USING (itemnumber)
134 LEFT JOIN biblioitems USING (biblioitemnumber)
135     WHERE date_due < CURDATE() 
136 ";
137     }
138
139     my @bind_parameters;
140     if ( exists $params->{'minimumdays'} and exists $params->{'maximumdays'} ) {
141         $statement .= ' AND TO_DAYS( NOW() )-TO_DAYS( date_due ) BETWEEN ? and ? ';
142         push @bind_parameters, $params->{'minimumdays'}, $params->{'maximumdays'};
143     } elsif ( exists $params->{'minimumdays'} ) {
144         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) > ? ';
145         push @bind_parameters, $params->{'minimumdays'};
146     } elsif ( exists $params->{'maximumdays'} ) {
147         $statement .= ' AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ? ';
148         push @bind_parameters, $params->{'maximumdays'};
149     }
150     $statement .= 'ORDER BY borrowernumber';
151     my $sth = $dbh->prepare( $statement );
152     $sth->execute( @bind_parameters );
153     return $sth->fetchall_arrayref({});
154 }
155
156
157 =head2 checkoverdues
158
159     ($count, $overdueitems) = checkoverdues($borrowernumber);
160
161 Returns a count and a list of overdueitems for a given borrowernumber
162
163 =cut
164
165 sub checkoverdues {
166     my $borrowernumber = shift or return;
167     # don't select biblioitems.marc or biblioitems.marcxml... too slow on large systems
168     my $sth = C4::Context->dbh->prepare(
169         "SELECT biblio.*, items.*, issues.*,
170                 biblioitems.volume,
171                 biblioitems.number,
172                 biblioitems.itemtype,
173                 biblioitems.isbn,
174                 biblioitems.issn,
175                 biblioitems.publicationyear,
176                 biblioitems.publishercode,
177                 biblioitems.volumedate,
178                 biblioitems.volumedesc,
179                 biblioitems.collectiontitle,
180                 biblioitems.collectionissn,
181                 biblioitems.collectionvolume,
182                 biblioitems.editionstatement,
183                 biblioitems.editionresponsibility,
184                 biblioitems.illus,
185                 biblioitems.pages,
186                 biblioitems.notes,
187                 biblioitems.size,
188                 biblioitems.place,
189                 biblioitems.lccn,
190                 biblioitems.url,
191                 biblioitems.cn_source,
192                 biblioitems.cn_class,
193                 biblioitems.cn_item,
194                 biblioitems.cn_suffix,
195                 biblioitems.cn_sort,
196                 biblioitems.totalissues
197          FROM issues
198          LEFT JOIN items       ON issues.itemnumber      = items.itemnumber
199          LEFT JOIN biblio      ON items.biblionumber     = biblio.biblionumber
200          LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
201             WHERE issues.borrowernumber  = ?
202             AND   issues.date_due < CURDATE()"
203     );
204     # FIXME: SELECT * across 4 tables?  do we really need the marc AND marcxml blobs??
205     $sth->execute($borrowernumber);
206     my $results = $sth->fetchall_arrayref({});
207     return ( scalar(@$results), $results);  # returning the count and the results is silly
208 }
209
210 =head2 CalcFine
211
212     ($amount, $chargename,  $daycounttotal) = &CalcFine($item,
213                                   $categorycode, $branch,
214                                   $start_dt, $end_dt );
215
216 Calculates the fine for a book.
217
218 The issuingrules table in the Koha database is a fine matrix, listing
219 the penalties for each type of patron for each type of item and each branch (e.g., the
220 standard fine for books might be $0.50, but $1.50 for DVDs, or staff
221 members might get a longer grace period between the first and second
222 reminders that a book is overdue).
223
224
225 C<$item> is an item object (hashref).
226
227 C<$categorycode> is the category code (string) of the patron who currently has
228 the book.
229
230 C<$branchcode> is the library (string) whose issuingrules govern this transaction.
231
232 C<$start_date> & C<$end_date> are DateTime objects
233 defining the date range over which to determine the fine.
234
235 Fines scripts should just supply the date range over which to calculate the fine.
236
237 C<&CalcFine> returns four values:
238
239 C<$amount> is the fine owed by the patron (see above).
240
241 C<$chargename> is the chargename field from the applicable record in
242 the categoryitem table, whatever that is.
243
244 C<$daycount> is the number of days between start and end dates, Calendar adjusted (where needed), 
245 minus any applicable grace period.
246
247 FIXME - What is chargename supposed to be ?
248
249 FIXME: previously attempted to return C<$message> as a text message, either "First Notice", "Second Notice",
250 or "Final Notice".  But CalcFine never defined any value.
251
252 =cut
253
254 sub CalcFine {
255     my ( $item, $bortype, $branchcode, $start_date, $end_date  ) = @_;
256     my $dbh = C4::Context->dbh;
257     my $amount = 0;
258     my $charge_duration;
259     # get issuingrules (fines part will be used)
260     my $data = C4::Circulation::GetIssuingRule($bortype, $item->{itemtype}, $branchcode);
261     if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
262         my $calendar = Koha::Calendar->new( branchcode => $branchcode );
263         $charge_duration = $calendar->days_between( $start_date, $end_date );
264     } else {
265         $charge_duration = $end_date - $start_date;
266     }
267     # correct for grace period.
268     my $fine_unit = $data->{lengthunit};
269     $fine_unit ||= 'days';
270     my $chargeable_units;
271     if ($fine_unit eq 'hours') {
272         $chargeable_units = $charge_duration->hours(); # TODO closed times???
273     }
274     else {
275         $chargeable_units = $charge_duration->day;
276     }
277     my $days_minus_grace = $chargeable_units - $data->{firstremind};
278     if ($data->{'chargeperiod'}  && $days_minus_grace  ) {
279         $amount = int($chargeable_units / $data->{'chargeperiod'}) * $data->{'fine'};# TODO fine calc should be in cents
280     } else {
281         # a zero (or null)  chargeperiod means no charge.
282     }
283     if(C4::Context->preference('maxFine') && ( $amount > C4::Context->preference('maxFine'))) {
284         $amount = C4::Context->preference('maxFine');
285     }
286     return ($amount, $data->{chargename}, $days_minus_grace);
287     # FIXME: chargename is NEVER populated anywhere.
288 }
289
290
291 =head2 GetSpecialHolidays
292
293     &GetSpecialHolidays($date_dues,$itemnumber);
294
295 return number of special days  between date of the day and date due
296
297 C<$date_dues> is the envisaged date of book return.
298
299 C<$itemnumber> is the book's item number.
300
301 =cut
302
303 sub GetSpecialHolidays {
304     my ( $date_dues, $itemnumber ) = @_;
305
306     # calcul the today date
307     my $today = join "-", &Today();
308
309     # return the holdingbranch
310     my $iteminfo = GetIssuesIteminfo($itemnumber);
311
312     # use sql request to find all date between date_due and today
313     my $dbh = C4::Context->dbh;
314     my $query =
315       qq|SELECT DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') as date
316 FROM `special_holidays`
317 WHERE DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') >= ?
318 AND   DATE_FORMAT(concat(year,'-',month,'-',day),'%Y-%m-%d') <= ?
319 AND branchcode=?
320 |;
321     my @result = GetWdayFromItemnumber($itemnumber);
322     my @result_date;
323     my $wday;
324     my $dateinsec;
325     my $sth = $dbh->prepare($query);
326     $sth->execute( $date_dues, $today, $iteminfo->{'branchcode'} )
327       ;    # FIXME: just use NOW() in SQL instead of passing in $today
328
329     while ( my $special_date = $sth->fetchrow_hashref ) {
330         push( @result_date, $special_date );
331     }
332
333     my $specialdaycount = scalar(@result_date);
334
335     for ( my $i = 0 ; $i < scalar(@result_date) ; $i++ ) {
336         $dateinsec = UnixDate( $result_date[$i]->{'date'}, "%o" );
337         ( undef, undef, undef, undef, undef, undef, $wday, undef, undef ) =
338           localtime($dateinsec);
339         for ( my $j = 0 ; $j < scalar(@result) ; $j++ ) {
340             if ( $wday == ( $result[$j]->{'weekday'} ) ) {
341                 $specialdaycount--;
342             }
343         }
344     }
345
346     return $specialdaycount;
347 }
348
349 =head2 GetRepeatableHolidays
350
351     &GetRepeatableHolidays($date_dues, $itemnumber, $difference,);
352
353 return number of day closed between date of the day and date due
354
355 C<$date_dues> is the envisaged date of book return.
356
357 C<$itemnumber> is item number.
358
359 C<$difference> numbers of between day date of the day and date due
360
361 =cut
362
363 sub GetRepeatableHolidays {
364     my ( $date_dues, $itemnumber, $difference ) = @_;
365     my $dateinsec = UnixDate( $date_dues, "%o" );
366     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
367       localtime($dateinsec);
368     my @result = GetWdayFromItemnumber($itemnumber);
369     my @dayclosedcount;
370     my $j;
371
372     for ( my $i = 0 ; $i < scalar(@result) ; $i++ ) {
373         my $k = $wday;
374
375         for ( $j = 0 ; $j < $difference ; $j++ ) {
376             if ( $result[$i]->{'weekday'} == $k ) {
377                 push( @dayclosedcount, $k );
378             }
379             $k++;
380             ( $k = 0 ) if ( $k eq 7 );
381         }
382     }
383     return scalar(@dayclosedcount);
384 }
385
386
387 =head2 GetWayFromItemnumber
388
389     &Getwdayfromitemnumber($itemnumber);
390
391 return the different week day from repeatable_holidays table
392
393 C<$itemnumber> is  item number.
394
395 =cut
396
397 sub GetWdayFromItemnumber {
398     my ($itemnumber) = @_;
399     my $iteminfo = GetIssuesIteminfo($itemnumber);
400     my @result;
401     my $query = qq|SELECT weekday
402     FROM repeatable_holidays
403     WHERE branchcode=?
404 |;
405     my $sth = C4::Context->dbh->prepare($query);
406
407     $sth->execute( $iteminfo->{'branchcode'} );
408     while ( my $weekday = $sth->fetchrow_hashref ) {
409         push( @result, $weekday );
410     }
411     return @result;
412 }
413
414
415 =head2 GetIssuesIteminfo
416
417     &GetIssuesIteminfo($itemnumber);
418
419 return all data from issues about item
420
421 C<$itemnumber> is  item number.
422
423 =cut
424
425 sub GetIssuesIteminfo {
426     my ($itemnumber) = @_;
427     my $dbh          = C4::Context->dbh;
428     my $query        = qq|SELECT *
429     FROM issues
430     WHERE itemnumber=?
431     |;
432     my $sth = $dbh->prepare($query);
433     $sth->execute($itemnumber);
434     my ($issuesinfo) = $sth->fetchrow_hashref;
435     return $issuesinfo;
436 }
437
438
439 =head2 UpdateFine
440
441     &UpdateFine($itemnumber, $borrowernumber, $amount, $type, $description);
442
443 (Note: the following is mostly conjecture and guesswork.)
444
445 Updates the fine owed on an overdue book.
446
447 C<$itemnumber> is the book's item number.
448
449 C<$borrowernumber> is the borrower number of the patron who currently
450 has the book on loan.
451
452 C<$amount> is the current amount owed by the patron.
453
454 C<$type> will be used in the description of the fine.
455
456 C<$description> is a string that must be present in the description of
457 the fine. I think this is expected to be a date in DD/MM/YYYY format.
458
459 C<&UpdateFine> looks up the amount currently owed on the given item
460 and sets it to C<$amount>, creating, if necessary, a new entry in the
461 accountlines table of the Koha database.
462
463 =cut
464
465 #
466 # Question: Why should the caller have to
467 # specify both the item number and the borrower number? A book can't
468 # be on loan to two different people, so the item number should be
469 # sufficient.
470 #
471 # Possible Answer: You might update a fine for a damaged item, *after* it is returned.
472 #
473 sub UpdateFine {
474     my ( $itemnum, $borrowernumber, $amount, $type, $due ) = @_;
475         $debug and warn "UpdateFine($itemnum, $borrowernumber, $amount, " . ($type||'""') . ", $due) called";
476     my $dbh = C4::Context->dbh;
477     # FIXME - What exactly is this query supposed to do? It looks up an
478     # entry in accountlines that matches the given item and borrower
479     # numbers, where the description contains $due, and where the
480     # account type has one of several values, but what does this _mean_?
481     # Does it look up existing fines for this item?
482     # FIXME - What are these various account types? ("FU", "O", "F", "M")
483         #       "L"   is LOST item
484         #   "A"   is Account Management Fee
485         #   "N"   is New Card
486         #   "M"   is Sundry
487         #   "O"   is Overdue ??
488         #   "F"   is Fine ??
489         #   "FU"  is Fine UPDATE??
490         #       "Pay" is Payment
491         #   "REF" is Cash Refund
492     my $sth = $dbh->prepare(
493         "SELECT * FROM accountlines
494                 WHERE itemnumber=?
495                 AND   borrowernumber=?
496                 AND   accounttype IN ('FU','O','F','M')
497                 AND   description like ? "
498     );
499     $sth->execute( $itemnum, $borrowernumber, "%$due%" );
500
501     if ( my $data = $sth->fetchrow_hashref ) {
502
503                 # we're updating an existing fine.  Only modify if amount changed
504         # Note that in the current implementation, you cannot pay against an accruing fine
505         # (i.e. , of accounttype 'FU').  Doing so will break accrual.
506         if ( $data->{'amount'} != $amount ) {
507             my $diff = $amount - $data->{'amount'};
508             #3341: diff could be positive or negative!
509             my $out  = $data->{'amountoutstanding'} + $diff;
510             my $query = "
511                 UPDATE accountlines
512                                 SET date=now(), amount=?, amountoutstanding=?,
513                                         lastincrement=?, accounttype='FU'
514                                 WHERE borrowernumber=?
515                                 AND   itemnumber=?
516                                 AND   accounttype IN ('FU','O')
517                                 AND   description LIKE ?
518                                 LIMIT 1 ";
519             my $sth2 = $dbh->prepare($query);
520                         # FIXME: BOGUS query cannot ensure uniqueness w/ LIKE %x% !!!
521                         #               LIMIT 1 added to prevent multiple affected lines
522                         # FIXME: accountlines table needs unique key!! Possibly a combo of borrowernumber and accountline.  
523                         #               But actually, we should just have a regular autoincrementing PK and forget accountline,
524                         #               including the bogus getnextaccountno function (doesn't prevent conflict on simultaneous ops).
525                         # FIXME: Why only 2 account types here?
526                         $debug and print STDERR "UpdateFine query: $query\n" .
527                                 "w/ args: $amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, \"\%$due\%\"\n";
528             $sth2->execute($amount, $out, $diff, $data->{'borrowernumber'}, $data->{'itemnumber'}, "%$due%");
529         } else {
530             #      print "no update needed $data->{'amount'}"
531         }
532     } else {
533         my $sth4 = $dbh->prepare(
534             "SELECT title FROM biblio LEFT JOIN items ON biblio.biblionumber=items.biblionumber WHERE items.itemnumber=?"
535         );
536         $sth4->execute($itemnum);
537         my $title = $sth4->fetchrow;
538
539 #         #   print "not in account";
540 #         my $sth3 = $dbh->prepare("Select max(accountno) from accountlines");
541 #         $sth3->execute;
542
543 #         # FIXME - Make $accountno a scalar.
544 #         my @accountno = $sth3->fetchrow_array;
545 #         $sth3->finish;
546 #         $accountno[0]++;
547 # begin transaction
548                 my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
549                 my $desc = ($type ? "$type " : '') . "$title $due";     # FIXEDME, avoid whitespace prefix on empty $type
550                 my $query = "INSERT INTO accountlines
551                     (borrowernumber,itemnumber,date,amount,description,accounttype,amountoutstanding,lastincrement,accountno)
552                             VALUES (?,?,now(),?,?,'FU',?,?,?)";
553                 my $sth2 = $dbh->prepare($query);
554                 $debug and print STDERR "UpdateFine query: $query\nw/ args: $borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno\n";
555         $sth2->execute($borrowernumber, $itemnum, $amount, $desc, $amount, $amount, $nextaccntno);
556     }
557     # logging action
558     &logaction(
559         "FINES",
560         $type,
561         $borrowernumber,
562         "due=".$due."  amount=".$amount." itemnumber=".$itemnum
563         ) if C4::Context->preference("FinesLog");
564 }
565
566 =head2 BorType
567
568     $borrower = &BorType($borrowernumber);
569
570 Looks up a patron by borrower number.
571
572 C<$borrower> is a reference-to-hash whose keys are all of the fields
573 from the borrowers and categories tables of the Koha database. Thus,
574 C<$borrower> contains all information about both the borrower and
575 category he or she belongs to.
576
577 =cut
578
579 #'
580 sub BorType {
581     my ($borrowernumber) = @_;
582     my $dbh              = C4::Context->dbh;
583     my $sth              = $dbh->prepare(
584         "SELECT * from borrowers
585       LEFT JOIN categories ON borrowers.categorycode=categories.categorycode 
586       WHERE borrowernumber=?"
587     );
588     $sth->execute($borrowernumber);
589     return $sth->fetchrow_hashref;
590 }
591
592 =head2 ReplacementCost
593
594     $cost = &ReplacementCost($itemnumber);
595
596 Returns the replacement cost of the item with the given item number.
597
598 =cut
599
600 #'
601 sub ReplacementCost {
602     my ($itemnum) = @_;
603     my $dbh       = C4::Context->dbh;
604     my $sth       =
605       $dbh->prepare("Select replacementprice from items where itemnumber=?");
606     $sth->execute($itemnum);
607
608     # FIXME - Use fetchrow_array or a slice.
609     my $data = $sth->fetchrow_hashref;
610     return ( $data->{'replacementprice'} );
611 }
612
613 =head2 GetFine
614
615     $data->{'sum(amountoutstanding)'} = &GetFine($itemnum,$borrowernumber);
616
617 return the total of fine
618
619 C<$itemnum> is item number
620
621 C<$borrowernumber> is the borrowernumber
622
623 =cut 
624
625
626 sub GetFine {
627     my ( $itemnum, $borrowernumber ) = @_;
628     my $dbh   = C4::Context->dbh();
629     my $query = q|SELECT sum(amountoutstanding) as fineamount FROM accountlines
630     where accounttype like 'F%'
631   AND amountoutstanding > 0 AND itemnumber = ? AND borrowernumber=?|;
632     my $sth = $dbh->prepare($query);
633     $sth->execute( $itemnum, $borrowernumber );
634     my $fine = $sth->fetchrow_hashref();
635     if ($fine->{fineamount}) {
636         return $fine->{fineamount};
637     }
638     return 0;
639 }
640
641
642 =head2 GetIssuingRules
643
644 FIXME - This sub should be deprecated and removed.
645 It ignores branch and defaults.
646
647     $data = &GetIssuingRules($itemtype,$categorycode);
648
649 Looks up for all issuingrules an item info 
650
651 C<$itemnumber> is a reference-to-hash whose keys are all of the fields
652 from the borrowers and categories tables of the Koha database. Thus,
653
654 C<$categorycode> contains  information about borrowers category 
655
656 C<$data> contains all information about both the borrower and
657 category he or she belongs to.
658 =cut 
659
660 sub GetIssuingRules {
661         warn "GetIssuingRules is deprecated: use GetIssuingRule from C4::Circulation instead.";
662    my ($itemtype,$categorycode)=@_;
663    my $dbh   = C4::Context->dbh();    
664    my $query=qq|SELECT *
665         FROM issuingrules
666         WHERE issuingrules.itemtype=?
667             AND issuingrules.categorycode=?
668         |;
669     my $sth = $dbh->prepare($query);
670     #  print $query;
671     $sth->execute($itemtype,$categorycode);
672     return $sth->fetchrow_hashref;
673 }
674
675
676 sub ReplacementCost2 {
677     my ( $itemnum, $borrowernumber ) = @_;
678     my $dbh   = C4::Context->dbh();
679     my $query = "SELECT amountoutstanding
680          FROM accountlines
681              WHERE accounttype like 'L'
682          AND amountoutstanding > 0
683          AND itemnumber = ?
684          AND borrowernumber= ?";
685     my $sth = $dbh->prepare($query);
686     $sth->execute( $itemnum, $borrowernumber );
687     my $data = $sth->fetchrow_hashref();
688     return ( $data->{'amountoutstanding'} );
689 }
690
691
692 =head2 GetNextIdNotify
693
694     ($result) = &GetNextIdNotify($reference);
695
696 Returns the new file number
697
698 C<$result> contains the next file number
699
700 C<$reference> contains the beggining of file number
701
702 =cut
703
704 sub GetNextIdNotify {
705     my ($reference) = @_;
706     my $query = qq|SELECT max(notify_id)
707          FROM accountlines
708          WHERE notify_id  like \"$reference%\"
709          |;
710
711     # AND borrowernumber=?|;
712     my $dbh = C4::Context->dbh;
713     my $sth = $dbh->prepare($query);
714     $sth->execute();
715     my $result = $sth->fetchrow;
716     my $count;
717     if ( $result eq '' ) {
718         ( $result = $reference . "01" );
719     }
720     else {
721         $count = substr( $result, 6 ) + 1;
722
723         if ( $count < 10 ) {
724             ( $count = "0" . $count );
725         }
726         $result = $reference . $count;
727     }
728     return $result;
729 }
730
731 =head2 NumberNotifyId
732
733     (@notify) = &NumberNotifyId($borrowernumber);
734
735 Returns amount for all file per borrowers
736 C<@notify> array contains all file per borrowers
737
738 C<$notify_id> contains the file number for the borrower number nad item number
739
740 =cut
741
742 sub NumberNotifyId{
743     my ($borrowernumber)=@_;
744     my $dbh = C4::Context->dbh;
745     my $query=qq|    SELECT distinct(notify_id)
746             FROM accountlines
747             WHERE borrowernumber=?|;
748     my @notify;
749     my $sth = $dbh->prepare($query);
750     $sth->execute($borrowernumber);
751     while ( my ($numberofnotify) = $sth->fetchrow ) {
752         push( @notify, $numberofnotify );
753     }
754     return (@notify);
755 }
756
757 =head2 AmountNotify
758
759     ($totalnotify) = &AmountNotify($notifyid);
760
761 Returns amount for all file per borrowers
762 C<$notifyid> is the file number
763
764 C<$totalnotify> contains amount of a file
765
766 C<$notify_id> contains the file number for the borrower number and item number
767
768 =cut
769
770 sub AmountNotify{
771     my ($notifyid,$borrowernumber)=@_;
772     my $dbh = C4::Context->dbh;
773     my $query=qq|    SELECT sum(amountoutstanding)
774             FROM accountlines
775             WHERE notify_id=? AND borrowernumber = ?|;
776     my $sth=$dbh->prepare($query);
777         $sth->execute($notifyid,$borrowernumber);
778         my $totalnotify=$sth->fetchrow;
779     $sth->finish;
780     return ($totalnotify);
781 }
782
783
784 =head2 GetNotifyId
785
786     ($notify_id) = &GetNotifyId($borrowernumber,$itemnumber);
787
788 Returns the file number per borrower and itemnumber
789
790 C<$borrowernumber> is a reference-to-hash whose keys are all of the fields
791 from the items tables of the Koha database. Thus,
792
793 C<$itemnumber> contains the borrower categorycode
794
795 C<$notify_id> contains the file number for the borrower number nad item number
796
797 =cut
798
799 sub GetNotifyId {
800     my ( $borrowernumber, $itemnumber ) = @_;
801     my $query = qq|SELECT notify_id
802            FROM accountlines
803            WHERE borrowernumber=?
804           AND itemnumber=?
805            AND (accounttype='FU' or accounttype='O')|;
806     my $dbh = C4::Context->dbh;
807     my $sth = $dbh->prepare($query);
808     $sth->execute( $borrowernumber, $itemnumber );
809     my ($notify_id) = $sth->fetchrow;
810     $sth->finish;
811     return ($notify_id);
812 }
813
814 =head2 CreateItemAccountLine
815
816     () = &CreateItemAccountLine($borrowernumber, $itemnumber, $date, $amount,
817                                $description, $accounttype, $amountoutstanding, 
818                                $timestamp, $notify_id, $level);
819
820 update the account lines with file number or with file level
821
822 C<$items> is a reference-to-hash whose keys are all of the fields
823 from the items tables of the Koha database. Thus,
824
825 C<$itemnumber> contains the item number
826
827 C<$borrowernumber> contains the borrower number
828
829 C<$date> contains the date of the day
830
831 C<$amount> contains item price
832
833 C<$description> contains the descritpion of accounttype 
834
835 C<$accounttype> contains the account type
836
837 C<$amountoutstanding> contains the $amountoutstanding 
838
839 C<$timestamp> contains the timestamp with time and the date of the day
840
841 C<$notify_id> contains the file number
842
843 C<$level> contains the file level
844
845 =cut
846
847 sub CreateItemAccountLine {
848     my (
849         $borrowernumber, $itemnumber,  $date,              $amount,
850         $description,    $accounttype, $amountoutstanding, $timestamp,
851         $notify_id,      $level
852     ) = @_;
853     my $dbh         = C4::Context->dbh;
854     my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
855     my $query       = "INSERT into accountlines
856          (borrowernumber,accountno,itemnumber,date,amount,description,accounttype,amountoutstanding,timestamp,notify_id,notify_level)
857           VALUES
858              (?,?,?,?,?,?,?,?,?,?,?)";
859
860     my $sth = $dbh->prepare($query);
861     $sth->execute(
862         $borrowernumber, $nextaccntno,       $itemnumber,
863         $date,           $amount,            $description,
864         $accounttype,    $amountoutstanding, $timestamp,
865         $notify_id,      $level
866     );
867 }
868
869 =head2 UpdateAccountLines
870
871     () = &UpdateAccountLines($notify_id,$notify_level,$borrowernumber,$itemnumber);
872
873 update the account lines with file number or with file level
874
875 C<$items> is a reference-to-hash whose keys are all of the fields
876 from the items tables of the Koha database. Thus,
877
878 C<$itemnumber> contains the item number
879
880 C<$notify_id> contains the file number
881
882 C<$notify_level> contains the file level
883
884 C<$borrowernumber> contains the borrowernumber
885
886 =cut
887
888 sub UpdateAccountLines {
889     my ( $notify_id, $notify_level, $borrowernumber, $itemnumber ) = @_;
890     my $query;
891     if ( $notify_id eq '' ) {
892         $query = qq|UPDATE accountlines
893     SET  notify_level=?
894     WHERE borrowernumber=? AND itemnumber=?
895     AND (accounttype='FU' or accounttype='O')|;
896     } else {
897         $query = qq|UPDATE accountlines
898      SET notify_id=?, notify_level=?
899    WHERE borrowernumber=?
900     AND itemnumber=?
901     AND (accounttype='FU' or accounttype='O')|;
902     }
903
904     my $sth = C4::Context->dbh->prepare($query);
905     if ( $notify_id eq '' ) {
906         $sth->execute( $notify_level, $borrowernumber, $itemnumber );
907     } else {
908         $sth->execute( $notify_id, $notify_level, $borrowernumber, $itemnumber );
909     }
910 }
911
912 =head2 GetItems
913
914     ($items) = &GetItems($itemnumber);
915
916 Returns the list of all delays from overduerules.
917
918 C<$items> is a reference-to-hash whose keys are all of the fields
919 from the items tables of the Koha database. Thus,
920
921 C<$itemnumber> contains the borrower categorycode
922
923 =cut
924
925 # FIXME: This is a bad function to have here.
926 # Shouldn't it be in C4::Items?
927 # Shouldn't it be called GetItem since you only get 1 row?
928 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
929
930 sub GetItems {
931     my $itemnumber = shift or return;
932     my $query = qq|SELECT *
933              FROM items
934               WHERE itemnumber=?|;
935     my $sth = C4::Context->dbh->prepare($query);
936     $sth->execute($itemnumber);
937     my ($items) = $sth->fetchrow_hashref;
938     return ($items);
939 }
940
941 =head2 GetOverdueDelays
942
943     (@delays) = &GetOverdueDelays($categorycode);
944
945 Returns the list of all delays from overduerules.
946
947 C<@delays> it's an array contains the three delays from overduerules table
948
949 C<$categorycode> contains the borrower categorycode
950
951 =cut
952
953 sub GetOverdueDelays {
954     my ($category) = @_;
955     my $query      = qq|SELECT delay1,delay2,delay3
956                 FROM overduerules
957                 WHERE categorycode=?|;
958     my $sth = C4::Context->dbh->prepare($query);
959     $sth->execute($category);
960     my (@delays) = $sth->fetchrow_array;
961     return (@delays);
962 }
963
964 =head2 GetBranchcodesWithOverdueRules
965
966     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
967
968 returns a list of branch codes for branches with overdue rules defined.
969
970 =cut
971
972 sub GetBranchcodesWithOverdueRules {
973     my $dbh               = C4::Context->dbh;
974     my $rqoverduebranches = $dbh->prepare("SELECT DISTINCT branchcode FROM overduerules WHERE delay1 IS NOT NULL AND branchcode <> '' ORDER BY branchcode");
975     $rqoverduebranches->execute;
976     my @branches = map { shift @$_ } @{ $rqoverduebranches->fetchall_arrayref };
977     if (!$branches[0]) {
978        my $availbranches = C4::Branch::GetBranches();
979        @branches = keys %$availbranches;
980     }
981     return @branches;
982 }
983
984 =head2 CheckAccountLineLevelInfo
985
986     ($exist) = &CheckAccountLineLevelInfo($borrowernumber,$itemnumber,$accounttype,notify_level);
987
988 Check and Returns the list of all overdue books.
989
990 C<$exist> contains number of line in accounlines
991 with the same .biblionumber,itemnumber,accounttype,and notify_level
992
993 C<$borrowernumber> contains the borrower number
994
995 C<$itemnumber> contains item number
996
997 C<$accounttype> contains account type
998
999 C<$notify_level> contains the accountline level 
1000
1001
1002 =cut
1003
1004 sub CheckAccountLineLevelInfo {
1005     my ( $borrowernumber, $itemnumber, $level ) = @_;
1006     my $dbh   = C4::Context->dbh;
1007     my $query = qq|SELECT count(*)
1008             FROM accountlines
1009             WHERE borrowernumber =?
1010             AND itemnumber = ?
1011             AND notify_level=?|;
1012     my $sth = $dbh->prepare($query);
1013     $sth->execute( $borrowernumber, $itemnumber, $level );
1014     my ($exist) = $sth->fetchrow;
1015     return ($exist);
1016 }
1017
1018 =head2 GetOverduerules
1019
1020     ($overduerules) = &GetOverduerules($categorycode);
1021
1022 Returns the value of borrowers (debarred or not) with notify level
1023
1024 C<$overduerules> return value of debbraed field in overduerules table
1025
1026 C<$category> contains the borrower categorycode
1027
1028 C<$notify_level> contains the notify level
1029
1030 =cut
1031
1032 sub GetOverduerules {
1033     my ( $category, $notify_level ) = @_;
1034     my $dbh   = C4::Context->dbh;
1035     my $query = qq|SELECT debarred$notify_level
1036                      FROM overduerules
1037                     WHERE categorycode=?|;
1038     my $sth = $dbh->prepare($query);
1039     $sth->execute($category);
1040     my ($overduerules) = $sth->fetchrow;
1041     return ($overduerules);
1042 }
1043
1044
1045 =head2 CheckBorrowerDebarred
1046
1047     ($debarredstatus) = &CheckBorrowerDebarred($borrowernumber);
1048
1049 Check if the borrowers is already debarred
1050
1051 C<$debarredstatus> return 0 for not debarred and return 1 for debarred
1052
1053 C<$borrowernumber> contains the borrower number
1054
1055 =cut
1056
1057 # FIXME: Shouldn't this be in C4::Members?
1058 sub CheckBorrowerDebarred {
1059     my ($borrowernumber) = @_;
1060     my $dbh   = C4::Context->dbh;
1061     my $query = qq|
1062         SELECT debarred
1063         FROM borrowers
1064         WHERE borrowernumber=?
1065         AND debarred > NOW()
1066     |;
1067     my $sth = $dbh->prepare($query);
1068     $sth->execute($borrowernumber);
1069     my $debarredstatus = $sth->fetchrow;
1070     return $debarredstatus;
1071 }
1072
1073
1074 =head2 CheckExistantNotifyid
1075
1076     ($exist) = &CheckExistantNotifyid($borrowernumber,$itemnumber,$accounttype,$notify_id);
1077
1078 Check and Returns the notify id if exist else return 0.
1079
1080 C<$exist> contains a notify_id 
1081
1082 C<$borrowernumber> contains the borrower number
1083
1084 C<$date_due> contains the date of item return 
1085
1086
1087 =cut
1088
1089 sub CheckExistantNotifyid {
1090     my ( $borrowernumber, $date_due ) = @_;
1091     my $dbh   = C4::Context->dbh;
1092     my $query = qq|SELECT notify_id FROM accountlines
1093              LEFT JOIN issues ON issues.itemnumber= accountlines.itemnumber
1094              WHERE accountlines.borrowernumber =?
1095               AND date_due = ?|;
1096     my $sth = $dbh->prepare($query);
1097     $sth->execute( $borrowernumber, $date_due );
1098     return $sth->fetchrow || 0;
1099 }
1100
1101 =head2 CheckAccountLineItemInfo
1102
1103     ($exist) = &CheckAccountLineItemInfo($borrowernumber,$itemnumber,$accounttype,$notify_id);
1104
1105 Check and Returns the list of all overdue items from the same file number(notify_id).
1106
1107 C<$exist> contains number of line in accounlines
1108 with the same .biblionumber,itemnumber,accounttype,notify_id
1109
1110 C<$borrowernumber> contains the borrower number
1111
1112 C<$itemnumber> contains item number
1113
1114 C<$accounttype> contains account type
1115
1116 C<$notify_id> contains the file number 
1117
1118 =cut
1119
1120 sub CheckAccountLineItemInfo {
1121     my ( $borrowernumber, $itemnumber, $accounttype, $notify_id ) = @_;
1122     my $dbh   = C4::Context->dbh;
1123     my $query = qq|SELECT count(*) FROM accountlines
1124              WHERE borrowernumber =?
1125              AND itemnumber = ?
1126               AND accounttype= ?
1127             AND notify_id = ?|;
1128     my $sth = $dbh->prepare($query);
1129     $sth->execute( $borrowernumber, $itemnumber, $accounttype, $notify_id );
1130     my ($exist) = $sth->fetchrow;
1131     return ($exist);
1132 }
1133
1134 =head2 CheckItemNotify
1135
1136 Sql request to check if the document has alreday been notified
1137 this function is not exported, only used with GetOverduesForBranch
1138
1139 =cut
1140
1141 sub CheckItemNotify {
1142     my ($notify_id,$notify_level,$itemnumber) = @_;
1143     my $dbh = C4::Context->dbh;
1144     my $sth = $dbh->prepare("
1145     SELECT COUNT(*)
1146      FROM notifys
1147     WHERE notify_id    = ?
1148      AND  notify_level = ? 
1149      AND  itemnumber   = ? ");
1150     $sth->execute($notify_id,$notify_level,$itemnumber);
1151     my $notified = $sth->fetchrow;
1152     return ($notified);
1153 }
1154
1155 =head2 GetOverduesForBranch
1156
1157 Sql request for display all information for branchoverdues.pl
1158 2 possibilities : with or without location .
1159 display is filtered by branch
1160
1161 FIXME: This function should be renamed.
1162
1163 =cut
1164
1165 sub GetOverduesForBranch {
1166     my ( $branch, $location) = @_;
1167         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
1168     my $dbh = C4::Context->dbh;
1169     my $select = "
1170     SELECT
1171             borrowers.borrowernumber,
1172             borrowers.surname,
1173             borrowers.firstname,
1174             borrowers.phone,
1175             borrowers.email,
1176                biblio.title,
1177                biblio.author,
1178                biblio.biblionumber,
1179                issues.date_due,
1180                issues.returndate,
1181                issues.branchcode,
1182              branches.branchname,
1183                 items.barcode,
1184                 items.homebranch,
1185                 items.itemcallnumber,
1186                 items.location,
1187                 items.itemnumber,
1188             itemtypes.description,
1189          accountlines.notify_id,
1190          accountlines.notify_level,
1191          accountlines.amountoutstanding
1192     FROM  accountlines
1193     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
1194                           AND   issues.borrowernumber = accountlines.borrowernumber
1195     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
1196     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
1197     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
1198     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1199     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
1200     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
1201     WHERE (accountlines.amountoutstanding  != '0.000000')
1202       AND (accountlines.accounttype         = 'FU'      )
1203       AND (issues.branchcode =  ?   )
1204       AND (issues.date_due  < NOW())
1205     ";
1206     my @getoverdues;
1207     my $i = 0;
1208     my $sth;
1209     if ($location) {
1210         $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
1211         $sth->execute($branch, $location);
1212     } else {
1213         $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
1214         $sth->execute($branch);
1215     }
1216     while ( my $data = $sth->fetchrow_hashref ) {
1217     #check if the document has already been notified
1218         my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
1219         if ($countnotify eq '0') {
1220             $getoverdues[$i] = $data;
1221             $i++;
1222         }
1223     }
1224     return (@getoverdues);
1225 }
1226
1227
1228 =head2 AddNotifyLine
1229
1230     &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
1231
1232 Create a line into notify, if the method is phone, the notification_send_date is implemented to
1233
1234 =cut
1235
1236 sub AddNotifyLine {
1237     my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
1238     my $dbh = C4::Context->dbh;
1239     if ( $method eq "phone" ) {
1240         my $sth = $dbh->prepare(
1241             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
1242         VALUES (?,?,now(),now(),?,?,?)"
1243         );
1244         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1245             $notifyId );
1246     }
1247     else {
1248         my $sth = $dbh->prepare(
1249             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
1250         VALUES (?,?,now(),?,?,?)"
1251         );
1252         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1253             $notifyId );
1254     }
1255     return 1;
1256 }
1257
1258 =head2 RemoveNotifyLine
1259
1260     &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
1261
1262 Cancel a notification
1263
1264 =cut
1265
1266 sub RemoveNotifyLine {
1267     my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
1268     my $dbh = C4::Context->dbh;
1269     my $sth = $dbh->prepare(
1270         "DELETE FROM notifys 
1271             WHERE
1272             borrowernumber=?
1273             AND itemnumber=?
1274             AND notify_date=?"
1275     );
1276     $sth->execute( $borrowernumber, $itemnumber, $notify_date );
1277     return 1;
1278 }
1279
1280 1;
1281 __END__
1282
1283 =head1 AUTHOR
1284
1285 Koha Development Team <http://koha-community.org/>
1286
1287 =cut