Merge remote-tracking branch 'origin/new/bug_8293'
[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
1482         # Since branch/category and branch/itemtype use the same per-branch
1483         # defaults tables, we have to check that the key we want is set, not
1484         # just that a row was returned
1485         $result->{'holdallowed'}  = $search_result->{'holdallowed'}  unless ( defined $result->{'holdallowed'} );
1486         $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} );
1487     }
1488     
1489     # built-in default circulation rule
1490     $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} );
1491     $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} );
1492
1493     return $result;
1494 }
1495
1496 =head2 AddReturn
1497
1498   ($doreturn, $messages, $iteminformation, $borrower) =
1499       &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1500
1501 Returns a book.
1502
1503 =over 4
1504
1505 =item C<$barcode> is the bar code of the book being returned.
1506
1507 =item C<$branch> is the code of the branch where the book is being returned.
1508
1509 =item C<$exemptfine> indicates that overdue charges for the item will be
1510 removed.
1511
1512 =item C<$dropbox> indicates that the check-in date is assumed to be
1513 yesterday, or the last non-holiday as defined in C4::Calendar .  If
1514 overdue charges are applied and C<$dropbox> is true, the last charge
1515 will be removed.  This assumes that the fines accrual script has run
1516 for _today_.
1517
1518 =back
1519
1520 C<&AddReturn> returns a list of four items:
1521
1522 C<$doreturn> is true iff the return succeeded.
1523
1524 C<$messages> is a reference-to-hash giving feedback on the operation.
1525 The keys of the hash are:
1526
1527 =over 4
1528
1529 =item C<BadBarcode>
1530
1531 No item with this barcode exists. The value is C<$barcode>.
1532
1533 =item C<NotIssued>
1534
1535 The book is not currently on loan. The value is C<$barcode>.
1536
1537 =item C<IsPermanent>
1538
1539 The book's home branch is a permanent collection. If you have borrowed
1540 this book, you are not allowed to return it. The value is the code for
1541 the book's home branch.
1542
1543 =item C<wthdrawn>
1544
1545 This book has been withdrawn/cancelled. The value should be ignored.
1546
1547 =item C<Wrongbranch>
1548
1549 This book has was returned to the wrong branch.  The value is a hashref
1550 so that C<$messages->{Wrongbranch}->{Wrongbranch}> and C<$messages->{Wrongbranch}->{Rightbranch}>
1551 contain the branchcode of the incorrect and correct return library, respectively.
1552
1553 =item C<ResFound>
1554
1555 The item was reserved. The value is a reference-to-hash whose keys are
1556 fields from the reserves table of the Koha database, and
1557 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1558 either C<Waiting>, C<Reserved>, or 0.
1559
1560 =back
1561
1562 C<$iteminformation> is a reference-to-hash, giving information about the
1563 returned item from the issues table.
1564
1565 C<$borrower> is a reference-to-hash, giving information about the
1566 patron who last borrowed the book.
1567
1568 =cut
1569
1570 sub AddReturn {
1571     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1572
1573     if ($branch and not GetBranchDetail($branch)) {
1574         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
1575         undef $branch;
1576     }
1577     $branch = C4::Context->userenv->{'branch'} unless $branch;  # we trust userenv to be a safe fallback/default
1578     my $messages;
1579     my $borrower;
1580     my $biblio;
1581     my $doreturn       = 1;
1582     my $validTransfert = 0;
1583     my $stat_type = 'return';    
1584
1585     # get information on item
1586     my $itemnumber = GetItemnumberFromBarcode( $barcode );
1587     unless ($itemnumber) {
1588         return (0, { BadBarcode => $barcode }); # no barcode means no item or borrower.  bail out.
1589     }
1590     my $issue  = GetItemIssue($itemnumber);
1591 #   warn Dumper($iteminformation);
1592     if ($issue and $issue->{borrowernumber}) {
1593         $borrower = C4::Members::GetMemberDetails($issue->{borrowernumber})
1594             or die "Data inconsistency: barcode $barcode (itemnumber:$itemnumber) claims to be issued to non-existant borrowernumber '$issue->{borrowernumber}'\n"
1595                 . Dumper($issue) . "\n";
1596     } else {
1597         $messages->{'NotIssued'} = $barcode;
1598         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
1599         $doreturn = 0;
1600         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
1601         # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on
1602         if (C4::Context->preference("RecordLocalUseOnReturn")) {
1603            $messages->{'LocalUse'} = 1;
1604            $stat_type = 'localuse';
1605         }
1606     }
1607
1608     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
1609         # full item data, but no borrowernumber or checkout info (no issue)
1610         # we know GetItem should work because GetItemnumberFromBarcode worked
1611     my $hbr      = GetBranchItemRule($item->{'homebranch'}, $item->{'itype'})->{'returnbranch'} || "homebranch";
1612         # get the proper branch to which to return the item
1613     $hbr = $item->{$hbr} || $branch ;
1614         # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch)
1615
1616     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
1617
1618     # check if the book is in a permanent collection....
1619     # FIXME -- This 'PE' attribute is largely undocumented.  afaict, there's no user interface that reflects this functionality.
1620     if ( $hbr ) {
1621         my $branches = GetBranches();    # a potentially expensive call for a non-feature.
1622         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
1623     }
1624
1625     # if indy branches and returning to different branch, refuse the return unless canreservefromotherbranches is turned on
1626     if ($hbr ne $branch && C4::Context->preference("IndependantBranches") && !(C4::Context->preference("canreservefromotherbranches"))){
1627         $messages->{'Wrongbranch'} = {
1628             Wrongbranch => $branch,
1629             Rightbranch => $hbr,
1630         };
1631         $doreturn = 0;
1632         # bailing out here - in this case, current desired behavior
1633         # is to act as if no return ever happened at all.
1634         # FIXME - even in an indy branches situation, there should
1635         # still be an option for the library to accept the item
1636         # and transfer it to its owning library.
1637         return ( $doreturn, $messages, $issue, $borrower );
1638     }
1639
1640     if ( $item->{'wthdrawn'} ) { # book has been cancelled
1641         $messages->{'wthdrawn'} = 1;
1642         $doreturn = 0;
1643     }
1644
1645     # case of a return of document (deal with issues and holdingbranch)
1646     if ($doreturn) {
1647     my $today = DateTime->now( time_zone => C4::Context->tz() );
1648     my $datedue = $issue->{date_due};
1649         $borrower or warn "AddReturn without current borrower";
1650                 my $circControlBranch;
1651         if ($dropbox) {
1652             # define circControlBranch only if dropbox mode is set
1653             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
1654             # FIXME: check issuedate > returndate, factoring in holidays
1655             #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
1656             $circControlBranch = _GetCircControlBranch($item,$borrower);
1657         my $datedue = $issue->{date_due};
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         # fix fine days
1722         my $debardate = _FixFineDaysOnReturn( $borrower, $item, $issue->{date_due} );
1723         $messages->{'Debarred'} = $debardate if ($debardate);
1724     }
1725
1726     # find reserves.....
1727     # if we don't have a reserve with the status W, we launch the Checkreserves routine
1728     my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
1729     if ($resfound) {
1730           $resrec->{'ResFound'} = $resfound;
1731         $messages->{'ResFound'} = $resrec;
1732     }
1733
1734     # update stats?
1735     # Record the fact that this book was returned.
1736     UpdateStats(
1737         $branch, $stat_type, '0', '',
1738         $item->{'itemnumber'},
1739         $biblio->{'itemtype'},
1740         $borrowernumber
1741     );
1742
1743     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
1744     my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1745     my %conditions = (
1746         branchcode   => $branch,
1747         categorycode => $borrower->{categorycode},
1748         item_type    => $item->{itype},
1749         notification => 'CHECKIN',
1750     );
1751     if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) {
1752         SendCirculationAlert({
1753             type     => 'CHECKIN',
1754             item     => $item,
1755             borrower => $borrower,
1756             branch   => $branch,
1757         });
1758     }
1759     
1760     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'})
1761         if C4::Context->preference("ReturnLog");
1762     
1763     # FIXME: make this comment intelligible.
1764     #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1765     #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1766
1767     if (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
1768         if ( C4::Context->preference("AutomaticItemReturn"    ) or
1769             (C4::Context->preference("UseBranchTransferLimits") and
1770              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
1771            )) {
1772             $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
1773             $debug and warn "item: " . Dumper($item);
1774             ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
1775             $messages->{'WasTransfered'} = 1;
1776         } else {
1777             $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
1778         }
1779     }
1780     return ( $doreturn, $messages, $issue, $borrower );
1781 }
1782
1783 =head2 MarkIssueReturned
1784
1785   MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy);
1786
1787 Unconditionally marks an issue as being returned by
1788 moving the C<issues> row to C<old_issues> and
1789 setting C<returndate> to the current date, or
1790 the last non-holiday date of the branccode specified in
1791 C<dropbox_branch> .  Assumes you've already checked that 
1792 it's safe to do this, i.e. last non-holiday > issuedate.
1793
1794 if C<$returndate> is specified (in iso format), it is used as the date
1795 of the return. It is ignored when a dropbox_branch is passed in.
1796
1797 C<$privacy> contains the privacy parameter. If the patron has set privacy to 2,
1798 the old_issue is immediately anonymised
1799
1800 Ideally, this function would be internal to C<C4::Circulation>,
1801 not exported, but it is currently needed by one 
1802 routine in C<C4::Accounts>.
1803
1804 =cut
1805
1806 sub MarkIssueReturned {
1807     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
1808
1809     my $dbh   = C4::Context->dbh;
1810     my $query = 'UPDATE issues SET returndate=';
1811     my @bind;
1812     if ($dropbox_branch) {
1813         my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
1814         my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
1815         $query .= ' ? ';
1816         push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
1817     } elsif ($returndate) {
1818         $query .= ' ? ';
1819         push @bind, $returndate;
1820     } else {
1821         $query .= ' now() ';
1822     }
1823     $query .= ' WHERE  borrowernumber = ?  AND itemnumber = ?';
1824     push @bind, $borrowernumber, $itemnumber;
1825     # FIXME transaction
1826     my $sth_upd  = $dbh->prepare($query);
1827     $sth_upd->execute(@bind);
1828     my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues
1829                                   WHERE borrowernumber = ?
1830                                   AND itemnumber = ?');
1831     $sth_copy->execute($borrowernumber, $itemnumber);
1832     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
1833     if ( $privacy == 2) {
1834         # The default of 0 does not work due to foreign key constraints
1835         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
1836         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
1837         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
1838                                   WHERE borrowernumber = ?
1839                                   AND itemnumber = ?");
1840        $sth_ano->execute($anonymouspatron, $borrowernumber, $itemnumber);
1841     }
1842     my $sth_del  = $dbh->prepare("DELETE FROM issues
1843                                   WHERE borrowernumber = ?
1844                                   AND itemnumber = ?");
1845     $sth_del->execute($borrowernumber, $itemnumber);
1846 }
1847
1848 =head2 _FixFineDaysOnReturn
1849
1850     &_FixFineDaysOnReturn($borrower, $item, $datedue);
1851
1852 C<$borrower> borrower hashref
1853
1854 C<$item> item hashref
1855
1856 C<$datedue> date due
1857
1858 Internal function, called only by AddReturn that calculate and update the user fine days, and debars him
1859
1860 =cut
1861
1862 sub _FixFineDaysOnReturn {
1863     my ( $borrower, $item, $datedue ) = @_;
1864     return unless ($datedue);
1865     
1866     my $dt_due =  dt_from_string( $datedue );
1867     my $dt_today = DateTime->now( time_zone => C4::Context->tz() );
1868
1869     my $branchcode = _GetCircControlBranch( $item, $borrower );
1870     my $calendar = Koha::Calendar->new( branchcode => $branchcode );
1871
1872     # $deltadays is a DateTime::Duration object
1873     my $deltadays = $calendar->days_between( $dt_due, $dt_today );
1874
1875     my $circcontrol = C4::Context::preference('CircControl');
1876     my $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
1877     my $finedays    = $issuingrule->{finedays};
1878     my $unit        = $issuingrule->{lengthunit};
1879
1880     # exit if no finedays defined
1881     return unless $finedays;
1882     # finedays is in days, so hourly loans must multiply by 24
1883     # thus 1 hour late equals 1 day suspension * finedays rate
1884     $finedays       = $finedays * 24 if ($unit eq 'hours');
1885
1886     # grace period is measured in the same units as the loan
1887     my $grace = DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
1888
1889     if ( ( $deltadays - $grace )->is_positive ) { # you can't compare DateTime::Durations with logical operators
1890         my $new_debar_dt = $dt_today->clone()->add_duration( $deltadays * $finedays );
1891         my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
1892         # check to see if the current debar date is a valid date
1893         if ( $borrower->{debarred} && $borrower_debar_dt ) {
1894         # if so, is it before the new date?  update only if true
1895             if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) == -1 ) {
1896                 C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
1897                 return $new_debar_dt->ymd();
1898             }
1899         # if the borrower's debar date is not set or valid, debar them
1900         } else {
1901             C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
1902             return $new_debar_dt->ymd();
1903         }
1904     }
1905 }
1906
1907 =head2 _FixOverduesOnReturn
1908
1909    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1910
1911 C<$brn> borrowernumber
1912
1913 C<$itm> itemnumber
1914
1915 C<$exemptfine> BOOL -- remove overdue charge associated with this issue. 
1916 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1917
1918 Internal function, called only by AddReturn
1919
1920 =cut
1921
1922 sub _FixOverduesOnReturn {
1923     my ($borrowernumber, $item);
1924     unless ($borrowernumber = shift) {
1925         warn "_FixOverduesOnReturn() not supplied valid borrowernumber";
1926         return;
1927     }
1928     unless ($item = shift) {
1929         warn "_FixOverduesOnReturn() not supplied valid itemnumber";
1930         return;
1931     }
1932     my ($exemptfine, $dropbox) = @_;
1933     my $dbh = C4::Context->dbh;
1934
1935     # check for overdue fine
1936     my $sth = $dbh->prepare(
1937 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1938     );
1939     $sth->execute( $borrowernumber, $item );
1940
1941     # alter fine to show that the book has been returned
1942     my $data = $sth->fetchrow_hashref;
1943     return 0 unless $data;    # no warning, there's just nothing to fix
1944
1945     my $uquery;
1946     my @bind = ($borrowernumber, $item, $data->{'accountno'});
1947     if ($exemptfine) {
1948         $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1949         if (C4::Context->preference("FinesLog")) {
1950             &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1951         }
1952     } elsif ($dropbox && $data->{lastincrement}) {
1953         my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1954         my $amt = $data->{amount} - $data->{lastincrement} ;
1955         if (C4::Context->preference("FinesLog")) {
1956             &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1957         }
1958          $uquery = "update accountlines set accounttype='F' ";
1959          if($outstanding  >= 0 && $amt >=0) {
1960             $uquery .= ", amount = ? , amountoutstanding=? ";
1961             unshift @bind, ($amt, $outstanding) ;
1962         }
1963     } else {
1964         $uquery = "update accountlines set accounttype='F' ";
1965     }
1966     $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1967     my $usth = $dbh->prepare($uquery);
1968     return $usth->execute(@bind);
1969 }
1970
1971 =head2 _FixAccountForLostAndReturned
1972
1973   &_FixAccountForLostAndReturned($itemnumber, [$borrowernumber, $barcode]);
1974
1975 Calculates the charge for a book lost and returned.
1976
1977 Internal function, not exported, called only by AddReturn.
1978
1979 FIXME: This function reflects how inscrutable fines logic is.  Fix both.
1980 FIXME: Give a positive return value on success.  It might be the $borrowernumber who received credit, or the amount forgiven.
1981
1982 =cut
1983
1984 sub _FixAccountForLostAndReturned {
1985     my $itemnumber     = shift or return;
1986     my $borrowernumber = @_ ? shift : undef;
1987     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
1988     my $dbh = C4::Context->dbh;
1989     # check for charge made for lost book
1990     my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
1991     $sth->execute($itemnumber);
1992     my $data = $sth->fetchrow_hashref;
1993     $data or return;    # bail if there is nothing to do
1994     $data->{accounttype} eq 'W' and return;    # Written off
1995
1996     # writeoff this amount
1997     my $offset;
1998     my $amount = $data->{'amount'};
1999     my $acctno = $data->{'accountno'};
2000     my $amountleft;                                             # Starts off undef/zero.
2001     if ($data->{'amountoutstanding'} == $amount) {
2002         $offset     = $data->{'amount'};
2003         $amountleft = 0;                                        # Hey, it's zero here, too.
2004     } else {
2005         $offset     = $amount - $data->{'amountoutstanding'};   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2006         $amountleft = $data->{'amountoutstanding'} - $amount;   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2007     }
2008     my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
2009         WHERE (borrowernumber = ?)
2010         AND (itemnumber = ?) AND (accountno = ?) ");
2011     $usth->execute($data->{'borrowernumber'},$itemnumber,$acctno);      # We might be adjusting an account for some OTHER borrowernumber now.  Not the one we passed in.  
2012     #check if any credit is left if so writeoff other accounts
2013     my $nextaccntno = getnextacctno($data->{'borrowernumber'});
2014     $amountleft *= -1 if ($amountleft < 0);
2015     if ($amountleft > 0) {
2016         my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
2017                             AND (amountoutstanding >0) ORDER BY date");     # might want to order by amountoustanding ASC (pay smallest first)
2018         $msth->execute($data->{'borrowernumber'});
2019         # offset transactions
2020         my $newamtos;
2021         my $accdata;
2022         while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
2023             if ($accdata->{'amountoutstanding'} < $amountleft) {
2024                 $newamtos = 0;
2025                 $amountleft -= $accdata->{'amountoutstanding'};
2026             }  else {
2027                 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
2028                 $amountleft = 0;
2029             }
2030             my $thisacct = $accdata->{'accountno'};
2031             # FIXME: move prepares outside while loop!
2032             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
2033                     WHERE (borrowernumber = ?)
2034                     AND (accountno=?)");
2035             $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');    # FIXME: '$thisacct' is a string literal!
2036             $usth = $dbh->prepare("INSERT INTO accountoffsets
2037                 (borrowernumber, accountno, offsetaccount,  offsetamount)
2038                 VALUES
2039                 (?,?,?,?)");
2040             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
2041         }
2042         $msth->finish;  # $msth might actually have data left
2043     }
2044     $amountleft *= -1 if ($amountleft > 0);
2045     my $desc = "Item Returned " . $item_id;
2046     $usth = $dbh->prepare("INSERT INTO accountlines
2047         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
2048         VALUES (?,?,now(),?,?,'CR',?)");
2049     $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
2050     if ($borrowernumber) {
2051         # FIXME: same as query above.  use 1 sth for both
2052         $usth = $dbh->prepare("INSERT INTO accountoffsets
2053             (borrowernumber, accountno, offsetaccount,  offsetamount)
2054             VALUES (?,?,?,?)");
2055         $usth->execute($borrowernumber, $data->{'accountno'}, $nextaccntno, $offset);
2056     }
2057     ModItem({ paidfor => '' }, undef, $itemnumber);
2058     return;
2059 }
2060
2061 =head2 _GetCircControlBranch
2062
2063    my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower);
2064
2065 Internal function : 
2066
2067 Return the library code to be used to determine which circulation
2068 policy applies to a transaction.  Looks up the CircControl and
2069 HomeOrHoldingBranch system preferences.
2070
2071 C<$iteminfos> is a hashref to iteminfo. Only {homebranch or holdingbranch} is used.
2072
2073 C<$borrower> is a hashref to borrower. Only {branchcode} is used.
2074
2075 =cut
2076
2077 sub _GetCircControlBranch {
2078     my ($item, $borrower) = @_;
2079     my $circcontrol = C4::Context->preference('CircControl');
2080     my $branch;
2081
2082     if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) {
2083         $branch= C4::Context->userenv->{'branch'};
2084     } elsif ($circcontrol eq 'PatronLibrary') {
2085         $branch=$borrower->{branchcode};
2086     } else {
2087         my $branchfield = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
2088         $branch = $item->{$branchfield};
2089         # default to item home branch if holdingbranch is used
2090         # and is not defined
2091         if (!defined($branch) && $branchfield eq 'holdingbranch') {
2092             $branch = $item->{homebranch};
2093         }
2094     }
2095     return $branch;
2096 }
2097
2098
2099
2100
2101
2102
2103 =head2 GetItemIssue
2104
2105   $issue = &GetItemIssue($itemnumber);
2106
2107 Returns patron currently having a book, or undef if not checked out.
2108
2109 C<$itemnumber> is the itemnumber.
2110
2111 C<$issue> is a hashref of the row from the issues table.
2112
2113 =cut
2114
2115 sub GetItemIssue {
2116     my ($itemnumber) = @_;
2117     return unless $itemnumber;
2118     my $sth = C4::Context->dbh->prepare(
2119         "SELECT *
2120         FROM issues
2121         LEFT JOIN items ON issues.itemnumber=items.itemnumber
2122         WHERE issues.itemnumber=?");
2123     $sth->execute($itemnumber);
2124     my $data = $sth->fetchrow_hashref;
2125     return unless $data;
2126     $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
2127     $data->{issuedate}->truncate(to => 'minute');
2128     $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
2129     $data->{date_due}->truncate(to => 'minute');
2130     my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minute');
2131     $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
2132     return $data;
2133 }
2134
2135 =head2 GetOpenIssue
2136
2137   $issue = GetOpenIssue( $itemnumber );
2138
2139 Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
2140
2141 C<$itemnumber> is the item's itemnumber
2142
2143 Returns a hashref
2144
2145 =cut
2146
2147 sub GetOpenIssue {
2148   my ( $itemnumber ) = @_;
2149
2150   my $dbh = C4::Context->dbh;  
2151   my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
2152   $sth->execute( $itemnumber );
2153   my $issue = $sth->fetchrow_hashref();
2154   return $issue;
2155 }
2156
2157 =head2 GetItemIssues
2158
2159   $issues = &GetItemIssues($itemnumber, $history);
2160
2161 Returns patrons that have issued a book
2162
2163 C<$itemnumber> is the itemnumber
2164 C<$history> is false if you just want the current "issuer" (if any)
2165 and true if you want issues history from old_issues also.
2166
2167 Returns reference to an array of hashes
2168
2169 =cut
2170
2171 sub GetItemIssues {
2172     my ( $itemnumber, $history ) = @_;
2173     
2174     my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
2175     $today->truncate( to => 'minute' );
2176     my $sql = "SELECT * FROM issues
2177               JOIN borrowers USING (borrowernumber)
2178               JOIN items     USING (itemnumber)
2179               WHERE issues.itemnumber = ? ";
2180     if ($history) {
2181         $sql .= "UNION ALL
2182                  SELECT * FROM old_issues
2183                  LEFT JOIN borrowers USING (borrowernumber)
2184                  JOIN items USING (itemnumber)
2185                  WHERE old_issues.itemnumber = ? ";
2186     }
2187     $sql .= "ORDER BY date_due DESC";
2188     my $sth = C4::Context->dbh->prepare($sql);
2189     if ($history) {
2190         $sth->execute($itemnumber, $itemnumber);
2191     } else {
2192         $sth->execute($itemnumber);
2193     }
2194     my $results = $sth->fetchall_arrayref({});
2195     foreach (@$results) {
2196         my $date_due = dt_from_string($_->{date_due},'sql');
2197         $date_due->truncate( to => 'minute' );
2198
2199         $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
2200     }
2201     return $results;
2202 }
2203
2204 =head2 GetBiblioIssues
2205
2206   $issues = GetBiblioIssues($biblionumber);
2207
2208 this function get all issues from a biblionumber.
2209
2210 Return:
2211 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
2212 tables issues and the firstname,surname & cardnumber from borrowers.
2213
2214 =cut
2215
2216 sub GetBiblioIssues {
2217     my $biblionumber = shift;
2218     return undef unless $biblionumber;
2219     my $dbh   = C4::Context->dbh;
2220     my $query = "
2221         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2222         FROM issues
2223             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
2224             LEFT JOIN items ON issues.itemnumber = items.itemnumber
2225             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2226             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2227         WHERE biblio.biblionumber = ?
2228         UNION ALL
2229         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2230         FROM old_issues
2231             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
2232             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
2233             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2234             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2235         WHERE biblio.biblionumber = ?
2236         ORDER BY timestamp
2237     ";
2238     my $sth = $dbh->prepare($query);
2239     $sth->execute($biblionumber, $biblionumber);
2240
2241     my @issues;
2242     while ( my $data = $sth->fetchrow_hashref ) {
2243         push @issues, $data;
2244     }
2245     return \@issues;
2246 }
2247
2248 =head2 GetUpcomingDueIssues
2249
2250   my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
2251
2252 =cut
2253
2254 sub GetUpcomingDueIssues {
2255     my $params = shift;
2256
2257     $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
2258     my $dbh = C4::Context->dbh;
2259
2260     my $statement = <<END_SQL;
2261 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due, branches.branchemail
2262 FROM issues 
2263 LEFT JOIN items USING (itemnumber)
2264 LEFT OUTER JOIN branches USING (branchcode)
2265 WhERE returndate is NULL
2266 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
2267 END_SQL
2268
2269     my @bind_parameters = ( $params->{'days_in_advance'} );
2270     
2271     my $sth = $dbh->prepare( $statement );
2272     $sth->execute( @bind_parameters );
2273     my $upcoming_dues = $sth->fetchall_arrayref({});
2274     $sth->finish;
2275
2276     return $upcoming_dues;
2277 }
2278
2279 =head2 CanBookBeRenewed
2280
2281   ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
2282
2283 Find out whether a borrowed item may be renewed.
2284
2285 C<$dbh> is a DBI handle to the Koha database.
2286
2287 C<$borrowernumber> is the borrower number of the patron who currently
2288 has the item on loan.
2289
2290 C<$itemnumber> is the number of the item to renew.
2291
2292 C<$override_limit>, if supplied with a true value, causes
2293 the limit on the number of times that the loan can be renewed
2294 (as controlled by the item type) to be ignored.
2295
2296 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
2297 item must currently be on loan to the specified borrower; renewals
2298 must be allowed for the item's type; and the borrower must not have
2299 already renewed the loan. $error will contain the reason the renewal can not proceed
2300
2301 =cut
2302
2303 sub CanBookBeRenewed {
2304
2305     # check renewal status
2306     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2307     my $dbh       = C4::Context->dbh;
2308     my $renews    = 1;
2309     my $renewokay = 0;
2310         my $error;
2311
2312     # Look in the issues table for this item, lent to this borrower,
2313     # and not yet returned.
2314
2315     # Look in the issues table for this item, lent to this borrower,
2316     # and not yet returned.
2317     my %branch = (
2318             'ItemHomeLibrary' => 'items.homebranch',
2319             'PickupLibrary'   => 'items.holdingbranch',
2320             'PatronLibrary'   => 'borrowers.branchcode'
2321             );
2322     my $controlbranch = $branch{C4::Context->preference('CircControl')};
2323     my $itype         = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
2324     
2325     my $sthcount = $dbh->prepare("
2326                    SELECT 
2327                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
2328                    FROM  issuingrules, 
2329                    issues
2330                    LEFT JOIN items USING (itemnumber) 
2331                    LEFT JOIN borrowers USING (borrowernumber) 
2332                    LEFT JOIN biblioitems USING (biblioitemnumber)
2333                    
2334                    WHERE
2335                     (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
2336                    AND
2337                     (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
2338                    AND
2339                     (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') 
2340                    AND 
2341                     borrowernumber = ? 
2342                    AND
2343                     itemnumber = ?
2344                    ORDER BY
2345                     issuingrules.categorycode desc,
2346                     issuingrules.itemtype desc,
2347                     issuingrules.branchcode desc
2348                    LIMIT 1;
2349                   ");
2350
2351     $sthcount->execute( $borrowernumber, $itemnumber );
2352     if ( my $data1 = $sthcount->fetchrow_hashref ) {
2353         
2354         if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
2355             $renewokay = 1;
2356         }
2357         else {
2358                         $error="too_many";
2359                 }
2360                 
2361         my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
2362         if ($resfound) {
2363             $renewokay = 0;
2364                         $error="on_reserve"
2365         }
2366
2367     }
2368     return ($renewokay,$error);
2369 }
2370
2371 =head2 AddRenewal
2372
2373   &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
2374
2375 Renews a loan.
2376
2377 C<$borrowernumber> is the borrower number of the patron who currently
2378 has the item.
2379
2380 C<$itemnumber> is the number of the item to renew.
2381
2382 C<$branch> is the library where the renewal took place (if any).
2383            The library that controls the circ policies for the renewal is retrieved from the issues record.
2384
2385 C<$datedue> can be a C4::Dates object used to set the due date.
2386
2387 C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate.  If
2388 this parameter is not supplied, lastreneweddate is set to the current date.
2389
2390 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2391 from the book's item type.
2392
2393 =cut
2394
2395 sub AddRenewal {
2396     my $borrowernumber  = shift or return undef;
2397     my $itemnumber      = shift or return undef;
2398     my $branch          = shift;
2399     my $datedue         = shift;
2400     my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
2401     my $item   = GetItem($itemnumber) or return undef;
2402     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2403
2404     my $dbh = C4::Context->dbh;
2405     # Find the issues record for this book
2406     my $sth =
2407       $dbh->prepare("SELECT * FROM issues
2408                         WHERE borrowernumber=? 
2409                         AND itemnumber=?"
2410       );
2411     $sth->execute( $borrowernumber, $itemnumber );
2412     my $issuedata = $sth->fetchrow_hashref;
2413     $sth->finish;
2414     if(defined $datedue && ref $datedue ne 'DateTime' ) {
2415         carp 'Invalid date passed to AddRenewal.';
2416         return;
2417     }
2418     # If the due date wasn't specified, calculate it by adding the
2419     # book's loan length to today's date or the current due date
2420     # based on the value of the RenewalPeriodBase syspref.
2421     unless ($datedue) {
2422
2423         my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
2424         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
2425
2426         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2427                                         $issuedata->{date_due} :
2428                                         DateTime->now( time_zone => C4::Context->tz());
2429         $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
2430     }
2431
2432     # Update the issues record to have the new due date, and a new count
2433     # of how many times it has been renewed.
2434     my $renews = $issuedata->{'renewals'} + 1;
2435     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
2436                             WHERE borrowernumber=? 
2437                             AND itemnumber=?"
2438     );
2439
2440     $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
2441     $sth->finish;
2442
2443     # Update the renewal count on the item, and tell zebra to reindex
2444     $renews = $biblio->{'renewals'} + 1;
2445     ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
2446
2447     # Charge a new rental fee, if applicable?
2448     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2449     if ( $charge > 0 ) {
2450         my $accountno = getnextacctno( $borrowernumber );
2451         my $item = GetBiblioFromItemNumber($itemnumber);
2452         my $manager_id = 0;
2453         $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
2454         $sth = $dbh->prepare(
2455                 "INSERT INTO accountlines
2456                     (date, borrowernumber, accountno, amount, manager_id,
2457                     description,accounttype, amountoutstanding, itemnumber)
2458                     VALUES (now(),?,?,?,?,?,?,?,?)"
2459         );
2460         $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
2461             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2462             'Rent', $charge, $itemnumber );
2463     }
2464     # Log the renewal
2465     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
2466         return $datedue;
2467 }
2468
2469 sub GetRenewCount {
2470     # check renewal status
2471     my ( $bornum, $itemno ) = @_;
2472     my $dbh           = C4::Context->dbh;
2473     my $renewcount    = 0;
2474     my $renewsallowed = 0;
2475     my $renewsleft    = 0;
2476
2477     my $borrower = C4::Members::GetMember( borrowernumber => $bornum);
2478     my $item     = GetItem($itemno); 
2479
2480     # Look in the issues table for this item, lent to this borrower,
2481     # and not yet returned.
2482
2483     # FIXME - I think this function could be redone to use only one SQL call.
2484     my $sth = $dbh->prepare(
2485         "select * from issues
2486                                 where (borrowernumber = ?)
2487                                 and (itemnumber = ?)"
2488     );
2489     $sth->execute( $bornum, $itemno );
2490     my $data = $sth->fetchrow_hashref;
2491     $renewcount = $data->{'renewals'} if $data->{'renewals'};
2492     $sth->finish;
2493     # $item and $borrower should be calculated
2494     my $branchcode = _GetCircControlBranch($item, $borrower);
2495     
2496     my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
2497     
2498     $renewsallowed = $issuingrule->{'renewalsallowed'};
2499     $renewsleft    = $renewsallowed - $renewcount;
2500     if($renewsleft < 0){ $renewsleft = 0; }
2501     return ( $renewcount, $renewsallowed, $renewsleft );
2502 }
2503
2504 =head2 GetIssuingCharges
2505
2506   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2507
2508 Calculate how much it would cost for a given patron to borrow a given
2509 item, including any applicable discounts.
2510
2511 C<$itemnumber> is the item number of item the patron wishes to borrow.
2512
2513 C<$borrowernumber> is the patron's borrower number.
2514
2515 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2516 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2517 if it's a video).
2518
2519 =cut
2520
2521 sub GetIssuingCharges {
2522
2523     # calculate charges due
2524     my ( $itemnumber, $borrowernumber ) = @_;
2525     my $charge = 0;
2526     my $dbh    = C4::Context->dbh;
2527     my $item_type;
2528
2529     # Get the book's item type and rental charge (via its biblioitem).
2530     my $charge_query = 'SELECT itemtypes.itemtype,rentalcharge FROM items
2531         LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber';
2532     $charge_query .= (C4::Context->preference('item-level_itypes'))
2533         ? ' LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype'
2534         : ' LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype';
2535
2536     $charge_query .= ' WHERE items.itemnumber =?';
2537
2538     my $sth = $dbh->prepare($charge_query);
2539     $sth->execute($itemnumber);
2540     if ( my $item_data = $sth->fetchrow_hashref ) {
2541         $item_type = $item_data->{itemtype};
2542         $charge    = $item_data->{rentalcharge};
2543         my $branch = C4::Branch::mybranch();
2544         my $discount_query = q|SELECT rentaldiscount,
2545             issuingrules.itemtype, issuingrules.branchcode
2546             FROM borrowers
2547             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2548             WHERE borrowers.borrowernumber = ?
2549             AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
2550             AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
2551         my $discount_sth = $dbh->prepare($discount_query);
2552         $discount_sth->execute( $borrowernumber, $item_type, $branch );
2553         my $discount_rules = $discount_sth->fetchall_arrayref({});
2554         if (@{$discount_rules}) {
2555             # We may have multiple rules so get the most specific
2556             my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
2557             $charge = ( $charge * ( 100 - $discount ) ) / 100;
2558         }
2559     }
2560
2561     $sth->finish; # we havent _explicitly_ fetched all rows
2562     return ( $charge, $item_type );
2563 }
2564
2565 # Select most appropriate discount rule from those returned
2566 sub _get_discount_from_rule {
2567     my ($rules_ref, $branch, $itemtype) = @_;
2568     my $discount;
2569
2570     if (@{$rules_ref} == 1) { # only 1 applicable rule use it
2571         $discount = $rules_ref->[0]->{rentaldiscount};
2572         return (defined $discount) ? $discount : 0;
2573     }
2574     # could have up to 4 does one match $branch and $itemtype
2575     my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
2576     if (@d) {
2577         $discount = $d[0]->{rentaldiscount};
2578         return (defined $discount) ? $discount : 0;
2579     }
2580     # do we have item type + all branches
2581     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
2582     if (@d) {
2583         $discount = $d[0]->{rentaldiscount};
2584         return (defined $discount) ? $discount : 0;
2585     }
2586     # do we all item types + this branch
2587     @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
2588     if (@d) {
2589         $discount = $d[0]->{rentaldiscount};
2590         return (defined $discount) ? $discount : 0;
2591     }
2592     # so all and all (surely we wont get here)
2593     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
2594     if (@d) {
2595         $discount = $d[0]->{rentaldiscount};
2596         return (defined $discount) ? $discount : 0;
2597     }
2598     # none of the above
2599     return 0;
2600 }
2601
2602 =head2 AddIssuingCharge
2603
2604   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2605
2606 =cut
2607
2608 sub AddIssuingCharge {
2609     my ( $itemnumber, $borrowernumber, $charge ) = @_;
2610     my $dbh = C4::Context->dbh;
2611     my $nextaccntno = getnextacctno( $borrowernumber );
2612     my $manager_id = 0;
2613     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
2614     my $query ="
2615         INSERT INTO accountlines
2616             (borrowernumber, itemnumber, accountno,
2617             date, amount, description, accounttype,
2618             amountoutstanding, manager_id)
2619         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?,?)
2620     ";
2621     my $sth = $dbh->prepare($query);
2622     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
2623     $sth->finish;
2624 }
2625
2626 =head2 GetTransfers
2627
2628   GetTransfers($itemnumber);
2629
2630 =cut
2631
2632 sub GetTransfers {
2633     my ($itemnumber) = @_;
2634
2635     my $dbh = C4::Context->dbh;
2636
2637     my $query = '
2638         SELECT datesent,
2639                frombranch,
2640                tobranch
2641         FROM branchtransfers
2642         WHERE itemnumber = ?
2643           AND datearrived IS NULL
2644         ';
2645     my $sth = $dbh->prepare($query);
2646     $sth->execute($itemnumber);
2647     my @row = $sth->fetchrow_array();
2648     $sth->finish;
2649     return @row;
2650 }
2651
2652 =head2 GetTransfersFromTo
2653
2654   @results = GetTransfersFromTo($frombranch,$tobranch);
2655
2656 Returns the list of pending transfers between $from and $to branch
2657
2658 =cut
2659
2660 sub GetTransfersFromTo {
2661     my ( $frombranch, $tobranch ) = @_;
2662     return unless ( $frombranch && $tobranch );
2663     my $dbh   = C4::Context->dbh;
2664     my $query = "
2665         SELECT itemnumber,datesent,frombranch
2666         FROM   branchtransfers
2667         WHERE  frombranch=?
2668           AND  tobranch=?
2669           AND datearrived IS NULL
2670     ";
2671     my $sth = $dbh->prepare($query);
2672     $sth->execute( $frombranch, $tobranch );
2673     my @gettransfers;
2674
2675     while ( my $data = $sth->fetchrow_hashref ) {
2676         push @gettransfers, $data;
2677     }
2678     $sth->finish;
2679     return (@gettransfers);
2680 }
2681
2682 =head2 DeleteTransfer
2683
2684   &DeleteTransfer($itemnumber);
2685
2686 =cut
2687
2688 sub DeleteTransfer {
2689     my ($itemnumber) = @_;
2690     my $dbh          = C4::Context->dbh;
2691     my $sth          = $dbh->prepare(
2692         "DELETE FROM branchtransfers
2693          WHERE itemnumber=?
2694          AND datearrived IS NULL "
2695     );
2696     $sth->execute($itemnumber);
2697     $sth->finish;
2698 }
2699
2700 =head2 AnonymiseIssueHistory
2701
2702   $rows = AnonymiseIssueHistory($date,$borrowernumber)
2703
2704 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2705 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2706
2707 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
2708 setting (force delete).
2709
2710 return the number of affected rows.
2711
2712 =cut
2713
2714 sub AnonymiseIssueHistory {
2715     my $date           = shift;
2716     my $borrowernumber = shift;
2717     my $dbh            = C4::Context->dbh;
2718     my $query          = "
2719         UPDATE old_issues
2720         SET    borrowernumber = ?
2721         WHERE  returndate < ?
2722           AND borrowernumber IS NOT NULL
2723     ";
2724
2725     # The default of 0 does not work due to foreign key constraints
2726     # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
2727     my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
2728     my @bind_params = ($anonymouspatron, $date);
2729     if (defined $borrowernumber) {
2730        $query .= " AND borrowernumber = ?";
2731        push @bind_params, $borrowernumber;
2732     } else {
2733        $query .= " AND (SELECT privacy FROM borrowers WHERE borrowers.borrowernumber=old_issues.borrowernumber) <> 0";
2734     }
2735     my $sth = $dbh->prepare($query);
2736     $sth->execute(@bind_params);
2737     my $rows_affected = $sth->rows;  ### doublecheck row count return function
2738     return $rows_affected;
2739 }
2740
2741 =head2 SendCirculationAlert
2742
2743 Send out a C<check-in> or C<checkout> alert using the messaging system.
2744
2745 B<Parameters>:
2746
2747 =over 4
2748
2749 =item type
2750
2751 Valid values for this parameter are: C<CHECKIN> and C<CHECKOUT>.
2752
2753 =item item
2754
2755 Hashref of information about the item being checked in or out.
2756
2757 =item borrower
2758
2759 Hashref of information about the borrower of the item.
2760
2761 =item branch
2762
2763 The branchcode from where the checkout or check-in took place.
2764
2765 =back
2766
2767 B<Example>:
2768
2769     SendCirculationAlert({
2770         type     => 'CHECKOUT',
2771         item     => $item,
2772         borrower => $borrower,
2773         branch   => $branch,
2774     });
2775
2776 =cut
2777
2778 sub SendCirculationAlert {
2779     my ($opts) = @_;
2780     my ($type, $item, $borrower, $branch) =
2781         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
2782     my %message_name = (
2783         CHECKIN  => 'Item_Check_in',
2784         CHECKOUT => 'Item_Checkout',
2785     );
2786     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
2787         borrowernumber => $borrower->{borrowernumber},
2788         message_name   => $message_name{$type},
2789     });
2790     my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
2791     my $letter =  C4::Letters::GetPreparedLetter (
2792         module => 'circulation',
2793         letter_code => $type,
2794         branchcode => $branch,
2795         tables => {
2796             $issues_table => $item->{itemnumber},
2797             'items'       => $item->{itemnumber},
2798             'biblio'      => $item->{biblionumber},
2799             'biblioitems' => $item->{biblionumber},
2800             'borrowers'   => $borrower,
2801             'branches'    => $branch,
2802         }
2803     ) or return;
2804
2805     my @transports = keys %{ $borrower_preferences->{transports} };
2806     # warn "no transports" unless @transports;
2807     for (@transports) {
2808         # warn "transport: $_";
2809         my $message = C4::Message->find_last_message($borrower, $type, $_);
2810         if (!$message) {
2811             #warn "create new message";
2812             C4::Message->enqueue($letter, $borrower, $_);
2813         } else {
2814             #warn "append to old message";
2815             $message->append($letter);
2816             $message->update;
2817         }
2818     }
2819
2820     return $letter;
2821 }
2822
2823 =head2 updateWrongTransfer
2824
2825   $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2826
2827 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 
2828
2829 =cut
2830
2831 sub updateWrongTransfer {
2832         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2833         my $dbh = C4::Context->dbh;     
2834 # first step validate the actual line of transfert .
2835         my $sth =
2836                 $dbh->prepare(
2837                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2838                 );
2839                 $sth->execute($FromLibrary,$itemNumber);
2840                 $sth->finish;
2841
2842 # second step create a new line of branchtransfer to the right location .
2843         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2844
2845 #third step changing holdingbranch of item
2846         UpdateHoldingbranch($FromLibrary,$itemNumber);
2847 }
2848
2849 =head2 UpdateHoldingbranch
2850
2851   $items = UpdateHoldingbranch($branch,$itmenumber);
2852
2853 Simple methode for updating hodlingbranch in items BDD line
2854
2855 =cut
2856
2857 sub UpdateHoldingbranch {
2858         my ( $branch,$itemnumber ) = @_;
2859     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2860 }
2861
2862 =head2 CalcDateDue
2863
2864 $newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower);
2865
2866 this function calculates the due date given the start date and configured circulation rules,
2867 checking against the holidays calendar as per the 'useDaysMode' syspref.
2868 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2869 C<$itemtype>  = itemtype code of item in question
2870 C<$branch>  = location whose calendar to use
2871 C<$borrower> = Borrower object
2872
2873 =cut
2874
2875 sub CalcDateDue {
2876     my ( $startdate, $itemtype, $branch, $borrower ) = @_;
2877
2878     # loanlength now a href
2879     my $loanlength =
2880       GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
2881
2882     my $datedue;
2883
2884     # if globalDueDate ON the datedue is set to that date
2885     if (C4::Context->preference('globalDueDate')
2886         && ( C4::Context->preference('globalDueDate') =~
2887             C4::Dates->regexp('syspref') )
2888       ) {
2889         $datedue = dt_from_string(
2890             C4::Context->preference('globalDueDate'),
2891             C4::Context->preference('dateformat')
2892         );
2893     } else {
2894
2895         # otherwise, calculate the datedue as normal
2896         if ( C4::Context->preference('useDaysMode') eq 'Days' )
2897         {    # ignoring calendar
2898             my $dt =
2899               DateTime->now( time_zone => C4::Context->tz() )
2900               ->truncate( to => 'minute' );
2901             if ( $loanlength->{lengthunit} eq 'hours' ) {
2902                 $dt->add( hours => $loanlength->{issuelength} );
2903                 return $dt;
2904             } else {    # days
2905                 $dt->add( days => $loanlength->{issuelength} );
2906                 $dt->set_hour(23);
2907                 $dt->set_minute(59);
2908                 return $dt;
2909             }
2910         } else {
2911             my $dur;
2912             if ($loanlength->{lengthunit} eq 'hours') {
2913                 $dur = DateTime::Duration->new( hours => $loanlength->{issuelength});
2914             }
2915             else { # days
2916                 $dur = DateTime::Duration->new( days => $loanlength->{issuelength});
2917             }
2918             if (ref $startdate ne 'DateTime' ) {
2919                 $startdate = dt_from_string($startdate);
2920             }
2921             my $calendar = Koha::Calendar->new( branchcode => $branch );
2922             $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} );
2923             if ($loanlength->{lengthunit} eq 'days') {
2924                 $datedue->set_hour(23);
2925                 $datedue->set_minute(59);
2926             }
2927         }
2928     }
2929
2930     # if Hard Due Dates are used, retreive them and apply as necessary
2931     my ( $hardduedate, $hardduedatecompare ) =
2932       GetHardDueDate( $borrower->{'categorycode'}, $itemtype, $branch );
2933     if ($hardduedate) {    # hardduedates are currently dates
2934         $hardduedate->truncate( to => 'minute' );
2935         $hardduedate->set_hour(23);
2936         $hardduedate->set_minute(59);
2937         my $cmp = DateTime->compare( $hardduedate, $datedue );
2938
2939 # if the calculated due date is after the 'before' Hard Due Date (ceiling), override
2940 # if the calculated date is before the 'after' Hard Due Date (floor), override
2941 # if the hard due date is set to 'exactly', overrride
2942         if ( $hardduedatecompare == 0 || $hardduedatecompare == $cmp ) {
2943             $datedue = $hardduedate->clone;
2944         }
2945
2946         # in all other cases, keep the date due as it is
2947     }
2948
2949     # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
2950     if ( C4::Context->preference('ReturnBeforeExpiry') ) {
2951         my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' );
2952         if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) {
2953             $datedue = $expiry_dt->clone;
2954         }
2955     }
2956
2957     return $datedue;
2958 }
2959
2960
2961 =head2 CheckRepeatableHolidays
2962
2963   $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2964
2965 This function checks if the date due is a repeatable holiday
2966
2967 C<$date_due>   = returndate calculate with no day check
2968 C<$itemnumber>  = itemnumber
2969 C<$branchcode>  = localisation of issue 
2970
2971 =cut
2972
2973 sub CheckRepeatableHolidays{
2974 my($itemnumber,$week_day,$branchcode)=@_;
2975 my $dbh = C4::Context->dbh;
2976 my $query = qq|SELECT count(*)  
2977         FROM repeatable_holidays 
2978         WHERE branchcode=?
2979         AND weekday=?|;
2980 my $sth = $dbh->prepare($query);
2981 $sth->execute($branchcode,$week_day);
2982 my $result=$sth->fetchrow;
2983 $sth->finish;
2984 return $result;
2985 }
2986
2987
2988 =head2 CheckSpecialHolidays
2989
2990   $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2991
2992 This function check if the date is a special holiday
2993
2994 C<$years>   = the years of datedue
2995 C<$month>   = the month of datedue
2996 C<$day>     = the day of datedue
2997 C<$itemnumber>  = itemnumber
2998 C<$branchcode>  = localisation of issue 
2999
3000 =cut
3001
3002 sub CheckSpecialHolidays{
3003 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
3004 my $dbh = C4::Context->dbh;
3005 my $query=qq|SELECT count(*) 
3006              FROM `special_holidays`
3007              WHERE year=?
3008              AND month=?
3009              AND day=?
3010              AND branchcode=?
3011             |;
3012 my $sth = $dbh->prepare($query);
3013 $sth->execute($years,$month,$day,$branchcode);
3014 my $countspecial=$sth->fetchrow ;
3015 $sth->finish;
3016 return $countspecial;
3017 }
3018
3019 =head2 CheckRepeatableSpecialHolidays
3020
3021   $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
3022
3023 This function check if the date is a repeatble special holidays
3024
3025 C<$month>   = the month of datedue
3026 C<$day>     = the day of datedue
3027 C<$itemnumber>  = itemnumber
3028 C<$branchcode>  = localisation of issue 
3029
3030 =cut
3031
3032 sub CheckRepeatableSpecialHolidays{
3033 my ($month,$day,$itemnumber,$branchcode) = @_;
3034 my $dbh = C4::Context->dbh;
3035 my $query=qq|SELECT count(*) 
3036              FROM `repeatable_holidays`
3037              WHERE month=?
3038              AND day=?
3039              AND branchcode=?
3040             |;
3041 my $sth = $dbh->prepare($query);
3042 $sth->execute($month,$day,$branchcode);
3043 my $countspecial=$sth->fetchrow ;
3044 $sth->finish;
3045 return $countspecial;
3046 }
3047
3048
3049
3050 sub CheckValidBarcode{
3051 my ($barcode) = @_;
3052 my $dbh = C4::Context->dbh;
3053 my $query=qq|SELECT count(*) 
3054              FROM items 
3055              WHERE barcode=?
3056             |;
3057 my $sth = $dbh->prepare($query);
3058 $sth->execute($barcode);
3059 my $exist=$sth->fetchrow ;
3060 $sth->finish;
3061 return $exist;
3062 }
3063
3064 =head2 IsBranchTransferAllowed
3065
3066   $allowed = IsBranchTransferAllowed( $toBranch, $fromBranch, $code );
3067
3068 Code is either an itemtype or collection doe depending on the pref BranchTransferLimitsType
3069
3070 =cut
3071
3072 sub IsBranchTransferAllowed {
3073         my ( $toBranch, $fromBranch, $code ) = @_;
3074
3075         if ( $toBranch eq $fromBranch ) { return 1; } ## Short circuit for speed.
3076         
3077         my $limitType = C4::Context->preference("BranchTransferLimitsType");   
3078         my $dbh = C4::Context->dbh;
3079             
3080         my $sth = $dbh->prepare("SELECT * FROM branch_transfer_limits WHERE toBranch = ? AND fromBranch = ? AND $limitType = ?");
3081         $sth->execute( $toBranch, $fromBranch, $code );
3082         my $limit = $sth->fetchrow_hashref();
3083                         
3084         ## If a row is found, then that combination is not allowed, if no matching row is found, then the combination *is allowed*
3085         if ( $limit->{'limitId'} ) {
3086                 return 0;
3087         } else {
3088                 return 1;
3089         }
3090 }                                                        
3091
3092 =head2 CreateBranchTransferLimit
3093
3094   CreateBranchTransferLimit( $toBranch, $fromBranch, $code );
3095
3096 $code is either itemtype or collection code depending on what the pref BranchTransferLimitsType is set to.
3097
3098 =cut
3099
3100 sub CreateBranchTransferLimit {
3101    my ( $toBranch, $fromBranch, $code ) = @_;
3102
3103    my $limitType = C4::Context->preference("BranchTransferLimitsType");
3104    
3105    my $dbh = C4::Context->dbh;
3106    
3107    my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
3108    $sth->execute( $code, $toBranch, $fromBranch );
3109 }
3110
3111 =head2 DeleteBranchTransferLimits
3112
3113 DeleteBranchTransferLimits($frombranch);
3114
3115 Deletes all the branch transfer limits for one branch
3116
3117 =cut
3118
3119 sub DeleteBranchTransferLimits {
3120     my $branch = shift;
3121     my $dbh    = C4::Context->dbh;
3122     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
3123     $sth->execute($branch);
3124 }
3125
3126 sub ReturnLostItem{
3127     my ( $borrowernumber, $itemnum ) = @_;
3128
3129     MarkIssueReturned( $borrowernumber, $itemnum );
3130     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
3131     my $item = C4::Items::GetItem( $itemnum );
3132     my $old_note = ($item->{'paidfor'} && ($item->{'paidfor'} ne q{})) ? $item->{'paidfor'}.' / ' : q{};
3133     my @datearr = localtime(time);
3134     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
3135     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
3136     ModItem({ paidfor =>  $old_note."Paid for by $bor $date" }, undef, $itemnum);
3137 }
3138
3139
3140 sub LostItem{
3141     my ($itemnumber, $mark_returned, $charge_fee) = @_;
3142
3143     my $dbh = C4::Context->dbh();
3144     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
3145                            FROM issues 
3146                            JOIN items USING (itemnumber) 
3147                            JOIN biblio USING (biblionumber)
3148                            WHERE issues.itemnumber=?");
3149     $sth->execute($itemnumber);
3150     my $issues=$sth->fetchrow_hashref();
3151     $sth->finish;
3152
3153     # if a borrower lost the item, add a replacement cost to the their record
3154     if ( my $borrowernumber = $issues->{borrowernumber} ){
3155         my $borrower = C4::Members::GetMemberDetails( $borrowernumber );
3156
3157         C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
3158           if $charge_fee;
3159         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
3160         #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
3161         MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
3162     }
3163 }
3164
3165 sub GetOfflineOperations {
3166     my $dbh = C4::Context->dbh;
3167     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
3168     $sth->execute(C4::Context->userenv->{'branch'});
3169     my $results = $sth->fetchall_arrayref({});
3170     $sth->finish;
3171     return $results;
3172 }
3173
3174 sub GetOfflineOperation {
3175     my $dbh = C4::Context->dbh;
3176     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
3177     $sth->execute( shift );
3178     my $result = $sth->fetchrow_hashref;
3179     $sth->finish;
3180     return $result;
3181 }
3182
3183 sub AddOfflineOperation {
3184     my $dbh = C4::Context->dbh;
3185     my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
3186     $sth->execute( @_ );
3187     return "Added.";
3188 }
3189
3190 sub DeleteOfflineOperation {
3191     my $dbh = C4::Context->dbh;
3192     my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?");
3193     $sth->execute( shift );
3194     return "Deleted.";
3195 }
3196
3197 sub ProcessOfflineOperation {
3198     my $operation = shift;
3199
3200     my $report;
3201     if ( $operation->{action} eq 'return' ) {
3202         $report = ProcessOfflineReturn( $operation );
3203     } elsif ( $operation->{action} eq 'issue' ) {
3204         $report = ProcessOfflineIssue( $operation );
3205     }
3206
3207     DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
3208
3209     return $report;
3210 }
3211
3212 sub ProcessOfflineReturn {
3213     my $operation = shift;
3214
3215     my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3216
3217     if ( $itemnumber ) {
3218         my $issue = GetOpenIssue( $itemnumber );
3219         if ( $issue ) {
3220             MarkIssueReturned(
3221                 $issue->{borrowernumber},
3222                 $itemnumber,
3223                 undef,
3224                 $operation->{timestamp},
3225             );
3226             ModItem(
3227                 { renewals => 0, onloan => undef },
3228                 $issue->{'biblionumber'},
3229                 $itemnumber
3230             );
3231             return "Success.";
3232         } else {
3233             return "Item not issued.";
3234         }
3235     } else {
3236         return "Item not found.";
3237     }
3238 }
3239
3240 sub ProcessOfflineIssue {
3241     my $operation = shift;
3242
3243     my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
3244
3245     if ( $borrower->{borrowernumber} ) {
3246         my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3247         unless ($itemnumber) {
3248             return "Barcode not found.";
3249         }
3250         my $issue = GetOpenIssue( $itemnumber );
3251
3252         if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned
3253             MarkIssueReturned(
3254                 $issue->{borrowernumber},
3255                 $itemnumber,
3256                 undef,
3257                 $operation->{timestamp},
3258             );
3259         }
3260         AddIssue(
3261             $borrower,
3262             $operation->{'barcode'},
3263             undef,
3264             1,
3265             $operation->{timestamp},
3266             undef,
3267         );
3268         return "Success.";
3269     } else {
3270         return "Borrower not found.";
3271     }
3272 }
3273
3274
3275
3276 =head2 TransferSlip
3277
3278   TransferSlip($user_branch, $itemnumber, $to_branch)
3279
3280   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
3281
3282 =cut
3283
3284 sub TransferSlip {
3285     my ($branch, $itemnumber, $to_branch) = @_;
3286
3287     my $item =  GetItem( $itemnumber )
3288       or return;
3289
3290     my $pulldate = C4::Dates->new();
3291
3292     return C4::Letters::GetPreparedLetter (
3293         module => 'circulation',
3294         letter_code => 'TRANSFERSLIP',
3295         branchcode => $branch,
3296         tables => {
3297             'branches'    => $to_branch,
3298             'biblio'      => $item->{biblionumber},
3299             'items'       => $item,
3300         },
3301     );
3302 }
3303
3304
3305 1;
3306
3307 __END__
3308
3309 =head1 AUTHOR
3310
3311 Koha Development Team <http://koha-community.org/>
3312
3313 =cut
3314