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