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