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