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