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