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