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