Add a working fines script, some changes to CalcFine and Circulation.pm
[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     
1116         $sth->execute( $borrowertype, $itemtype, $branchcode );
1117     my $irule = $sth->fetchrow_hashref;
1118     return $irule if defined($irule) ;
1119
1120     $sth->execute( $borrowertype, $itemtype, "*" );
1121     my $irule = $sth->fetchrow_hashref;
1122     return $irule if defined($irule) ;
1123
1124     $sth->execute( $borrowertype, "*", $branchcode );
1125     my $irule = $sth->fetchrow_hashref;
1126     return $irule if defined($irule) ;
1127
1128     $sth->execute( "*", $itemtype, $branchcode );
1129     my $irule = $sth->fetchrow_hashref;
1130     return $irule if defined($irule) ;
1131
1132     $sth->execute( $borrowertype, "*", "*" );
1133     my $irule = $sth->fetchrow_hashref;
1134     return $irule if defined($irule) ;
1135
1136     $sth->execute( "*", "*", $branchcode );
1137     my $irule = $sth->fetchrow_hashref;
1138     return $irule if defined($irule) ;
1139
1140     $sth->execute( "*", $itemtype, "*" );
1141     my $irule = $sth->fetchrow_hashref;
1142     return $irule if defined($irule) ;
1143
1144     $sth->execute( "*", "*", "*" );
1145     my $irule = $sth->fetchrow_hashref;
1146     return $irule if defined($irule) ;
1147
1148     # if no rule matches,
1149     return undef;
1150 }
1151
1152 =head2 AddReturn
1153
1154 ($doreturn, $messages, $iteminformation, $borrower) =
1155     &AddReturn($barcode, $branch, $exemptfine);
1156
1157 Returns a book.
1158
1159 C<$barcode> is the bar code of the book being returned. C<$branch> is
1160 the code of the branch where the book is being returned.  C<$exemptfine>
1161 indicates that overdue charges for the item will not be applied.
1162
1163 C<&AddReturn> returns a list of four items:
1164
1165 C<$doreturn> is true iff the return succeeded.
1166
1167 C<$messages> is a reference-to-hash giving the reason for failure:
1168
1169 =over 4
1170
1171 =item C<BadBarcode>
1172
1173 No item with this barcode exists. The value is C<$barcode>.
1174
1175 =item C<NotIssued>
1176
1177 The book is not currently on loan. The value is C<$barcode>.
1178
1179 =item C<IsPermanent>
1180
1181 The book's home branch is a permanent collection. If you have borrowed
1182 this book, you are not allowed to return it. The value is the code for
1183 the book's home branch.
1184
1185 =item C<wthdrawn>
1186
1187 This book has been withdrawn/cancelled. The value should be ignored.
1188
1189 =item C<ResFound>
1190
1191 The item was reserved. The value is a reference-to-hash whose keys are
1192 fields from the reserves table of the Koha database, and
1193 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1194 either C<Waiting>, C<Reserved>, or 0.
1195
1196 =back
1197
1198 C<$borrower> is a reference-to-hash, giving information about the
1199 patron who last borrowed the book.
1200
1201 =cut
1202
1203 sub AddReturn {
1204     my ( $barcode, $branch, $exemptfine ) = @_;
1205     my $dbh      = C4::Context->dbh;
1206     my $messages;
1207     my $doreturn = 1;
1208     my $borrower;
1209     my $validTransfert = 0;
1210     my $reserveDone = 0;
1211     
1212     # get information on item
1213     my $iteminformation = GetItemIssue( GetItemnumberFromBarcode($barcode));
1214     my $biblio = GetBiblioItemData($iteminformation->{'biblioitemnumber'});
1215 #     use Data::Dumper;warn Data::Dumper::Dumper($iteminformation);  
1216     unless ($iteminformation->{'itemnumber'} ) {
1217         $messages->{'BadBarcode'} = $barcode;
1218         $doreturn = 0;
1219     } else {
1220         # find the borrower
1221         if ( ( not $iteminformation->{borrowernumber} ) && $doreturn ) {
1222             $messages->{'NotIssued'} = $barcode;
1223             # even though item is not on loan, it may still
1224             # be transferred; therefore, get current branch information
1225             my $curr_iteminfo = GetItem($iteminformation->{'itemnumber'});
1226             $iteminformation->{'homebranch'} = $curr_iteminfo->{'homebranch'};
1227             $iteminformation->{'holdingbranch'} = $curr_iteminfo->{'holdingbranch'};
1228             $doreturn = 0;
1229         }
1230     
1231         # check if the book is in a permanent collection....
1232         my $hbr      = $iteminformation->{'homebranch'};
1233         my $branches = GetBranches();
1234         if ( $hbr && $branches->{$hbr}->{'PE'} ) {
1235             $messages->{'IsPermanent'} = $hbr;
1236         }
1237                 
1238                     # if independent branches are on and returning to different branch, refuse the return
1239         if ($hbr ne C4::Context->userenv->{'branch'} && C4::Context->preference("IndependantBranches")){
1240                           $messages->{'Wrongbranch'} = 1;
1241                           $doreturn=0;
1242                     }
1243                         
1244         # check that the book has been cancelled
1245         if ( $iteminformation->{'wthdrawn'} ) {
1246             $messages->{'wthdrawn'} = 1;
1247             $doreturn = 0;
1248         }
1249     
1250     #     new op dev : if the book returned in an other branch update the holding branch
1251     
1252     # update issues, thereby returning book (should push this out into another subroutine
1253         $borrower = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1254     
1255     # case of a return of document (deal with issues and holdingbranch)
1256     
1257         if ($doreturn) {
1258             MarkIssueReturned($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'});
1259             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?
1260         }
1261     
1262     # continue to deal with returns cases, but not only if we have an issue
1263     
1264         # the holdingbranch is updated if the document is returned in an other location .
1265         if ( $iteminformation->{'holdingbranch'} ne C4::Context->userenv->{'branch'} ) {
1266                         UpdateHoldingbranch(C4::Context->userenv->{'branch'},$iteminformation->{'itemnumber'}); 
1267                         #               reload iteminformation holdingbranch with the userenv value
1268                         $iteminformation->{'holdingbranch'} = C4::Context->userenv->{'branch'};
1269         }
1270         ModDateLastSeen( $iteminformation->{'itemnumber'} );
1271         ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
1272                     
1273                     if ($iteminformation->{borrowernumber}){
1274                           ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1275         }       
1276         # fix up the accounts.....
1277         if ( $iteminformation->{'itemlost'} ) {
1278             $messages->{'WasLost'} = 1;
1279         }
1280     
1281     # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1282     #     check if we have a transfer for this document
1283         my ($datesent,$frombranch,$tobranch) = GetTransfers( $iteminformation->{'itemnumber'} );
1284     
1285     #     if we have a transfer to do, we update the line of transfers with the datearrived
1286         if ($datesent) {
1287             if ( $tobranch eq C4::Context->userenv->{'branch'} ) {
1288                     my $sth =
1289                     $dbh->prepare(
1290                             "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1291                     );
1292                     $sth->execute( $iteminformation->{'itemnumber'} );
1293                     $sth->finish;
1294     #         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'
1295             C4::Reserves::ModReserveStatus( $iteminformation->{'itemnumber'},'W' );
1296             }
1297         else {
1298             $messages->{'WrongTransfer'} = $tobranch;
1299             $messages->{'WrongTransferItem'} = $iteminformation->{'itemnumber'};
1300         }
1301         $validTransfert = 1;
1302         }
1303     
1304     # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
1305         # fix up the accounts.....
1306         if ($iteminformation->{'itemlost'}) {
1307                 FixAccountForLostAndReturned($iteminformation, $borrower);
1308                 $messages->{'WasLost'} = 1;
1309         }
1310         # fix up the overdues in accounts...
1311         FixOverduesOnReturn( $borrower->{'borrowernumber'},
1312             $iteminformation->{'itemnumber'}, $exemptfine );
1313     
1314     # find reserves.....
1315     #     if we don't have a reserve with the status W, we launch the Checkreserves routine
1316         my ( $resfound, $resrec ) =
1317         C4::Reserves::CheckReserves( $iteminformation->{'itemnumber'} );
1318         if ($resfound) {
1319             $resrec->{'ResFound'}   = $resfound;
1320             $messages->{'ResFound'} = $resrec;
1321             $reserveDone = 1;
1322         }
1323     
1324         # update stats?
1325         # Record the fact that this book was returned.
1326         UpdateStats(
1327             $branch, 'return', '0', '',
1328             $iteminformation->{'itemnumber'},
1329             $biblio->{'itemtype'},
1330             $borrower->{'borrowernumber'}
1331         );
1332         
1333         logaction("CIRCULATION", "RETURN", $iteminformation->{borrowernumber}, $iteminformation->{'biblionumber'}) 
1334             if C4::Context->preference("ReturnLog");
1335         
1336         #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1337         #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1338         
1339         if ( ($iteminformation->{'holdingbranch'} ne $iteminformation->{'homebranch'}) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) and ($reserveDone ne 1) ){
1340                         if (C4::Context->preference("AutomaticItemReturn") == 1) {
1341                                 ModItemTransfer($iteminformation->{'itemnumber'}, C4::Context->userenv->{'branch'}, $iteminformation->{'homebranch'});
1342                                 $messages->{'WasTransfered'} = 1;
1343                         }
1344                         else {
1345                                 $messages->{'NeedsTransfer'} = 1;
1346                         }
1347         }
1348     }
1349     return ( $doreturn, $messages, $iteminformation, $borrower );
1350 }
1351
1352 =head2 MarkIssueReturned
1353
1354 =over 4
1355
1356 MarkIssueReturned($borrowernumber, $itemnumber);
1357
1358 =back
1359
1360 Unconditionally marks an issue as being returned by
1361 moving the C<issues> row to C<old_issues> and
1362 setting C<returndate> to the current date.
1363
1364 Ideally, this function would be internal to C<C4::Circulation>,
1365 not exported, but it is currently needed by one 
1366 routine in C<C4::Accounts>.
1367
1368 =cut
1369
1370 sub MarkIssueReturned {
1371     my ($borrowernumber, $itemnumber) = @_;
1372
1373     my $dbh = C4::Context->dbh;
1374     # FIXME transaction
1375     my $sth_upd  = $dbh->prepare("UPDATE issues SET returndate = now() 
1376                                   WHERE borrowernumber = ?
1377                                   AND itemnumber = ?");
1378     $sth_upd->execute($borrowernumber, $itemnumber);
1379     my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues 
1380                                   WHERE borrowernumber = ?
1381                                   AND itemnumber = ?");
1382     $sth_copy->execute($borrowernumber, $itemnumber);
1383     my $sth_del  = $dbh->prepare("DELETE FROM issues
1384                                   WHERE borrowernumber = ?
1385                                   AND itemnumber = ?");
1386     $sth_del->execute($borrowernumber, $itemnumber);
1387 }
1388
1389 =head2 FixOverduesOnReturn
1390
1391     &FixOverduesOnReturn($brn,$itm, $exemptfine);
1392
1393 C<$brn> borrowernumber
1394
1395 C<$itm> itemnumber
1396
1397 internal function, called only by AddReturn
1398
1399 =cut
1400
1401 sub FixOverduesOnReturn {
1402     my ( $borrowernumber, $item, $exemptfine ) = @_;
1403     my $dbh = C4::Context->dbh;
1404
1405     # check for overdue fine
1406     my $sth =
1407       $dbh->prepare(
1408 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1409       );
1410     $sth->execute( $borrowernumber, $item );
1411
1412     # alter fine to show that the book has been returned
1413    my $data; 
1414         if ($data = $sth->fetchrow_hashref) {
1415         my $uquery =($exemptfine)? "update accountlines set accounttype='FFOR', amountoutstanding=0":"update accountlines set accounttype='F' ";
1416                 $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1417         my $usth = $dbh->prepare($uquery);
1418         $usth->execute($borrowernumber,$item ,$data->{'accountno'});
1419         $usth->finish();
1420     }
1421
1422     $sth->finish();
1423     return;
1424 }
1425
1426 =head2 FixAccountForLostAndReturned
1427
1428         &FixAccountForLostAndReturned($iteminfo,$borrower);
1429
1430 Calculates the charge for a book lost and returned (Not exported & used only once)
1431
1432 C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used.
1433
1434 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1435
1436 Internal function, called by AddReturn
1437
1438 =cut
1439
1440 sub FixAccountForLostAndReturned {
1441         my ($iteminfo, $borrower) = @_;
1442         my %env;
1443         my $dbh = C4::Context->dbh;
1444         my $itm = $iteminfo->{'itemnumber'};
1445         # check for charge made for lost book
1446         my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
1447         $sth->execute($itm);
1448         if (my $data = $sth->fetchrow_hashref) {
1449         # writeoff this amount
1450                 my $offset;
1451                 my $amount = $data->{'amount'};
1452                 my $acctno = $data->{'accountno'};
1453                 my $amountleft;
1454                 if ($data->{'amountoutstanding'} == $amount) {
1455                 $offset = $data->{'amount'};
1456                 $amountleft = 0;
1457                 } else {
1458                 $offset = $amount - $data->{'amountoutstanding'};
1459                 $amountleft = $data->{'amountoutstanding'} - $amount;
1460                 }
1461                 my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
1462                         WHERE (borrowernumber = ?)
1463                         AND (itemnumber = ?) AND (accountno = ?) ");
1464                 $usth->execute($data->{'borrowernumber'},$itm,$acctno);
1465                 $usth->finish;
1466         #check if any credit is left if so writeoff other accounts
1467                 my $nextaccntno = getnextacctno(\%env,$data->{'borrowernumber'},$dbh);
1468                 if ($amountleft < 0){
1469                 $amountleft*=-1;
1470                 }
1471                 if ($amountleft > 0){
1472                 my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
1473                                                         AND (amountoutstanding >0) ORDER BY date");
1474                 $msth->execute($data->{'borrowernumber'});
1475         # offset transactions
1476                 my $newamtos;
1477                 my $accdata;
1478                 while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1479                         if ($accdata->{'amountoutstanding'} < $amountleft) {
1480                         $newamtos = 0;
1481                         $amountleft -= $accdata->{'amountoutstanding'};
1482                         }  else {
1483                         $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1484                         $amountleft = 0;
1485                         }
1486                         my $thisacct = $accdata->{'accountno'};
1487                         my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
1488                                         WHERE (borrowernumber = ?)
1489                                         AND (accountno=?)");
1490                         $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');
1491                         $usth->finish;
1492                         $usth = $dbh->prepare("INSERT INTO accountoffsets
1493                                 (borrowernumber, accountno, offsetaccount,  offsetamount)
1494                                 VALUES
1495                                 (?,?,?,?)");
1496                         $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1497                         $usth->finish;
1498                 }
1499                 $msth->finish;
1500                 }
1501                 if ($amountleft > 0){
1502                         $amountleft*=-1;
1503                 }
1504                 my $desc="Item Returned ".$iteminfo->{'barcode'};
1505                 $usth = $dbh->prepare("INSERT INTO accountlines
1506                         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1507                         VALUES (?,?,now(),?,?,'CR',?)");
1508                 $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1509                 $usth->finish;
1510                 $usth = $dbh->prepare("INSERT INTO accountoffsets
1511                         (borrowernumber, accountno, offsetaccount,  offsetamount)
1512                         VALUES (?,?,?,?)");
1513                 $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset);
1514                 $usth->finish;
1515         ModItem({ paidfor => '' }, undef, $itm);
1516         }
1517         $sth->finish;
1518         return;
1519 }
1520
1521 =head2 GetItemIssue
1522
1523 $issues = &GetItemIssue($itemnumber);
1524
1525 Returns patrons currently having a book. nothing if item is not issued atm
1526
1527 C<$itemnumber> is the itemnumber
1528
1529 Returns an array of hashes
1530
1531 =cut
1532
1533 sub GetItemIssue {
1534     my ( $itemnumber) = @_;
1535     return unless $itemnumber;
1536     my $dbh = C4::Context->dbh;
1537     my @GetItemIssues;
1538     
1539     # get today date
1540     my $today = POSIX::strftime("%Y%m%d", localtime);
1541
1542     my $sth = $dbh->prepare(
1543         "SELECT * FROM issues 
1544         LEFT JOIN items ON issues.itemnumber=items.itemnumber
1545     WHERE
1546     issues.itemnumber=?");
1547     $sth->execute($itemnumber);
1548     my $data = $sth->fetchrow_hashref;
1549     my $datedue = $data->{'date_due'};
1550     $datedue =~ s/-//g;
1551     if ( $datedue < $today ) {
1552         $data->{'overdue'} = 1;
1553     }
1554     $data->{'itemnumber'} = $itemnumber; # fill itemnumber, in case item is not on issue
1555     $sth->finish;
1556     return ($data);
1557 }
1558
1559 =head2 GetItemIssues
1560
1561 $issues = &GetItemIssues($itemnumber, $history);
1562
1563 Returns patrons that have issued a book
1564
1565 C<$itemnumber> is the itemnumber
1566 C<$history> is 0 if you want actuel "issuer" (if it exist) and 1 if you want issues history
1567
1568 Returns an array of hashes
1569
1570 =cut
1571
1572 sub GetItemIssues {
1573     my ( $itemnumber,$history ) = @_;
1574     my $dbh = C4::Context->dbh;
1575     my @GetItemIssues;
1576     
1577     # get today date
1578     my $today = POSIX::strftime("%Y%m%d", localtime);
1579
1580     my $sql = "SELECT * FROM issues 
1581               JOIN borrowers USING (borrowernumber)
1582               JOIN items USING (itemnumber)
1583               WHERE issues.itemnumber = ? ";
1584     if ($history) {
1585         $sql .= "UNION ALL
1586                  SELECT * FROM old_issues 
1587                  LEFT JOIN borrowers USING (borrowernumber)
1588                  JOIN items USING (itemnumber)
1589                  WHERE old_issues.itemnumber = ? ";
1590     }
1591     $sql .= "ORDER BY date_due DESC";
1592     my $sth = $dbh->prepare($sql);
1593     if ($history) {
1594         $sth->execute($itemnumber, $itemnumber);
1595     } else {
1596         $sth->execute($itemnumber);
1597     }
1598     while ( my $data = $sth->fetchrow_hashref ) {
1599         my $datedue = $data->{'date_due'};
1600         $datedue =~ s/-//g;
1601         if ( $datedue < $today ) {
1602             $data->{'overdue'} = 1;
1603         }
1604         my $itemnumber = $data->{'itemnumber'};
1605         push @GetItemIssues, $data;
1606     }
1607     $sth->finish;
1608     return ( \@GetItemIssues );
1609 }
1610
1611 =head2 GetBiblioIssues
1612
1613 $issues = GetBiblioIssues($biblionumber);
1614
1615 this function get all issues from a biblionumber.
1616
1617 Return:
1618 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
1619 tables issues and the firstname,surname & cardnumber from borrowers.
1620
1621 =cut
1622
1623 sub GetBiblioIssues {
1624     my $biblionumber = shift;
1625     return undef unless $biblionumber;
1626     my $dbh   = C4::Context->dbh;
1627     my $query = "
1628         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1629         FROM issues
1630             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
1631             LEFT JOIN items ON issues.itemnumber = items.itemnumber
1632             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1633             LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1634         WHERE biblio.biblionumber = ?
1635         UNION ALL
1636         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1637         FROM old_issues
1638             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
1639             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
1640             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1641             LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1642         WHERE biblio.biblionumber = ?
1643         ORDER BY timestamp
1644     ";
1645     my $sth = $dbh->prepare($query);
1646     $sth->execute($biblionumber, $biblionumber);
1647
1648     my @issues;
1649     while ( my $data = $sth->fetchrow_hashref ) {
1650         push @issues, $data;
1651     }
1652     return \@issues;
1653 }
1654
1655 =head2 CanBookBeRenewed
1656
1657 ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber);
1658
1659 Find out whether a borrowed item may be renewed.
1660
1661 C<$dbh> is a DBI handle to the Koha database.
1662
1663 C<$borrowernumber> is the borrower number of the patron who currently
1664 has the item on loan.
1665
1666 C<$itemnumber> is the number of the item to renew.
1667
1668 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
1669 item must currently be on loan to the specified borrower; renewals
1670 must be allowed for the item's type; and the borrower must not have
1671 already renewed the loan. $error will contain the reason the renewal can not proceed
1672
1673 =cut
1674
1675 sub CanBookBeRenewed {
1676
1677     # check renewal status
1678     my ( $borrowernumber, $itemnumber ) = @_;
1679     my $dbh       = C4::Context->dbh;
1680     my $renews    = 1;
1681     my $renewokay = 0;
1682         my $error;
1683
1684     # Look in the issues table for this item, lent to this borrower,
1685     # and not yet returned.
1686
1687     # FIXME - I think this function could be redone to use only one SQL call.
1688     my $sth1 = $dbh->prepare(
1689         "SELECT * FROM issues
1690             WHERE borrowernumber = ?
1691             AND itemnumber = ?"
1692     );
1693     $sth1->execute( $borrowernumber, $itemnumber );
1694     if ( my $data1 = $sth1->fetchrow_hashref ) {
1695
1696         # Found a matching item
1697
1698         # See if this item may be renewed. This query is convoluted
1699         # because it's a bit messy: given the item number, we need to find
1700         # the biblioitem, which gives us the itemtype, which tells us
1701         # whether it may be renewed.
1702         my $query = "SELECT renewalsallowed FROM items ";
1703         $query .= (C4::Context->preference('item-level_itypes'))
1704                     ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1705                     : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
1706                        LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1707         $query .= "WHERE items.itemnumber = ?";
1708         my $sth2 = $dbh->prepare($query);
1709         $sth2->execute($itemnumber);
1710         if ( my $data2 = $sth2->fetchrow_hashref ) {
1711             $renews = $data2->{'renewalsallowed'};
1712         }
1713         if ( $renews && $renews > $data1->{'renewals'} ) {
1714             $renewokay = 1;
1715         }
1716         else {
1717                         $error="too_many";
1718                 }
1719         $sth2->finish;
1720         my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
1721         if ($resfound) {
1722             $renewokay = 0;
1723                         $error="on_reserve"
1724         }
1725
1726     }
1727     $sth1->finish;
1728     return ($renewokay,$error);
1729 }
1730
1731 =head2 AddRenewal
1732
1733 &AddRenewal($borrowernumber, $itemnumber, $datedue);
1734
1735 Renews a loan.
1736
1737 C<$borrowernumber> is the borrower number of the patron who currently
1738 has the item.
1739
1740 C<$itemnumber> is the number of the item to renew.
1741
1742 C<$datedue> can be used to set the due date. If C<$datedue> is the
1743 empty string, C<&AddRenewal> will calculate the due date automatically
1744 from the book's item type. If you wish to set the due date manually,
1745 C<$datedue> should be in the form YYYY-MM-DD.
1746
1747 =cut
1748
1749 sub AddRenewal {
1750
1751     my ( $borrowernumber, $itemnumber, $branch ,$datedue ) = @_;
1752     my $dbh = C4::Context->dbh;
1753     my $biblio = GetBiblioFromItemNumber($itemnumber);
1754     # If the due date wasn't specified, calculate it by adding the
1755     # book's loan length to today's date.
1756     unless ( $datedue->output('iso') ) {
1757
1758
1759         my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 );
1760         my $loanlength = GetLoanLength(
1761             $borrower->{'categorycode'},
1762              (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
1763                         $borrower->{'branchcode'}
1764         );
1765                 #FIXME --  choose issuer or borrower branch -- use circControl.
1766
1767                 #FIXME -- $debug-ify the (0)
1768         #my @darray = Add_Delta_DHMS( Today_and_Now(), $loanlength, 0, 0, 0 );
1769         #$datedue = C4::Dates->new( sprintf("%04d-%02d-%02d",@darray[0..2]), 'iso');
1770                 #(0) and print STDERR  "C4::Dates->new->output = " . C4::Dates->new()->output()
1771                 #               . "\ndatedue->output = " . $datedue->output()
1772                 #               . "\n(Y,M,D) = " . join ',', @darray;
1773                 #$datedue=CheckValidDatedue($datedue,$itemnumber,$branch,$loanlength);
1774                 $datedue =  CalcDateDue(C4::Dates->new(),$loanlength,$branch);
1775     }
1776
1777     # Find the issues record for this book
1778     my $sth =
1779       $dbh->prepare("SELECT * FROM issues
1780                         WHERE borrowernumber=? 
1781                         AND itemnumber=?"
1782       );
1783     $sth->execute( $borrowernumber, $itemnumber );
1784     my $issuedata = $sth->fetchrow_hashref;
1785     $sth->finish;
1786
1787     # Update the issues record to have the new due date, and a new count
1788     # of how many times it has been renewed.
1789     my $renews = $issuedata->{'renewals'} + 1;
1790     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?
1791                             WHERE borrowernumber=? 
1792                             AND itemnumber=?"
1793     );
1794     $sth->execute( $datedue->output('iso'), $renews, $borrowernumber, $itemnumber );
1795     $sth->finish;
1796
1797     # Update the renewal count on the item, and tell zebra to reindex
1798     $renews = $biblio->{'renewals'} + 1;
1799     ModItem({ renewals => $renews }, $biblio->{'biblionumber'}, $itemnumber);
1800
1801     # Charge a new rental fee, if applicable?
1802     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
1803     if ( $charge > 0 ) {
1804         my $accountno = getnextacctno( $borrowernumber );
1805         my $item = GetBiblioFromItemNumber($itemnumber);
1806         $sth = $dbh->prepare(
1807                 "INSERT INTO accountlines
1808                     (borrowernumber,accountno,date,amount,
1809                         description,accounttype,amountoutstanding,
1810                     itemnumber)
1811                     VALUES (?,?,now(),?,?,?,?,?)"
1812         );
1813         $sth->execute( $borrowernumber, $accountno, $charge,
1814             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
1815             'Rent', $charge, $itemnumber );
1816         $sth->finish;
1817     }
1818     # Log the renewal
1819     UpdateStats( $branch, 'renew', $charge, '', $itemnumber );
1820 }
1821
1822 sub GetRenewCount {
1823     # check renewal status
1824     my ($bornum,$itemno)=@_;
1825     my $dbh = C4::Context->dbh;
1826     my $renewcount = 0;
1827         my $renewsallowed = 0;
1828         my $renewsleft = 0;
1829     # Look in the issues table for this item, lent to this borrower,
1830     # and not yet returned.
1831
1832     # FIXME - I think this function could be redone to use only one SQL call.
1833     my $sth = $dbh->prepare("select * from issues
1834                                 where (borrowernumber = ?)
1835                                 and (itemnumber = ?)");
1836     $sth->execute($bornum,$itemno);
1837     my $data = $sth->fetchrow_hashref;
1838     $renewcount = $data->{'renewals'} if $data->{'renewals'};
1839     $sth->finish;
1840     my $query = "SELECT renewalsallowed FROM items ";
1841     $query .= (C4::Context->preference('item-level_itypes'))
1842                 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1843                 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
1844                    LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1845     $query .= "WHERE items.itemnumber = ?";
1846     my $sth2 = $dbh->prepare($query);
1847     $sth2->execute($itemno);
1848     my $data2 = $sth2->fetchrow_hashref();
1849     $renewsallowed = $data2->{'renewalsallowed'};
1850     $renewsleft = $renewsallowed - $renewcount;
1851     return ($renewcount,$renewsallowed,$renewsleft);
1852 }
1853
1854 =head2 GetIssuingCharges
1855
1856 ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
1857
1858 Calculate how much it would cost for a given patron to borrow a given
1859 item, including any applicable discounts.
1860
1861 C<$itemnumber> is the item number of item the patron wishes to borrow.
1862
1863 C<$borrowernumber> is the patron's borrower number.
1864
1865 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
1866 and C<$item_type> is the code for the item's item type (e.g., C<VID>
1867 if it's a video).
1868
1869 =cut
1870
1871 sub GetIssuingCharges {
1872
1873     # calculate charges due
1874     my ( $itemnumber, $borrowernumber ) = @_;
1875     my $charge = 0;
1876     my $dbh    = C4::Context->dbh;
1877     my $item_type;
1878
1879     # Get the book's item type and rental charge (via its biblioitem).
1880     my $qcharge =     "SELECT itemtypes.itemtype,rentalcharge FROM items
1881             LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
1882         $qcharge .= (C4::Context->preference('item-level_itypes'))
1883                 ? " LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1884                 : " LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1885         
1886     $qcharge .=      "WHERE items.itemnumber =?";
1887    
1888     my $sth1 = $dbh->prepare($qcharge);
1889     $sth1->execute($itemnumber);
1890     if ( my $data1 = $sth1->fetchrow_hashref ) {
1891         $item_type = $data1->{'itemtype'};
1892         $charge    = $data1->{'rentalcharge'};
1893         my $q2 = "SELECT rentaldiscount FROM borrowers
1894             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
1895             WHERE borrowers.borrowernumber = ?
1896             AND issuingrules.itemtype = ?";
1897         my $sth2 = $dbh->prepare($q2);
1898         $sth2->execute( $borrowernumber, $item_type );
1899         if ( my $data2 = $sth2->fetchrow_hashref ) {
1900             my $discount = $data2->{'rentaldiscount'};
1901             if ( $discount eq 'NULL' ) {
1902                 $discount = 0;
1903             }
1904             $charge = ( $charge * ( 100 - $discount ) ) / 100;
1905         }
1906         $sth2->finish;
1907     }
1908
1909     $sth1->finish;
1910     return ( $charge, $item_type );
1911 }
1912
1913 =head2 AddIssuingCharge
1914
1915 &AddIssuingCharge( $itemno, $borrowernumber, $charge )
1916
1917 =cut
1918
1919 sub AddIssuingCharge {
1920     my ( $itemnumber, $borrowernumber, $charge ) = @_;
1921     my $dbh = C4::Context->dbh;
1922     my $nextaccntno = getnextacctno( $borrowernumber );
1923     my $query ="
1924         INSERT INTO accountlines
1925             (borrowernumber, itemnumber, accountno,
1926             date, amount, description, accounttype,
1927             amountoutstanding)
1928         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?)
1929     ";
1930     my $sth = $dbh->prepare($query);
1931     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge );
1932     $sth->finish;
1933 }
1934
1935 =head2 GetTransfers
1936
1937 GetTransfers($itemnumber);
1938
1939 =cut
1940
1941 sub GetTransfers {
1942     my ($itemnumber) = @_;
1943
1944     my $dbh = C4::Context->dbh;
1945
1946     my $query = '
1947         SELECT datesent,
1948                frombranch,
1949                tobranch
1950         FROM branchtransfers
1951         WHERE itemnumber = ?
1952           AND datearrived IS NULL
1953         ';
1954     my $sth = $dbh->prepare($query);
1955     $sth->execute($itemnumber);
1956     my @row = $sth->fetchrow_array();
1957     $sth->finish;
1958     return @row;
1959 }
1960
1961
1962 =head2 GetTransfersFromTo
1963
1964 @results = GetTransfersFromTo($frombranch,$tobranch);
1965
1966 Returns the list of pending transfers between $from and $to branch
1967
1968 =cut
1969
1970 sub GetTransfersFromTo {
1971     my ( $frombranch, $tobranch ) = @_;
1972     return unless ( $frombranch && $tobranch );
1973     my $dbh   = C4::Context->dbh;
1974     my $query = "
1975         SELECT itemnumber,datesent,frombranch
1976         FROM   branchtransfers
1977         WHERE  frombranch=?
1978           AND  tobranch=?
1979           AND datearrived IS NULL
1980     ";
1981     my $sth = $dbh->prepare($query);
1982     $sth->execute( $frombranch, $tobranch );
1983     my @gettransfers;
1984
1985     while ( my $data = $sth->fetchrow_hashref ) {
1986         push @gettransfers, $data;
1987     }
1988     $sth->finish;
1989     return (@gettransfers);
1990 }
1991
1992 =head2 DeleteTransfer
1993
1994 &DeleteTransfer($itemnumber);
1995
1996 =cut
1997
1998 sub DeleteTransfer {
1999     my ($itemnumber) = @_;
2000     my $dbh          = C4::Context->dbh;
2001     my $sth          = $dbh->prepare(
2002         "DELETE FROM branchtransfers
2003          WHERE itemnumber=?
2004          AND datearrived IS NULL "
2005     );
2006     $sth->execute($itemnumber);
2007     $sth->finish;
2008 }
2009
2010 =head2 AnonymiseIssueHistory
2011
2012 $rows = AnonymiseIssueHistory($borrowernumber,$date)
2013
2014 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2015 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2016
2017 return the number of affected rows.
2018
2019 =cut
2020
2021 sub AnonymiseIssueHistory {
2022     my $date           = shift;
2023     my $borrowernumber = shift;
2024     my $dbh            = C4::Context->dbh;
2025     my $query          = "
2026         UPDATE old_issues
2027         SET    borrowernumber = NULL
2028         WHERE  returndate < '".$date."'
2029           AND borrowernumber IS NOT NULL
2030     ";
2031     $query .= " AND borrowernumber = '".$borrowernumber."'" if defined $borrowernumber;
2032     my $rows_affected = $dbh->do($query);
2033     return $rows_affected;
2034 }
2035
2036 =head2 updateWrongTransfer
2037
2038 $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2039
2040 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 
2041
2042 =cut
2043
2044 sub updateWrongTransfer {
2045         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2046         my $dbh = C4::Context->dbh;     
2047 # first step validate the actual line of transfert .
2048         my $sth =
2049                 $dbh->prepare(
2050                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2051                 );
2052                 $sth->execute($FromLibrary,$itemNumber);
2053                 $sth->finish;
2054
2055 # second step create a new line of branchtransfer to the right location .
2056         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2057
2058 #third step changing holdingbranch of item
2059         UpdateHoldingbranch($FromLibrary,$itemNumber);
2060 }
2061
2062 =head2 UpdateHoldingbranch
2063
2064 $items = UpdateHoldingbranch($branch,$itmenumber);
2065 Simple methode for updating hodlingbranch in items BDD line
2066
2067 =cut
2068
2069 sub UpdateHoldingbranch {
2070         my ( $branch,$itemnumber ) = @_;
2071     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2072 }
2073
2074 =head2 CalcDateDue
2075
2076 $newdatedue = CalcDateDue($startdate,$loanlength,$branchcode);
2077 this function calculates the due date given the loan length ,
2078 checking against the holidays calendar as per the 'useDaysMode' syspref.
2079 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2080 C<$branch>  = location whose calendar to use
2081 C<$loanlength>  = loan length prior to adjustment
2082 =cut
2083
2084 sub CalcDateDue { 
2085         my ($startdate,$loanlength,$branch) = @_;
2086         if(C4::Context->preference('useDaysMode') eq 'Days') {  # ignoring calendar
2087                 my $datedue = time + ($loanlength) * 86400;
2088         #FIXME - assumes now even though we take a startdate 
2089                 my @datearr  = localtime($datedue);
2090                 return C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
2091         } else {
2092                 my $calendar = C4::Calendar->new(  branchcode => $branch );
2093                 my $datedue = $calendar->addDate($startdate, $loanlength);
2094                 return $datedue;
2095         }
2096 }
2097
2098 =head2 CheckValidDatedue
2099        This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
2100        To be replaced by CalcDateDue() once C4::Calendar use is tested.
2101
2102 $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
2103 this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
2104 C<$date_due>   = returndate calculate with no day check
2105 C<$itemnumber>  = itemnumber
2106 C<$branchcode>  = location of issue (affected by 'CircControl' syspref)
2107 C<$loanlength>  = loan length prior to adjustment
2108 =cut
2109
2110 sub CheckValidDatedue {
2111 my ($date_due,$itemnumber,$branchcode)=@_;
2112 my @datedue=split('-',$date_due->output('iso'));
2113 my $years=$datedue[0];
2114 my $month=$datedue[1];
2115 my $day=$datedue[2];
2116 # die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
2117 my $dow;
2118 for (my $i=0;$i<2;$i++){
2119     $dow=Day_of_Week($years,$month,$day);
2120     ($dow=0) if ($dow>6);
2121     my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
2122     my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2123     my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2124         if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
2125         $i=0;
2126         (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
2127         }
2128     }
2129     my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
2130 return $newdatedue;
2131 }
2132
2133
2134 =head2 CheckRepeatableHolidays
2135
2136 $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2137 this function checks if the date due is a repeatable holiday
2138 C<$date_due>   = returndate calculate with no day check
2139 C<$itemnumber>  = itemnumber
2140 C<$branchcode>  = localisation of issue 
2141
2142 =cut
2143
2144 sub CheckRepeatableHolidays{
2145 my($itemnumber,$week_day,$branchcode)=@_;
2146 my $dbh = C4::Context->dbh;
2147 my $query = qq|SELECT count(*)  
2148         FROM repeatable_holidays 
2149         WHERE branchcode=?
2150         AND weekday=?|;
2151 my $sth = $dbh->prepare($query);
2152 $sth->execute($branchcode,$week_day);
2153 my $result=$sth->fetchrow;
2154 $sth->finish;
2155 return $result;
2156 }
2157
2158
2159 =head2 CheckSpecialHolidays
2160
2161 $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2162 this function check if the date is a special holiday
2163 C<$years>   = the years of datedue
2164 C<$month>   = the month of datedue
2165 C<$day>     = the day of datedue
2166 C<$itemnumber>  = itemnumber
2167 C<$branchcode>  = localisation of issue 
2168
2169 =cut
2170
2171 sub CheckSpecialHolidays{
2172 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
2173 my $dbh = C4::Context->dbh;
2174 my $query=qq|SELECT count(*) 
2175              FROM `special_holidays`
2176              WHERE year=?
2177              AND month=?
2178              AND day=?
2179              AND branchcode=?
2180             |;
2181 my $sth = $dbh->prepare($query);
2182 $sth->execute($years,$month,$day,$branchcode);
2183 my $countspecial=$sth->fetchrow ;
2184 $sth->finish;
2185 return $countspecial;
2186 }
2187
2188 =head2 CheckRepeatableSpecialHolidays
2189
2190 $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2191 this function check if the date is a repeatble special holidays
2192 C<$month>   = the month of datedue
2193 C<$day>     = the day of datedue
2194 C<$itemnumber>  = itemnumber
2195 C<$branchcode>  = localisation of issue 
2196
2197 =cut
2198
2199 sub CheckRepeatableSpecialHolidays{
2200 my ($month,$day,$itemnumber,$branchcode) = @_;
2201 my $dbh = C4::Context->dbh;
2202 my $query=qq|SELECT count(*) 
2203              FROM `repeatable_holidays`
2204              WHERE month=?
2205              AND day=?
2206              AND branchcode=?
2207             |;
2208 my $sth = $dbh->prepare($query);
2209 $sth->execute($month,$day,$branchcode);
2210 my $countspecial=$sth->fetchrow ;
2211 $sth->finish;
2212 return $countspecial;
2213 }
2214
2215
2216
2217 sub CheckValidBarcode{
2218 my ($barcode) = @_;
2219 my $dbh = C4::Context->dbh;
2220 my $query=qq|SELECT count(*) 
2221              FROM items 
2222              WHERE barcode=?
2223             |;
2224 my $sth = $dbh->prepare($query);
2225 $sth->execute($barcode);
2226 my $exist=$sth->fetchrow ;
2227 $sth->finish;
2228 return $exist;
2229 }
2230
2231 1;
2232
2233 __END__
2234
2235 =head1 AUTHOR
2236
2237 Koha Developement team <info@koha.org>
2238
2239 =cut
2240