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