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