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