(bug #3778) GetBranchesLoop and searchMyLibraryFirst
[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         # find the borrower
1389         if ( not $iteminformation->{borrowernumber} ) {
1390             $messages->{'NotIssued'} = $barcode;
1391             $doreturn = 0;
1392         }
1393         
1394         # even though item is not on loan, it may still
1395         # be transferred; therefore, get current branch information
1396         my $curr_iteminfo = GetItem($itemnumber);
1397         $iteminformation->{'homebranch'} = $curr_iteminfo->{'homebranch'};
1398         $iteminformation->{'holdingbranch'} = $curr_iteminfo->{'holdingbranch'};
1399         $iteminformation->{'itemlost'} = $curr_iteminfo->{'itemlost'};
1400         
1401         # check if the book is in a permanent collection....
1402         my $hbr      = $iteminformation->{C4::Context->preference("HomeOrHoldingBranch")};
1403         my $branches = GetBranches();
1404                 # FIXME -- This 'PE' attribute is largely undocumented.  afaict, there's no user interface that reflects this functionality.
1405         if ( $hbr && $branches->{$hbr}->{'PE'} ) {
1406             $messages->{'IsPermanent'} = $hbr;
1407         }
1408                 
1409                     # if independent branches are on and returning to different branch, refuse the return
1410         if ($hbr ne $branch && C4::Context->preference("IndependantBranches") && $iteminformation->{borrowernumber}){
1411                           $messages->{'Wrongbranch'} = 1;
1412                           $doreturn=0;
1413                     }
1414                         
1415         # check that the book has been cancelled
1416         if ( $iteminformation->{'wthdrawn'} ) {
1417             $messages->{'wthdrawn'} = 1;
1418             $doreturn = 0;
1419         }
1420     
1421
1422     #     new op dev : if the book returned in an other branch update the holding branch
1423     
1424     # update issues, thereby returning book (should push this out into another subroutine
1425         $borrower = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1426     
1427     # case of a return of document (deal with issues and holdingbranch)
1428     
1429         if ($doreturn) {
1430                         my $circControlBranch = _GetCircControlBranch($iteminformation,$borrower);
1431                         if($dropbox) {
1432                                 # don't allow dropbox mode to create an invalid entry in issues (issuedate > returndate) FIXME: actually checks eq, not gt
1433                                 undef($dropbox) if ( $iteminformation->{'issuedate'} eq C4::Dates->today('iso') );
1434                         }
1435             MarkIssueReturned($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'},$circControlBranch);
1436             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  
1437             # continue to deal with returns cases, but not only if we have an issue
1438             
1439             
1440             # We update the holdingbranch from circControlBranch variable
1441             UpdateHoldingbranch($branch,$iteminformation->{'itemnumber'});
1442             $iteminformation->{'holdingbranch'} = $branch;
1443         
1444             
1445             ModDateLastSeen( $iteminformation->{'itemnumber'} );
1446             ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
1447
1448             if ($iteminformation->{borrowernumber}){
1449               ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1450             }
1451         }
1452     
1453     # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1454     #     check if we have a transfer for this document
1455         my ($datesent,$frombranch,$tobranch) = GetTransfers( $iteminformation->{'itemnumber'} );
1456     
1457     #     if we have a transfer to do, we update the line of transfers with the datearrived
1458         if ($datesent) {
1459             if ( $tobranch eq $branch ) {
1460                     my $sth =
1461                     $dbh->prepare(
1462                             "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1463                     );
1464                     $sth->execute( $iteminformation->{'itemnumber'} );
1465                     $sth->finish;
1466     #         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'
1467             C4::Reserves::ModReserveStatus( $iteminformation->{'itemnumber'},'W' );
1468             }
1469         else {
1470             $messages->{'WrongTransfer'} = $tobranch;
1471             $messages->{'WrongTransferItem'} = $iteminformation->{'itemnumber'};
1472         }
1473         $validTransfert = 1;
1474         }
1475     
1476     # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
1477         # fix up the accounts.....
1478         if ($iteminformation->{'itemlost'}) {
1479                 FixAccountForLostAndReturned($iteminformation, $borrower);
1480                 ModItem({ itemlost => '0' }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
1481                 $messages->{'WasLost'} = 1;
1482         }
1483         # fix up the overdues in accounts...
1484         FixOverduesOnReturn( $borrower->{'borrowernumber'},
1485             $iteminformation->{'itemnumber'}, $exemptfine, $dropbox );
1486     
1487     # find reserves.....
1488     #     if we don't have a reserve with the status W, we launch the Checkreserves routine
1489         my ( $resfound, $resrec ) = 
1490         C4::Reserves::CheckReserves( $itemnumber, $barcode );
1491         if ($resfound) {
1492             $resrec->{'ResFound'}   = $resfound;
1493             $messages->{'ResFound'} = $resrec;
1494             $reserveDone = 1;
1495         }
1496     
1497         # update stats?
1498         # Record the fact that this book was returned.
1499         UpdateStats(
1500             $branch, 'return', '0', '',
1501             $iteminformation->{'itemnumber'},
1502             $biblio->{'itemtype'},
1503             $borrower->{'borrowernumber'}
1504         );
1505         
1506         logaction("CIRCULATION", "RETURN", $iteminformation->{borrowernumber}, $iteminformation->{'biblionumber'}) 
1507             if C4::Context->preference("ReturnLog");
1508         
1509         #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1510         #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1511         if (($doreturn or $messages->{'NotIssued'}) 
1512             and ($branch ne $hbr) 
1513             and not $messages->{'WrongTransfer'} 
1514             and ($validTransfert ne 1) 
1515             and ($reserveDone ne 1) ){
1516                         if (C4::Context->preference("AutomaticItemReturn") == 1) {
1517                                 ModItemTransfer($iteminformation->{'itemnumber'}, $branch, $iteminformation->{$hbr});
1518                                 $messages->{'WasTransfered'} = 1;
1519                         }
1520                         else {
1521                                 $messages->{'NeedsTransfer'} = 1;
1522                         }
1523         }
1524     }
1525     return ( $doreturn, $messages, $iteminformation, $borrower );
1526 }
1527
1528 =head2 MarkIssueReturned
1529
1530 =over 4
1531
1532 MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate);
1533
1534 =back
1535
1536 Unconditionally marks an issue as being returned by
1537 moving the C<issues> row to C<old_issues> and
1538 setting C<returndate> to the current date, or
1539 the last non-holiday date of the branccode specified in
1540 C<dropbox_branch> .  Assumes you've already checked that 
1541 it's safe to do this, i.e. last non-holiday > issuedate.
1542
1543 if C<$returndate> is specified (in iso format), it is used as the date
1544 of the return. It is ignored when a dropbox_branch is passed in.
1545
1546 Ideally, this function would be internal to C<C4::Circulation>,
1547 not exported, but it is currently needed by one 
1548 routine in C<C4::Accounts>.
1549
1550 =cut
1551
1552 sub MarkIssueReturned {
1553     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate ) = @_;
1554     my $dbh   = C4::Context->dbh;
1555     my $query = "UPDATE issues SET returndate=";
1556     my @bind;
1557     if ($dropbox_branch) {
1558         my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
1559         my $dropboxdate = $calendar->addDate( C4::Dates->new(), -1 );
1560         $query .= " ? ";
1561         push @bind, $dropboxdate->output('iso');
1562     } elsif ($returndate) {
1563         $query .= " ? ";
1564         push @bind, $returndate;
1565     } else {
1566         $query .= " now() ";
1567     }
1568     $query .= " WHERE  borrowernumber = ?  AND itemnumber = ?";
1569     push @bind, $borrowernumber, $itemnumber;
1570     # FIXME transaction
1571     my $sth_upd  = $dbh->prepare($query);
1572     $sth_upd->execute(@bind);
1573     my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues 
1574                                   WHERE borrowernumber = ?
1575                                   AND itemnumber = ?");
1576     $sth_copy->execute($borrowernumber, $itemnumber);
1577     my $sth_del  = $dbh->prepare("DELETE FROM issues
1578                                   WHERE borrowernumber = ?
1579                                   AND itemnumber = ?");
1580     $sth_del->execute($borrowernumber, $itemnumber);
1581 }
1582
1583 =head2 FixOverduesOnReturn
1584
1585     &FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1586
1587 C<$brn> borrowernumber
1588
1589 C<$itm> itemnumber
1590
1591 C<$exemptfine> BOOL -- remove overdue charge associated with this issue. 
1592 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1593
1594 internal function, called only by AddReturn
1595
1596 =cut
1597
1598 sub FixOverduesOnReturn {
1599     my ( $borrowernumber, $item, $exemptfine, $dropbox ) = @_;
1600     my $dbh = C4::Context->dbh;
1601
1602     # check for overdue fine
1603     my $sth =
1604       $dbh->prepare(
1605 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1606       );
1607     $sth->execute( $borrowernumber, $item );
1608
1609     # alter fine to show that the book has been returned
1610    my $data; 
1611         if ($data = $sth->fetchrow_hashref) {
1612         my $uquery;
1613                 my @bind = ($borrowernumber,$item ,$data->{'accountno'});
1614                 if ($exemptfine) {
1615                         $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1616                         if (C4::Context->preference("FinesLog")) {
1617                         &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1618                         }
1619                 } elsif ($dropbox && $data->{lastincrement}) {
1620                         my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1621                         my $amt = $data->{amount} - $data->{lastincrement} ;
1622                         if (C4::Context->preference("FinesLog")) {
1623                         &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1624                         }
1625                          $uquery = "update accountlines set accounttype='F' ";
1626                          if($outstanding  >= 0 && $amt >=0) {
1627                                 $uquery .= ", amount = ? , amountoutstanding=? ";
1628                                 unshift @bind, ($amt, $outstanding) ;
1629                         }
1630                 } else {
1631                         $uquery = "update accountlines set accounttype='F' ";
1632                 }
1633                 $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1634         my $usth = $dbh->prepare($uquery);
1635         $usth->execute(@bind);
1636         $usth->finish();
1637     }
1638
1639     $sth->finish();
1640     return;
1641 }
1642
1643 =head2 FixAccountForLostAndReturned
1644
1645         &FixAccountForLostAndReturned($iteminfo,$borrower);
1646
1647 Calculates the charge for a book lost and returned (Not exported & used only once)
1648
1649 C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used.
1650
1651 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1652
1653 Internal function, called by AddReturn
1654
1655 =cut
1656
1657 sub FixAccountForLostAndReturned {
1658         my ($iteminfo, $borrower) = @_;
1659         my $dbh = C4::Context->dbh;
1660         my $itm = $iteminfo->{'itemnumber'};
1661         # check for charge made for lost book
1662         my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
1663         $sth->execute($itm);
1664         if (my $data = $sth->fetchrow_hashref) {
1665         # writeoff this amount
1666                 my $offset;
1667                 my $amount = $data->{'amount'};
1668                 my $acctno = $data->{'accountno'};
1669                 my $amountleft;
1670                 if ($data->{'amountoutstanding'} == $amount) {
1671                 $offset = $data->{'amount'};
1672                 $amountleft = 0;
1673                 } else {
1674                 $offset = $amount - $data->{'amountoutstanding'};
1675                 $amountleft = $data->{'amountoutstanding'} - $amount;
1676                 }
1677                 my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
1678                         WHERE (borrowernumber = ?)
1679                         AND (itemnumber = ?) AND (accountno = ?) ");
1680                 $usth->execute($data->{'borrowernumber'},$itm,$acctno);
1681         #check if any credit is left if so writeoff other accounts
1682                 my $nextaccntno = getnextacctno($data->{'borrowernumber'});
1683                 if ($amountleft < 0){
1684                 $amountleft*=-1;
1685                 }
1686                 if ($amountleft > 0){
1687                 my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
1688                                                         AND (amountoutstanding >0) ORDER BY date");
1689                 $msth->execute($data->{'borrowernumber'});
1690         # offset transactions
1691                 my $newamtos;
1692                 my $accdata;
1693                 while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1694                         if ($accdata->{'amountoutstanding'} < $amountleft) {
1695                         $newamtos = 0;
1696                         $amountleft -= $accdata->{'amountoutstanding'};
1697                         }  else {
1698                         $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1699                         $amountleft = 0;
1700                         }
1701                         my $thisacct = $accdata->{'accountno'};
1702                         my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
1703                                         WHERE (borrowernumber = ?)
1704                                         AND (accountno=?)");
1705                         $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');
1706                         $usth->finish;
1707                         $usth = $dbh->prepare("INSERT INTO accountoffsets
1708                                 (borrowernumber, accountno, offsetaccount,  offsetamount)
1709                                 VALUES
1710                                 (?,?,?,?)");
1711                         $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1712                 }
1713                 $msth->finish;  # $msth might actually have data left
1714                 }
1715                 if ($amountleft > 0){
1716                         $amountleft*=-1;
1717                 }
1718                 my $desc="Item Returned ".$iteminfo->{'barcode'};
1719                 $usth = $dbh->prepare("INSERT INTO accountlines
1720                         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1721                         VALUES (?,?,now(),?,?,'CR',?)");
1722                 $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1723                 $usth = $dbh->prepare("INSERT INTO accountoffsets
1724                         (borrowernumber, accountno, offsetaccount,  offsetamount)
1725                         VALUES (?,?,?,?)");
1726                 $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset);
1727         ModItem({ paidfor => '' }, undef, $itm);
1728         }
1729         $sth->finish;
1730         return;
1731 }
1732
1733 =head2 _GetCircControlBranch
1734
1735    my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower);
1736
1737 Internal function : 
1738
1739 Return the library code to be used to determine which circulation
1740 policy applies to a transaction.  Looks up the CircControl and
1741 HomeOrHoldingBranch system preferences.
1742
1743 C<$iteminfos> is a hashref to iteminfo. Only {itemnumber} is used.
1744
1745 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1746
1747 =cut
1748
1749 sub _GetCircControlBranch {
1750     my ($iteminfos, $borrower) = @_;
1751     my $circcontrol = C4::Context->preference('CircControl');
1752     my $branch;
1753
1754     if ($circcontrol eq 'PickupLibrary') {
1755         $branch= C4::Context->userenv->{'branch'};
1756     } elsif ($circcontrol eq 'PatronLibrary') {
1757         $branch=$borrower->{branchcode};
1758     } else {
1759         my $branchfield = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
1760         $branch = $iteminfos->{$branchfield};
1761     }
1762     return $branch;
1763 }
1764
1765 =head2 GetItemIssue
1766
1767 $issues = &GetItemIssue($itemnumber);
1768
1769 Returns patron currently having a book, or undef if not checked out.
1770
1771 C<$itemnumber> is the itemnumber
1772
1773 C<$issues> is an array of hashes.
1774
1775 =cut
1776
1777 sub GetItemIssue {
1778     my ($itemnumber) = @_;
1779     return unless $itemnumber;
1780     my $sth = C4::Context->dbh->prepare(
1781         "SELECT *
1782         FROM issues 
1783         LEFT JOIN items ON issues.itemnumber=items.itemnumber
1784         WHERE issues.itemnumber=?");
1785     $sth->execute($itemnumber);
1786     my $data = $sth->fetchrow_hashref;
1787     return unless $data;
1788     $data->{'overdue'} = ($data->{'date_due'} lt C4::Dates->today('iso')) ? 1 : 0;
1789     $data->{'itemnumber'} = $itemnumber; # fill itemnumber, in case item is not on issue.
1790     # FIXME: that would mean issues.itemnumber IS NULL and we didn't really match it.
1791     return ($data);
1792 }
1793
1794 =head2 GetItemIssues
1795
1796 $issues = &GetItemIssues($itemnumber, $history);
1797
1798 Returns patrons that have issued a book
1799
1800 C<$itemnumber> is the itemnumber
1801 C<$history> is false if you just want the current "issuer" (if any)
1802 and true if you want issues history from old_issues also.
1803
1804 Returns reference to an array of hashes
1805
1806 =cut
1807
1808 sub GetItemIssues {
1809     my ( $itemnumber, $history ) = @_;
1810     
1811     my $today = C4::Dates->today('iso');  # get today date
1812     my $sql = "SELECT * FROM issues 
1813               JOIN borrowers USING (borrowernumber)
1814               JOIN items     USING (itemnumber)
1815               WHERE issues.itemnumber = ? ";
1816     if ($history) {
1817         $sql .= "UNION ALL
1818                  SELECT * FROM old_issues 
1819                  LEFT JOIN borrowers USING (borrowernumber)
1820                  JOIN items USING (itemnumber)
1821                  WHERE old_issues.itemnumber = ? ";
1822     }
1823     $sql .= "ORDER BY date_due DESC";
1824     my $sth = C4::Context->dbh->prepare($sql);
1825     if ($history) {
1826         $sth->execute($itemnumber, $itemnumber);
1827     } else {
1828         $sth->execute($itemnumber);
1829     }
1830     my $results = $sth->fetchall_arrayref({});
1831     foreach (@$results) {
1832         $_->{'overdue'} = ($_->{'date_due'} lt $today) ? 1 : 0;
1833     }
1834     return $results;
1835 }
1836
1837 =head2 GetBiblioIssues
1838
1839 $issues = GetBiblioIssues($biblionumber);
1840
1841 this function get all issues from a biblionumber.
1842
1843 Return:
1844 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
1845 tables issues and the firstname,surname & cardnumber from borrowers.
1846
1847 =cut
1848
1849 sub GetBiblioIssues {
1850     my $biblionumber = shift;
1851     return undef unless $biblionumber;
1852     my $dbh   = C4::Context->dbh;
1853     my $query = "
1854         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1855         FROM issues
1856             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
1857             LEFT JOIN items ON issues.itemnumber = items.itemnumber
1858             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1859             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1860         WHERE biblio.biblionumber = ?
1861         UNION ALL
1862         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1863         FROM old_issues
1864             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
1865             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
1866             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1867             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1868         WHERE biblio.biblionumber = ?
1869         ORDER BY timestamp
1870     ";
1871     my $sth = $dbh->prepare($query);
1872     $sth->execute($biblionumber, $biblionumber);
1873
1874     my @issues;
1875     while ( my $data = $sth->fetchrow_hashref ) {
1876         push @issues, $data;
1877     }
1878     return \@issues;
1879 }
1880
1881 =head2 GetUpcomingDueIssues
1882
1883 =over 4
1884  
1885 my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
1886
1887 =back
1888
1889 =cut
1890
1891 sub GetUpcomingDueIssues {
1892     my $params = shift;
1893
1894     $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
1895     my $dbh = C4::Context->dbh;
1896
1897     my $statement = <<END_SQL;
1898 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due
1899 FROM issues 
1900 LEFT JOIN items USING (itemnumber)
1901 WhERE returndate is NULL
1902 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
1903 END_SQL
1904
1905     my @bind_parameters = ( $params->{'days_in_advance'} );
1906     
1907     my $sth = $dbh->prepare( $statement );
1908     $sth->execute( @bind_parameters );
1909     my $upcoming_dues = $sth->fetchall_arrayref({});
1910     $sth->finish;
1911
1912     return $upcoming_dues;
1913 }
1914
1915 =head2 CanBookBeRenewed
1916
1917 ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
1918
1919 Find out whether a borrowed item may be renewed.
1920
1921 C<$dbh> is a DBI handle to the Koha database.
1922
1923 C<$borrowernumber> is the borrower number of the patron who currently
1924 has the item on loan.
1925
1926 C<$itemnumber> is the number of the item to renew.
1927
1928 C<$override_limit>, if supplied with a true value, causes
1929 the limit on the number of times that the loan can be renewed
1930 (as controlled by the item type) to be ignored.
1931
1932 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
1933 item must currently be on loan to the specified borrower; renewals
1934 must be allowed for the item's type; and the borrower must not have
1935 already renewed the loan. $error will contain the reason the renewal can not proceed
1936
1937 =cut
1938
1939 sub CanBookBeRenewed {
1940
1941     # check renewal status
1942     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
1943     my $dbh       = C4::Context->dbh;
1944     my $renews    = 1;
1945     my $renewokay = 0;
1946         my $error;
1947
1948     # Look in the issues table for this item, lent to this borrower,
1949     # and not yet returned.
1950
1951     # FIXME - I think this function could be redone to use only one SQL call.
1952     my $sth1 = $dbh->prepare(
1953         "SELECT * FROM issues
1954             WHERE borrowernumber = ?
1955             AND itemnumber = ?"
1956     );
1957     $sth1->execute( $borrowernumber, $itemnumber );
1958     if ( my $data1 = $sth1->fetchrow_hashref ) {
1959
1960         # Found a matching item
1961
1962         # See if this item may be renewed. This query is convoluted
1963         # because it's a bit messy: given the item number, we need to find
1964         # the biblioitem, which gives us the itemtype, which tells us
1965         # whether it may be renewed.
1966         my $query = "SELECT renewalsallowed FROM items ";
1967         $query .= (C4::Context->preference('item-level_itypes'))
1968                     ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1969                     : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
1970                        LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1971         $query .= "WHERE items.itemnumber = ?";
1972         my $sth2 = $dbh->prepare($query);
1973         $sth2->execute($itemnumber);
1974         if ( my $data2 = $sth2->fetchrow_hashref ) {
1975             $renews = $data2->{'renewalsallowed'};
1976         }
1977         if ( ( $renews && $renews > $data1->{'renewals'} ) || $override_limit ) {
1978             $renewokay = 1;
1979         }
1980         else {
1981                         $error="too_many";
1982                 }
1983         $sth2->finish;
1984         my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
1985         if ($resfound) {
1986             $renewokay = 0;
1987                         $error="on_reserve"
1988         }
1989
1990     }
1991     $sth1->finish;
1992     return ($renewokay,$error);
1993 }
1994
1995 =head2 AddRenewal
1996
1997 &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
1998
1999 Renews a loan.
2000
2001 C<$borrowernumber> is the borrower number of the patron who currently
2002 has the item.
2003
2004 C<$itemnumber> is the number of the item to renew.
2005
2006 C<$branch> is the library where the renewal took place (if any).
2007            The library that controls the circ policies for the renewal is retrieved from the issues record.
2008
2009 C<$datedue> can be a C4::Dates object used to set the due date.
2010
2011 C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate.  If
2012 this parameter is not supplied, lastreneweddate is set to the current date.
2013
2014 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2015 from the book's item type.
2016
2017 =cut
2018
2019 sub AddRenewal {
2020     
2021     my $borrowernumber  = shift or return undef;
2022     my $itemnumber      = shift or return undef;
2023     my $item   = GetItem($itemnumber) or return undef;
2024     my $branch  = (@_) ? shift : $item->{homebranch};   # opac-renew doesn't send branch
2025     my $datedue         = shift;
2026     my $lastreneweddate = shift || C4::Dates->new()->output('iso');
2027
2028
2029     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2030
2031     # If the due date wasn't specified, calculate it by adding the
2032     # book's loan length to today's date.
2033     unless ($datedue && $datedue->output('iso')) {
2034
2035         my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
2036         my $loanlength = GetLoanLength(
2037             $borrower->{'categorycode'},
2038              (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
2039                         $item->{homebranch}                     # item's homebranch determines loanlength OR do we want the branch specified by the AddRenewal argument?
2040         );
2041                 #FIXME -- use circControl?
2042                 $datedue =  CalcDateDue(C4::Dates->new(),$loanlength,$branch,$borrower);        # this branch is the transactional branch.
2043                                                                 # The question of whether to use item's homebranch calendar is open.
2044     }
2045
2046     # $lastreneweddate defaults to today.
2047     unless (defined $lastreneweddate) {
2048         $lastreneweddate = strftime( "%Y-%m-%d", localtime );
2049     }
2050
2051     my $dbh = C4::Context->dbh;
2052     # Find the issues record for this book
2053     my $sth =
2054       $dbh->prepare("SELECT * FROM issues
2055                         WHERE borrowernumber=? 
2056                         AND itemnumber=?"
2057       );
2058     $sth->execute( $borrowernumber, $itemnumber );
2059     my $issuedata = $sth->fetchrow_hashref;
2060     $sth->finish;
2061     if($datedue && ! $datedue->output('iso')){
2062         warn "Invalid date passed to AddRenewal.";
2063         return undef;
2064     }
2065     # If the due date wasn't specified, calculate it by adding the
2066     # book's loan length to today's date or the current due date
2067     # based on the value of the RenewalPeriodBase syspref.
2068     unless ($datedue) {
2069
2070         my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
2071         my $loanlength = GetLoanLength(
2072                     $borrower->{'categorycode'},
2073                     (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
2074                                 $issuedata->{'branchcode'}  );   # that's the circ control branch.
2075
2076         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2077                                         C4::Dates->new($issuedata->{date_due}, 'iso') :
2078                                         C4::Dates->new();
2079         $datedue =  CalcDateDue($datedue,$loanlength,$issuedata->{'branchcode'},$borrower);
2080     }
2081
2082     # Update the issues record to have the new due date, and a new count
2083     # of how many times it has been renewed.
2084     my $renews = $issuedata->{'renewals'} + 1;
2085     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
2086                             WHERE borrowernumber=? 
2087                             AND itemnumber=?"
2088     );
2089     $sth->execute( $datedue->output('iso'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
2090     $sth->finish;
2091
2092     # Update the renewal count on the item, and tell zebra to reindex
2093     $renews = $biblio->{'renewals'} + 1;
2094     ModItem({ renewals => $renews, onloan => $datedue->output('iso') }, $biblio->{'biblionumber'}, $itemnumber);
2095
2096     # Charge a new rental fee, if applicable?
2097     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2098     if ( $charge > 0 ) {
2099         my $accountno = getnextacctno( $borrowernumber );
2100         my $item = GetBiblioFromItemNumber($itemnumber);
2101         $sth = $dbh->prepare(
2102                 "INSERT INTO accountlines
2103                     (date,
2104                                         borrowernumber, accountno, amount,
2105                     description,
2106                                         accounttype, amountoutstanding, itemnumber
2107                                         )
2108                     VALUES (now(),?,?,?,?,?,?,?)"
2109         );
2110         $sth->execute( $borrowernumber, $accountno, $charge,
2111             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2112             'Rent', $charge, $itemnumber );
2113         $sth->finish;
2114     }
2115     # Log the renewal
2116     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
2117         return $datedue;
2118 }
2119
2120 sub GetRenewCount {
2121     # check renewal status
2122     my ($bornum,$itemno)=@_;
2123     my $dbh = C4::Context->dbh;
2124     my $renewcount = 0;
2125         my $renewsallowed = 0;
2126         my $renewsleft = 0;
2127     # Look in the issues table for this item, lent to this borrower,
2128     # and not yet returned.
2129
2130     # FIXME - I think this function could be redone to use only one SQL call.
2131     my $sth = $dbh->prepare("select * from issues
2132                                 where (borrowernumber = ?)
2133                                 and (itemnumber = ?)");
2134     $sth->execute($bornum,$itemno);
2135     my $data = $sth->fetchrow_hashref;
2136     $renewcount = $data->{'renewals'} if $data->{'renewals'};
2137     $sth->finish;
2138     my $query = "SELECT renewalsallowed FROM items ";
2139     $query .= (C4::Context->preference('item-level_itypes'))
2140                 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2141                 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
2142                    LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2143     $query .= "WHERE items.itemnumber = ?";
2144     my $sth2 = $dbh->prepare($query);
2145     $sth2->execute($itemno);
2146     my $data2 = $sth2->fetchrow_hashref();
2147     $renewsallowed = $data2->{'renewalsallowed'};
2148     $renewsleft = $renewsallowed - $renewcount;
2149     return ($renewcount,$renewsallowed,$renewsleft);
2150 }
2151
2152 =head2 GetIssuingCharges
2153
2154 ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2155
2156 Calculate how much it would cost for a given patron to borrow a given
2157 item, including any applicable discounts.
2158
2159 C<$itemnumber> is the item number of item the patron wishes to borrow.
2160
2161 C<$borrowernumber> is the patron's borrower number.
2162
2163 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2164 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2165 if it's a video).
2166
2167 =cut
2168
2169 sub GetIssuingCharges {
2170
2171     # calculate charges due
2172     my ( $itemnumber, $borrowernumber ) = @_;
2173     my $charge = 0;
2174     my $dbh    = C4::Context->dbh;
2175     my $item_type;
2176
2177     # Get the book's item type and rental charge (via its biblioitem).
2178     my $qcharge =     "SELECT itemtypes.itemtype,rentalcharge FROM items
2179             LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
2180         $qcharge .= (C4::Context->preference('item-level_itypes'))
2181                 ? " LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2182                 : " LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2183         
2184     $qcharge .=      "WHERE items.itemnumber =?";
2185    
2186     my $sth1 = $dbh->prepare($qcharge);
2187     $sth1->execute($itemnumber);
2188     if ( my $data1 = $sth1->fetchrow_hashref ) {
2189         $item_type = $data1->{'itemtype'};
2190         $charge    = $data1->{'rentalcharge'};
2191         my $q2 = "SELECT rentaldiscount FROM borrowers
2192             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2193             WHERE borrowers.borrowernumber = ?
2194             AND issuingrules.itemtype = ?";
2195         my $sth2 = $dbh->prepare($q2);
2196         $sth2->execute( $borrowernumber, $item_type );
2197         if ( my $data2 = $sth2->fetchrow_hashref ) {
2198             my $discount = $data2->{'rentaldiscount'};
2199             if ( $discount eq 'NULL' ) {
2200                 $discount = 0;
2201             }
2202             $charge = ( $charge * ( 100 - $discount ) ) / 100;
2203         }
2204         $sth2->finish;
2205     }
2206
2207     $sth1->finish;
2208     return ( $charge, $item_type );
2209 }
2210
2211 =head2 AddIssuingCharge
2212
2213 &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2214
2215 =cut
2216
2217 sub AddIssuingCharge {
2218     my ( $itemnumber, $borrowernumber, $charge ) = @_;
2219     my $dbh = C4::Context->dbh;
2220     my $nextaccntno = getnextacctno( $borrowernumber );
2221     my $query ="
2222         INSERT INTO accountlines
2223             (borrowernumber, itemnumber, accountno,
2224             date, amount, description, accounttype,
2225             amountoutstanding)
2226         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?)
2227     ";
2228     my $sth = $dbh->prepare($query);
2229     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge );
2230     $sth->finish;
2231 }
2232
2233 =head2 GetTransfers
2234
2235 GetTransfers($itemnumber);
2236
2237 =cut
2238
2239 sub GetTransfers {
2240     my ($itemnumber) = @_;
2241
2242     my $dbh = C4::Context->dbh;
2243
2244     my $query = '
2245         SELECT datesent,
2246                frombranch,
2247                tobranch
2248         FROM branchtransfers
2249         WHERE itemnumber = ?
2250           AND datearrived IS NULL
2251         ';
2252     my $sth = $dbh->prepare($query);
2253     $sth->execute($itemnumber);
2254     my @row = $sth->fetchrow_array();
2255     $sth->finish;
2256     return @row;
2257 }
2258
2259
2260 =head2 GetTransfersFromTo
2261
2262 @results = GetTransfersFromTo($frombranch,$tobranch);
2263
2264 Returns the list of pending transfers between $from and $to branch
2265
2266 =cut
2267
2268 sub GetTransfersFromTo {
2269     my ( $frombranch, $tobranch ) = @_;
2270     return unless ( $frombranch && $tobranch );
2271     my $dbh   = C4::Context->dbh;
2272     my $query = "
2273         SELECT itemnumber,datesent,frombranch
2274         FROM   branchtransfers
2275         WHERE  frombranch=?
2276           AND  tobranch=?
2277           AND datearrived IS NULL
2278     ";
2279     my $sth = $dbh->prepare($query);
2280     $sth->execute( $frombranch, $tobranch );
2281     my @gettransfers;
2282
2283     while ( my $data = $sth->fetchrow_hashref ) {
2284         push @gettransfers, $data;
2285     }
2286     $sth->finish;
2287     return (@gettransfers);
2288 }
2289
2290 =head2 DeleteTransfer
2291
2292 &DeleteTransfer($itemnumber);
2293
2294 =cut
2295
2296 sub DeleteTransfer {
2297     my ($itemnumber) = @_;
2298     my $dbh          = C4::Context->dbh;
2299     my $sth          = $dbh->prepare(
2300         "DELETE FROM branchtransfers
2301          WHERE itemnumber=?
2302          AND datearrived IS NULL "
2303     );
2304     $sth->execute($itemnumber);
2305     $sth->finish;
2306 }
2307
2308 =head2 AnonymiseIssueHistory
2309
2310 $rows = AnonymiseIssueHistory($borrowernumber,$date)
2311
2312 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2313 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2314
2315 return the number of affected rows.
2316
2317 =cut
2318
2319 sub AnonymiseIssueHistory {
2320     my $date           = shift;
2321     my $borrowernumber = shift;
2322     my $dbh            = C4::Context->dbh;
2323     my $query          = "
2324         UPDATE old_issues
2325         SET    borrowernumber = NULL
2326         WHERE  returndate < '".$date."'
2327           AND borrowernumber IS NOT NULL
2328     ";
2329     $query .= " AND borrowernumber = '".$borrowernumber."'" if defined $borrowernumber;
2330     my $rows_affected = $dbh->do($query);
2331     return $rows_affected;
2332 }
2333
2334 =head2 updateWrongTransfer
2335
2336 $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2337
2338 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 
2339
2340 =cut
2341
2342 sub updateWrongTransfer {
2343         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2344         my $dbh = C4::Context->dbh;     
2345 # first step validate the actual line of transfert .
2346         my $sth =
2347                 $dbh->prepare(
2348                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2349                 );
2350                 $sth->execute($FromLibrary,$itemNumber);
2351                 $sth->finish;
2352
2353 # second step create a new line of branchtransfer to the right location .
2354         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2355
2356 #third step changing holdingbranch of item
2357         UpdateHoldingbranch($FromLibrary,$itemNumber);
2358 }
2359
2360 =head2 UpdateHoldingbranch
2361
2362 $items = UpdateHoldingbranch($branch,$itmenumber);
2363 Simple methode for updating hodlingbranch in items BDD line
2364
2365 =cut
2366
2367 sub UpdateHoldingbranch {
2368         my ( $branch,$itemnumber ) = @_;
2369     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2370 }
2371
2372 =head2 CalcDateDue
2373
2374 $newdatedue = CalcDateDue($startdate,$loanlength,$branchcode);
2375 this function calculates the due date given the loan length ,
2376 checking against the holidays calendar as per the 'useDaysMode' syspref.
2377 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2378 C<$branch>  = location whose calendar to use
2379 C<$loanlength>  = loan length prior to adjustment
2380 =cut
2381
2382 sub CalcDateDue { 
2383         my ($startdate,$loanlength,$branch,$borrower) = @_;
2384         my $datedue;
2385
2386         if(C4::Context->preference('useDaysMode') eq 'Days') {  # ignoring calendar
2387                 my $timedue = time + ($loanlength) * 86400;
2388         #FIXME - assumes now even though we take a startdate 
2389                 my @datearr  = localtime($timedue);
2390                 $datedue = C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
2391         } else {
2392                 my $calendar = C4::Calendar->new(  branchcode => $branch );
2393                 $datedue = $calendar->addDate($startdate, $loanlength);
2394         }
2395
2396         # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
2397         if ( C4::Context->preference('ReturnBeforeExpiry') && $datedue->output('iso') gt $borrower->{dateexpiry} ) {
2398             $datedue = C4::Dates->new( $borrower->{dateexpiry}, 'iso' );
2399         }
2400
2401         # if ceilingDueDate ON the datedue can't be after the ceiling date
2402         if ( C4::Context->preference('ceilingDueDate')
2403              && ( C4::Context->preference('ceilingDueDate') =~ C4::Dates->regexp('syspref') )
2404              && $datedue->output gt C4::Context->preference('ceilingDueDate') ) {
2405             $datedue = C4::Dates->new( C4::Context->preference('ceilingDueDate') );
2406         }
2407
2408         return $datedue;
2409 }
2410
2411 =head2 CheckValidDatedue
2412        This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
2413        To be replaced by CalcDateDue() once C4::Calendar use is tested.
2414
2415 $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
2416 this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
2417 C<$date_due>   = returndate calculate with no day check
2418 C<$itemnumber>  = itemnumber
2419 C<$branchcode>  = location of issue (affected by 'CircControl' syspref)
2420 C<$loanlength>  = loan length prior to adjustment
2421 =cut
2422
2423 sub CheckValidDatedue {
2424 my ($date_due,$itemnumber,$branchcode)=@_;
2425 my @datedue=split('-',$date_due->output('iso'));
2426 my $years=$datedue[0];
2427 my $month=$datedue[1];
2428 my $day=$datedue[2];
2429 # die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
2430 my $dow;
2431 for (my $i=0;$i<2;$i++){
2432     $dow=Day_of_Week($years,$month,$day);
2433     ($dow=0) if ($dow>6);
2434     my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
2435     my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2436     my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2437         if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
2438         $i=0;
2439         (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
2440         }
2441     }
2442     my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
2443 return $newdatedue;
2444 }
2445
2446
2447 =head2 CheckRepeatableHolidays
2448
2449 $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2450 this function checks if the date due is a repeatable holiday
2451 C<$date_due>   = returndate calculate with no day check
2452 C<$itemnumber>  = itemnumber
2453 C<$branchcode>  = localisation of issue 
2454
2455 =cut
2456
2457 sub CheckRepeatableHolidays{
2458 my($itemnumber,$week_day,$branchcode)=@_;
2459 my $dbh = C4::Context->dbh;
2460 my $query = qq|SELECT count(*)  
2461         FROM repeatable_holidays 
2462         WHERE branchcode=?
2463         AND weekday=?|;
2464 my $sth = $dbh->prepare($query);
2465 $sth->execute($branchcode,$week_day);
2466 my $result=$sth->fetchrow;
2467 $sth->finish;
2468 return $result;
2469 }
2470
2471
2472 =head2 CheckSpecialHolidays
2473
2474 $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2475 this function check if the date is a special holiday
2476 C<$years>   = the years of datedue
2477 C<$month>   = the month of datedue
2478 C<$day>     = the day of datedue
2479 C<$itemnumber>  = itemnumber
2480 C<$branchcode>  = localisation of issue 
2481
2482 =cut
2483
2484 sub CheckSpecialHolidays{
2485 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
2486 my $dbh = C4::Context->dbh;
2487 my $query=qq|SELECT count(*) 
2488              FROM `special_holidays`
2489              WHERE year=?
2490              AND month=?
2491              AND day=?
2492              AND branchcode=?
2493             |;
2494 my $sth = $dbh->prepare($query);
2495 $sth->execute($years,$month,$day,$branchcode);
2496 my $countspecial=$sth->fetchrow ;
2497 $sth->finish;
2498 return $countspecial;
2499 }
2500
2501 =head2 CheckRepeatableSpecialHolidays
2502
2503 $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2504 this function check if the date is a repeatble special holidays
2505 C<$month>   = the month of datedue
2506 C<$day>     = the day of datedue
2507 C<$itemnumber>  = itemnumber
2508 C<$branchcode>  = localisation of issue 
2509
2510 =cut
2511
2512 sub CheckRepeatableSpecialHolidays{
2513 my ($month,$day,$itemnumber,$branchcode) = @_;
2514 my $dbh = C4::Context->dbh;
2515 my $query=qq|SELECT count(*) 
2516              FROM `repeatable_holidays`
2517              WHERE month=?
2518              AND day=?
2519              AND branchcode=?
2520             |;
2521 my $sth = $dbh->prepare($query);
2522 $sth->execute($month,$day,$branchcode);
2523 my $countspecial=$sth->fetchrow ;
2524 $sth->finish;
2525 return $countspecial;
2526 }
2527
2528
2529
2530 sub CheckValidBarcode{
2531 my ($barcode) = @_;
2532 my $dbh = C4::Context->dbh;
2533 my $query=qq|SELECT count(*) 
2534              FROM items 
2535              WHERE barcode=?
2536             |;
2537 my $sth = $dbh->prepare($query);
2538 $sth->execute($barcode);
2539 my $exist=$sth->fetchrow ;
2540 $sth->finish;
2541 return $exist;
2542 }
2543
2544 1;
2545
2546 __END__
2547
2548 =head1 AUTHOR
2549
2550 Koha Developement team <info@koha.org>
2551
2552 =cut
2553