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