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