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