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