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