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