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