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