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