Merge remote-tracking branch 'origin/new/bug_6865'
[koha.git] / C4 / Circulation.pm
1 package C4::Circulation;
2
3 # Copyright 2000-2002 Katipo Communications
4 # copyright 2010 BibLibre
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22 use strict;
23 #use warnings; FIXME - Bug 2505
24 use C4::Context;
25 use C4::Stats;
26 use C4::Reserves;
27 use C4::Biblio;
28 use C4::Items;
29 use C4::Members;
30 use C4::Dates;
31 use C4::Calendar;
32 use C4::Accounts;
33 use C4::ItemCirculationAlertPreference;
34 use C4::Dates qw(format_date);
35 use C4::Message;
36 use C4::Debug;
37 use Date::Calc qw(
38   Today
39   Today_and_Now
40   Add_Delta_YM
41   Add_Delta_DHMS
42   Date_to_Days
43   Day_of_Week
44   Add_Delta_Days        
45   check_date
46   Delta_Days
47 );
48 use POSIX qw(strftime);
49 use C4::Branch; # GetBranches
50 use C4::Log; # logaction
51
52 use Data::Dumper;
53
54 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
55
56 BEGIN {
57         require Exporter;
58         $VERSION = 3.02;        # for version checking
59         @ISA    = qw(Exporter);
60
61         # FIXME subs that should probably be elsewhere
62         push @EXPORT, qw(
63                 &barcodedecode
64         &LostItem
65         &ReturnLostItem
66         );
67
68         # subs to deal with issuing a book
69         push @EXPORT, qw(
70                 &CanBookBeIssued
71                 &CanBookBeRenewed
72                 &AddIssue
73                 &AddRenewal
74                 &GetRenewCount
75                 &GetItemIssue
76                 &GetItemIssues
77                 &GetIssuingCharges
78                 &GetIssuingRule
79         &GetBranchBorrowerCircRule
80         &GetBranchItemRule
81                 &GetBiblioIssues
82                 &GetOpenIssue
83                 &AnonymiseIssueHistory
84         );
85
86         # subs to deal with returns
87         push @EXPORT, qw(
88                 &AddReturn
89         &MarkIssueReturned
90         );
91
92         # subs to deal with transfers
93         push @EXPORT, qw(
94                 &transferbook
95                 &GetTransfers
96                 &GetTransfersFromTo
97                 &updateWrongTransfer
98                 &DeleteTransfer
99                 &IsBranchTransferAllowed
100                 &CreateBranchTransferLimit
101                 &DeleteBranchTransferLimits
102         );
103 }
104
105 =head1 NAME
106
107 C4::Circulation - Koha circulation module
108
109 =head1 SYNOPSIS
110
111 use C4::Circulation;
112
113 =head1 DESCRIPTION
114
115 The functions in this module deal with circulation, issues, and
116 returns, as well as general information about the library.
117 Also deals with stocktaking.
118
119 =head1 FUNCTIONS
120
121 =head2 barcodedecode
122
123   $str = &barcodedecode($barcode, [$filter]);
124
125 Generic filter function for barcode string.
126 Called on every circ if the System Pref itemBarcodeInputFilter is set.
127 Will do some manipulation of the barcode for systems that deliver a barcode
128 to circulation.pl that differs from the barcode stored for the item.
129 For proper functioning of this filter, calling the function on the 
130 correct barcode string (items.barcode) should return an unaltered barcode.
131
132 The optional $filter argument is to allow for testing or explicit 
133 behavior that ignores the System Pref.  Valid values are the same as the 
134 System Pref options.
135
136 =cut
137
138 # FIXME -- the &decode fcn below should be wrapped into this one.
139 # FIXME -- these plugins should be moved out of Circulation.pm
140 #
141 sub barcodedecode {
142     my ($barcode, $filter) = @_;
143     my $branch = C4::Branch::mybranch();
144     $filter = C4::Context->preference('itemBarcodeInputFilter') unless $filter;
145     $filter or return $barcode;     # ensure filter is defined, else return untouched barcode
146         if ($filter eq 'whitespace') {
147                 $barcode =~ s/\s//g;
148         } elsif ($filter eq 'cuecat') {
149                 chomp($barcode);
150             my @fields = split( /\./, $barcode );
151             my @results = map( decode($_), @fields[ 1 .. $#fields ] );
152             ($#results == 2) and return $results[2];
153         } elsif ($filter eq 'T-prefix') {
154                 if ($barcode =~ /^[Tt](\d)/) {
155                         (defined($1) and $1 eq '0') and return $barcode;
156             $barcode = substr($barcode, 2) + 0;     # FIXME: probably should be substr($barcode, 1)
157                 }
158         return sprintf("T%07d", $barcode);
159         # FIXME: $barcode could be "T1", causing warning: substr outside of string
160         # Why drop the nonzero digit after the T?
161         # Why pass non-digits (or empty string) to "T%07d"?
162         } elsif ($filter eq 'libsuite8') {
163                 unless($barcode =~ m/^($branch)-/i){    #if barcode starts with branch code its in Koha style. Skip it.
164                         if($barcode =~ m/^(\d)/i){      #Some barcodes even start with 0's & numbers and are assumed to have b as the item type in the libsuite8 software
165                                 $barcode =~ s/^[0]*(\d+)$/$branch-b-$1/i;
166                         }else{
167                                 $barcode =~ s/^(\D+)[0]*(\d+)$/$branch-$1-$2/i;
168                         }
169                 }
170         }
171     return $barcode;    # return barcode, modified or not
172 }
173
174 =head2 decode
175
176   $str = &decode($chunk);
177
178 Decodes a segment of a string emitted by a CueCat barcode scanner and
179 returns it.
180
181 FIXME: Should be replaced with Barcode::Cuecat from CPAN
182 or Javascript based decoding on the client side.
183
184 =cut
185
186 sub decode {
187     my ($encoded) = @_;
188     my $seq =
189       'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
190     my @s = map { index( $seq, $_ ); } split( //, $encoded );
191     my $l = ( $#s + 1 ) % 4;
192     if ($l) {
193         if ( $l == 1 ) {
194             # warn "Error: Cuecat decode parsing failed!";
195             return;
196         }
197         $l = 4 - $l;
198         $#s += $l;
199     }
200     my $r = '';
201     while ( $#s >= 0 ) {
202         my $n = ( ( $s[0] << 6 | $s[1] ) << 6 | $s[2] ) << 6 | $s[3];
203         $r .=
204             chr( ( $n >> 16 ) ^ 67 )
205          .chr( ( $n >> 8 & 255 ) ^ 67 )
206          .chr( ( $n & 255 ) ^ 67 );
207         @s = @s[ 4 .. $#s ];
208     }
209     $r = substr( $r, 0, length($r) - $l );
210     return $r;
211 }
212
213 =head2 transferbook
214
215   ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, 
216                                             $barcode, $ignore_reserves);
217
218 Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
219
220 C<$newbranch> is the code for the branch to which the item should be transferred.
221
222 C<$barcode> is the barcode of the item to be transferred.
223
224 If C<$ignore_reserves> is true, C<&transferbook> ignores reserves.
225 Otherwise, if an item is reserved, the transfer fails.
226
227 Returns three values:
228
229 =over
230
231 =item $dotransfer 
232
233 is true if the transfer was successful.
234
235 =item $messages
236
237 is a reference-to-hash which may have any of the following keys:
238
239 =over
240
241 =item C<BadBarcode>
242
243 There is no item in the catalog with the given barcode. The value is C<$barcode>.
244
245 =item C<IsPermanent>
246
247 The item's home branch is permanent. This doesn't prevent the item from being transferred, though. The value is the code of the item's home branch.
248
249 =item C<DestinationEqualsHolding>
250
251 The item is already at the branch to which it is being transferred. The transfer is nonetheless considered to have failed. The value should be ignored.
252
253 =item C<WasReturned>
254
255 The item was on loan, and C<&transferbook> automatically returned it before transferring it. The value is the borrower number of the patron who had the item.
256
257 =item C<ResFound>
258
259 The item was reserved. The value is a reference-to-hash whose keys are fields from the reserves table of the Koha database, and C<biblioitemnumber>. It also has the key C<ResFound>, whose value is either C<Waiting> or C<Reserved>.
260
261 =item C<WasTransferred>
262
263 The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
264
265 =back
266
267 =back
268
269 =cut
270
271 sub transferbook {
272     my ( $tbr, $barcode, $ignoreRs ) = @_;
273     my $messages;
274     my $dotransfer      = 1;
275     my $branches        = GetBranches();
276     my $itemnumber = GetItemnumberFromBarcode( $barcode );
277     my $issue      = GetItemIssue($itemnumber);
278     my $biblio = GetBiblioFromItemNumber($itemnumber);
279
280     # bad barcode..
281     if ( not $itemnumber ) {
282         $messages->{'BadBarcode'} = $barcode;
283         $dotransfer = 0;
284     }
285
286     # get branches of book...
287     my $hbr = $biblio->{'homebranch'};
288     my $fbr = $biblio->{'holdingbranch'};
289
290     # if using Branch Transfer Limits
291     if ( C4::Context->preference("UseBranchTransferLimits") == 1 ) {
292         if ( C4::Context->preference("item-level_itypes") && C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ) {
293             if ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{'itype'} ) ) {
294                 $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{'itype'};
295                 $dotransfer = 0;
296             }
297         } elsif ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{ C4::Context->preference("BranchTransferLimitsType") } ) ) {
298             $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{ C4::Context->preference("BranchTransferLimitsType") };
299             $dotransfer = 0;
300         }
301     }
302
303     # if is permanent...
304     if ( $hbr && $branches->{$hbr}->{'PE'} ) {
305         $messages->{'IsPermanent'} = $hbr;
306         $dotransfer = 0;
307     }
308
309     # can't transfer book if is already there....
310     if ( $fbr eq $tbr ) {
311         $messages->{'DestinationEqualsHolding'} = 1;
312         $dotransfer = 0;
313     }
314
315     # check if it is still issued to someone, return it...
316     if ($issue->{borrowernumber}) {
317         AddReturn( $barcode, $fbr );
318         $messages->{'WasReturned'} = $issue->{borrowernumber};
319     }
320
321     # find reserves.....
322     # That'll save a database query.
323     my ( $resfound, $resrec, undef ) =
324       CheckReserves( $itemnumber );
325     if ( $resfound and not $ignoreRs ) {
326         $resrec->{'ResFound'} = $resfound;
327
328         #         $messages->{'ResFound'} = $resrec;
329         $dotransfer = 1;
330     }
331
332     #actually do the transfer....
333     if ($dotransfer) {
334         ModItemTransfer( $itemnumber, $fbr, $tbr );
335
336         # don't need to update MARC anymore, we do it in batch now
337         $messages->{'WasTransfered'} = 1;
338
339     }
340     ModDateLastSeen( $itemnumber );
341     return ( $dotransfer, $messages, $biblio );
342 }
343
344
345 sub TooMany {
346     my $borrower        = shift;
347     my $biblionumber = shift;
348         my $item                = shift;
349     my $cat_borrower    = $borrower->{'categorycode'};
350     my $dbh             = C4::Context->dbh;
351         my $branch;
352         # Get which branchcode we need
353         $branch = _GetCircControlBranch($item,$borrower);
354         my $type = (C4::Context->preference('item-level_itypes')) 
355                         ? $item->{'itype'}         # item-level
356                         : $item->{'itemtype'};     # biblio-level
357  
358     # given branch, patron category, and item type, determine
359     # applicable issuing rule
360     my $issuing_rule = GetIssuingRule($cat_borrower, $type, $branch);
361
362     # if a rule is found and has a loan limit set, count
363     # how many loans the patron already has that meet that
364     # rule
365     if (defined($issuing_rule) and defined($issuing_rule->{'maxissueqty'})) {
366         my @bind_params;
367         my $count_query = "SELECT COUNT(*) FROM issues
368                            JOIN items USING (itemnumber) ";
369
370         my $rule_itemtype = $issuing_rule->{itemtype};
371         if ($rule_itemtype eq "*") {
372             # matching rule has the default item type, so count only
373             # those existing loans that don't fall under a more
374             # specific rule
375             if (C4::Context->preference('item-level_itypes')) {
376                 $count_query .= " WHERE items.itype NOT IN (
377                                     SELECT itemtype FROM issuingrules
378                                     WHERE branchcode = ?
379                                     AND   (categorycode = ? OR categorycode = ?)
380                                     AND   itemtype <> '*'
381                                   ) ";
382             } else { 
383                 $count_query .= " JOIN  biblioitems USING (biblionumber) 
384                                   WHERE biblioitems.itemtype NOT IN (
385                                     SELECT itemtype FROM issuingrules
386                                     WHERE branchcode = ?
387                                     AND   (categorycode = ? OR categorycode = ?)
388                                     AND   itemtype <> '*'
389                                   ) ";
390             }
391             push @bind_params, $issuing_rule->{branchcode};
392             push @bind_params, $issuing_rule->{categorycode};
393             push @bind_params, $cat_borrower;
394         } else {
395             # rule has specific item type, so count loans of that
396             # specific item type
397             if (C4::Context->preference('item-level_itypes')) {
398                 $count_query .= " WHERE items.itype = ? ";
399             } else { 
400                 $count_query .= " JOIN  biblioitems USING (biblionumber) 
401                                   WHERE biblioitems.itemtype= ? ";
402             }
403             push @bind_params, $type;
404         }
405
406         $count_query .= " AND borrowernumber = ? ";
407         push @bind_params, $borrower->{'borrowernumber'};
408         my $rule_branch = $issuing_rule->{branchcode};
409         if ($rule_branch ne "*") {
410             if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
411                 $count_query .= " AND issues.branchcode = ? ";
412                 push @bind_params, $branch;
413             } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
414                 ; # if branch is the patron's home branch, then count all loans by patron
415             } else {
416                 $count_query .= " AND items.homebranch = ? ";
417                 push @bind_params, $branch;
418             }
419         }
420
421         my $count_sth = $dbh->prepare($count_query);
422         $count_sth->execute(@bind_params);
423         my ($current_loan_count) = $count_sth->fetchrow_array;
424
425         my $max_loans_allowed = $issuing_rule->{'maxissueqty'};
426         if ($current_loan_count >= $max_loans_allowed) {
427             return ($current_loan_count, $max_loans_allowed);
428         }
429     }
430
431     # Now count total loans against the limit for the branch
432     my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
433     if (defined($branch_borrower_circ_rule->{maxissueqty})) {
434         my @bind_params = ();
435         my $branch_count_query = "SELECT COUNT(*) FROM issues 
436                                   JOIN items USING (itemnumber)
437                                   WHERE borrowernumber = ? ";
438         push @bind_params, $borrower->{borrowernumber};
439
440         if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
441             $branch_count_query .= " AND issues.branchcode = ? ";
442             push @bind_params, $branch;
443         } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
444             ; # if branch is the patron's home branch, then count all loans by patron
445         } else {
446             $branch_count_query .= " AND items.homebranch = ? ";
447             push @bind_params, $branch;
448         }
449         my $branch_count_sth = $dbh->prepare($branch_count_query);
450         $branch_count_sth->execute(@bind_params);
451         my ($current_loan_count) = $branch_count_sth->fetchrow_array;
452
453         my $max_loans_allowed = $branch_borrower_circ_rule->{maxissueqty};
454         if ($current_loan_count >= $max_loans_allowed) {
455             return ($current_loan_count, $max_loans_allowed);
456         }
457     }
458
459     # OK, the patron can issue !!!
460     return;
461 }
462
463 =head2 itemissues
464
465   @issues = &itemissues($biblioitemnumber, $biblio);
466
467 Looks up information about who has borrowed the bookZ<>(s) with the
468 given biblioitemnumber.
469
470 C<$biblio> is ignored.
471
472 C<&itemissues> returns an array of references-to-hash. The keys
473 include the fields from the C<items> table in the Koha database.
474 Additional keys include:
475
476 =over 4
477
478 =item C<date_due>
479
480 If the item is currently on loan, this gives the due date.
481
482 If the item is not on loan, then this is either "Available" or
483 "Cancelled", if the item has been withdrawn.
484
485 =item C<card>
486
487 If the item is currently on loan, this gives the card number of the
488 patron who currently has the item.
489
490 =item C<timestamp0>, C<timestamp1>, C<timestamp2>
491
492 These give the timestamp for the last three times the item was
493 borrowed.
494
495 =item C<card0>, C<card1>, C<card2>
496
497 The card number of the last three patrons who borrowed this item.
498
499 =item C<borrower0>, C<borrower1>, C<borrower2>
500
501 The borrower number of the last three patrons who borrowed this item.
502
503 =back
504
505 =cut
506
507 #'
508 sub itemissues {
509     my ( $bibitem, $biblio ) = @_;
510     my $dbh = C4::Context->dbh;
511     my $sth =
512       $dbh->prepare("Select * from items where items.biblioitemnumber = ?")
513       || die $dbh->errstr;
514     my $i = 0;
515     my @results;
516
517     $sth->execute($bibitem) || die $sth->errstr;
518
519     while ( my $data = $sth->fetchrow_hashref ) {
520
521         # Find out who currently has this item.
522         # FIXME - Wouldn't it be better to do this as a left join of
523         # some sort? Currently, this code assumes that if
524         # fetchrow_hashref() fails, then the book is on the shelf.
525         # fetchrow_hashref() can fail for any number of reasons (e.g.,
526         # database server crash), not just because no items match the
527         # search criteria.
528         my $sth2 = $dbh->prepare(
529             "SELECT * FROM issues
530                 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
531                 WHERE itemnumber = ?
532             "
533         );
534
535         $sth2->execute( $data->{'itemnumber'} );
536         if ( my $data2 = $sth2->fetchrow_hashref ) {
537             $data->{'date_due'} = $data2->{'date_due'};
538             $data->{'card'}     = $data2->{'cardnumber'};
539             $data->{'borrower'} = $data2->{'borrowernumber'};
540         }
541         else {
542             $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
543         }
544
545
546         # Find the last 3 people who borrowed this item.
547         $sth2 = $dbh->prepare(
548             "SELECT * FROM old_issues
549                 LEFT JOIN borrowers ON  issues.borrowernumber = borrowers.borrowernumber
550                 WHERE itemnumber = ?
551                 ORDER BY returndate DESC,timestamp DESC"
552         );
553
554         $sth2->execute( $data->{'itemnumber'} );
555         for ( my $i2 = 0 ; $i2 < 2 ; $i2++ )
556         {    # FIXME : error if there is less than 3 pple borrowing this item
557             if ( my $data2 = $sth2->fetchrow_hashref ) {
558                 $data->{"timestamp$i2"} = $data2->{'timestamp'};
559                 $data->{"card$i2"}      = $data2->{'cardnumber'};
560                 $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
561             }    # if
562         }    # for
563
564         $results[$i] = $data;
565         $i++;
566     }
567
568     return (@results);
569 }
570
571 =head2 CanBookBeIssued
572
573   ( $issuingimpossible, $needsconfirmation ) =  CanBookBeIssued( $borrower, 
574                                       $barcode, $duedatespec, $inprocess );
575
576 Check if a book can be issued.
577
578 C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
579
580 =over 4
581
582 =item C<$borrower> hash with borrower informations (from GetMember or GetMemberDetails)
583
584 =item C<$barcode> is the bar code of the book being issued.
585
586 =item C<$duedatespec> is a C4::Dates object.
587
588 =item C<$inprocess>
589
590 =back
591
592 Returns :
593
594 =over 4
595
596 =item C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
597 Possible values are :
598
599 =back
600
601 =head3 INVALID_DATE 
602
603 sticky due date is invalid
604
605 =head3 GNA
606
607 borrower gone with no address
608
609 =head3 CARD_LOST
610
611 borrower declared it's card lost
612
613 =head3 DEBARRED
614
615 borrower debarred
616
617 =head3 UNKNOWN_BARCODE
618
619 barcode unknown
620
621 =head3 NOT_FOR_LOAN
622
623 item is not for loan
624
625 =head3 WTHDRAWN
626
627 item withdrawn.
628
629 =head3 RESTRICTED
630
631 item is restricted (set by ??)
632
633 C<$needsconfirmation> a reference to a hash. It contains reasons why the loan 
634 could be prevented, but ones that can be overriden by the operator.
635
636 Possible values are :
637
638 =head3 DEBT
639
640 borrower has debts.
641
642 =head3 RENEW_ISSUE
643
644 renewing, not issuing
645
646 =head3 ISSUED_TO_ANOTHER
647
648 issued to someone else.
649
650 =head3 RESERVED
651
652 reserved for someone else.
653
654 =head3 INVALID_DATE
655
656 sticky due date is invalid or due date in the past
657
658 =head3 TOO_MANY
659
660 if the borrower borrows to much things
661
662 =cut
663
664 sub CanBookBeIssued {
665     my ( $borrower, $barcode, $duedate, $inprocess ) = @_;
666     my %needsconfirmation;    # filled with problems that needs confirmations
667     my %issuingimpossible;    # filled with problems that causes the issue to be IMPOSSIBLE
668     my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
669     my $issue = GetItemIssue($item->{itemnumber});
670         my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
671         $item->{'itemtype'}=$item->{'itype'}; 
672     my $dbh             = C4::Context->dbh;
673
674     # MANDATORY CHECKS - unless item exists, nothing else matters
675     unless ( $item->{barcode} ) {
676         $issuingimpossible{UNKNOWN_BARCODE} = 1;
677     }
678         return ( \%issuingimpossible, \%needsconfirmation ) if %issuingimpossible;
679
680     #
681     # DUE DATE is OK ? -- should already have checked.
682     #
683     unless ( $duedate ) {
684         my $issuedate = strftime( "%Y-%m-%d", localtime );
685
686         my $branch = _GetCircControlBranch($item,$borrower);
687         my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
688         $duedate = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower );
689
690         # Offline circ calls AddIssue directly, doesn't run through here
691         #  So issuingimpossible should be ok.
692     }
693     if ($duedate) {
694         $needsconfirmation{INVALID_DATE} = $duedate->output('syspref')
695           unless $duedate->output('iso') ge C4::Dates->today('iso');
696     } else {
697         $issuingimpossible{INVALID_DATE} = $duedate->output('syspref');
698     }
699
700     #
701     # BORROWER STATUS
702     #
703     if ( $borrower->{'category_type'} eq 'X' && (  $item->{barcode}  )) { 
704         # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1  .
705         &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'});
706         ModDateLastSeen( $item->{'itemnumber'} );
707         return( { STATS => 1 }, {});
708     }
709     if ( $borrower->{flags}->{GNA} ) {
710         $issuingimpossible{GNA} = 1;
711     }
712     if ( $borrower->{flags}->{'LOST'} ) {
713         $issuingimpossible{CARD_LOST} = 1;
714     }
715     if ( $borrower->{flags}->{'DBARRED'} ) {
716         $issuingimpossible{DEBARRED} = 1;
717     }
718     if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
719         $issuingimpossible{EXPIRED} = 1;
720     } else {
721         my @expirydate=  split /-/,$borrower->{'dateexpiry'};
722         if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
723             Date_to_Days(Today) > Date_to_Days( @expirydate )) {
724             $issuingimpossible{EXPIRED} = 1;                                   
725         }
726     }
727     #
728     # BORROWER STATUS
729     #
730
731     # DEBTS
732     my ($amount) =
733       C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') );
734     my $amountlimit = C4::Context->preference("noissuescharge");
735     my $allowfineoverride = C4::Context->preference("AllowFineOverride");
736     my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
737     if ( C4::Context->preference("IssuingInProcess") ) {
738         if ( $amount > $amountlimit && !$inprocess && !$allowfineoverride) {
739             $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
740         } elsif ( $amount > $amountlimit && !$inprocess && $allowfineoverride) {
741             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
742         } elsif ( $allfinesneedoverride && $amount > 0 && $amount <= $amountlimit && !$inprocess ) {
743             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
744         }
745     }
746     else {
747         if ( $amount > $amountlimit && $allowfineoverride ) {
748             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
749         } elsif ( $amount > $amountlimit && !$allowfineoverride) {
750             $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
751         } elsif ( $amount > 0 && $allfinesneedoverride ) {
752             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
753         }
754     }
755
756     my ($blocktype, $count) = C4::Members::IsMemberBlocked($borrower->{'borrowernumber'});
757     if ($blocktype == -1) {
758         ## patron has outstanding overdue loans
759             if ( C4::Context->preference("OverduesBlockCirc") eq 'block'){
760                 $issuingimpossible{USERBLOCKEDOVERDUE} = $count;
761             }
762             elsif ( C4::Context->preference("OverduesBlockCirc") eq 'confirmation'){
763                 $needsconfirmation{USERBLOCKEDOVERDUE} = $count;
764             }
765     } elsif($blocktype == 1) {
766         # patron has accrued fine days
767         $issuingimpossible{USERBLOCKEDREMAINING} = $count;
768     }
769
770 #
771     # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
772     #
773         my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item );
774     # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book
775     if ($max_loans_allowed eq 0) {
776         $needsconfirmation{PATRON_CANT} = 1;
777     } else {
778         if($max_loans_allowed){
779             $needsconfirmation{TOO_MANY} = 1;
780             $needsconfirmation{current_loan_count} = $current_loan_count;
781             $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
782         }
783     }
784
785     #
786     # ITEM CHECKING
787     #
788     if (   $item->{'notforloan'}
789         && $item->{'notforloan'} > 0 )
790     {
791         if(!C4::Context->preference("AllowNotForLoanOverride")){
792             $issuingimpossible{NOT_FOR_LOAN} = 1;
793         }else{
794             $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
795         }
796     }
797     elsif ( !$item->{'notforloan'} ){
798         # we have to check itemtypes.notforloan also
799         if (C4::Context->preference('item-level_itypes')){
800             # this should probably be a subroutine
801             my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
802             $sth->execute($item->{'itemtype'});
803             my $notforloan=$sth->fetchrow_hashref();
804             $sth->finish();
805             if ($notforloan->{'notforloan'}) {
806                 if (!C4::Context->preference("AllowNotForLoanOverride")) {
807                     $issuingimpossible{NOT_FOR_LOAN} = 1;
808                 } else {
809                     $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
810                 }
811             }
812         }
813         elsif ($biblioitem->{'notforloan'} == 1){
814             if (!C4::Context->preference("AllowNotForLoanOverride")) {
815                 $issuingimpossible{NOT_FOR_LOAN} = 1;
816             } else {
817                 $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
818             }
819         }
820     }
821     if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 )
822     {
823         $issuingimpossible{WTHDRAWN} = 1;
824     }
825     if (   $item->{'restricted'}
826         && $item->{'restricted'} == 1 )
827     {
828         $issuingimpossible{RESTRICTED} = 1;
829     }
830     if ( C4::Context->preference("IndependantBranches") ) {
831         my $userenv = C4::Context->userenv;
832         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
833             $issuingimpossible{ITEMNOTSAMEBRANCH} = 1
834               if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
835             $needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} )
836               if ( $borrower->{'branchcode'} ne $userenv->{branch} );
837         }
838     }
839
840     #
841     # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
842     #
843     if ( $issue->{borrowernumber} && $issue->{borrowernumber} eq $borrower->{'borrowernumber'} )
844     {
845
846         # Already issued to current borrower. Ask whether the loan should
847         # be renewed.
848         my ($CanBookBeRenewed,$renewerror) = CanBookBeRenewed(
849             $borrower->{'borrowernumber'},
850             $item->{'itemnumber'}
851         );
852         if ( $CanBookBeRenewed == 0 ) {    # no more renewals allowed
853             $issuingimpossible{NO_MORE_RENEWALS} = 1;
854         }
855         else {
856             $needsconfirmation{RENEW_ISSUE} = 1;
857         }
858     }
859     elsif ($issue->{borrowernumber}) {
860
861         # issued to someone else
862         my $currborinfo =    C4::Members::GetMember( borrowernumber => $issue->{borrowernumber} );
863
864 #        warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
865         $needsconfirmation{ISSUED_TO_ANOTHER} = 1;
866         $needsconfirmation{issued_firstname} = $currborinfo->{'firstname'};
867         $needsconfirmation{issued_surname} = $currborinfo->{'surname'};
868         $needsconfirmation{issued_cardnumber} = $currborinfo->{'cardnumber'};
869         $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'};
870     }
871
872     # See if the item is on reserve.
873     my ( $restype, $res, undef ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
874     if ($restype) {
875                 my $resbor = $res->{'borrowernumber'};
876                 my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor );
877                 my $branches  = GetBranches();
878                 my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
879         if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
880         {
881             # The item is on reserve and waiting, but has been
882             # reserved by some other patron.
883             $needsconfirmation{RESERVE_WAITING} = 1;
884             $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
885             $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
886             $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
887             $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
888             $needsconfirmation{'resbranchname'} = $branchname;
889             $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
890         }
891         elsif ( $restype eq "Reserved" ) {
892             # The item is on reserve for someone else.
893             $needsconfirmation{RESERVED} = 1;
894             $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
895             $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
896             $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
897             $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
898             $needsconfirmation{'resbranchname'} = $branchname;
899             $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
900         }
901     }
902         return ( \%issuingimpossible, \%needsconfirmation );
903 }
904
905 =head2 AddIssue
906
907   &AddIssue($borrower, $barcode, [$datedue], [$cancelreserve], [$issuedate])
908
909 Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
910
911 =over 4
912
913 =item C<$borrower> is a hash with borrower informations (from GetMember or GetMemberDetails).
914
915 =item C<$barcode> is the barcode of the item being issued.
916
917 =item C<$datedue> is a C4::Dates object for the max date of return, i.e. the date due (optional).
918 Calculated if empty.
919
920 =item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
921
922 =item C<$issuedate> is the date to issue the item in iso (YYYY-MM-DD) format (optional).
923 Defaults to today.  Unlike C<$datedue>, NOT a C4::Dates object, unfortunately.
924
925 AddIssue does the following things :
926
927   - step 01: check that there is a borrowernumber & a barcode provided
928   - check for RENEWAL (book issued & being issued to the same patron)
929       - renewal YES = Calculate Charge & renew
930       - renewal NO  =
931           * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
932           * RESERVE PLACED ?
933               - fill reserve if reserve to this patron
934               - cancel reserve or not, otherwise
935           * TRANSFERT PENDING ?
936               - complete the transfert
937           * ISSUE THE BOOK
938
939 =back
940
941 =cut
942
943 sub AddIssue {
944     my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
945     my $dbh = C4::Context->dbh;
946         my $barcodecheck=CheckValidBarcode($barcode);
947     # $issuedate defaults to today.
948     if ( ! defined $issuedate ) {
949         $issuedate = strftime( "%Y-%m-%d", localtime );
950         # TODO: for hourly circ, this will need to be a C4::Dates object
951         # and all calls to AddIssue including issuedate will need to pass a Dates object.
952     }
953         if ($borrower and $barcode and $barcodecheck ne '0'){
954                 # find which item we issue
955                 my $item = GetItem('', $barcode) or return undef;       # if we don't get an Item, abort.
956                 my $branch = _GetCircControlBranch($item,$borrower);
957                 
958                 # get actual issuing if there is one
959                 my $actualissue = GetItemIssue( $item->{itemnumber});
960                 
961                 # get biblioinformation for this item
962                 my $biblio = GetBiblioFromItemNumber($item->{itemnumber});
963                 
964                 #
965                 # check if we just renew the issue.
966                 #
967                 if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
968                         $datedue = AddRenewal(
969                                 $borrower->{'borrowernumber'},
970                                 $item->{'itemnumber'},
971                                 $branch,
972                                 $datedue,
973                 $issuedate, # here interpreted as the renewal date
974                         );
975                 }
976                 else {
977         # it's NOT a renewal
978                         if ( $actualissue->{borrowernumber}) {
979                                 # This book is currently on loan, but not to the person
980                                 # who wants to borrow it now. mark it returned before issuing to the new borrower
981                                 AddReturn(
982                                         $item->{'barcode'},
983                                         C4::Context->userenv->{'branch'}
984                                 );
985                         }
986
987             MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
988
989                         # Starting process for transfer job (checking transfert and validate it if we have one)
990             my ($datesent) = GetTransfers($item->{'itemnumber'});
991             if ($datesent) {
992         #       updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for visibility of this case (maybe for stats ....)
993                 my $sth =
994                     $dbh->prepare(
995                     "UPDATE branchtransfers 
996                         SET datearrived = now(),
997                         tobranch = ?,
998                         comments = 'Forced branchtransfer'
999                     WHERE itemnumber= ? AND datearrived IS NULL"
1000                     );
1001                 $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
1002             }
1003
1004         # Record in the database the fact that the book was issued.
1005         my $sth =
1006           $dbh->prepare(
1007                 "INSERT INTO issues 
1008                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
1009                 VALUES (?,?,?,?,?)"
1010           );
1011         unless ($datedue) {
1012             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
1013             $datedue = CalcDateDue( C4::Dates->new( $issuedate, 'iso' ), $itype, $branch, $borrower );
1014
1015         }
1016         $sth->execute(
1017             $borrower->{'borrowernumber'},      # borrowernumber
1018             $item->{'itemnumber'},              # itemnumber
1019             $issuedate,                         # issuedate
1020             $datedue->output('iso'),            # date_due
1021             C4::Context->userenv->{'branch'}    # branchcode
1022         );
1023         $sth->finish;
1024         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
1025           CartToShelf( $item->{'itemnumber'} );
1026         }
1027         $item->{'issues'}++;
1028
1029         ## If item was lost, it has now been found, reverse any list item charges if neccessary.
1030         if ( $item->{'itemlost'} ) {
1031             _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
1032         }
1033
1034         ModItem({ issues           => $item->{'issues'},
1035                   holdingbranch    => C4::Context->userenv->{'branch'},
1036                   itemlost         => 0,
1037                   datelastborrowed => C4::Dates->new()->output('iso'),
1038                   onloan           => $datedue->output('iso'),
1039                 }, $item->{'biblionumber'}, $item->{'itemnumber'});
1040         ModDateLastSeen( $item->{'itemnumber'} );
1041
1042         # If it costs to borrow this book, charge it to the patron's account.
1043         my ( $charge, $itemtype ) = GetIssuingCharges(
1044             $item->{'itemnumber'},
1045             $borrower->{'borrowernumber'}
1046         );
1047         if ( $charge > 0 ) {
1048             AddIssuingCharge(
1049                 $item->{'itemnumber'},
1050                 $borrower->{'borrowernumber'}, $charge
1051             );
1052             $item->{'charge'} = $charge;
1053         }
1054
1055         # Record the fact that this book was issued.
1056         &UpdateStats(
1057             C4::Context->userenv->{'branch'},
1058             'issue', $charge,
1059             ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
1060             $item->{'itype'}, $borrower->{'borrowernumber'}
1061         );
1062
1063         # Send a checkout slip.
1064         my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1065         my %conditions = (
1066             branchcode   => $branch,
1067             categorycode => $borrower->{categorycode},
1068             item_type    => $item->{itype},
1069             notification => 'CHECKOUT',
1070         );
1071         if ($circulation_alert->is_enabled_for(\%conditions)) {
1072             SendCirculationAlert({
1073                 type     => 'CHECKOUT',
1074                 item     => $item,
1075                 borrower => $borrower,
1076                 branch   => $branch,
1077             });
1078         }
1079     }
1080
1081     logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
1082         if C4::Context->preference("IssueLog");
1083   }
1084   return ($datedue);    # not necessarily the same as when it came in!
1085 }
1086
1087 =head2 GetLoanLength
1088
1089   my $loanlength = &GetLoanLength($borrowertype,$itemtype,branchcode)
1090
1091 Get loan length for an itemtype, a borrower type and a branch
1092
1093 =cut
1094
1095 sub GetLoanLength {
1096     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1097     my $dbh = C4::Context->dbh;
1098     my $sth =
1099       $dbh->prepare(
1100 "select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"
1101       );
1102 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
1103 # try to find issuelength & return the 1st available.
1104 # check with borrowertype, itemtype and branchcode, then without one of those parameters
1105     $sth->execute( $borrowertype, $itemtype, $branchcode );
1106     my $loanlength = $sth->fetchrow_hashref;
1107     return $loanlength->{issuelength}
1108       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1109
1110     $sth->execute( $borrowertype, "*", $branchcode );
1111     $loanlength = $sth->fetchrow_hashref;
1112     return $loanlength->{issuelength}
1113       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1114
1115     $sth->execute( "*", $itemtype, $branchcode );
1116     $loanlength = $sth->fetchrow_hashref;
1117     return $loanlength->{issuelength}
1118       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1119
1120     $sth->execute( "*", "*", $branchcode );
1121     $loanlength = $sth->fetchrow_hashref;
1122     return $loanlength->{issuelength}
1123       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1124
1125     $sth->execute( $borrowertype, $itemtype, "*" );
1126     $loanlength = $sth->fetchrow_hashref;
1127     return $loanlength->{issuelength}
1128       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1129
1130     $sth->execute( $borrowertype, "*", "*" );
1131     $loanlength = $sth->fetchrow_hashref;
1132     return $loanlength->{issuelength}
1133       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1134
1135     $sth->execute( "*", $itemtype, "*" );
1136     $loanlength = $sth->fetchrow_hashref;
1137     return $loanlength->{issuelength}
1138       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1139
1140     $sth->execute( "*", "*", "*" );
1141     $loanlength = $sth->fetchrow_hashref;
1142     return $loanlength->{issuelength}
1143       if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1144
1145     # if no rule is set => 21 days (hardcoded)
1146     return 21;
1147 }
1148
1149
1150 =head2 GetHardDueDate
1151
1152   my ($hardduedate,$hardduedatecompare) = &GetHardDueDate($borrowertype,$itemtype,branchcode)
1153
1154 Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a branch
1155
1156 =cut
1157
1158 sub GetHardDueDate {
1159     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1160     my $dbh = C4::Context->dbh;
1161     my $sth =
1162       $dbh->prepare(
1163 "select hardduedate, hardduedatecompare from issuingrules where categorycode=? and itemtype=? and branchcode=?"
1164       );
1165     $sth->execute( $borrowertype, $itemtype, $branchcode );
1166     my $results = $sth->fetchrow_hashref;
1167     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1168       if defined($results) && $results->{hardduedate} ne 'NULL';
1169
1170     $sth->execute( $borrowertype, "*", $branchcode );
1171     $results = $sth->fetchrow_hashref;
1172     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1173       if defined($results) && $results->{hardduedate} ne 'NULL';
1174
1175     $sth->execute( "*", $itemtype, $branchcode );
1176     $results = $sth->fetchrow_hashref;
1177     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1178       if defined($results) && $results->{hardduedate} ne 'NULL';
1179
1180     $sth->execute( "*", "*", $branchcode );
1181     $results = $sth->fetchrow_hashref;
1182     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1183       if defined($results) && $results->{hardduedate} ne 'NULL';
1184
1185     $sth->execute( $borrowertype, $itemtype, "*" );
1186     $results = $sth->fetchrow_hashref;
1187     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1188       if defined($results) && $results->{hardduedate} ne 'NULL';
1189
1190     $sth->execute( $borrowertype, "*", "*" );
1191     $results = $sth->fetchrow_hashref;
1192     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1193       if defined($results) && $results->{hardduedate} ne 'NULL';
1194
1195     $sth->execute( "*", $itemtype, "*" );
1196     $results = $sth->fetchrow_hashref;
1197     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1198       if defined($results) && $results->{hardduedate} ne 'NULL';
1199
1200     $sth->execute( "*", "*", "*" );
1201     $results = $sth->fetchrow_hashref;
1202     return (C4::Dates->new($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
1203       if defined($results) && $results->{hardduedate} ne 'NULL';
1204
1205     # if no rule is set => return undefined
1206     return (undef, undef);
1207 }
1208
1209 =head2 GetIssuingRule
1210
1211   my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode)
1212
1213 FIXME - This is a copy-paste of GetLoanLength
1214 as a stop-gap.  Do not wish to change API for GetLoanLength 
1215 this close to release, however, Overdues::GetIssuingRules is broken.
1216
1217 Get the issuing rule for an itemtype, a borrower type and a branch
1218 Returns a hashref from the issuingrules table.
1219
1220 =cut
1221
1222 sub GetIssuingRule {
1223     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1224     my $dbh = C4::Context->dbh;
1225     my $sth =  $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"  );
1226     my $irule;
1227
1228         $sth->execute( $borrowertype, $itemtype, $branchcode );
1229     $irule = $sth->fetchrow_hashref;
1230     return $irule if defined($irule) ;
1231
1232     $sth->execute( $borrowertype, "*", $branchcode );
1233     $irule = $sth->fetchrow_hashref;
1234     return $irule if defined($irule) ;
1235
1236     $sth->execute( "*", $itemtype, $branchcode );
1237     $irule = $sth->fetchrow_hashref;
1238     return $irule if defined($irule) ;
1239
1240     $sth->execute( "*", "*", $branchcode );
1241     $irule = $sth->fetchrow_hashref;
1242     return $irule if defined($irule) ;
1243
1244     $sth->execute( $borrowertype, $itemtype, "*" );
1245     $irule = $sth->fetchrow_hashref;
1246     return $irule if defined($irule) ;
1247
1248     $sth->execute( $borrowertype, "*", "*" );
1249     $irule = $sth->fetchrow_hashref;
1250     return $irule if defined($irule) ;
1251
1252     $sth->execute( "*", $itemtype, "*" );
1253     $irule = $sth->fetchrow_hashref;
1254     return $irule if defined($irule) ;
1255
1256     $sth->execute( "*", "*", "*" );
1257     $irule = $sth->fetchrow_hashref;
1258     return $irule if defined($irule) ;
1259
1260     # if no rule matches,
1261     return undef;
1262 }
1263
1264 =head2 GetBranchBorrowerCircRule
1265
1266   my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode);
1267
1268 Retrieves circulation rule attributes that apply to the given
1269 branch and patron category, regardless of item type.  
1270 The return value is a hashref containing the following key:
1271
1272 maxissueqty - maximum number of loans that a
1273 patron of the given category can have at the given
1274 branch.  If the value is undef, no limit.
1275
1276 This will first check for a specific branch and
1277 category match from branch_borrower_circ_rules. 
1278
1279 If no rule is found, it will then check default_branch_circ_rules
1280 (same branch, default category).  If no rule is found,
1281 it will then check default_borrower_circ_rules (default 
1282 branch, same category), then failing that, default_circ_rules
1283 (default branch, default category).
1284
1285 If no rule has been found in the database, it will default to
1286 the buillt in rule:
1287
1288 maxissueqty - undef
1289
1290 C<$branchcode> and C<$categorycode> should contain the
1291 literal branch code and patron category code, respectively - no
1292 wildcards.
1293
1294 =cut
1295
1296 sub GetBranchBorrowerCircRule {
1297     my $branchcode = shift;
1298     my $categorycode = shift;
1299
1300     my $branch_cat_query = "SELECT maxissueqty
1301                             FROM branch_borrower_circ_rules
1302                             WHERE branchcode = ?
1303                             AND   categorycode = ?";
1304     my $dbh = C4::Context->dbh();
1305     my $sth = $dbh->prepare($branch_cat_query);
1306     $sth->execute($branchcode, $categorycode);
1307     my $result;
1308     if ($result = $sth->fetchrow_hashref()) {
1309         return $result;
1310     }
1311
1312     # try same branch, default borrower category
1313     my $branch_query = "SELECT maxissueqty
1314                         FROM default_branch_circ_rules
1315                         WHERE branchcode = ?";
1316     $sth = $dbh->prepare($branch_query);
1317     $sth->execute($branchcode);
1318     if ($result = $sth->fetchrow_hashref()) {
1319         return $result;
1320     }
1321
1322     # try default branch, same borrower category
1323     my $category_query = "SELECT maxissueqty
1324                           FROM default_borrower_circ_rules
1325                           WHERE categorycode = ?";
1326     $sth = $dbh->prepare($category_query);
1327     $sth->execute($categorycode);
1328     if ($result = $sth->fetchrow_hashref()) {
1329         return $result;
1330     }
1331   
1332     # try default branch, default borrower category
1333     my $default_query = "SELECT maxissueqty
1334                           FROM default_circ_rules";
1335     $sth = $dbh->prepare($default_query);
1336     $sth->execute();
1337     if ($result = $sth->fetchrow_hashref()) {
1338         return $result;
1339     }
1340     
1341     # built-in default circulation rule
1342     return {
1343         maxissueqty => undef,
1344     };
1345 }
1346
1347 =head2 GetBranchItemRule
1348
1349   my $branch_item_rule = GetBranchItemRule($branchcode, $itemtype);
1350
1351 Retrieves circulation rule attributes that apply to the given
1352 branch and item type, regardless of patron category.
1353
1354 The return value is a hashref containing the following key:
1355
1356 holdallowed => Hold policy for this branch and itemtype. Possible values:
1357   0: No holds allowed.
1358   1: Holds allowed only by patrons that have the same homebranch as the item.
1359   2: Holds allowed from any patron.
1360
1361 This searches branchitemrules in the following order:
1362
1363   * Same branchcode and itemtype
1364   * Same branchcode, itemtype '*'
1365   * branchcode '*', same itemtype
1366   * branchcode and itemtype '*'
1367
1368 Neither C<$branchcode> nor C<$categorycode> should be '*'.
1369
1370 =cut
1371
1372 sub GetBranchItemRule {
1373     my ( $branchcode, $itemtype ) = @_;
1374     my $dbh = C4::Context->dbh();
1375     my $result = {};
1376
1377     my @attempts = (
1378         ['SELECT holdallowed
1379             FROM branch_item_rules
1380             WHERE branchcode = ?
1381               AND itemtype = ?', $branchcode, $itemtype],
1382         ['SELECT holdallowed
1383             FROM default_branch_circ_rules
1384             WHERE branchcode = ?', $branchcode],
1385         ['SELECT holdallowed
1386             FROM default_branch_item_rules
1387             WHERE itemtype = ?', $itemtype],
1388         ['SELECT holdallowed
1389             FROM default_circ_rules'],
1390     );
1391
1392     foreach my $attempt (@attempts) {
1393         my ($query, @bind_params) = @{$attempt};
1394
1395         # Since branch/category and branch/itemtype use the same per-branch
1396         # defaults tables, we have to check that the key we want is set, not
1397         # just that a row was returned
1398         return $result if ( defined( $result->{'holdallowed'} = $dbh->selectrow_array( $query, {}, @bind_params ) ) );
1399     }
1400     
1401     # built-in default circulation rule
1402     return {
1403         holdallowed => 2,
1404     };
1405 }
1406
1407 =head2 AddReturn
1408
1409   ($doreturn, $messages, $iteminformation, $borrower) =
1410       &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1411
1412 Returns a book.
1413
1414 =over 4
1415
1416 =item C<$barcode> is the bar code of the book being returned.
1417
1418 =item C<$branch> is the code of the branch where the book is being returned.
1419
1420 =item C<$exemptfine> indicates that overdue charges for the item will be
1421 removed.
1422
1423 =item C<$dropbox> indicates that the check-in date is assumed to be
1424 yesterday, or the last non-holiday as defined in C4::Calendar .  If
1425 overdue charges are applied and C<$dropbox> is true, the last charge
1426 will be removed.  This assumes that the fines accrual script has run
1427 for _today_.
1428
1429 =back
1430
1431 C<&AddReturn> returns a list of four items:
1432
1433 C<$doreturn> is true iff the return succeeded.
1434
1435 C<$messages> is a reference-to-hash giving feedback on the operation.
1436 The keys of the hash are:
1437
1438 =over 4
1439
1440 =item C<BadBarcode>
1441
1442 No item with this barcode exists. The value is C<$barcode>.
1443
1444 =item C<NotIssued>
1445
1446 The book is not currently on loan. The value is C<$barcode>.
1447
1448 =item C<IsPermanent>
1449
1450 The book's home branch is a permanent collection. If you have borrowed
1451 this book, you are not allowed to return it. The value is the code for
1452 the book's home branch.
1453
1454 =item C<wthdrawn>
1455
1456 This book has been withdrawn/cancelled. The value should be ignored.
1457
1458 =item C<Wrongbranch>
1459
1460 This book has was returned to the wrong branch.  The value is a hashref
1461 so that C<$messages->{Wrongbranch}->{Wrongbranch}> and C<$messages->{Wrongbranch}->{Rightbranch}>
1462 contain the branchcode of the incorrect and correct return library, respectively.
1463
1464 =item C<ResFound>
1465
1466 The item was reserved. The value is a reference-to-hash whose keys are
1467 fields from the reserves table of the Koha database, and
1468 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1469 either C<Waiting>, C<Reserved>, or 0.
1470
1471 =back
1472
1473 C<$iteminformation> is a reference-to-hash, giving information about the
1474 returned item from the issues table.
1475
1476 C<$borrower> is a reference-to-hash, giving information about the
1477 patron who last borrowed the book.
1478
1479 =cut
1480
1481 sub AddReturn {
1482     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1483     if ($branch and not GetBranchDetail($branch)) {
1484         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
1485         undef $branch;
1486     }
1487     $branch = C4::Context->userenv->{'branch'} unless $branch;  # we trust userenv to be a safe fallback/default
1488     my $messages;
1489     my $borrower;
1490     my $biblio;
1491     my $doreturn       = 1;
1492     my $validTransfert = 0;
1493     my $stat_type = 'return';    
1494
1495     # get information on item
1496     my $itemnumber = GetItemnumberFromBarcode( $barcode );
1497     unless ($itemnumber) {
1498         return (0, { BadBarcode => $barcode }); # no barcode means no item or borrower.  bail out.
1499     }
1500     my $issue  = GetItemIssue($itemnumber);
1501 #   warn Dumper($iteminformation);
1502     if ($issue and $issue->{borrowernumber}) {
1503         $borrower = C4::Members::GetMemberDetails($issue->{borrowernumber})
1504             or die "Data inconsistency: barcode $barcode (itemnumber:$itemnumber) claims to be issued to non-existant borrowernumber '$issue->{borrowernumber}'\n"
1505                 . Dumper($issue) . "\n";
1506     } else {
1507         $messages->{'NotIssued'} = $barcode;
1508         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
1509         $doreturn = 0;
1510         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
1511         # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on
1512         if (C4::Context->preference("RecordLocalUseOnReturn")) {
1513            $messages->{'LocalUse'} = 1;
1514            $stat_type = 'localuse';
1515         }
1516     }
1517
1518     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
1519         # full item data, but no borrowernumber or checkout info (no issue)
1520         # we know GetItem should work because GetItemnumberFromBarcode worked
1521     my $hbr      = C4::Context->preference("HomeOrHoldingBranchReturn") || "homebranch";
1522     $hbr = $item->{$hbr} || '';
1523         # item must be from items table -- issues table has branchcode and issuingbranch, not homebranch nor holdingbranch
1524
1525     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
1526
1527     # check if the book is in a permanent collection....
1528     # FIXME -- This 'PE' attribute is largely undocumented.  afaict, there's no user interface that reflects this functionality.
1529     if ( $hbr ) {
1530         my $branches = GetBranches();    # a potentially expensive call for a non-feature.
1531         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
1532     }
1533
1534     # if indy branches and returning to different branch, refuse the return
1535     if ($hbr ne $branch && C4::Context->preference("IndependantBranches")){
1536         $messages->{'Wrongbranch'} = {
1537             Wrongbranch => $branch,
1538             Rightbranch => $hbr,
1539         };
1540         $doreturn = 0;
1541         # bailing out here - in this case, current desired behavior
1542         # is to act as if no return ever happened at all.
1543         # FIXME - even in an indy branches situation, there should
1544         # still be an option for the library to accept the item
1545         # and transfer it to its owning library.
1546         return ( $doreturn, $messages, $issue, $borrower );
1547     }
1548
1549     if ( $item->{'wthdrawn'} ) { # book has been cancelled
1550         $messages->{'wthdrawn'} = 1;
1551         $doreturn = 0;
1552     }
1553
1554     # case of a return of document (deal with issues and holdingbranch)
1555     if ($doreturn) {
1556         $borrower or warn "AddReturn without current borrower";
1557                 my $circControlBranch;
1558         if ($dropbox) {
1559             # define circControlBranch only if dropbox mode is set
1560             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
1561             # FIXME: check issuedate > returndate, factoring in holidays
1562             $circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
1563         }
1564
1565         if ($borrowernumber) {
1566             MarkIssueReturned($borrowernumber, $item->{'itemnumber'}, $circControlBranch, '', $borrower->{'privacy'});
1567             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  This could be the borrower hash.
1568         }
1569
1570         ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
1571     }
1572
1573     # the holdingbranch is updated if the document is returned to another location.
1574     # this is always done regardless of whether the item was on loan or not
1575     if ($item->{'holdingbranch'} ne $branch) {
1576         UpdateHoldingbranch($branch, $item->{'itemnumber'});
1577         $item->{'holdingbranch'} = $branch; # update item data holdingbranch too
1578     }
1579     ModDateLastSeen( $item->{'itemnumber'} );
1580
1581     # check if we have a transfer for this document
1582     my ($datesent,$frombranch,$tobranch) = GetTransfers( $item->{'itemnumber'} );
1583
1584     # if we have a transfer to do, we update the line of transfers with the datearrived
1585     if ($datesent) {
1586         if ( $tobranch eq $branch ) {
1587             my $sth = C4::Context->dbh->prepare(
1588                 "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1589             );
1590             $sth->execute( $item->{'itemnumber'} );
1591             # if we have a reservation with valid transfer, we can set it's status to 'W'
1592             C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W');
1593         } else {
1594             $messages->{'WrongTransfer'}     = $tobranch;
1595             $messages->{'WrongTransferItem'} = $item->{'itemnumber'};
1596         }
1597         $validTransfert = 1;
1598     }
1599
1600     # fix up the accounts.....
1601     if ($item->{'itemlost'}) {
1602         _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode);    # can tolerate undef $borrowernumber
1603         $messages->{'WasLost'} = 1;
1604     }
1605
1606     # fix up the overdues in accounts...
1607     if ($borrowernumber) {
1608         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
1609         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
1610         
1611         # fix fine days
1612         my $debardate = _FixFineDaysOnReturn( $borrower, $item, $issue->{date_due} );
1613         $messages->{'Debarred'} = $debardate if ($debardate);
1614     }
1615
1616     # find reserves.....
1617     # if we don't have a reserve with the status W, we launch the Checkreserves routine
1618     my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
1619     if ($resfound) {
1620           $resrec->{'ResFound'} = $resfound;
1621         $messages->{'ResFound'} = $resrec;
1622     }
1623
1624     # update stats?
1625     # Record the fact that this book was returned.
1626     UpdateStats(
1627         $branch, $stat_type, '0', '',
1628         $item->{'itemnumber'},
1629         $biblio->{'itemtype'},
1630         $borrowernumber
1631     );
1632
1633     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
1634     my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1635     my %conditions = (
1636         branchcode   => $branch,
1637         categorycode => $borrower->{categorycode},
1638         item_type    => $item->{itype},
1639         notification => 'CHECKIN',
1640     );
1641     if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) {
1642         SendCirculationAlert({
1643             type     => 'CHECKIN',
1644             item     => $item,
1645             borrower => $borrower,
1646             branch   => $branch,
1647         });
1648     }
1649     
1650     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'})
1651         if C4::Context->preference("ReturnLog");
1652     
1653     # FIXME: make this comment intelligible.
1654     #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1655     #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1656
1657     if (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
1658         if ( C4::Context->preference("AutomaticItemReturn"    ) or
1659             (C4::Context->preference("UseBranchTransferLimits") and
1660              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
1661            )) {
1662             $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
1663             $debug and warn "item: " . Dumper($item);
1664             ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
1665             $messages->{'WasTransfered'} = 1;
1666         } else {
1667             $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
1668         }
1669     }
1670     return ( $doreturn, $messages, $issue, $borrower );
1671 }
1672
1673 =head2 MarkIssueReturned
1674
1675   MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy);
1676
1677 Unconditionally marks an issue as being returned by
1678 moving the C<issues> row to C<old_issues> and
1679 setting C<returndate> to the current date, or
1680 the last non-holiday date of the branccode specified in
1681 C<dropbox_branch> .  Assumes you've already checked that 
1682 it's safe to do this, i.e. last non-holiday > issuedate.
1683
1684 if C<$returndate> is specified (in iso format), it is used as the date
1685 of the return. It is ignored when a dropbox_branch is passed in.
1686
1687 C<$privacy> contains the privacy parameter. If the patron has set privacy to 2,
1688 the old_issue is immediately anonymised
1689
1690 Ideally, this function would be internal to C<C4::Circulation>,
1691 not exported, but it is currently needed by one 
1692 routine in C<C4::Accounts>.
1693
1694 =cut
1695
1696 sub MarkIssueReturned {
1697     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
1698     my $dbh   = C4::Context->dbh;
1699     my $query = "UPDATE issues SET returndate=";
1700     my @bind;
1701     if ($dropbox_branch) {
1702         my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
1703         my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 );
1704         $query .= " ? ";
1705         push @bind, $dropboxdate->output('iso');
1706     } elsif ($returndate) {
1707         $query .= " ? ";
1708         push @bind, $returndate;
1709     } else {
1710         $query .= " now() ";
1711     }
1712     $query .= " WHERE  borrowernumber = ?  AND itemnumber = ?";
1713     push @bind, $borrowernumber, $itemnumber;
1714     # FIXME transaction
1715     my $sth_upd  = $dbh->prepare($query);
1716     $sth_upd->execute(@bind);
1717     my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues 
1718                                   WHERE borrowernumber = ?
1719                                   AND itemnumber = ?");
1720     $sth_copy->execute($borrowernumber, $itemnumber);
1721     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
1722     if ( $privacy == 2) {
1723         # The default of 0 does not work due to foreign key constraints
1724         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
1725         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
1726         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
1727                                   WHERE borrowernumber = ?
1728                                   AND itemnumber = ?");
1729        $sth_ano->execute($anonymouspatron, $borrowernumber, $itemnumber);
1730     }
1731     my $sth_del  = $dbh->prepare("DELETE FROM issues
1732                                   WHERE borrowernumber = ?
1733                                   AND itemnumber = ?");
1734     $sth_del->execute($borrowernumber, $itemnumber);
1735 }
1736
1737 =head2 _FixFineDaysOnReturn
1738
1739     &_FixFineDaysOnReturn($borrower, $item, $datedue);
1740
1741 C<$borrower> borrower hashref
1742
1743 C<$item> item hashref
1744
1745 C<$datedue> date due
1746
1747 Internal function, called only by AddReturn that calculate and update the user fine days, and debars him
1748
1749 =cut
1750
1751 sub _FixFineDaysOnReturn {
1752     my ( $borrower, $item, $datedue ) = @_;
1753
1754     if ($datedue) {
1755         $datedue = C4::Dates->new( $datedue, "iso" );
1756     } else {
1757         return;
1758     }
1759
1760     my $branchcode = _GetCircControlBranch( $item, $borrower );
1761     my $calendar = C4::Calendar->new( branchcode => $branchcode );
1762     my $today = C4::Dates->new();
1763
1764     my $deltadays = $calendar->daysBetween( $datedue, C4::Dates->new() );
1765
1766     my $circcontrol = C4::Context::preference('CircControl');
1767     my $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
1768     my $finedays    = $issuingrule->{finedays};
1769
1770     # exit if no finedays defined
1771     return unless $finedays;
1772     my $grace = $issuingrule->{firstremind};
1773
1774     if ( $deltadays - $grace > 0 ) {
1775         my @newdate = Add_Delta_Days( Today(), $deltadays * $finedays );
1776         my $isonewdate = join( '-', @newdate );
1777         my ( $deby, $debm, $debd ) = split( /-/, $borrower->{debarred} );
1778         if ( check_date( $deby, $debm, $debd ) ) {
1779             my @olddate = split( /-/, $borrower->{debarred} );
1780
1781             if ( Delta_Days( @olddate, @newdate ) > 0 ) {
1782                 C4::Members::DebarMember( $borrower->{borrowernumber}, $isonewdate );
1783                 return $isonewdate;
1784             }
1785         } else {
1786             C4::Members::DebarMember( $borrower->{borrowernumber}, $isonewdate );
1787             return $isonewdate;
1788         }
1789     }
1790 }
1791
1792 =head2 _FixOverduesOnReturn
1793
1794    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1795
1796 C<$brn> borrowernumber
1797
1798 C<$itm> itemnumber
1799
1800 C<$exemptfine> BOOL -- remove overdue charge associated with this issue. 
1801 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1802
1803 Internal function, called only by AddReturn
1804
1805 =cut
1806
1807 sub _FixOverduesOnReturn {
1808     my ($borrowernumber, $item);
1809     unless ($borrowernumber = shift) {
1810         warn "_FixOverduesOnReturn() not supplied valid borrowernumber";
1811         return;
1812     }
1813     unless ($item = shift) {
1814         warn "_FixOverduesOnReturn() not supplied valid itemnumber";
1815         return;
1816     }
1817     my ($exemptfine, $dropbox) = @_;
1818     my $dbh = C4::Context->dbh;
1819
1820     # check for overdue fine
1821     my $sth = $dbh->prepare(
1822 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1823     );
1824     $sth->execute( $borrowernumber, $item );
1825
1826     # alter fine to show that the book has been returned
1827     my $data = $sth->fetchrow_hashref;
1828     return 0 unless $data;    # no warning, there's just nothing to fix
1829
1830     my $uquery;
1831     my @bind = ($borrowernumber, $item, $data->{'accountno'});
1832     if ($exemptfine) {
1833         $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1834         if (C4::Context->preference("FinesLog")) {
1835             &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1836         }
1837     } elsif ($dropbox && $data->{lastincrement}) {
1838         my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1839         my $amt = $data->{amount} - $data->{lastincrement} ;
1840         if (C4::Context->preference("FinesLog")) {
1841             &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1842         }
1843          $uquery = "update accountlines set accounttype='F' ";
1844          if($outstanding  >= 0 && $amt >=0) {
1845             $uquery .= ", amount = ? , amountoutstanding=? ";
1846             unshift @bind, ($amt, $outstanding) ;
1847         }
1848     } else {
1849         $uquery = "update accountlines set accounttype='F' ";
1850     }
1851     $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1852     my $usth = $dbh->prepare($uquery);
1853     return $usth->execute(@bind);
1854 }
1855
1856 =head2 _FixAccountForLostAndReturned
1857
1858   &_FixAccountForLostAndReturned($itemnumber, [$borrowernumber, $barcode]);
1859
1860 Calculates the charge for a book lost and returned.
1861
1862 Internal function, not exported, called only by AddReturn.
1863
1864 FIXME: This function reflects how inscrutable fines logic is.  Fix both.
1865 FIXME: Give a positive return value on success.  It might be the $borrowernumber who received credit, or the amount forgiven.
1866
1867 =cut
1868
1869 sub _FixAccountForLostAndReturned {
1870     my $itemnumber     = shift or return;
1871     my $borrowernumber = @_ ? shift : undef;
1872     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
1873     my $dbh = C4::Context->dbh;
1874     # check for charge made for lost book
1875     my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
1876     $sth->execute($itemnumber);
1877     my $data = $sth->fetchrow_hashref;
1878     $data or return;    # bail if there is nothing to do
1879     $data->{accounttype} eq 'W' and return;    # Written off
1880
1881     # writeoff this amount
1882     my $offset;
1883     my $amount = $data->{'amount'};
1884     my $acctno = $data->{'accountno'};
1885     my $amountleft;                                             # Starts off undef/zero.
1886     if ($data->{'amountoutstanding'} == $amount) {
1887         $offset     = $data->{'amount'};
1888         $amountleft = 0;                                        # Hey, it's zero here, too.
1889     } else {
1890         $offset     = $amount - $data->{'amountoutstanding'};   # Um, isn't this the same as ZERO?  We just tested those two things are ==
1891         $amountleft = $data->{'amountoutstanding'} - $amount;   # Um, isn't this the same as ZERO?  We just tested those two things are ==
1892     }
1893     my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
1894         WHERE (borrowernumber = ?)
1895         AND (itemnumber = ?) AND (accountno = ?) ");
1896     $usth->execute($data->{'borrowernumber'},$itemnumber,$acctno);      # We might be adjusting an account for some OTHER borrowernumber now.  Not the one we passed in.  
1897     #check if any credit is left if so writeoff other accounts
1898     my $nextaccntno = getnextacctno($data->{'borrowernumber'});
1899     $amountleft *= -1 if ($amountleft < 0);
1900     if ($amountleft > 0) {
1901         my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
1902                             AND (amountoutstanding >0) ORDER BY date");     # might want to order by amountoustanding ASC (pay smallest first)
1903         $msth->execute($data->{'borrowernumber'});
1904         # offset transactions
1905         my $newamtos;
1906         my $accdata;
1907         while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1908             if ($accdata->{'amountoutstanding'} < $amountleft) {
1909                 $newamtos = 0;
1910                 $amountleft -= $accdata->{'amountoutstanding'};
1911             }  else {
1912                 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1913                 $amountleft = 0;
1914             }
1915             my $thisacct = $accdata->{'accountno'};
1916             # FIXME: move prepares outside while loop!
1917             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
1918                     WHERE (borrowernumber = ?)
1919                     AND (accountno=?)");
1920             $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');    # FIXME: '$thisacct' is a string literal!
1921             $usth = $dbh->prepare("INSERT INTO accountoffsets
1922                 (borrowernumber, accountno, offsetaccount,  offsetamount)
1923                 VALUES
1924                 (?,?,?,?)");
1925             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1926         }
1927         $msth->finish;  # $msth might actually have data left
1928     }
1929     $amountleft *= -1 if ($amountleft > 0);
1930     my $desc = "Item Returned " . $item_id;
1931     $usth = $dbh->prepare("INSERT INTO accountlines
1932         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1933         VALUES (?,?,now(),?,?,'CR',?)");
1934     $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1935     if ($borrowernumber) {
1936         # FIXME: same as query above.  use 1 sth for both
1937         $usth = $dbh->prepare("INSERT INTO accountoffsets
1938             (borrowernumber, accountno, offsetaccount,  offsetamount)
1939             VALUES (?,?,?,?)");
1940         $usth->execute($borrowernumber, $data->{'accountno'}, $nextaccntno, $offset);
1941     }
1942     ModItem({ paidfor => '' }, undef, $itemnumber);
1943     return;
1944 }
1945
1946 =head2 _GetCircControlBranch
1947
1948    my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower);
1949
1950 Internal function : 
1951
1952 Return the library code to be used to determine which circulation
1953 policy applies to a transaction.  Looks up the CircControl and
1954 HomeOrHoldingBranch system preferences.
1955
1956 C<$iteminfos> is a hashref to iteminfo. Only {homebranch or holdingbranch} is used.
1957
1958 C<$borrower> is a hashref to borrower. Only {branchcode} is used.
1959
1960 =cut
1961
1962 sub _GetCircControlBranch {
1963     my ($item, $borrower) = @_;
1964     my $circcontrol = C4::Context->preference('CircControl');
1965     my $branch;
1966
1967     if ($circcontrol eq 'PickupLibrary') {
1968         $branch= C4::Context->userenv->{'branch'} if C4::Context->userenv;
1969     } elsif ($circcontrol eq 'PatronLibrary') {
1970         $branch=$borrower->{branchcode};
1971     } else {
1972         my $branchfield = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
1973         $branch = $item->{$branchfield};
1974         # default to item home branch if holdingbranch is used
1975         # and is not defined
1976         if (!defined($branch) && $branchfield eq 'holdingbranch') {
1977             $branch = $item->{homebranch};
1978         }
1979     }
1980     return $branch;
1981 }
1982
1983
1984
1985
1986
1987
1988 =head2 GetItemIssue
1989
1990   $issue = &GetItemIssue($itemnumber);
1991
1992 Returns patron currently having a book, or undef if not checked out.
1993
1994 C<$itemnumber> is the itemnumber.
1995
1996 C<$issue> is a hashref of the row from the issues table.
1997
1998 =cut
1999
2000 sub GetItemIssue {
2001     my ($itemnumber) = @_;
2002     return unless $itemnumber;
2003     my $sth = C4::Context->dbh->prepare(
2004         "SELECT *
2005         FROM issues 
2006         LEFT JOIN items ON issues.itemnumber=items.itemnumber
2007         WHERE issues.itemnumber=?");
2008     $sth->execute($itemnumber);
2009     my $data = $sth->fetchrow_hashref;
2010     return unless $data;
2011     $data->{'overdue'} = ($data->{'date_due'} lt C4::Dates->today('iso')) ? 1 : 0;
2012     return ($data);
2013 }
2014
2015 =head2 GetOpenIssue
2016
2017   $issue = GetOpenIssue( $itemnumber );
2018
2019 Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
2020
2021 C<$itemnumber> is the item's itemnumber
2022
2023 Returns a hashref
2024
2025 =cut
2026
2027 sub GetOpenIssue {
2028   my ( $itemnumber ) = @_;
2029
2030   my $dbh = C4::Context->dbh;  
2031   my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
2032   $sth->execute( $itemnumber );
2033   my $issue = $sth->fetchrow_hashref();
2034   return $issue;
2035 }
2036
2037 =head2 GetItemIssues
2038
2039   $issues = &GetItemIssues($itemnumber, $history);
2040
2041 Returns patrons that have issued a book
2042
2043 C<$itemnumber> is the itemnumber
2044 C<$history> is false if you just want the current "issuer" (if any)
2045 and true if you want issues history from old_issues also.
2046
2047 Returns reference to an array of hashes
2048
2049 =cut
2050
2051 sub GetItemIssues {
2052     my ( $itemnumber, $history ) = @_;
2053     
2054     my $today = C4::Dates->today('iso');  # get today date
2055     my $sql = "SELECT * FROM issues 
2056               JOIN borrowers USING (borrowernumber)
2057               JOIN items     USING (itemnumber)
2058               WHERE issues.itemnumber = ? ";
2059     if ($history) {
2060         $sql .= "UNION ALL
2061                  SELECT * FROM old_issues 
2062                  LEFT JOIN borrowers USING (borrowernumber)
2063                  JOIN items USING (itemnumber)
2064                  WHERE old_issues.itemnumber = ? ";
2065     }
2066     $sql .= "ORDER BY date_due DESC";
2067     my $sth = C4::Context->dbh->prepare($sql);
2068     if ($history) {
2069         $sth->execute($itemnumber, $itemnumber);
2070     } else {
2071         $sth->execute($itemnumber);
2072     }
2073     my $results = $sth->fetchall_arrayref({});
2074     foreach (@$results) {
2075         $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0;
2076     }
2077     return $results;
2078 }
2079
2080 =head2 GetBiblioIssues
2081
2082   $issues = GetBiblioIssues($biblionumber);
2083
2084 this function get all issues from a biblionumber.
2085
2086 Return:
2087 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
2088 tables issues and the firstname,surname & cardnumber from borrowers.
2089
2090 =cut
2091
2092 sub GetBiblioIssues {
2093     my $biblionumber = shift;
2094     return undef unless $biblionumber;
2095     my $dbh   = C4::Context->dbh;
2096     my $query = "
2097         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2098         FROM issues
2099             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
2100             LEFT JOIN items ON issues.itemnumber = items.itemnumber
2101             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2102             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2103         WHERE biblio.biblionumber = ?
2104         UNION ALL
2105         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2106         FROM old_issues
2107             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
2108             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
2109             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2110             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2111         WHERE biblio.biblionumber = ?
2112         ORDER BY timestamp
2113     ";
2114     my $sth = $dbh->prepare($query);
2115     $sth->execute($biblionumber, $biblionumber);
2116
2117     my @issues;
2118     while ( my $data = $sth->fetchrow_hashref ) {
2119         push @issues, $data;
2120     }
2121     return \@issues;
2122 }
2123
2124 =head2 GetUpcomingDueIssues
2125
2126   my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
2127
2128 =cut
2129
2130 sub GetUpcomingDueIssues {
2131     my $params = shift;
2132
2133     $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
2134     my $dbh = C4::Context->dbh;
2135
2136     my $statement = <<END_SQL;
2137 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due, branches.branchemail
2138 FROM issues 
2139 LEFT JOIN items USING (itemnumber)
2140 LEFT OUTER JOIN branches USING (branchcode)
2141 WhERE returndate is NULL
2142 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
2143 END_SQL
2144
2145     my @bind_parameters = ( $params->{'days_in_advance'} );
2146     
2147     my $sth = $dbh->prepare( $statement );
2148     $sth->execute( @bind_parameters );
2149     my $upcoming_dues = $sth->fetchall_arrayref({});
2150     $sth->finish;
2151
2152     return $upcoming_dues;
2153 }
2154
2155 =head2 CanBookBeRenewed
2156
2157   ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
2158
2159 Find out whether a borrowed item may be renewed.
2160
2161 C<$dbh> is a DBI handle to the Koha database.
2162
2163 C<$borrowernumber> is the borrower number of the patron who currently
2164 has the item on loan.
2165
2166 C<$itemnumber> is the number of the item to renew.
2167
2168 C<$override_limit>, if supplied with a true value, causes
2169 the limit on the number of times that the loan can be renewed
2170 (as controlled by the item type) to be ignored.
2171
2172 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
2173 item must currently be on loan to the specified borrower; renewals
2174 must be allowed for the item's type; and the borrower must not have
2175 already renewed the loan. $error will contain the reason the renewal can not proceed
2176
2177 =cut
2178
2179 sub CanBookBeRenewed {
2180
2181     # check renewal status
2182     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2183     my $dbh       = C4::Context->dbh;
2184     my $renews    = 1;
2185     my $renewokay = 0;
2186         my $error;
2187
2188     # Look in the issues table for this item, lent to this borrower,
2189     # and not yet returned.
2190
2191     # Look in the issues table for this item, lent to this borrower,
2192     # and not yet returned.
2193     my %branch = (
2194             'ItemHomeLibrary' => 'items.homebranch',
2195             'PickupLibrary'   => 'items.holdingbranch',
2196             'PatronLibrary'   => 'borrowers.branchcode'
2197             );
2198     my $controlbranch = $branch{C4::Context->preference('CircControl')};
2199     my $itype         = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
2200     
2201     my $sthcount = $dbh->prepare("
2202                    SELECT 
2203                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
2204                    FROM  issuingrules, 
2205                    issues 
2206                    LEFT JOIN items USING (itemnumber) 
2207                    LEFT JOIN borrowers USING (borrowernumber) 
2208                    LEFT JOIN biblioitems USING (biblioitemnumber)
2209                    
2210                    WHERE
2211                     (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
2212                    AND
2213                     (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
2214                    AND
2215                     (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') 
2216                    AND 
2217                     borrowernumber = ? 
2218                    AND
2219                     itemnumber = ?
2220                    ORDER BY
2221                     issuingrules.categorycode desc,
2222                     issuingrules.itemtype desc,
2223                     issuingrules.branchcode desc
2224                    LIMIT 1;
2225                   ");
2226
2227     $sthcount->execute( $borrowernumber, $itemnumber );
2228     if ( my $data1 = $sthcount->fetchrow_hashref ) {
2229         
2230         if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
2231             $renewokay = 1;
2232         }
2233         else {
2234                         $error="too_many";
2235                 }
2236                 
2237         my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
2238         if ($resfound) {
2239             $renewokay = 0;
2240                         $error="on_reserve"
2241         }
2242
2243     }
2244     return ($renewokay,$error);
2245 }
2246
2247 =head2 AddRenewal
2248
2249   &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
2250
2251 Renews a loan.
2252
2253 C<$borrowernumber> is the borrower number of the patron who currently
2254 has the item.
2255
2256 C<$itemnumber> is the number of the item to renew.
2257
2258 C<$branch> is the library where the renewal took place (if any).
2259            The library that controls the circ policies for the renewal is retrieved from the issues record.
2260
2261 C<$datedue> can be a C4::Dates object used to set the due date.
2262
2263 C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate.  If
2264 this parameter is not supplied, lastreneweddate is set to the current date.
2265
2266 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2267 from the book's item type.
2268
2269 =cut
2270
2271 sub AddRenewal {
2272     my $borrowernumber  = shift or return undef;
2273     my $itemnumber      = shift or return undef;
2274     my $branch          = shift;
2275     my $datedue         = shift;
2276     my $lastreneweddate = shift || C4::Dates->new()->output('iso');
2277     my $item   = GetItem($itemnumber) or return undef;
2278     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2279
2280     my $dbh = C4::Context->dbh;
2281     # Find the issues record for this book
2282     my $sth =
2283       $dbh->prepare("SELECT * FROM issues
2284                         WHERE borrowernumber=? 
2285                         AND itemnumber=?"
2286       );
2287     $sth->execute( $borrowernumber, $itemnumber );
2288     my $issuedata = $sth->fetchrow_hashref;
2289     $sth->finish;
2290     if($datedue && ! $datedue->output('iso')){
2291         warn "Invalid date passed to AddRenewal.";
2292         return undef;
2293     }
2294     # If the due date wasn't specified, calculate it by adding the
2295     # book's loan length to today's date or the current due date
2296     # based on the value of the RenewalPeriodBase syspref.
2297     unless ($datedue) {
2298
2299         my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
2300         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
2301
2302         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2303                                         C4::Dates->new($issuedata->{date_due}, 'iso') :
2304                                         C4::Dates->new();
2305         $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
2306     }
2307
2308     # Update the issues record to have the new due date, and a new count
2309     # of how many times it has been renewed.
2310     my $renews = $issuedata->{'renewals'} + 1;
2311     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
2312                             WHERE borrowernumber=? 
2313                             AND itemnumber=?"
2314     );
2315     $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
2316     $sth->finish;
2317
2318     # Update the renewal count on the item, and tell zebra to reindex
2319     $renews = $biblio->{'renewals'} + 1;
2320     ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
2321
2322     # Charge a new rental fee, if applicable?
2323     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2324     if ( $charge > 0 ) {
2325         my $accountno = getnextacctno( $borrowernumber );
2326         my $item = GetBiblioFromItemNumber($itemnumber);
2327         my $manager_id = 0;
2328         $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
2329         $sth = $dbh->prepare(
2330                 "INSERT INTO accountlines
2331                     (date, borrowernumber, accountno, amount, manager_id,
2332                     description,accounttype, amountoutstanding, itemnumber)
2333                     VALUES (now(),?,?,?,?,?,?,?,?)"
2334         );
2335         $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
2336             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2337             'Rent', $charge, $itemnumber );
2338         $sth->finish;
2339     }
2340     # Log the renewal
2341     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
2342         return $datedue;
2343 }
2344
2345 sub GetRenewCount {
2346     # check renewal status
2347     my ( $bornum, $itemno ) = @_;
2348     my $dbh           = C4::Context->dbh;
2349     my $renewcount    = 0;
2350     my $renewsallowed = 0;
2351     my $renewsleft    = 0;
2352
2353     my $borrower = C4::Members::GetMember( borrowernumber => $bornum);
2354     my $item     = GetItem($itemno); 
2355
2356     # Look in the issues table for this item, lent to this borrower,
2357     # and not yet returned.
2358
2359     # FIXME - I think this function could be redone to use only one SQL call.
2360     my $sth = $dbh->prepare(
2361         "select * from issues
2362                                 where (borrowernumber = ?)
2363                                 and (itemnumber = ?)"
2364     );
2365     $sth->execute( $bornum, $itemno );
2366     my $data = $sth->fetchrow_hashref;
2367     $renewcount = $data->{'renewals'} if $data->{'renewals'};
2368     $sth->finish;
2369     # $item and $borrower should be calculated
2370     my $branchcode = _GetCircControlBranch($item, $borrower);
2371     
2372     my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
2373     
2374     $renewsallowed = $issuingrule->{'renewalsallowed'};
2375     $renewsleft    = $renewsallowed - $renewcount;
2376     if($renewsleft < 0){ $renewsleft = 0; }
2377     return ( $renewcount, $renewsallowed, $renewsleft );
2378 }
2379
2380 =head2 GetIssuingCharges
2381
2382   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2383
2384 Calculate how much it would cost for a given patron to borrow a given
2385 item, including any applicable discounts.
2386
2387 C<$itemnumber> is the item number of item the patron wishes to borrow.
2388
2389 C<$borrowernumber> is the patron's borrower number.
2390
2391 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2392 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2393 if it's a video).
2394
2395 =cut
2396
2397 sub GetIssuingCharges {
2398
2399     # calculate charges due
2400     my ( $itemnumber, $borrowernumber ) = @_;
2401     my $charge = 0;
2402     my $dbh    = C4::Context->dbh;
2403     my $item_type;
2404
2405     # Get the book's item type and rental charge (via its biblioitem).
2406     my $charge_query = 'SELECT itemtypes.itemtype,rentalcharge FROM items
2407         LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber';
2408     $charge_query .= (C4::Context->preference('item-level_itypes'))
2409         ? ' LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype'
2410         : ' LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype';
2411
2412     $charge_query .= ' WHERE items.itemnumber =?';
2413
2414     my $sth = $dbh->prepare($charge_query);
2415     $sth->execute($itemnumber);
2416     if ( my $item_data = $sth->fetchrow_hashref ) {
2417         $item_type = $item_data->{itemtype};
2418         $charge    = $item_data->{rentalcharge};
2419         my $branch = C4::Branch::mybranch();
2420         my $discount_query = q|SELECT rentaldiscount,
2421             issuingrules.itemtype, issuingrules.branchcode
2422             FROM borrowers
2423             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2424             WHERE borrowers.borrowernumber = ?
2425             AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
2426             AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
2427         my $discount_sth = $dbh->prepare($discount_query);
2428         $discount_sth->execute( $borrowernumber, $item_type, $branch );
2429         my $discount_rules = $discount_sth->fetchall_arrayref({});
2430         if (@{$discount_rules}) {
2431             # We may have multiple rules so get the most specific
2432             my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
2433             $charge = ( $charge * ( 100 - $discount ) ) / 100;
2434         }
2435     }
2436
2437     $sth->finish; # we havent _explicitly_ fetched all rows
2438     return ( $charge, $item_type );
2439 }
2440
2441 # Select most appropriate discount rule from those returned
2442 sub _get_discount_from_rule {
2443     my ($rules_ref, $branch, $itemtype) = @_;
2444     my $discount;
2445
2446     if (@{$rules_ref} == 1) { # only 1 applicable rule use it
2447         $discount = $rules_ref->[0]->{rentaldiscount};
2448         return (defined $discount) ? $discount : 0;
2449     }
2450     # could have up to 4 does one match $branch and $itemtype
2451     my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
2452     if (@d) {
2453         $discount = $d[0]->{rentaldiscount};
2454         return (defined $discount) ? $discount : 0;
2455     }
2456     # do we have item type + all branches
2457     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
2458     if (@d) {
2459         $discount = $d[0]->{rentaldiscount};
2460         return (defined $discount) ? $discount : 0;
2461     }
2462     # do we all item types + this branch
2463     @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
2464     if (@d) {
2465         $discount = $d[0]->{rentaldiscount};
2466         return (defined $discount) ? $discount : 0;
2467     }
2468     # so all and all (surely we wont get here)
2469     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
2470     if (@d) {
2471         $discount = $d[0]->{rentaldiscount};
2472         return (defined $discount) ? $discount : 0;
2473     }
2474     # none of the above
2475     return 0;
2476 }
2477
2478 =head2 AddIssuingCharge
2479
2480   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2481
2482 =cut
2483
2484 sub AddIssuingCharge {
2485     my ( $itemnumber, $borrowernumber, $charge ) = @_;
2486     my $dbh = C4::Context->dbh;
2487     my $nextaccntno = getnextacctno( $borrowernumber );
2488     my $manager_id = 0;
2489     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
2490     my $query ="
2491         INSERT INTO accountlines
2492             (borrowernumber, itemnumber, accountno,
2493             date, amount, description, accounttype,
2494             amountoutstanding, manager_id)
2495         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?,?)
2496     ";
2497     my $sth = $dbh->prepare($query);
2498     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
2499     $sth->finish;
2500 }
2501
2502 =head2 GetTransfers
2503
2504   GetTransfers($itemnumber);
2505
2506 =cut
2507
2508 sub GetTransfers {
2509     my ($itemnumber) = @_;
2510
2511     my $dbh = C4::Context->dbh;
2512
2513     my $query = '
2514         SELECT datesent,
2515                frombranch,
2516                tobranch
2517         FROM branchtransfers
2518         WHERE itemnumber = ?
2519           AND datearrived IS NULL
2520         ';
2521     my $sth = $dbh->prepare($query);
2522     $sth->execute($itemnumber);
2523     my @row = $sth->fetchrow_array();
2524     $sth->finish;
2525     return @row;
2526 }
2527
2528 =head2 GetTransfersFromTo
2529
2530   @results = GetTransfersFromTo($frombranch,$tobranch);
2531
2532 Returns the list of pending transfers between $from and $to branch
2533
2534 =cut
2535
2536 sub GetTransfersFromTo {
2537     my ( $frombranch, $tobranch ) = @_;
2538     return unless ( $frombranch && $tobranch );
2539     my $dbh   = C4::Context->dbh;
2540     my $query = "
2541         SELECT itemnumber,datesent,frombranch
2542         FROM   branchtransfers
2543         WHERE  frombranch=?
2544           AND  tobranch=?
2545           AND datearrived IS NULL
2546     ";
2547     my $sth = $dbh->prepare($query);
2548     $sth->execute( $frombranch, $tobranch );
2549     my @gettransfers;
2550
2551     while ( my $data = $sth->fetchrow_hashref ) {
2552         push @gettransfers, $data;
2553     }
2554     $sth->finish;
2555     return (@gettransfers);
2556 }
2557
2558 =head2 DeleteTransfer
2559
2560   &DeleteTransfer($itemnumber);
2561
2562 =cut
2563
2564 sub DeleteTransfer {
2565     my ($itemnumber) = @_;
2566     my $dbh          = C4::Context->dbh;
2567     my $sth          = $dbh->prepare(
2568         "DELETE FROM branchtransfers
2569          WHERE itemnumber=?
2570          AND datearrived IS NULL "
2571     );
2572     $sth->execute($itemnumber);
2573     $sth->finish;
2574 }
2575
2576 =head2 AnonymiseIssueHistory
2577
2578   $rows = AnonymiseIssueHistory($date,$borrowernumber)
2579
2580 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2581 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2582
2583 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
2584 setting (force delete).
2585
2586 return the number of affected rows.
2587
2588 =cut
2589
2590 sub AnonymiseIssueHistory {
2591     my $date           = shift;
2592     my $borrowernumber = shift;
2593     my $dbh            = C4::Context->dbh;
2594     my $query          = "
2595         UPDATE old_issues
2596         SET    borrowernumber = ?
2597         WHERE  returndate < ?
2598           AND borrowernumber IS NOT NULL
2599     ";
2600
2601     # The default of 0 does not work due to foreign key constraints
2602     # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
2603     my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
2604     my @bind_params = ($anonymouspatron, $date);
2605     if (defined $borrowernumber) {
2606        $query .= " AND borrowernumber = ?";
2607        push @bind_params, $borrowernumber;
2608     } else {
2609        $query .= " AND (SELECT privacy FROM borrowers WHERE borrowers.borrowernumber=old_issues.borrowernumber) <> 0";
2610     }
2611     my $sth = $dbh->prepare($query);
2612     $sth->execute(@bind_params);
2613     my $rows_affected = $sth->rows;  ### doublecheck row count return function
2614     return $rows_affected;
2615 }
2616
2617 =head2 SendCirculationAlert
2618
2619 Send out a C<check-in> or C<checkout> alert using the messaging system.
2620
2621 B<Parameters>:
2622
2623 =over 4
2624
2625 =item type
2626
2627 Valid values for this parameter are: C<CHECKIN> and C<CHECKOUT>.
2628
2629 =item item
2630
2631 Hashref of information about the item being checked in or out.
2632
2633 =item borrower
2634
2635 Hashref of information about the borrower of the item.
2636
2637 =item branch
2638
2639 The branchcode from where the checkout or check-in took place.
2640
2641 =back
2642
2643 B<Example>:
2644
2645     SendCirculationAlert({
2646         type     => 'CHECKOUT',
2647         item     => $item,
2648         borrower => $borrower,
2649         branch   => $branch,
2650     });
2651
2652 =cut
2653
2654 sub SendCirculationAlert {
2655     my ($opts) = @_;
2656     my ($type, $item, $borrower, $branch) =
2657         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
2658     my %message_name = (
2659         CHECKIN  => 'Item_Check_in',
2660         CHECKOUT => 'Item_Checkout',
2661     );
2662     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
2663         borrowernumber => $borrower->{borrowernumber},
2664         message_name   => $message_name{$type},
2665     });
2666     my $letter = C4::Letters::getletter('circulation', $type);
2667     C4::Letters::parseletter($letter, 'biblio',      $item->{biblionumber});
2668     C4::Letters::parseletter($letter, 'biblioitems', $item->{biblionumber});
2669     C4::Letters::parseletter($letter, 'borrowers',   $borrower->{borrowernumber});
2670     C4::Letters::parseletter($letter, 'branches',    $branch);
2671     my @transports = @{ $borrower_preferences->{transports} };
2672     # warn "no transports" unless @transports;
2673     for (@transports) {
2674         # warn "transport: $_";
2675         my $message = C4::Message->find_last_message($borrower, $type, $_);
2676         if (!$message) {
2677             #warn "create new message";
2678             C4::Message->enqueue($letter, $borrower, $_);
2679         } else {
2680             #warn "append to old message";
2681             $message->append($letter);
2682             $message->update;
2683         }
2684     }
2685     $letter;
2686 }
2687
2688 =head2 updateWrongTransfer
2689
2690   $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2691
2692 This function validate the line of brachtransfer but with the wrong destination (mistake from a librarian ...), and create a new line in branchtransfer from the actual library to the original library of reservation 
2693
2694 =cut
2695
2696 sub updateWrongTransfer {
2697         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2698         my $dbh = C4::Context->dbh;     
2699 # first step validate the actual line of transfert .
2700         my $sth =
2701                 $dbh->prepare(
2702                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2703                 );
2704                 $sth->execute($FromLibrary,$itemNumber);
2705                 $sth->finish;
2706
2707 # second step create a new line of branchtransfer to the right location .
2708         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2709
2710 #third step changing holdingbranch of item
2711         UpdateHoldingbranch($FromLibrary,$itemNumber);
2712 }
2713
2714 =head2 UpdateHoldingbranch
2715
2716   $items = UpdateHoldingbranch($branch,$itmenumber);
2717
2718 Simple methode for updating hodlingbranch in items BDD line
2719
2720 =cut
2721
2722 sub UpdateHoldingbranch {
2723         my ( $branch,$itemnumber ) = @_;
2724     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2725 }
2726
2727 =head2 CalcDateDue
2728
2729 $newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower);
2730
2731 this function calculates the due date given the start date and configured circulation rules,
2732 checking against the holidays calendar as per the 'useDaysMode' syspref.
2733 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2734 C<$itemtype>  = itemtype code of item in question
2735 C<$branch>  = location whose calendar to use
2736 C<$borrower> = Borrower object
2737
2738 =cut
2739
2740 sub CalcDateDue { 
2741         my ($startdate,$itemtype,$branch,$borrower) = @_;
2742         my $datedue;
2743         my $loanlength = GetLoanLength($borrower->{'categorycode'},$itemtype, $branch);
2744
2745         # if globalDueDate ON the datedue is set to that date
2746         if ( C4::Context->preference('globalDueDate')
2747              && ( C4::Context->preference('globalDueDate') =~ C4::Dates->regexp('syspref') ) ) {
2748             $datedue = C4::Dates->new( C4::Context->preference('globalDueDate') );
2749         } else {
2750         # otherwise, calculate the datedue as normal
2751                 if(C4::Context->preference('useDaysMode') eq 'Days') {  # ignoring calendar
2752                         my $timedue = time + ($loanlength) * 86400;
2753                 #FIXME - assumes now even though we take a startdate 
2754                         my @datearr  = localtime($timedue);
2755                         $datedue = C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
2756                 } else {
2757                         my $calendar = C4::Calendar->new(  branchcode => $branch );
2758                         $datedue = $calendar->addDate($startdate, $loanlength);
2759                 }
2760         }
2761
2762         # if Hard Due Dates are used, retreive them and apply as necessary
2763         my ($hardduedate, $hardduedatecompare) = GetHardDueDate($borrower->{'categorycode'},$itemtype, $branch);
2764         if ( $hardduedate && $hardduedate->output('iso') && $hardduedate->output('iso') ne '0000-00-00') {
2765             # if the calculated due date is after the 'before' Hard Due Date (ceiling), override
2766             if ( $datedue->output( 'iso' ) gt $hardduedate->output( 'iso' ) && $hardduedatecompare == -1) {
2767                 $datedue = $hardduedate;
2768             # if the calculated date is before the 'after' Hard Due Date (floor), override
2769             } elsif ( $datedue->output( 'iso' ) lt $hardduedate->output( 'iso' ) && $hardduedatecompare == 1) {
2770                 $datedue = $hardduedate;               
2771             # if the hard due date is set to 'exactly', overrride
2772             } elsif ( $hardduedatecompare == 0) {
2773                 $datedue = $hardduedate;
2774             }
2775             # in all other cases, keep the date due as it is
2776         }
2777
2778         # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
2779         if ( C4::Context->preference('ReturnBeforeExpiry') && $datedue->output('iso') gt $borrower->{dateexpiry} ) {
2780             $datedue = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
2781         }
2782
2783         return $datedue;
2784 }
2785
2786 =head2 CheckValidDatedue
2787
2788   $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
2789
2790 This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
2791 To be replaced by CalcDateDue() once C4::Calendar use is tested.
2792
2793 this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
2794 C<$date_due>   = returndate calculate with no day check
2795 C<$itemnumber>  = itemnumber
2796 C<$branchcode>  = location of issue (affected by 'CircControl' syspref)
2797 C<$loanlength>  = loan length prior to adjustment
2798
2799 =cut
2800
2801 sub CheckValidDatedue {
2802 my ($date_due,$itemnumber,$branchcode)=@_;
2803 my @datedue=split('-',$date_due->output('iso'));
2804 my $years=$datedue[0];
2805 my $month=$datedue[1];
2806 my $day=$datedue[2];
2807 # die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
2808 my $dow;
2809 for (my $i=0;$i<2;$i++){
2810     $dow=Day_of_Week($years,$month,$day);
2811     ($dow=0) if ($dow>6);
2812     my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
2813     my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2814     my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2815         if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
2816         $i=0;
2817         (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
2818         }
2819     }
2820     my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
2821 return $newdatedue;
2822 }
2823
2824
2825 =head2 CheckRepeatableHolidays
2826
2827   $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2828
2829 This function checks if the date due is a repeatable holiday
2830
2831 C<$date_due>   = returndate calculate with no day check
2832 C<$itemnumber>  = itemnumber
2833 C<$branchcode>  = localisation of issue 
2834
2835 =cut
2836
2837 sub CheckRepeatableHolidays{
2838 my($itemnumber,$week_day,$branchcode)=@_;
2839 my $dbh = C4::Context->dbh;
2840 my $query = qq|SELECT count(*)  
2841         FROM repeatable_holidays 
2842         WHERE branchcode=?
2843         AND weekday=?|;
2844 my $sth = $dbh->prepare($query);
2845 $sth->execute($branchcode,$week_day);
2846 my $result=$sth->fetchrow;
2847 $sth->finish;
2848 return $result;
2849 }
2850
2851
2852 =head2 CheckSpecialHolidays
2853
2854   $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2855
2856 This function check if the date is a special holiday
2857
2858 C<$years>   = the years of datedue
2859 C<$month>   = the month of datedue
2860 C<$day>     = the day of datedue
2861 C<$itemnumber>  = itemnumber
2862 C<$branchcode>  = localisation of issue 
2863
2864 =cut
2865
2866 sub CheckSpecialHolidays{
2867 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
2868 my $dbh = C4::Context->dbh;
2869 my $query=qq|SELECT count(*) 
2870              FROM `special_holidays`
2871              WHERE year=?
2872              AND month=?
2873              AND day=?
2874              AND branchcode=?
2875             |;
2876 my $sth = $dbh->prepare($query);
2877 $sth->execute($years,$month,$day,$branchcode);
2878 my $countspecial=$sth->fetchrow ;
2879 $sth->finish;
2880 return $countspecial;
2881 }
2882
2883 =head2 CheckRepeatableSpecialHolidays
2884
2885   $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2886
2887 This function check if the date is a repeatble special holidays
2888
2889 C<$month>   = the month of datedue
2890 C<$day>     = the day of datedue
2891 C<$itemnumber>  = itemnumber
2892 C<$branchcode>  = localisation of issue 
2893
2894 =cut
2895
2896 sub CheckRepeatableSpecialHolidays{
2897 my ($month,$day,$itemnumber,$branchcode) = @_;
2898 my $dbh = C4::Context->dbh;
2899 my $query=qq|SELECT count(*) 
2900              FROM `repeatable_holidays`
2901              WHERE month=?
2902              AND day=?
2903              AND branchcode=?
2904             |;
2905 my $sth = $dbh->prepare($query);
2906 $sth->execute($month,$day,$branchcode);
2907 my $countspecial=$sth->fetchrow ;
2908 $sth->finish;
2909 return $countspecial;
2910 }
2911
2912
2913
2914 sub CheckValidBarcode{
2915 my ($barcode) = @_;
2916 my $dbh = C4::Context->dbh;
2917 my $query=qq|SELECT count(*) 
2918              FROM items 
2919              WHERE barcode=?
2920             |;
2921 my $sth = $dbh->prepare($query);
2922 $sth->execute($barcode);
2923 my $exist=$sth->fetchrow ;
2924 $sth->finish;
2925 return $exist;
2926 }
2927
2928 =head2 IsBranchTransferAllowed
2929
2930   $allowed = IsBranchTransferAllowed( $toBranch, $fromBranch, $code );
2931
2932 Code is either an itemtype or collection doe depending on the pref BranchTransferLimitsType
2933
2934 =cut
2935
2936 sub IsBranchTransferAllowed {
2937         my ( $toBranch, $fromBranch, $code ) = @_;
2938
2939         if ( $toBranch eq $fromBranch ) { return 1; } ## Short circuit for speed.
2940         
2941         my $limitType = C4::Context->preference("BranchTransferLimitsType");   
2942         my $dbh = C4::Context->dbh;
2943             
2944         my $sth = $dbh->prepare("SELECT * FROM branch_transfer_limits WHERE toBranch = ? AND fromBranch = ? AND $limitType = ?");
2945         $sth->execute( $toBranch, $fromBranch, $code );
2946         my $limit = $sth->fetchrow_hashref();
2947                         
2948         ## If a row is found, then that combination is not allowed, if no matching row is found, then the combination *is allowed*
2949         if ( $limit->{'limitId'} ) {
2950                 return 0;
2951         } else {
2952                 return 1;
2953         }
2954 }                                                        
2955
2956 =head2 CreateBranchTransferLimit
2957
2958   CreateBranchTransferLimit( $toBranch, $fromBranch, $code );
2959
2960 $code is either itemtype or collection code depending on what the pref BranchTransferLimitsType is set to.
2961
2962 =cut
2963
2964 sub CreateBranchTransferLimit {
2965    my ( $toBranch, $fromBranch, $code ) = @_;
2966
2967    my $limitType = C4::Context->preference("BranchTransferLimitsType");
2968    
2969    my $dbh = C4::Context->dbh;
2970    
2971    my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
2972    $sth->execute( $code, $toBranch, $fromBranch );
2973 }
2974
2975 =head2 DeleteBranchTransferLimits
2976
2977 DeleteBranchTransferLimits($frombranch);
2978
2979 Deletes all the branch transfer limits for one branch
2980
2981 =cut
2982
2983 sub DeleteBranchTransferLimits {
2984     my $branch = shift;
2985     my $dbh    = C4::Context->dbh;
2986     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
2987     $sth->execute($branch);
2988 }
2989
2990 sub ReturnLostItem{
2991     my ( $borrowernumber, $itemnum ) = @_;
2992
2993     MarkIssueReturned( $borrowernumber, $itemnum );
2994     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
2995     my @datearr = localtime(time);
2996     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
2997     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
2998     ModItem({ paidfor =>  "Paid for by $bor $date" }, undef, $itemnum);
2999 }
3000
3001
3002 sub LostItem{
3003     my ($itemnumber, $mark_returned, $charge_fee) = @_;
3004
3005     my $dbh = C4::Context->dbh();
3006     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
3007                            FROM issues 
3008                            JOIN items USING (itemnumber) 
3009                            JOIN biblio USING (biblionumber)
3010                            WHERE issues.itemnumber=?");
3011     $sth->execute($itemnumber);
3012     my $issues=$sth->fetchrow_hashref();
3013     $sth->finish;
3014
3015     # if a borrower lost the item, add a replacement cost to the their record
3016     if ( my $borrowernumber = $issues->{borrowernumber} ){
3017
3018         C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
3019           if $charge_fee;
3020         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
3021         #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
3022         MarkIssueReturned($borrowernumber,$itemnumber) if $mark_returned;
3023     }
3024 }
3025
3026
3027 1;
3028
3029 __END__
3030
3031 =head1 AUTHOR
3032
3033 Koha Development Team <http://koha-community.org/>
3034
3035 =cut
3036