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