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