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