bug_7090: AllowItemsOnHandCheckout syspref
[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 = q|SELECT sum(amountoutstanding) as fineamount 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 $fine = $sth->fetchrow_hashref();
652     if ($fine->{fineamount}) {
653         return $fine->{fineamount};
654     }
655     return 0;
656 }
657
658
659 =head2 GetIssuingRules
660
661 FIXME - This sub should be deprecated and removed.
662 It ignores branch and defaults.
663
664     $data = &GetIssuingRules($itemtype,$categorycode);
665
666 Looks up for all issuingrules an item info 
667
668 C<$itemnumber> is a reference-to-hash whose keys are all of the fields
669 from the borrowers and categories tables of the Koha database. Thus,
670
671 C<$categorycode> contains  information about borrowers category 
672
673 C<$data> contains all information about both the borrower and
674 category he or she belongs to.
675 =cut 
676
677 sub GetIssuingRules {
678         warn "GetIssuingRules is deprecated: use GetIssuingRule from C4::Circulation instead.";
679    my ($itemtype,$categorycode)=@_;
680    my $dbh   = C4::Context->dbh();    
681    my $query=qq|SELECT *
682         FROM issuingrules
683         WHERE issuingrules.itemtype=?
684             AND issuingrules.categorycode=?
685         |;
686     my $sth = $dbh->prepare($query);
687     #  print $query;
688     $sth->execute($itemtype,$categorycode);
689     return $sth->fetchrow_hashref;
690 }
691
692
693 sub ReplacementCost2 {
694     my ( $itemnum, $borrowernumber ) = @_;
695     my $dbh   = C4::Context->dbh();
696     my $query = "SELECT amountoutstanding
697          FROM accountlines
698              WHERE accounttype like 'L'
699          AND amountoutstanding > 0
700          AND itemnumber = ?
701          AND borrowernumber= ?";
702     my $sth = $dbh->prepare($query);
703     $sth->execute( $itemnum, $borrowernumber );
704     my $data = $sth->fetchrow_hashref();
705     return ( $data->{'amountoutstanding'} );
706 }
707
708
709 =head2 GetNextIdNotify
710
711     ($result) = &GetNextIdNotify($reference);
712
713 Returns the new file number
714
715 C<$result> contains the next file number
716
717 C<$reference> contains the beggining of file number
718
719 =cut
720
721 sub GetNextIdNotify {
722     my ($reference) = @_;
723     my $query = qq|SELECT max(notify_id)
724          FROM accountlines
725          WHERE notify_id  like \"$reference%\"
726          |;
727
728     # AND borrowernumber=?|;
729     my $dbh = C4::Context->dbh;
730     my $sth = $dbh->prepare($query);
731     $sth->execute();
732     my $result = $sth->fetchrow;
733     my $count;
734     if ( $result eq '' ) {
735         ( $result = $reference . "01" );
736     }
737     else {
738         $count = substr( $result, 6 ) + 1;
739
740         if ( $count < 10 ) {
741             ( $count = "0" . $count );
742         }
743         $result = $reference . $count;
744     }
745     return $result;
746 }
747
748 =head2 NumberNotifyId
749
750     (@notify) = &NumberNotifyId($borrowernumber);
751
752 Returns amount for all file per borrowers
753 C<@notify> array contains all file per borrowers
754
755 C<$notify_id> contains the file number for the borrower number nad item number
756
757 =cut
758
759 sub NumberNotifyId{
760     my ($borrowernumber)=@_;
761     my $dbh = C4::Context->dbh;
762     my $query=qq|    SELECT distinct(notify_id)
763             FROM accountlines
764             WHERE borrowernumber=?|;
765     my @notify;
766     my $sth = $dbh->prepare($query);
767     $sth->execute($borrowernumber);
768     while ( my ($numberofnotify) = $sth->fetchrow ) {
769         push( @notify, $numberofnotify );
770     }
771     return (@notify);
772 }
773
774 =head2 AmountNotify
775
776     ($totalnotify) = &AmountNotify($notifyid);
777
778 Returns amount for all file per borrowers
779 C<$notifyid> is the file number
780
781 C<$totalnotify> contains amount of a file
782
783 C<$notify_id> contains the file number for the borrower number and item number
784
785 =cut
786
787 sub AmountNotify{
788     my ($notifyid,$borrowernumber)=@_;
789     my $dbh = C4::Context->dbh;
790     my $query=qq|    SELECT sum(amountoutstanding)
791             FROM accountlines
792             WHERE notify_id=? AND borrowernumber = ?|;
793     my $sth=$dbh->prepare($query);
794         $sth->execute($notifyid,$borrowernumber);
795         my $totalnotify=$sth->fetchrow;
796     $sth->finish;
797     return ($totalnotify);
798 }
799
800
801 =head2 GetNotifyId
802
803     ($notify_id) = &GetNotifyId($borrowernumber,$itemnumber);
804
805 Returns the file number per borrower and itemnumber
806
807 C<$borrowernumber> is a reference-to-hash whose keys are all of the fields
808 from the items tables of the Koha database. Thus,
809
810 C<$itemnumber> contains the borrower categorycode
811
812 C<$notify_id> contains the file number for the borrower number nad item number
813
814 =cut
815
816 sub GetNotifyId {
817     my ( $borrowernumber, $itemnumber ) = @_;
818     my $query = qq|SELECT notify_id
819            FROM accountlines
820            WHERE borrowernumber=?
821           AND itemnumber=?
822            AND (accounttype='FU' or accounttype='O')|;
823     my $dbh = C4::Context->dbh;
824     my $sth = $dbh->prepare($query);
825     $sth->execute( $borrowernumber, $itemnumber );
826     my ($notify_id) = $sth->fetchrow;
827     $sth->finish;
828     return ($notify_id);
829 }
830
831 =head2 CreateItemAccountLine
832
833     () = &CreateItemAccountLine($borrowernumber, $itemnumber, $date, $amount,
834                                $description, $accounttype, $amountoutstanding, 
835                                $timestamp, $notify_id, $level);
836
837 update the account lines with file number or with file level
838
839 C<$items> is a reference-to-hash whose keys are all of the fields
840 from the items tables of the Koha database. Thus,
841
842 C<$itemnumber> contains the item number
843
844 C<$borrowernumber> contains the borrower number
845
846 C<$date> contains the date of the day
847
848 C<$amount> contains item price
849
850 C<$description> contains the descritpion of accounttype 
851
852 C<$accounttype> contains the account type
853
854 C<$amountoutstanding> contains the $amountoutstanding 
855
856 C<$timestamp> contains the timestamp with time and the date of the day
857
858 C<$notify_id> contains the file number
859
860 C<$level> contains the file level
861
862 =cut
863
864 sub CreateItemAccountLine {
865     my (
866         $borrowernumber, $itemnumber,  $date,              $amount,
867         $description,    $accounttype, $amountoutstanding, $timestamp,
868         $notify_id,      $level
869     ) = @_;
870     my $dbh         = C4::Context->dbh;
871     my $nextaccntno = C4::Accounts::getnextacctno($borrowernumber);
872     my $query       = "INSERT into accountlines
873          (borrowernumber,accountno,itemnumber,date,amount,description,accounttype,amountoutstanding,timestamp,notify_id,notify_level)
874           VALUES
875              (?,?,?,?,?,?,?,?,?,?,?)";
876
877     my $sth = $dbh->prepare($query);
878     $sth->execute(
879         $borrowernumber, $nextaccntno,       $itemnumber,
880         $date,           $amount,            $description,
881         $accounttype,    $amountoutstanding, $timestamp,
882         $notify_id,      $level
883     );
884 }
885
886 =head2 UpdateAccountLines
887
888     () = &UpdateAccountLines($notify_id,$notify_level,$borrowernumber,$itemnumber);
889
890 update the account lines with file number or with file level
891
892 C<$items> is a reference-to-hash whose keys are all of the fields
893 from the items tables of the Koha database. Thus,
894
895 C<$itemnumber> contains the item number
896
897 C<$notify_id> contains the file number
898
899 C<$notify_level> contains the file level
900
901 C<$borrowernumber> contains the borrowernumber
902
903 =cut
904
905 sub UpdateAccountLines {
906     my ( $notify_id, $notify_level, $borrowernumber, $itemnumber ) = @_;
907     my $query;
908     if ( $notify_id eq '' ) {
909         $query = qq|UPDATE accountlines
910     SET  notify_level=?
911     WHERE borrowernumber=? AND itemnumber=?
912     AND (accounttype='FU' or accounttype='O')|;
913     } else {
914         $query = qq|UPDATE accountlines
915      SET notify_id=?, notify_level=?
916    WHERE borrowernumber=?
917     AND itemnumber=?
918     AND (accounttype='FU' or accounttype='O')|;
919     }
920
921     my $sth = C4::Context->dbh->prepare($query);
922     if ( $notify_id eq '' ) {
923         $sth->execute( $notify_level, $borrowernumber, $itemnumber );
924     } else {
925         $sth->execute( $notify_id, $notify_level, $borrowernumber, $itemnumber );
926     }
927 }
928
929 =head2 GetItems
930
931     ($items) = &GetItems($itemnumber);
932
933 Returns the list of all delays from overduerules.
934
935 C<$items> is a reference-to-hash whose keys are all of the fields
936 from the items tables of the Koha database. Thus,
937
938 C<$itemnumber> contains the borrower categorycode
939
940 =cut
941
942 # FIXME: This is a bad function to have here.
943 # Shouldn't it be in C4::Items?
944 # Shouldn't it be called GetItem since you only get 1 row?
945 # Shouldn't it be called GetItem since you give it only 1 itemnumber?
946
947 sub GetItems {
948     my $itemnumber = shift or return;
949     my $query = qq|SELECT *
950              FROM items
951               WHERE itemnumber=?|;
952     my $sth = C4::Context->dbh->prepare($query);
953     $sth->execute($itemnumber);
954     my ($items) = $sth->fetchrow_hashref;
955     return ($items);
956 }
957
958 =head2 GetOverdueDelays
959
960     (@delays) = &GetOverdueDelays($categorycode);
961
962 Returns the list of all delays from overduerules.
963
964 C<@delays> it's an array contains the three delays from overduerules table
965
966 C<$categorycode> contains the borrower categorycode
967
968 =cut
969
970 sub GetOverdueDelays {
971     my ($category) = @_;
972     my $query      = qq|SELECT delay1,delay2,delay3
973                 FROM overduerules
974                 WHERE categorycode=?|;
975     my $sth = C4::Context->dbh->prepare($query);
976     $sth->execute($category);
977     my (@delays) = $sth->fetchrow_array;
978     return (@delays);
979 }
980
981 =head2 GetBranchcodesWithOverdueRules
982
983     my @branchcodes = C4::Overdues::GetBranchcodesWithOverdueRules()
984
985 returns a list of branch codes for branches with overdue rules defined.
986
987 =cut
988
989 sub GetBranchcodesWithOverdueRules {
990     my $dbh               = C4::Context->dbh;
991     my $rqoverduebranches = $dbh->prepare("SELECT DISTINCT branchcode FROM overduerules WHERE delay1 IS NOT NULL AND branchcode <> '' ORDER BY branchcode");
992     $rqoverduebranches->execute;
993     my @branches = map { shift @$_ } @{ $rqoverduebranches->fetchall_arrayref };
994     if (!$branches[0]) {
995        my $availbranches = C4::Branch::GetBranches();
996        @branches = keys %$availbranches;
997     }
998     return @branches;
999 }
1000
1001 =head2 CheckAccountLineLevelInfo
1002
1003     ($exist) = &CheckAccountLineLevelInfo($borrowernumber,$itemnumber,$accounttype,notify_level);
1004
1005 Check and Returns the list of all overdue books.
1006
1007 C<$exist> contains number of line in accounlines
1008 with the same .biblionumber,itemnumber,accounttype,and notify_level
1009
1010 C<$borrowernumber> contains the borrower number
1011
1012 C<$itemnumber> contains item number
1013
1014 C<$accounttype> contains account type
1015
1016 C<$notify_level> contains the accountline level 
1017
1018
1019 =cut
1020
1021 sub CheckAccountLineLevelInfo {
1022     my ( $borrowernumber, $itemnumber, $level ) = @_;
1023     my $dbh   = C4::Context->dbh;
1024     my $query = qq|SELECT count(*)
1025             FROM accountlines
1026             WHERE borrowernumber =?
1027             AND itemnumber = ?
1028             AND notify_level=?|;
1029     my $sth = $dbh->prepare($query);
1030     $sth->execute( $borrowernumber, $itemnumber, $level );
1031     my ($exist) = $sth->fetchrow;
1032     return ($exist);
1033 }
1034
1035 =head2 GetOverduerules
1036
1037     ($overduerules) = &GetOverduerules($categorycode);
1038
1039 Returns the value of borrowers (debarred or not) with notify level
1040
1041 C<$overduerules> return value of debbraed field in overduerules table
1042
1043 C<$category> contains the borrower categorycode
1044
1045 C<$notify_level> contains the notify level
1046
1047 =cut
1048
1049 sub GetOverduerules {
1050     my ( $category, $notify_level ) = @_;
1051     my $dbh   = C4::Context->dbh;
1052     my $query = qq|SELECT debarred$notify_level
1053                      FROM overduerules
1054                     WHERE categorycode=?|;
1055     my $sth = $dbh->prepare($query);
1056     $sth->execute($category);
1057     my ($overduerules) = $sth->fetchrow;
1058     return ($overduerules);
1059 }
1060
1061
1062 =head2 CheckBorrowerDebarred
1063
1064     ($debarredstatus) = &CheckBorrowerDebarred($borrowernumber);
1065
1066 Check if the borrowers is already debarred
1067
1068 C<$debarredstatus> return 0 for not debarred and return 1 for debarred
1069
1070 C<$borrowernumber> contains the borrower number
1071
1072 =cut
1073
1074 # FIXME: Shouldn't this be in C4::Members?
1075 sub CheckBorrowerDebarred {
1076     my ($borrowernumber) = @_;
1077     my $dbh   = C4::Context->dbh;
1078     my $query = qq|
1079         SELECT debarred
1080         FROM borrowers
1081         WHERE borrowernumber=?
1082         AND debarred > NOW()
1083     |;
1084     my $sth = $dbh->prepare($query);
1085     $sth->execute($borrowernumber);
1086     my $debarredstatus = $sth->fetchrow;
1087     return $debarredstatus;
1088 }
1089
1090
1091 =head2 CheckExistantNotifyid
1092
1093     ($exist) = &CheckExistantNotifyid($borrowernumber,$itemnumber,$accounttype,$notify_id);
1094
1095 Check and Returns the notify id if exist else return 0.
1096
1097 C<$exist> contains a notify_id 
1098
1099 C<$borrowernumber> contains the borrower number
1100
1101 C<$date_due> contains the date of item return 
1102
1103
1104 =cut
1105
1106 sub CheckExistantNotifyid {
1107     my ( $borrowernumber, $date_due ) = @_;
1108     my $dbh   = C4::Context->dbh;
1109     my $query = qq|SELECT notify_id FROM accountlines
1110              LEFT JOIN issues ON issues.itemnumber= accountlines.itemnumber
1111              WHERE accountlines.borrowernumber =?
1112               AND date_due = ?|;
1113     my $sth = $dbh->prepare($query);
1114     $sth->execute( $borrowernumber, $date_due );
1115     return $sth->fetchrow || 0;
1116 }
1117
1118 =head2 CheckAccountLineItemInfo
1119
1120     ($exist) = &CheckAccountLineItemInfo($borrowernumber,$itemnumber,$accounttype,$notify_id);
1121
1122 Check and Returns the list of all overdue items from the same file number(notify_id).
1123
1124 C<$exist> contains number of line in accounlines
1125 with the same .biblionumber,itemnumber,accounttype,notify_id
1126
1127 C<$borrowernumber> contains the borrower number
1128
1129 C<$itemnumber> contains item number
1130
1131 C<$accounttype> contains account type
1132
1133 C<$notify_id> contains the file number 
1134
1135 =cut
1136
1137 sub CheckAccountLineItemInfo {
1138     my ( $borrowernumber, $itemnumber, $accounttype, $notify_id ) = @_;
1139     my $dbh   = C4::Context->dbh;
1140     my $query = qq|SELECT count(*) FROM accountlines
1141              WHERE borrowernumber =?
1142              AND itemnumber = ?
1143               AND accounttype= ?
1144             AND notify_id = ?|;
1145     my $sth = $dbh->prepare($query);
1146     $sth->execute( $borrowernumber, $itemnumber, $accounttype, $notify_id );
1147     my ($exist) = $sth->fetchrow;
1148     return ($exist);
1149 }
1150
1151 =head2 CheckItemNotify
1152
1153 Sql request to check if the document has alreday been notified
1154 this function is not exported, only used with GetOverduesForBranch
1155
1156 =cut
1157
1158 sub CheckItemNotify {
1159     my ($notify_id,$notify_level,$itemnumber) = @_;
1160     my $dbh = C4::Context->dbh;
1161     my $sth = $dbh->prepare("
1162     SELECT COUNT(*)
1163      FROM notifys
1164     WHERE notify_id    = ?
1165      AND  notify_level = ? 
1166      AND  itemnumber   = ? ");
1167     $sth->execute($notify_id,$notify_level,$itemnumber);
1168     my $notified = $sth->fetchrow;
1169     return ($notified);
1170 }
1171
1172 =head2 GetOverduesForBranch
1173
1174 Sql request for display all information for branchoverdues.pl
1175 2 possibilities : with or without location .
1176 display is filtered by branch
1177
1178 FIXME: This function should be renamed.
1179
1180 =cut
1181
1182 sub GetOverduesForBranch {
1183     my ( $branch, $location) = @_;
1184         my $itype_link =  (C4::Context->preference('item-level_itypes')) ?  " items.itype " :  " biblioitems.itemtype ";
1185     my $dbh = C4::Context->dbh;
1186     my $select = "
1187     SELECT
1188             borrowers.borrowernumber,
1189             borrowers.surname,
1190             borrowers.firstname,
1191             borrowers.phone,
1192             borrowers.email,
1193                biblio.title,
1194                biblio.author,
1195                biblio.biblionumber,
1196                issues.date_due,
1197                issues.returndate,
1198                issues.branchcode,
1199              branches.branchname,
1200                 items.barcode,
1201                 items.homebranch,
1202                 items.itemcallnumber,
1203                 items.location,
1204                 items.itemnumber,
1205             itemtypes.description,
1206          accountlines.notify_id,
1207          accountlines.notify_level,
1208          accountlines.amountoutstanding
1209     FROM  accountlines
1210     LEFT JOIN issues      ON    issues.itemnumber     = accountlines.itemnumber
1211                           AND   issues.borrowernumber = accountlines.borrowernumber
1212     LEFT JOIN borrowers   ON borrowers.borrowernumber = accountlines.borrowernumber
1213     LEFT JOIN items       ON     items.itemnumber     = issues.itemnumber
1214     LEFT JOIN biblio      ON      biblio.biblionumber =  items.biblionumber
1215     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1216     LEFT JOIN itemtypes   ON itemtypes.itemtype       = $itype_link
1217     LEFT JOIN branches    ON  branches.branchcode     = issues.branchcode
1218     WHERE (accountlines.amountoutstanding  != '0.000000')
1219       AND (accountlines.accounttype         = 'FU'      )
1220       AND (issues.branchcode =  ?   )
1221       AND (issues.date_due  < CURDATE())
1222     ";
1223     my @getoverdues;
1224     my $i = 0;
1225     my $sth;
1226     if ($location) {
1227         $sth = $dbh->prepare("$select AND items.location = ? ORDER BY borrowers.surname, borrowers.firstname");
1228         $sth->execute($branch, $location);
1229     } else {
1230         $sth = $dbh->prepare("$select ORDER BY borrowers.surname, borrowers.firstname");
1231         $sth->execute($branch);
1232     }
1233     while ( my $data = $sth->fetchrow_hashref ) {
1234     #check if the document has already been notified
1235         my $countnotify = CheckItemNotify($data->{'notify_id'}, $data->{'notify_level'}, $data->{'itemnumber'});
1236         if ($countnotify eq '0') {
1237             $getoverdues[$i] = $data;
1238             $i++;
1239         }
1240     }
1241     return (@getoverdues);
1242 }
1243
1244
1245 =head2 AddNotifyLine
1246
1247     &AddNotifyLine($borrowernumber, $itemnumber, $overduelevel, $method, $notifyId)
1248
1249 Create a line into notify, if the method is phone, the notification_send_date is implemented to
1250
1251 =cut
1252
1253 sub AddNotifyLine {
1254     my ( $borrowernumber, $itemnumber, $overduelevel, $method, $notifyId ) = @_;
1255     my $dbh = C4::Context->dbh;
1256     if ( $method eq "phone" ) {
1257         my $sth = $dbh->prepare(
1258             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_send_date,notify_level,method,notify_id)
1259         VALUES (?,?,now(),now(),?,?,?)"
1260         );
1261         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1262             $notifyId );
1263     }
1264     else {
1265         my $sth = $dbh->prepare(
1266             "INSERT INTO notifys (borrowernumber,itemnumber,notify_date,notify_level,method,notify_id)
1267         VALUES (?,?,now(),?,?,?)"
1268         );
1269         $sth->execute( $borrowernumber, $itemnumber, $overduelevel, $method,
1270             $notifyId );
1271     }
1272     return 1;
1273 }
1274
1275 =head2 RemoveNotifyLine
1276
1277     &RemoveNotifyLine( $borrowernumber, $itemnumber, $notify_date );
1278
1279 Cancel a notification
1280
1281 =cut
1282
1283 sub RemoveNotifyLine {
1284     my ( $borrowernumber, $itemnumber, $notify_date ) = @_;
1285     my $dbh = C4::Context->dbh;
1286     my $sth = $dbh->prepare(
1287         "DELETE FROM notifys 
1288             WHERE
1289             borrowernumber=?
1290             AND itemnumber=?
1291             AND notify_date=?"
1292     );
1293     $sth->execute( $borrowernumber, $itemnumber, $notify_date );
1294     return 1;
1295 }
1296
1297 1;
1298 __END__
1299
1300 =head1 AUTHOR
1301
1302 Koha Development Team <http://koha-community.org/>
1303
1304 =cut