Bug 7671 : add a real primary key accountlines_id in accountlines
[koha.git] / C4 / Circulation.pm
1 package C4::Circulation;
2
3 # Copyright 2000-2002 Katipo Communications
4 # copyright 2010 BibLibre
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22 use strict;
23 #use warnings; FIXME - Bug 2505
24 use DateTime;
25 use C4::Context;
26 use C4::Stats;
27 use C4::Reserves;
28 use C4::Biblio;
29 use C4::Items;
30 use C4::Members;
31 use C4::Dates;
32 use C4::Dates qw(format_date);
33 use C4::Accounts;
34 use C4::ItemCirculationAlertPreference;
35 use C4::Message;
36 use C4::Debug;
37 use C4::Branch; # GetBranches
38 use C4::Log; # logaction
39 use C4::Koha qw(GetAuthorisedValueByCode);
40 use C4::Overdues qw(CalcFine UpdateFine);
41 use Algorithm::CheckDigits;
42
43 use Data::Dumper;
44 use Koha::DateUtils;
45 use Koha::Calendar;
46 use Carp;
47 use Date::Calc qw(
48   Today
49   Today_and_Now
50   Add_Delta_YM
51   Add_Delta_DHMS
52   Date_to_Days
53   Day_of_Week
54   Add_Delta_Days
55 );
56 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
57
58 BEGIN {
59         require Exporter;
60     $VERSION = 3.07.00.049;     # for version checking
61         @ISA    = qw(Exporter);
62
63         # FIXME subs that should probably be elsewhere
64         push @EXPORT, qw(
65                 &barcodedecode
66         &LostItem
67         &ReturnLostItem
68         );
69
70         # subs to deal with issuing a book
71         push @EXPORT, qw(
72                 &CanBookBeIssued
73                 &CanBookBeRenewed
74                 &AddIssue
75                 &AddRenewal
76                 &GetRenewCount
77                 &GetItemIssue
78                 &GetItemIssues
79                 &GetIssuingCharges
80                 &GetIssuingRule
81         &GetBranchBorrowerCircRule
82         &GetBranchItemRule
83                 &GetBiblioIssues
84                 &GetOpenIssue
85                 &AnonymiseIssueHistory
86         );
87
88         # subs to deal with returns
89         push @EXPORT, qw(
90                 &AddReturn
91         &MarkIssueReturned
92         );
93
94         # subs to deal with transfers
95         push @EXPORT, qw(
96                 &transferbook
97                 &GetTransfers
98                 &GetTransfersFromTo
99                 &updateWrongTransfer
100                 &DeleteTransfer
101                 &IsBranchTransferAllowed
102                 &CreateBranchTransferLimit
103                 &DeleteBranchTransferLimits
104         &TransferSlip
105         );
106
107     # subs to deal with offline circulation
108     push @EXPORT, qw(
109       &GetOfflineOperations
110       &GetOfflineOperation
111       &AddOfflineOperation
112       &DeleteOfflineOperation
113       &ProcessOfflineOperation
114     );
115 }
116
117 =head1 NAME
118
119 C4::Circulation - Koha circulation module
120
121 =head1 SYNOPSIS
122
123 use C4::Circulation;
124
125 =head1 DESCRIPTION
126
127 The functions in this module deal with circulation, issues, and
128 returns, as well as general information about the library.
129 Also deals with stocktaking.
130
131 =head1 FUNCTIONS
132
133 =head2 barcodedecode
134
135   $str = &barcodedecode($barcode, [$filter]);
136
137 Generic filter function for barcode string.
138 Called on every circ if the System Pref itemBarcodeInputFilter is set.
139 Will do some manipulation of the barcode for systems that deliver a barcode
140 to circulation.pl that differs from the barcode stored for the item.
141 For proper functioning of this filter, calling the function on the 
142 correct barcode string (items.barcode) should return an unaltered barcode.
143
144 The optional $filter argument is to allow for testing or explicit 
145 behavior that ignores the System Pref.  Valid values are the same as the 
146 System Pref options.
147
148 =cut
149
150 # FIXME -- the &decode fcn below should be wrapped into this one.
151 # FIXME -- these plugins should be moved out of Circulation.pm
152 #
153 sub barcodedecode {
154     my ($barcode, $filter) = @_;
155     my $branch = C4::Branch::mybranch();
156     $filter = C4::Context->preference('itemBarcodeInputFilter') unless $filter;
157     $filter or return $barcode;     # ensure filter is defined, else return untouched barcode
158         if ($filter eq 'whitespace') {
159                 $barcode =~ s/\s//g;
160         } elsif ($filter eq 'cuecat') {
161                 chomp($barcode);
162             my @fields = split( /\./, $barcode );
163             my @results = map( decode($_), @fields[ 1 .. $#fields ] );
164             ($#results == 2) and return $results[2];
165         } elsif ($filter eq 'T-prefix') {
166                 if ($barcode =~ /^[Tt](\d)/) {
167                         (defined($1) and $1 eq '0') and return $barcode;
168             $barcode = substr($barcode, 2) + 0;     # FIXME: probably should be substr($barcode, 1)
169                 }
170         return sprintf("T%07d", $barcode);
171         # FIXME: $barcode could be "T1", causing warning: substr outside of string
172         # Why drop the nonzero digit after the T?
173         # Why pass non-digits (or empty string) to "T%07d"?
174         } elsif ($filter eq 'libsuite8') {
175                 unless($barcode =~ m/^($branch)-/i){    #if barcode starts with branch code its in Koha style. Skip it.
176                         if($barcode =~ m/^(\d)/i){      #Some barcodes even start with 0's & numbers and are assumed to have b as the item type in the libsuite8 software
177                                 $barcode =~ s/^[0]*(\d+)$/$branch-b-$1/i;
178                         }else{
179                                 $barcode =~ s/^(\D+)[0]*(\d+)$/$branch-$1-$2/i;
180                         }
181                 }
182     } elsif ($filter eq 'EAN13') {
183         my $ean = CheckDigits('ean');
184         if ( $ean->is_valid($barcode) ) {
185             #$barcode = sprintf('%013d',$barcode); # this doesn't work on 32-bit systems
186             $barcode = '0' x ( 13 - length($barcode) ) . $barcode;
187         } else {
188             warn "# [$barcode] not valid EAN-13/UPC-A\n";
189         }
190         }
191     return $barcode;    # return barcode, modified or not
192 }
193
194 =head2 decode
195
196   $str = &decode($chunk);
197
198 Decodes a segment of a string emitted by a CueCat barcode scanner and
199 returns it.
200
201 FIXME: Should be replaced with Barcode::Cuecat from CPAN
202 or Javascript based decoding on the client side.
203
204 =cut
205
206 sub decode {
207     my ($encoded) = @_;
208     my $seq =
209       'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
210     my @s = map { index( $seq, $_ ); } split( //, $encoded );
211     my $l = ( $#s + 1 ) % 4;
212     if ($l) {
213         if ( $l == 1 ) {
214             # warn "Error: Cuecat decode parsing failed!";
215             return;
216         }
217         $l = 4 - $l;
218         $#s += $l;
219     }
220     my $r = '';
221     while ( $#s >= 0 ) {
222         my $n = ( ( $s[0] << 6 | $s[1] ) << 6 | $s[2] ) << 6 | $s[3];
223         $r .=
224             chr( ( $n >> 16 ) ^ 67 )
225          .chr( ( $n >> 8 & 255 ) ^ 67 )
226          .chr( ( $n & 255 ) ^ 67 );
227         @s = @s[ 4 .. $#s ];
228     }
229     $r = substr( $r, 0, length($r) - $l );
230     return $r;
231 }
232
233 =head2 transferbook
234
235   ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, 
236                                             $barcode, $ignore_reserves);
237
238 Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
239
240 C<$newbranch> is the code for the branch to which the item should be transferred.
241
242 C<$barcode> is the barcode of the item to be transferred.
243
244 If C<$ignore_reserves> is true, C<&transferbook> ignores reserves.
245 Otherwise, if an item is reserved, the transfer fails.
246
247 Returns three values:
248
249 =over
250
251 =item $dotransfer 
252
253 is true if the transfer was successful.
254
255 =item $messages
256
257 is a reference-to-hash which may have any of the following keys:
258
259 =over
260
261 =item C<BadBarcode>
262
263 There is no item in the catalog with the given barcode. The value is C<$barcode>.
264
265 =item C<IsPermanent>
266
267 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.
268
269 =item C<DestinationEqualsHolding>
270
271 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.
272
273 =item C<WasReturned>
274
275 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.
276
277 =item C<ResFound>
278
279 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>.
280
281 =item C<WasTransferred>
282
283 The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
284
285 =back
286
287 =back
288
289 =cut
290
291 sub transferbook {
292     my ( $tbr, $barcode, $ignoreRs ) = @_;
293     my $messages;
294     my $dotransfer      = 1;
295     my $branches        = GetBranches();
296     my $itemnumber = GetItemnumberFromBarcode( $barcode );
297     my $issue      = GetItemIssue($itemnumber);
298     my $biblio = GetBiblioFromItemNumber($itemnumber);
299
300     # bad barcode..
301     if ( not $itemnumber ) {
302         $messages->{'BadBarcode'} = $barcode;
303         $dotransfer = 0;
304     }
305
306     # get branches of book...
307     my $hbr = $biblio->{'homebranch'};
308     my $fbr = $biblio->{'holdingbranch'};
309
310     # if using Branch Transfer Limits
311     if ( C4::Context->preference("UseBranchTransferLimits") == 1 ) {
312         if ( C4::Context->preference("item-level_itypes") && C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ) {
313             if ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{'itype'} ) ) {
314                 $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{'itype'};
315                 $dotransfer = 0;
316             }
317         } elsif ( ! IsBranchTransferAllowed( $tbr, $fbr, $biblio->{ C4::Context->preference("BranchTransferLimitsType") } ) ) {
318             $messages->{'NotAllowed'} = $tbr . "::" . $biblio->{ C4::Context->preference("BranchTransferLimitsType") };
319             $dotransfer = 0;
320         }
321     }
322
323     # if is permanent...
324     if ( $hbr && $branches->{$hbr}->{'PE'} ) {
325         $messages->{'IsPermanent'} = $hbr;
326         $dotransfer = 0;
327     }
328
329     # can't transfer book if is already there....
330     if ( $fbr eq $tbr ) {
331         $messages->{'DestinationEqualsHolding'} = 1;
332         $dotransfer = 0;
333     }
334
335     # check if it is still issued to someone, return it...
336     if ($issue->{borrowernumber}) {
337         AddReturn( $barcode, $fbr );
338         $messages->{'WasReturned'} = $issue->{borrowernumber};
339     }
340
341     # find reserves.....
342     # That'll save a database query.
343     my ( $resfound, $resrec, undef ) =
344       CheckReserves( $itemnumber );
345     if ( $resfound and not $ignoreRs ) {
346         $resrec->{'ResFound'} = $resfound;
347
348         #         $messages->{'ResFound'} = $resrec;
349         $dotransfer = 1;
350     }
351
352     #actually do the transfer....
353     if ($dotransfer) {
354         ModItemTransfer( $itemnumber, $fbr, $tbr );
355
356         # don't need to update MARC anymore, we do it in batch now
357         $messages->{'WasTransfered'} = 1;
358
359     }
360     ModDateLastSeen( $itemnumber );
361     return ( $dotransfer, $messages, $biblio );
362 }
363
364
365 sub TooMany {
366     my $borrower        = shift;
367     my $biblionumber = shift;
368         my $item                = shift;
369     my $cat_borrower    = $borrower->{'categorycode'};
370     my $dbh             = C4::Context->dbh;
371         my $branch;
372         # Get which branchcode we need
373         $branch = _GetCircControlBranch($item,$borrower);
374         my $type = (C4::Context->preference('item-level_itypes')) 
375                         ? $item->{'itype'}         # item-level
376                         : $item->{'itemtype'};     # biblio-level
377  
378     # given branch, patron category, and item type, determine
379     # applicable issuing rule
380     my $issuing_rule = GetIssuingRule($cat_borrower, $type, $branch);
381
382     # if a rule is found and has a loan limit set, count
383     # how many loans the patron already has that meet that
384     # rule
385     if (defined($issuing_rule) and defined($issuing_rule->{'maxissueqty'})) {
386         my @bind_params;
387         my $count_query = "SELECT COUNT(*) FROM issues
388                            JOIN items USING (itemnumber) ";
389
390         my $rule_itemtype = $issuing_rule->{itemtype};
391         if ($rule_itemtype eq "*") {
392             # matching rule has the default item type, so count only
393             # those existing loans that don't fall under a more
394             # specific rule
395             if (C4::Context->preference('item-level_itypes')) {
396                 $count_query .= " WHERE items.itype NOT IN (
397                                     SELECT itemtype FROM issuingrules
398                                     WHERE branchcode = ?
399                                     AND   (categorycode = ? OR categorycode = ?)
400                                     AND   itemtype <> '*'
401                                   ) ";
402             } else { 
403                 $count_query .= " JOIN  biblioitems USING (biblionumber) 
404                                   WHERE biblioitems.itemtype NOT IN (
405                                     SELECT itemtype FROM issuingrules
406                                     WHERE branchcode = ?
407                                     AND   (categorycode = ? OR categorycode = ?)
408                                     AND   itemtype <> '*'
409                                   ) ";
410             }
411             push @bind_params, $issuing_rule->{branchcode};
412             push @bind_params, $issuing_rule->{categorycode};
413             push @bind_params, $cat_borrower;
414         } else {
415             # rule has specific item type, so count loans of that
416             # specific item type
417             if (C4::Context->preference('item-level_itypes')) {
418                 $count_query .= " WHERE items.itype = ? ";
419             } else { 
420                 $count_query .= " JOIN  biblioitems USING (biblionumber) 
421                                   WHERE biblioitems.itemtype= ? ";
422             }
423             push @bind_params, $type;
424         }
425
426         $count_query .= " AND borrowernumber = ? ";
427         push @bind_params, $borrower->{'borrowernumber'};
428         my $rule_branch = $issuing_rule->{branchcode};
429         if ($rule_branch ne "*") {
430             if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
431                 $count_query .= " AND issues.branchcode = ? ";
432                 push @bind_params, $branch;
433             } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
434                 ; # if branch is the patron's home branch, then count all loans by patron
435             } else {
436                 $count_query .= " AND items.homebranch = ? ";
437                 push @bind_params, $branch;
438             }
439         }
440
441         my $count_sth = $dbh->prepare($count_query);
442         $count_sth->execute(@bind_params);
443         my ($current_loan_count) = $count_sth->fetchrow_array;
444
445         my $max_loans_allowed = $issuing_rule->{'maxissueqty'};
446         if ($current_loan_count >= $max_loans_allowed) {
447             return ($current_loan_count, $max_loans_allowed);
448         }
449     }
450
451     # Now count total loans against the limit for the branch
452     my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
453     if (defined($branch_borrower_circ_rule->{maxissueqty})) {
454         my @bind_params = ();
455         my $branch_count_query = "SELECT COUNT(*) FROM issues
456                                   JOIN items USING (itemnumber)
457                                   WHERE borrowernumber = ? ";
458         push @bind_params, $borrower->{borrowernumber};
459
460         if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
461             $branch_count_query .= " AND issues.branchcode = ? ";
462             push @bind_params, $branch;
463         } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
464             ; # if branch is the patron's home branch, then count all loans by patron
465         } else {
466             $branch_count_query .= " AND items.homebranch = ? ";
467             push @bind_params, $branch;
468         }
469         my $branch_count_sth = $dbh->prepare($branch_count_query);
470         $branch_count_sth->execute(@bind_params);
471         my ($current_loan_count) = $branch_count_sth->fetchrow_array;
472
473         my $max_loans_allowed = $branch_borrower_circ_rule->{maxissueqty};
474         if ($current_loan_count >= $max_loans_allowed) {
475             return ($current_loan_count, $max_loans_allowed);
476         }
477     }
478
479     # OK, the patron can issue !!!
480     return;
481 }
482
483 =head2 itemissues
484
485   @issues = &itemissues($biblioitemnumber, $biblio);
486
487 Looks up information about who has borrowed the bookZ<>(s) with the
488 given biblioitemnumber.
489
490 C<$biblio> is ignored.
491
492 C<&itemissues> returns an array of references-to-hash. The keys
493 include the fields from the C<items> table in the Koha database.
494 Additional keys include:
495
496 =over 4
497
498 =item C<date_due>
499
500 If the item is currently on loan, this gives the due date.
501
502 If the item is not on loan, then this is either "Available" or
503 "Cancelled", if the item has been withdrawn.
504
505 =item C<card>
506
507 If the item is currently on loan, this gives the card number of the
508 patron who currently has the item.
509
510 =item C<timestamp0>, C<timestamp1>, C<timestamp2>
511
512 These give the timestamp for the last three times the item was
513 borrowed.
514
515 =item C<card0>, C<card1>, C<card2>
516
517 The card number of the last three patrons who borrowed this item.
518
519 =item C<borrower0>, C<borrower1>, C<borrower2>
520
521 The borrower number of the last three patrons who borrowed this item.
522
523 =back
524
525 =cut
526
527 #'
528 sub itemissues {
529     my ( $bibitem, $biblio ) = @_;
530     my $dbh = C4::Context->dbh;
531     my $sth =
532       $dbh->prepare("Select * from items where items.biblioitemnumber = ?")
533       || die $dbh->errstr;
534     my $i = 0;
535     my @results;
536
537     $sth->execute($bibitem) || die $sth->errstr;
538
539     while ( my $data = $sth->fetchrow_hashref ) {
540
541         # Find out who currently has this item.
542         # FIXME - Wouldn't it be better to do this as a left join of
543         # some sort? Currently, this code assumes that if
544         # fetchrow_hashref() fails, then the book is on the shelf.
545         # fetchrow_hashref() can fail for any number of reasons (e.g.,
546         # database server crash), not just because no items match the
547         # search criteria.
548         my $sth2 = $dbh->prepare(
549             "SELECT * FROM issues
550                 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
551                 WHERE itemnumber = ?
552             "
553         );
554
555         $sth2->execute( $data->{'itemnumber'} );
556         if ( my $data2 = $sth2->fetchrow_hashref ) {
557             $data->{'date_due'} = $data2->{'date_due'};
558             $data->{'card'}     = $data2->{'cardnumber'};
559             $data->{'borrower'} = $data2->{'borrowernumber'};
560         }
561         else {
562             $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
563         }
564
565
566         # Find the last 3 people who borrowed this item.
567         $sth2 = $dbh->prepare(
568             "SELECT * FROM old_issues
569                 LEFT JOIN borrowers ON  issues.borrowernumber = borrowers.borrowernumber
570                 WHERE itemnumber = ?
571                 ORDER BY returndate DESC,timestamp DESC"
572         );
573
574         $sth2->execute( $data->{'itemnumber'} );
575         for ( my $i2 = 0 ; $i2 < 2 ; $i2++ )
576         {    # FIXME : error if there is less than 3 pple borrowing this item
577             if ( my $data2 = $sth2->fetchrow_hashref ) {
578                 $data->{"timestamp$i2"} = $data2->{'timestamp'};
579                 $data->{"card$i2"}      = $data2->{'cardnumber'};
580                 $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
581             }    # if
582         }    # for
583
584         $results[$i] = $data;
585         $i++;
586     }
587
588     return (@results);
589 }
590
591 =head2 CanBookBeIssued
592
593   ( $issuingimpossible, $needsconfirmation ) =  CanBookBeIssued( $borrower, 
594                       $barcode, $duedatespec, $inprocess, $ignore_reserves );
595
596 Check if a book can be issued.
597
598 C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
599
600 =over 4
601
602 =item C<$borrower> hash with borrower informations (from GetMember or GetMemberDetails)
603
604 =item C<$barcode> is the bar code of the book being issued.
605
606 =item C<$duedatespec> is a C4::Dates object.
607
608 =item C<$inprocess> boolean switch
609 =item C<$ignore_reserves> boolean switch
610
611 =back
612
613 Returns :
614
615 =over 4
616
617 =item C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
618 Possible values are :
619
620 =back
621
622 =head3 INVALID_DATE 
623
624 sticky due date is invalid
625
626 =head3 GNA
627
628 borrower gone with no address
629
630 =head3 CARD_LOST
631
632 borrower declared it's card lost
633
634 =head3 DEBARRED
635
636 borrower debarred
637
638 =head3 UNKNOWN_BARCODE
639
640 barcode unknown
641
642 =head3 NOT_FOR_LOAN
643
644 item is not for loan
645
646 =head3 WTHDRAWN
647
648 item withdrawn.
649
650 =head3 RESTRICTED
651
652 item is restricted (set by ??)
653
654 C<$needsconfirmation> a reference to a hash. It contains reasons why the loan 
655 could be prevented, but ones that can be overriden by the operator.
656
657 Possible values are :
658
659 =head3 DEBT
660
661 borrower has debts.
662
663 =head3 RENEW_ISSUE
664
665 renewing, not issuing
666
667 =head3 ISSUED_TO_ANOTHER
668
669 issued to someone else.
670
671 =head3 RESERVED
672
673 reserved for someone else.
674
675 =head3 INVALID_DATE
676
677 sticky due date is invalid or due date in the past
678
679 =head3 TOO_MANY
680
681 if the borrower borrows to much things
682
683 =cut
684
685 sub CanBookBeIssued {
686     my ( $borrower, $barcode, $duedate, $inprocess, $ignore_reserves ) = @_;
687     my %needsconfirmation;    # filled with problems that needs confirmations
688     my %issuingimpossible;    # filled with problems that causes the issue to be IMPOSSIBLE
689     my %alerts;               # filled with messages that shouldn't stop issuing, but the librarian should be aware of.
690
691     my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
692     my $issue = GetItemIssue($item->{itemnumber});
693         my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
694         $item->{'itemtype'}=$item->{'itype'}; 
695     my $dbh             = C4::Context->dbh;
696
697     # MANDATORY CHECKS - unless item exists, nothing else matters
698     unless ( $item->{barcode} ) {
699         $issuingimpossible{UNKNOWN_BARCODE} = 1;
700     }
701         return ( \%issuingimpossible, \%needsconfirmation ) if %issuingimpossible;
702
703     #
704     # DUE DATE is OK ? -- should already have checked.
705     #
706     if ($duedate && ref $duedate ne 'DateTime') {
707         $duedate = dt_from_string($duedate);
708     }
709     my $now = DateTime->now( time_zone => C4::Context->tz() );
710     unless ( $duedate ) {
711         my $issuedate = $now->clone();
712
713         my $branch = _GetCircControlBranch($item,$borrower);
714         my $itype = ( C4::Context->preference('item-level_itypes') ) ? $item->{'itype'} : $biblioitem->{'itemtype'};
715         $duedate = CalcDateDue( $issuedate, $itype, $branch, $borrower );
716
717         # Offline circ calls AddIssue directly, doesn't run through here
718         #  So issuingimpossible should be ok.
719     }
720     if ($duedate) {
721         my $today = $now->clone();
722         $today->truncate( to => 'minute');
723         if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
724             $needsconfirmation{INVALID_DATE} = output_pref($duedate);
725         }
726     } else {
727             $issuingimpossible{INVALID_DATE} = output_pref($duedate);
728     }
729
730     #
731     # BORROWER STATUS
732     #
733     if ( $borrower->{'category_type'} eq 'X' && (  $item->{barcode}  )) { 
734         # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1  .
735         &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'}, undef, $item->{'ccode'});
736         ModDateLastSeen( $item->{'itemnumber'} );
737         return( { STATS => 1 }, {});
738     }
739     if ( $borrower->{flags}->{GNA} ) {
740         $issuingimpossible{GNA} = 1;
741     }
742     if ( $borrower->{flags}->{'LOST'} ) {
743         $issuingimpossible{CARD_LOST} = 1;
744     }
745     if ( $borrower->{flags}->{'DBARRED'} ) {
746         $issuingimpossible{DEBARRED} = 1;
747     }
748     if ( !defined $borrower->{dateexpiry} || $borrower->{'dateexpiry'} eq '0000-00-00') {
749         $issuingimpossible{EXPIRED} = 1;
750     } else {
751         my ($y, $m, $d) =  split /-/,$borrower->{'dateexpiry'};
752         if ($y && $m && $d) { # are we really writing oinvalid dates to borrs
753             my $expiry_dt = DateTime->new(
754                 year => $y,
755                 month => $m,
756                 day   => $d,
757                 time_zone => C4::Context->tz,
758             );
759             $expiry_dt->truncate( to => 'day');
760             my $today = $now->clone()->truncate(to => 'day');
761             if (DateTime->compare($today, $expiry_dt) == 1) {
762                 $issuingimpossible{EXPIRED} = 1;
763             }
764         } else {
765             carp("Invalid expity date in borr");
766             $issuingimpossible{EXPIRED} = 1;
767         }
768     }
769     #
770     # BORROWER STATUS
771     #
772
773     # DEBTS
774     my ($amount) =
775       C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->ymd() );
776     my $amountlimit = C4::Context->preference("noissuescharge");
777     my $allowfineoverride = C4::Context->preference("AllowFineOverride");
778     my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
779     if ( C4::Context->preference("IssuingInProcess") ) {
780         if ( $amount > $amountlimit && !$inprocess && !$allowfineoverride) {
781             $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
782         } elsif ( $amount > $amountlimit && !$inprocess && $allowfineoverride) {
783             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
784         } elsif ( $allfinesneedoverride && $amount > 0 && $amount <= $amountlimit && !$inprocess ) {
785             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
786         }
787     }
788     else {
789         if ( $amount > $amountlimit && $allowfineoverride ) {
790             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
791         } elsif ( $amount > $amountlimit && !$allowfineoverride) {
792             $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
793         } elsif ( $amount > 0 && $allfinesneedoverride ) {
794             $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
795         }
796     }
797
798     my ($blocktype, $count) = C4::Members::IsMemberBlocked($borrower->{'borrowernumber'});
799     if ($blocktype == -1) {
800         ## patron has outstanding overdue loans
801             if ( C4::Context->preference("OverduesBlockCirc") eq 'block'){
802                 $issuingimpossible{USERBLOCKEDOVERDUE} = $count;
803             }
804             elsif ( C4::Context->preference("OverduesBlockCirc") eq 'confirmation'){
805                 $needsconfirmation{USERBLOCKEDOVERDUE} = $count;
806             }
807     } elsif($blocktype == 1) {
808         # patron has accrued fine days
809         $issuingimpossible{USERBLOCKEDREMAINING} = $count;
810     }
811
812 #
813     # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
814     #
815         my ($current_loan_count, $max_loans_allowed) = TooMany( $borrower, $item->{biblionumber}, $item );
816     # if TooMany max_loans_allowed returns 0 the user doesn't have permission to check out this book
817     if (defined $max_loans_allowed && $max_loans_allowed == 0) {
818         $needsconfirmation{PATRON_CANT} = 1;
819     } else {
820         if($max_loans_allowed){
821             $needsconfirmation{TOO_MANY} = 1;
822             $needsconfirmation{current_loan_count} = $current_loan_count;
823             $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
824         }
825     }
826
827     #
828     # ITEM CHECKING
829     #
830     if (   $item->{'notforloan'}
831         && $item->{'notforloan'} > 0 )
832     {
833         if(!C4::Context->preference("AllowNotForLoanOverride")){
834             $issuingimpossible{NOT_FOR_LOAN} = 1;
835         }else{
836             $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
837         }
838     }
839     elsif ( !$item->{'notforloan'} ){
840         # we have to check itemtypes.notforloan also
841         if (C4::Context->preference('item-level_itypes')){
842             # this should probably be a subroutine
843             my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
844             $sth->execute($item->{'itemtype'});
845             my $notforloan=$sth->fetchrow_hashref();
846             $sth->finish();
847             if ($notforloan->{'notforloan'}) {
848                 if (!C4::Context->preference("AllowNotForLoanOverride")) {
849                     $issuingimpossible{NOT_FOR_LOAN} = 1;
850                 } else {
851                     $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
852                 }
853             }
854         }
855         elsif ($biblioitem->{'notforloan'} == 1){
856             if (!C4::Context->preference("AllowNotForLoanOverride")) {
857                 $issuingimpossible{NOT_FOR_LOAN} = 1;
858             } else {
859                 $needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
860             }
861         }
862     }
863     if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 )
864     {
865         $issuingimpossible{WTHDRAWN} = 1;
866     }
867     if (   $item->{'restricted'}
868         && $item->{'restricted'} == 1 )
869     {
870         $issuingimpossible{RESTRICTED} = 1;
871     }
872     if ( $item->{'itemlost'} && C4::Context->preference("IssueLostItem") ne 'nothing' ) {
873         my $code = GetAuthorisedValueByCode( 'LOST', $item->{'itemlost'} );
874         $needsconfirmation{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'confirm' );
875         $alerts{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'alert' );
876     }
877     if ( C4::Context->preference("IndependantBranches") ) {
878         my $userenv = C4::Context->userenv;
879         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
880             $issuingimpossible{ITEMNOTSAMEBRANCH} = 1
881               if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
882             $needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} )
883               if ( $borrower->{'branchcode'} ne $userenv->{branch} );
884         }
885     }
886
887     #
888     # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
889     #
890     if ( $issue->{borrowernumber} && $issue->{borrowernumber} eq $borrower->{'borrowernumber'} )
891     {
892
893         # Already issued to current borrower. Ask whether the loan should
894         # be renewed.
895         my ($CanBookBeRenewed,$renewerror) = CanBookBeRenewed(
896             $borrower->{'borrowernumber'},
897             $item->{'itemnumber'}
898         );
899         if ( $CanBookBeRenewed == 0 ) {    # no more renewals allowed
900             $issuingimpossible{NO_MORE_RENEWALS} = 1;
901         }
902         else {
903             $needsconfirmation{RENEW_ISSUE} = 1;
904         }
905     }
906     elsif ($issue->{borrowernumber}) {
907
908         # issued to someone else
909         my $currborinfo =    C4::Members::GetMember( borrowernumber => $issue->{borrowernumber} );
910
911 #        warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
912         $needsconfirmation{ISSUED_TO_ANOTHER} = 1;
913         $needsconfirmation{issued_firstname} = $currborinfo->{'firstname'};
914         $needsconfirmation{issued_surname} = $currborinfo->{'surname'};
915         $needsconfirmation{issued_cardnumber} = $currborinfo->{'cardnumber'};
916         $needsconfirmation{issued_borrowernumber} = $currborinfo->{'borrowernumber'};
917     }
918
919     unless ( $ignore_reserves ) {
920         # See if the item is on reserve.
921         my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
922         if ($restype) {
923             my $resbor = $res->{'borrowernumber'};
924             if ( $resbor ne $borrower->{'borrowernumber'} ) {
925                 my ( $resborrower ) = C4::Members::GetMember( borrowernumber => $resbor );
926                 my $branchname = GetBranchName( $res->{'branchcode'} );
927                 if ( $restype eq "Waiting" )
928                 {
929                     # The item is on reserve and waiting, but has been
930                     # reserved by some other patron.
931                     $needsconfirmation{RESERVE_WAITING} = 1;
932                     $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
933                     $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
934                     $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
935                     $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
936                     $needsconfirmation{'resbranchname'} = $branchname;
937                     $needsconfirmation{'reswaitingdate'} = format_date($res->{'waitingdate'});
938                 }
939                 elsif ( $restype eq "Reserved" ) {
940                     # The item is on reserve for someone else.
941                     $needsconfirmation{RESERVED} = 1;
942                     $needsconfirmation{'resfirstname'} = $resborrower->{'firstname'};
943                     $needsconfirmation{'ressurname'} = $resborrower->{'surname'};
944                     $needsconfirmation{'rescardnumber'} = $resborrower->{'cardnumber'};
945                     $needsconfirmation{'resborrowernumber'} = $resborrower->{'borrowernumber'};
946                     $needsconfirmation{'resbranchname'} = $branchname;
947                     $needsconfirmation{'resreservedate'} = format_date($res->{'reservedate'});
948                 }
949             }
950         }
951     }
952     #
953     # CHECK AGE RESTRICTION
954     #
955
956     # get $marker from preferences. Could be something like "FSK|PEGI|Alter|Age:"
957     my $markers = C4::Context->preference('AgeRestrictionMarker' );
958     my $bibvalues = $biblioitem->{'agerestriction'};
959     if (($markers)&&($bibvalues))
960     {
961         # Split $bibvalues to something like FSK 16 or PEGI 6
962         my @values = split ' ', $bibvalues;
963
964         # Search first occurence of one of the markers
965         my @markers = split /\|/, $markers;
966         my $index = 0;
967         my $take = -1;
968         for my $value (@values) {
969             $index ++;
970             for my $marker (@markers) {
971                 $marker =~ s/^\s+//; #remove leading spaces
972                 $marker =~ s/\s+$//; #remove trailing spaces
973                 if (uc($marker) eq uc($value)) {
974                     $take = $index;
975                     last;
976                 }
977             }
978             if ($take > -1) {
979                 last;
980             }
981         }
982         # Index points to the next value
983         my $restrictionyear = 0;
984         if (($take <= $#values) && ($take >= 0)){
985             $restrictionyear += $values[$take];
986         }
987
988         if ($restrictionyear > 0) {
989             if ( $borrower->{'dateofbirth'}  ) {
990                 my @alloweddate =  split /-/,$borrower->{'dateofbirth'} ;
991                 $alloweddate[0] += $restrictionyear;
992                 #Prevent runime eror on leap year (invalid date)
993                 if (($alloweddate[1] == 2) && ($alloweddate[2] == 29)) {
994                     $alloweddate[2] = 28;
995                 }
996
997                 if ( Date_to_Days(Today) <  Date_to_Days(@alloweddate) -1  ) {
998                     if (C4::Context->preference('AgeRestrictionOverride' )) {
999                         $needsconfirmation{AGE_RESTRICTION} = "$bibvalues";
1000                     }
1001                     else {
1002                         $issuingimpossible{AGE_RESTRICTION} = "$bibvalues";
1003                     }
1004                 }
1005             }
1006         }
1007     }
1008     return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
1009 }
1010
1011 =head2 AddIssue
1012
1013   &AddIssue($borrower, $barcode, [$datedue], [$cancelreserve], [$issuedate])
1014
1015 Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
1016
1017 =over 4
1018
1019 =item C<$borrower> is a hash with borrower informations (from GetMember or GetMemberDetails).
1020
1021 =item C<$barcode> is the barcode of the item being issued.
1022
1023 =item C<$datedue> is a C4::Dates object for the max date of return, i.e. the date due (optional).
1024 Calculated if empty.
1025
1026 =item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
1027
1028 =item C<$issuedate> is the date to issue the item in iso (YYYY-MM-DD) format (optional).
1029 Defaults to today.  Unlike C<$datedue>, NOT a C4::Dates object, unfortunately.
1030
1031 AddIssue does the following things :
1032
1033   - step 01: check that there is a borrowernumber & a barcode provided
1034   - check for RENEWAL (book issued & being issued to the same patron)
1035       - renewal YES = Calculate Charge & renew
1036       - renewal NO  =
1037           * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
1038           * RESERVE PLACED ?
1039               - fill reserve if reserve to this patron
1040               - cancel reserve or not, otherwise
1041           * TRANSFERT PENDING ?
1042               - complete the transfert
1043           * ISSUE THE BOOK
1044
1045 =back
1046
1047 =cut
1048
1049 sub AddIssue {
1050     my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
1051     my $dbh = C4::Context->dbh;
1052         my $barcodecheck=CheckValidBarcode($barcode);
1053     if ($datedue && ref $datedue ne 'DateTime') {
1054         $datedue = dt_from_string($datedue);
1055     }
1056     # $issuedate defaults to today.
1057     if ( ! defined $issuedate ) {
1058         $issuedate = DateTime->now(time_zone => C4::Context->tz());
1059     }
1060     else {
1061         if ( ref $issuedate ne 'DateTime') {
1062             $issuedate = dt_from_string($issuedate);
1063
1064         }
1065     }
1066         if ($borrower and $barcode and $barcodecheck ne '0'){#??? wtf
1067                 # find which item we issue
1068                 my $item = GetItem('', $barcode) or return undef;       # if we don't get an Item, abort.
1069                 my $branch = _GetCircControlBranch($item,$borrower);
1070                 
1071                 # get actual issuing if there is one
1072                 my $actualissue = GetItemIssue( $item->{itemnumber});
1073                 
1074                 # get biblioinformation for this item
1075                 my $biblio = GetBiblioFromItemNumber($item->{itemnumber});
1076                 
1077                 #
1078                 # check if we just renew the issue.
1079                 #
1080                 if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
1081                     $datedue = AddRenewal(
1082                         $borrower->{'borrowernumber'},
1083                         $item->{'itemnumber'},
1084                         $branch,
1085                         $datedue,
1086                         $issuedate, # here interpreted as the renewal date
1087                         );
1088                 }
1089                 else {
1090         # it's NOT a renewal
1091                         if ( $actualissue->{borrowernumber}) {
1092                                 # This book is currently on loan, but not to the person
1093                                 # who wants to borrow it now. mark it returned before issuing to the new borrower
1094                                 AddReturn(
1095                                         $item->{'barcode'},
1096                                         C4::Context->userenv->{'branch'}
1097                                 );
1098                         }
1099
1100             MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
1101                         # Starting process for transfer job (checking transfert and validate it if we have one)
1102             my ($datesent) = GetTransfers($item->{'itemnumber'});
1103             if ($datesent) {
1104         #       updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for visibility of this case (maybe for stats ....)
1105                 my $sth =
1106                     $dbh->prepare(
1107                     "UPDATE branchtransfers 
1108                         SET datearrived = now(),
1109                         tobranch = ?,
1110                         comments = 'Forced branchtransfer'
1111                     WHERE itemnumber= ? AND datearrived IS NULL"
1112                     );
1113                 $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
1114             }
1115
1116         # Record in the database the fact that the book was issued.
1117         my $sth =
1118           $dbh->prepare(
1119                 "INSERT INTO issues
1120                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
1121                 VALUES (?,?,?,?,?)"
1122           );
1123         unless ($datedue) {
1124             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
1125             $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
1126
1127         }
1128         $datedue->truncate( to => 'minute');
1129         $sth->execute(
1130             $borrower->{'borrowernumber'},      # borrowernumber
1131             $item->{'itemnumber'},              # itemnumber
1132             $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate
1133             $datedue->strftime('%Y-%m-%d %H:%M:00'),   # date_due
1134             C4::Context->userenv->{'branch'}    # branchcode
1135         );
1136         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
1137           CartToShelf( $item->{'itemnumber'} );
1138         }
1139         $item->{'issues'}++;
1140         if ( C4::Context->preference('UpdateTotalIssuesOnCirc') ) {
1141             UpdateTotalIssues($item->{'biblionumber'}, 1);
1142         }
1143
1144         ## If item was lost, it has now been found, reverse any list item charges if neccessary.
1145         if ( $item->{'itemlost'} ) {
1146             _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
1147         }
1148
1149         ModItem({ issues           => $item->{'issues'},
1150                   holdingbranch    => C4::Context->userenv->{'branch'},
1151                   itemlost         => 0,
1152                   datelastborrowed => DateTime->now(time_zone => C4::Context->tz())->ymd(),
1153                   onloan           => $datedue->ymd(),
1154                 }, $item->{'biblionumber'}, $item->{'itemnumber'});
1155         ModDateLastSeen( $item->{'itemnumber'} );
1156
1157         # If it costs to borrow this book, charge it to the patron's account.
1158         my ( $charge, $itemtype ) = GetIssuingCharges(
1159             $item->{'itemnumber'},
1160             $borrower->{'borrowernumber'}
1161         );
1162         if ( $charge > 0 ) {
1163             AddIssuingCharge(
1164                 $item->{'itemnumber'},
1165                 $borrower->{'borrowernumber'}, $charge
1166             );
1167             $item->{'charge'} = $charge;
1168         }
1169
1170         # Record the fact that this book was issued.
1171         &UpdateStats(
1172             C4::Context->userenv->{'branch'},
1173             'issue', $charge,
1174             ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
1175             $item->{'itype'}, $borrower->{'borrowernumber'}, undef, $item->{'ccode'}
1176         );
1177
1178         # Send a checkout slip.
1179         my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1180         my %conditions = (
1181             branchcode   => $branch,
1182             categorycode => $borrower->{categorycode},
1183             item_type    => $item->{itype},
1184             notification => 'CHECKOUT',
1185         );
1186         if ($circulation_alert->is_enabled_for(\%conditions)) {
1187             SendCirculationAlert({
1188                 type     => 'CHECKOUT',
1189                 item     => $item,
1190                 borrower => $borrower,
1191                 branch   => $branch,
1192             });
1193         }
1194     }
1195
1196     logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
1197         if C4::Context->preference("IssueLog");
1198   }
1199   return ($datedue);    # not necessarily the same as when it came in!
1200 }
1201
1202 =head2 GetLoanLength
1203
1204   my $loanlength = &GetLoanLength($borrowertype,$itemtype,branchcode)
1205
1206 Get loan length for an itemtype, a borrower type and a branch
1207
1208 =cut
1209
1210 sub GetLoanLength {
1211     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1212     my $dbh = C4::Context->dbh;
1213     my $sth =
1214       $dbh->prepare(
1215 'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null'
1216       );
1217 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
1218 # try to find issuelength & return the 1st available.
1219 # check with borrowertype, itemtype and branchcode, then without one of those parameters
1220     $sth->execute( $borrowertype, $itemtype, $branchcode );
1221     my $loanlength = $sth->fetchrow_hashref;
1222     return $loanlength
1223       if defined($loanlength) && $loanlength->{issuelength};
1224
1225     $sth->execute( $borrowertype, '*', $branchcode );
1226     $loanlength = $sth->fetchrow_hashref;
1227     return $loanlength
1228       if defined($loanlength) && $loanlength->{issuelength};
1229
1230     $sth->execute( '*', $itemtype, $branchcode );
1231     $loanlength = $sth->fetchrow_hashref;
1232     return $loanlength
1233       if defined($loanlength) && $loanlength->{issuelength};
1234
1235     $sth->execute( '*', '*', $branchcode );
1236     $loanlength = $sth->fetchrow_hashref;
1237     return $loanlength
1238       if defined($loanlength) && $loanlength->{issuelength};
1239
1240     $sth->execute( $borrowertype, $itemtype, '*' );
1241     $loanlength = $sth->fetchrow_hashref;
1242     return $loanlength
1243       if defined($loanlength) && $loanlength->{issuelength};
1244
1245     $sth->execute( $borrowertype, '*', '*' );
1246     $loanlength = $sth->fetchrow_hashref;
1247     return $loanlength
1248       if defined($loanlength) && $loanlength->{issuelength};
1249
1250     $sth->execute( '*', $itemtype, '*' );
1251     $loanlength = $sth->fetchrow_hashref;
1252     return $loanlength
1253       if defined($loanlength) && $loanlength->{issuelength};
1254
1255     $sth->execute( '*', '*', '*' );
1256     $loanlength = $sth->fetchrow_hashref;
1257     return $loanlength
1258       if defined($loanlength) && $loanlength->{issuelength};
1259
1260     # if no rule is set => 21 days (hardcoded)
1261     return {
1262         issuelength => 21,
1263         lengthunit => 'days',
1264     };
1265
1266 }
1267
1268
1269 =head2 GetHardDueDate
1270
1271   my ($hardduedate,$hardduedatecompare) = &GetHardDueDate($borrowertype,$itemtype,branchcode)
1272
1273 Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a branch
1274
1275 =cut
1276
1277 sub GetHardDueDate {
1278     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1279
1280     my $rule = GetIssuingRule( $borrowertype, $itemtype, $branchcode );
1281
1282     if ( defined( $rule ) ) {
1283         if ( $rule->{hardduedate} ) {
1284             return (dt_from_string($rule->{hardduedate}, 'iso'),$rule->{hardduedatecompare});
1285         } else {
1286             return (undef, undef);
1287         }
1288     }
1289 }
1290
1291 =head2 GetIssuingRule
1292
1293   my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode)
1294
1295 FIXME - This is a copy-paste of GetLoanLength
1296 as a stop-gap.  Do not wish to change API for GetLoanLength 
1297 this close to release, however, Overdues::GetIssuingRules is broken.
1298
1299 Get the issuing rule for an itemtype, a borrower type and a branch
1300 Returns a hashref from the issuingrules table.
1301
1302 =cut
1303
1304 sub GetIssuingRule {
1305     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1306     my $dbh = C4::Context->dbh;
1307     my $sth =  $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"  );
1308     my $irule;
1309
1310         $sth->execute( $borrowertype, $itemtype, $branchcode );
1311     $irule = $sth->fetchrow_hashref;
1312     return $irule if defined($irule) ;
1313
1314     $sth->execute( $borrowertype, "*", $branchcode );
1315     $irule = $sth->fetchrow_hashref;
1316     return $irule if defined($irule) ;
1317
1318     $sth->execute( "*", $itemtype, $branchcode );
1319     $irule = $sth->fetchrow_hashref;
1320     return $irule if defined($irule) ;
1321
1322     $sth->execute( "*", "*", $branchcode );
1323     $irule = $sth->fetchrow_hashref;
1324     return $irule if defined($irule) ;
1325
1326     $sth->execute( $borrowertype, $itemtype, "*" );
1327     $irule = $sth->fetchrow_hashref;
1328     return $irule if defined($irule) ;
1329
1330     $sth->execute( $borrowertype, "*", "*" );
1331     $irule = $sth->fetchrow_hashref;
1332     return $irule if defined($irule) ;
1333
1334     $sth->execute( "*", $itemtype, "*" );
1335     $irule = $sth->fetchrow_hashref;
1336     return $irule if defined($irule) ;
1337
1338     $sth->execute( "*", "*", "*" );
1339     $irule = $sth->fetchrow_hashref;
1340     return $irule if defined($irule) ;
1341
1342     # if no rule matches,
1343     return undef;
1344 }
1345
1346 =head2 GetBranchBorrowerCircRule
1347
1348   my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode);
1349
1350 Retrieves circulation rule attributes that apply to the given
1351 branch and patron category, regardless of item type.  
1352 The return value is a hashref containing the following key:
1353
1354 maxissueqty - maximum number of loans that a
1355 patron of the given category can have at the given
1356 branch.  If the value is undef, no limit.
1357
1358 This will first check for a specific branch and
1359 category match from branch_borrower_circ_rules. 
1360
1361 If no rule is found, it will then check default_branch_circ_rules
1362 (same branch, default category).  If no rule is found,
1363 it will then check default_borrower_circ_rules (default 
1364 branch, same category), then failing that, default_circ_rules
1365 (default branch, default category).
1366
1367 If no rule has been found in the database, it will default to
1368 the buillt in rule:
1369
1370 maxissueqty - undef
1371
1372 C<$branchcode> and C<$categorycode> should contain the
1373 literal branch code and patron category code, respectively - no
1374 wildcards.
1375
1376 =cut
1377
1378 sub GetBranchBorrowerCircRule {
1379     my $branchcode = shift;
1380     my $categorycode = shift;
1381
1382     my $branch_cat_query = "SELECT maxissueqty
1383                             FROM branch_borrower_circ_rules
1384                             WHERE branchcode = ?
1385                             AND   categorycode = ?";
1386     my $dbh = C4::Context->dbh();
1387     my $sth = $dbh->prepare($branch_cat_query);
1388     $sth->execute($branchcode, $categorycode);
1389     my $result;
1390     if ($result = $sth->fetchrow_hashref()) {
1391         return $result;
1392     }
1393
1394     # try same branch, default borrower category
1395     my $branch_query = "SELECT maxissueqty
1396                         FROM default_branch_circ_rules
1397                         WHERE branchcode = ?";
1398     $sth = $dbh->prepare($branch_query);
1399     $sth->execute($branchcode);
1400     if ($result = $sth->fetchrow_hashref()) {
1401         return $result;
1402     }
1403
1404     # try default branch, same borrower category
1405     my $category_query = "SELECT maxissueqty
1406                           FROM default_borrower_circ_rules
1407                           WHERE categorycode = ?";
1408     $sth = $dbh->prepare($category_query);
1409     $sth->execute($categorycode);
1410     if ($result = $sth->fetchrow_hashref()) {
1411         return $result;
1412     }
1413   
1414     # try default branch, default borrower category
1415     my $default_query = "SELECT maxissueqty
1416                           FROM default_circ_rules";
1417     $sth = $dbh->prepare($default_query);
1418     $sth->execute();
1419     if ($result = $sth->fetchrow_hashref()) {
1420         return $result;
1421     }
1422     
1423     # built-in default circulation rule
1424     return {
1425         maxissueqty => undef,
1426     };
1427 }
1428
1429 =head2 GetBranchItemRule
1430
1431   my $branch_item_rule = GetBranchItemRule($branchcode, $itemtype);
1432
1433 Retrieves circulation rule attributes that apply to the given
1434 branch and item type, regardless of patron category.
1435
1436 The return value is a hashref containing the following keys:
1437
1438 holdallowed => Hold policy for this branch and itemtype. Possible values:
1439   0: No holds allowed.
1440   1: Holds allowed only by patrons that have the same homebranch as the item.
1441   2: Holds allowed from any patron.
1442
1443 returnbranch => branch to which to return item.  Possible values:
1444   noreturn: do not return, let item remain where checked in (floating collections)
1445   homebranch: return to item's home branch
1446
1447 This searches branchitemrules in the following order:
1448
1449   * Same branchcode and itemtype
1450   * Same branchcode, itemtype '*'
1451   * branchcode '*', same itemtype
1452   * branchcode and itemtype '*'
1453
1454 Neither C<$branchcode> nor C<$itemtype> should be '*'.
1455
1456 =cut
1457
1458 sub GetBranchItemRule {
1459     my ( $branchcode, $itemtype ) = @_;
1460     my $dbh = C4::Context->dbh();
1461     my $result = {};
1462
1463     my @attempts = (
1464         ['SELECT holdallowed, returnbranch
1465             FROM branch_item_rules
1466             WHERE branchcode = ?
1467               AND itemtype = ?', $branchcode, $itemtype],
1468         ['SELECT holdallowed, returnbranch
1469             FROM default_branch_circ_rules
1470             WHERE branchcode = ?', $branchcode],
1471         ['SELECT holdallowed, returnbranch
1472             FROM default_branch_item_rules
1473             WHERE itemtype = ?', $itemtype],
1474         ['SELECT holdallowed, returnbranch
1475             FROM default_circ_rules'],
1476     );
1477
1478     foreach my $attempt (@attempts) {
1479         my ($query, @bind_params) = @{$attempt};
1480         my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params )
1481           or next;
1482
1483         # Since branch/category and branch/itemtype use the same per-branch
1484         # defaults tables, we have to check that the key we want is set, not
1485         # just that a row was returned
1486         $result->{'holdallowed'}  = $search_result->{'holdallowed'}  unless ( defined $result->{'holdallowed'} );
1487         $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} );
1488     }
1489     
1490     # built-in default circulation rule
1491     $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} );
1492     $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} );
1493
1494     return $result;
1495 }
1496
1497 =head2 AddReturn
1498
1499   ($doreturn, $messages, $iteminformation, $borrower) =
1500       &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1501
1502 Returns a book.
1503
1504 =over 4
1505
1506 =item C<$barcode> is the bar code of the book being returned.
1507
1508 =item C<$branch> is the code of the branch where the book is being returned.
1509
1510 =item C<$exemptfine> indicates that overdue charges for the item will be
1511 removed.
1512
1513 =item C<$dropbox> indicates that the check-in date is assumed to be
1514 yesterday, or the last non-holiday as defined in C4::Calendar .  If
1515 overdue charges are applied and C<$dropbox> is true, the last charge
1516 will be removed.  This assumes that the fines accrual script has run
1517 for _today_.
1518
1519 =back
1520
1521 C<&AddReturn> returns a list of four items:
1522
1523 C<$doreturn> is true iff the return succeeded.
1524
1525 C<$messages> is a reference-to-hash giving feedback on the operation.
1526 The keys of the hash are:
1527
1528 =over 4
1529
1530 =item C<BadBarcode>
1531
1532 No item with this barcode exists. The value is C<$barcode>.
1533
1534 =item C<NotIssued>
1535
1536 The book is not currently on loan. The value is C<$barcode>.
1537
1538 =item C<IsPermanent>
1539
1540 The book's home branch is a permanent collection. If you have borrowed
1541 this book, you are not allowed to return it. The value is the code for
1542 the book's home branch.
1543
1544 =item C<wthdrawn>
1545
1546 This book has been withdrawn/cancelled. The value should be ignored.
1547
1548 =item C<Wrongbranch>
1549
1550 This book has was returned to the wrong branch.  The value is a hashref
1551 so that C<$messages->{Wrongbranch}->{Wrongbranch}> and C<$messages->{Wrongbranch}->{Rightbranch}>
1552 contain the branchcode of the incorrect and correct return library, respectively.
1553
1554 =item C<ResFound>
1555
1556 The item was reserved. The value is a reference-to-hash whose keys are
1557 fields from the reserves table of the Koha database, and
1558 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1559 either C<Waiting>, C<Reserved>, or 0.
1560
1561 =back
1562
1563 C<$iteminformation> is a reference-to-hash, giving information about the
1564 returned item from the issues table.
1565
1566 C<$borrower> is a reference-to-hash, giving information about the
1567 patron who last borrowed the book.
1568
1569 =cut
1570
1571 sub AddReturn {
1572     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1573
1574     if ($branch and not GetBranchDetail($branch)) {
1575         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
1576         undef $branch;
1577     }
1578     $branch = C4::Context->userenv->{'branch'} unless $branch;  # we trust userenv to be a safe fallback/default
1579     my $messages;
1580     my $borrower;
1581     my $biblio;
1582     my $doreturn       = 1;
1583     my $validTransfert = 0;
1584     my $stat_type = 'return';    
1585
1586     # get information on item
1587     my $itemnumber = GetItemnumberFromBarcode( $barcode );
1588     unless ($itemnumber) {
1589         return (0, { BadBarcode => $barcode }); # no barcode means no item or borrower.  bail out.
1590     }
1591     my $issue  = GetItemIssue($itemnumber);
1592 #   warn Dumper($iteminformation);
1593     if ($issue and $issue->{borrowernumber}) {
1594         $borrower = C4::Members::GetMemberDetails($issue->{borrowernumber})
1595             or die "Data inconsistency: barcode $barcode (itemnumber:$itemnumber) claims to be issued to non-existant borrowernumber '$issue->{borrowernumber}'\n"
1596                 . Dumper($issue) . "\n";
1597     } else {
1598         $messages->{'NotIssued'} = $barcode;
1599         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
1600         $doreturn = 0;
1601         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
1602         # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on
1603         if (C4::Context->preference("RecordLocalUseOnReturn")) {
1604            $messages->{'LocalUse'} = 1;
1605            $stat_type = 'localuse';
1606         }
1607     }
1608
1609     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
1610         # full item data, but no borrowernumber or checkout info (no issue)
1611         # we know GetItem should work because GetItemnumberFromBarcode worked
1612     my $hbr      = GetBranchItemRule($item->{'homebranch'}, $item->{'itype'})->{'returnbranch'} || "homebranch";
1613         # get the proper branch to which to return the item
1614     $hbr = $item->{$hbr} || $branch ;
1615         # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch)
1616
1617     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
1618
1619     # check if the book is in a permanent collection....
1620     # FIXME -- This 'PE' attribute is largely undocumented.  afaict, there's no user interface that reflects this functionality.
1621     if ( $hbr ) {
1622         my $branches = GetBranches();    # a potentially expensive call for a non-feature.
1623         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
1624     }
1625
1626     # if indy branches and returning to different branch, refuse the return unless canreservefromotherbranches is turned on
1627     if ($hbr ne $branch && C4::Context->preference("IndependantBranches") && !(C4::Context->preference("canreservefromotherbranches"))){
1628         $messages->{'Wrongbranch'} = {
1629             Wrongbranch => $branch,
1630             Rightbranch => $hbr,
1631         };
1632         $doreturn = 0;
1633         # bailing out here - in this case, current desired behavior
1634         # is to act as if no return ever happened at all.
1635         # FIXME - even in an indy branches situation, there should
1636         # still be an option for the library to accept the item
1637         # and transfer it to its owning library.
1638         return ( $doreturn, $messages, $issue, $borrower );
1639     }
1640
1641     if ( $item->{'wthdrawn'} ) { # book has been cancelled
1642         $messages->{'wthdrawn'} = 1;
1643         $doreturn = 0;
1644     }
1645
1646     # case of a return of document (deal with issues and holdingbranch)
1647     my $today = DateTime->now( time_zone => C4::Context->tz() );
1648     if ($doreturn) {
1649     my $datedue = $issue->{date_due};
1650         $borrower or warn "AddReturn without current borrower";
1651                 my $circControlBranch;
1652         if ($dropbox) {
1653             # define circControlBranch only if dropbox mode is set
1654             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
1655             # FIXME: check issuedate > returndate, factoring in holidays
1656             #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
1657             $circControlBranch = _GetCircControlBranch($item,$borrower);
1658         $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
1659         }
1660
1661         if ($borrowernumber) {
1662         if($issue->{'overdue'}){
1663                 my ( $amount, $type, $unitcounttotal ) = C4::Overdues::CalcFine( $item, $borrower->{categorycode},$branch, $datedue, $today );
1664                 $type ||= q{};
1665         if ( $amount > 0 && ( C4::Context->preference('finesMode') eq 'production' )) {
1666           C4::Overdues::UpdateFine(
1667               $issue->{itemnumber},
1668               $issue->{borrowernumber},
1669                       $amount, $type, output_pref($datedue)
1670               );
1671         }
1672             }
1673             MarkIssueReturned($borrowernumber, $item->{'itemnumber'}, $circControlBranch, '', $borrower->{'privacy'});
1674             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  This could be the borrower hash.
1675         }
1676
1677         ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
1678     }
1679
1680     # the holdingbranch is updated if the document is returned to another location.
1681     # this is always done regardless of whether the item was on loan or not
1682     if ($item->{'holdingbranch'} ne $branch) {
1683         UpdateHoldingbranch($branch, $item->{'itemnumber'});
1684         $item->{'holdingbranch'} = $branch; # update item data holdingbranch too
1685     }
1686     ModDateLastSeen( $item->{'itemnumber'} );
1687
1688     # check if we have a transfer for this document
1689     my ($datesent,$frombranch,$tobranch) = GetTransfers( $item->{'itemnumber'} );
1690
1691     # if we have a transfer to do, we update the line of transfers with the datearrived
1692     if ($datesent) {
1693         if ( $tobranch eq $branch ) {
1694             my $sth = C4::Context->dbh->prepare(
1695                 "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1696             );
1697             $sth->execute( $item->{'itemnumber'} );
1698             # if we have a reservation with valid transfer, we can set it's status to 'W'
1699             ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
1700             C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W');
1701         } else {
1702             $messages->{'WrongTransfer'}     = $tobranch;
1703             $messages->{'WrongTransferItem'} = $item->{'itemnumber'};
1704         }
1705         $validTransfert = 1;
1706     } else {
1707         ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
1708     }
1709
1710     # fix up the accounts.....
1711     if ($item->{'itemlost'}) {
1712         _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode);    # can tolerate undef $borrowernumber
1713         $messages->{'WasLost'} = 1;
1714     }
1715
1716     # fix up the overdues in accounts...
1717     if ($borrowernumber) {
1718         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
1719         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
1720         
1721         if ( $issue->{overdue} && $issue->{date_due} ) {
1722 # fix fine days
1723             my $debardate =
1724               _debar_user_on_return( $borrower, $item, $issue->{date_due}, $today );
1725             $messages->{Debarred} = $debardate if ($debardate);
1726         }
1727     }
1728
1729     # find reserves.....
1730     # if we don't have a reserve with the status W, we launch the Checkreserves routine
1731     my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
1732     if ($resfound) {
1733           $resrec->{'ResFound'} = $resfound;
1734         $messages->{'ResFound'} = $resrec;
1735     }
1736
1737     # update stats?
1738     # Record the fact that this book was returned.
1739     UpdateStats(
1740         $branch, $stat_type, '0', '',
1741         $item->{'itemnumber'},
1742         $biblio->{'itemtype'},
1743         $borrowernumber, undef, $item->{'ccode'}
1744     );
1745
1746     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
1747     my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1748     my %conditions = (
1749         branchcode   => $branch,
1750         categorycode => $borrower->{categorycode},
1751         item_type    => $item->{itype},
1752         notification => 'CHECKIN',
1753     );
1754     if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) {
1755         SendCirculationAlert({
1756             type     => 'CHECKIN',
1757             item     => $item,
1758             borrower => $borrower,
1759             branch   => $branch,
1760         });
1761     }
1762     
1763     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'})
1764         if C4::Context->preference("ReturnLog");
1765     
1766     # FIXME: make this comment intelligible.
1767     #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1768     #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1769
1770     if (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
1771         if ( C4::Context->preference("AutomaticItemReturn"    ) or
1772             (C4::Context->preference("UseBranchTransferLimits") and
1773              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
1774            )) {
1775             $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
1776             $debug and warn "item: " . Dumper($item);
1777             ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
1778             $messages->{'WasTransfered'} = 1;
1779         } else {
1780             $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
1781         }
1782     }
1783     return ( $doreturn, $messages, $issue, $borrower );
1784 }
1785
1786 =head2 MarkIssueReturned
1787
1788   MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy);
1789
1790 Unconditionally marks an issue as being returned by
1791 moving the C<issues> row to C<old_issues> and
1792 setting C<returndate> to the current date, or
1793 the last non-holiday date of the branccode specified in
1794 C<dropbox_branch> .  Assumes you've already checked that 
1795 it's safe to do this, i.e. last non-holiday > issuedate.
1796
1797 if C<$returndate> is specified (in iso format), it is used as the date
1798 of the return. It is ignored when a dropbox_branch is passed in.
1799
1800 C<$privacy> contains the privacy parameter. If the patron has set privacy to 2,
1801 the old_issue is immediately anonymised
1802
1803 Ideally, this function would be internal to C<C4::Circulation>,
1804 not exported, but it is currently needed by one 
1805 routine in C<C4::Accounts>.
1806
1807 =cut
1808
1809 sub MarkIssueReturned {
1810     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
1811
1812     my $dbh   = C4::Context->dbh;
1813     my $query = 'UPDATE issues SET returndate=';
1814     my @bind;
1815     if ($dropbox_branch) {
1816         my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
1817         my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
1818         $query .= ' ? ';
1819         push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
1820     } elsif ($returndate) {
1821         $query .= ' ? ';
1822         push @bind, $returndate;
1823     } else {
1824         $query .= ' now() ';
1825     }
1826     $query .= ' WHERE  borrowernumber = ?  AND itemnumber = ?';
1827     push @bind, $borrowernumber, $itemnumber;
1828     # FIXME transaction
1829     my $sth_upd  = $dbh->prepare($query);
1830     $sth_upd->execute(@bind);
1831     my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues
1832                                   WHERE borrowernumber = ?
1833                                   AND itemnumber = ?');
1834     $sth_copy->execute($borrowernumber, $itemnumber);
1835     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
1836     if ( $privacy == 2) {
1837         # The default of 0 does not work due to foreign key constraints
1838         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
1839         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
1840         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
1841                                   WHERE borrowernumber = ?
1842                                   AND itemnumber = ?");
1843        $sth_ano->execute($anonymouspatron, $borrowernumber, $itemnumber);
1844     }
1845     my $sth_del  = $dbh->prepare("DELETE FROM issues
1846                                   WHERE borrowernumber = ?
1847                                   AND itemnumber = ?");
1848     $sth_del->execute($borrowernumber, $itemnumber);
1849 }
1850
1851 =head2 _debar_user_on_return
1852
1853     _debar_user_on_return($borrower, $item, $datedue, today);
1854
1855 C<$borrower> borrower hashref
1856
1857 C<$item> item hashref
1858
1859 C<$datedue> date due DateTime object
1860
1861 C<$today> DateTime object representing the return time
1862
1863 Internal function, called only by AddReturn that calculates and updates
1864  the user fine days, and debars him if necessary.
1865
1866 Should only be called for overdue returns
1867
1868 =cut
1869
1870 sub _debar_user_on_return {
1871     my ( $borrower, $item, $dt_due, $dt_today ) = @_;
1872
1873     my $branchcode = _GetCircControlBranch( $item, $borrower );
1874     my $calendar = Koha::Calendar->new( branchcode => $branchcode );
1875
1876     # $deltadays is a DateTime::Duration object
1877     my $deltadays = $calendar->days_between( $dt_due, $dt_today );
1878
1879     my $circcontrol = C4::Context::preference('CircControl');
1880     my $issuingrule =
1881       GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
1882     my $finedays = $issuingrule->{finedays};
1883     my $unit     = $issuingrule->{lengthunit};
1884
1885     if ($finedays) {
1886
1887         # finedays is in days, so hourly loans must multiply by 24
1888         # thus 1 hour late equals 1 day suspension * finedays rate
1889         $finedays = $finedays * 24 if ( $unit eq 'hours' );
1890
1891         # grace period is measured in the same units as the loan
1892         my $grace =
1893           DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
1894         if ( $deltadays->subtract($grace)->is_positive() ) {
1895
1896             my $new_debar_dt =
1897               $dt_today->clone()->add_duration( $deltadays * $finedays );
1898             if ( $borrower->{debarred} ) {
1899                 my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
1900
1901                 # Update patron only if new date > old
1902                 if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) !=
1903                     -1 )
1904                 {
1905                     return;
1906                 }
1907
1908             }
1909             C4::Members::DebarMember( $borrower->{borrowernumber},
1910                 $new_debar_dt->ymd() );
1911             return $new_debar_dt->ymd();
1912         }
1913     }
1914     return;
1915 }
1916
1917 =head2 _FixOverduesOnReturn
1918
1919    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1920
1921 C<$brn> borrowernumber
1922
1923 C<$itm> itemnumber
1924
1925 C<$exemptfine> BOOL -- remove overdue charge associated with this issue. 
1926 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1927
1928 Internal function, called only by AddReturn
1929
1930 =cut
1931
1932 sub _FixOverduesOnReturn {
1933     my ($borrowernumber, $item);
1934     unless ($borrowernumber = shift) {
1935         warn "_FixOverduesOnReturn() not supplied valid borrowernumber";
1936         return;
1937     }
1938     unless ($item = shift) {
1939         warn "_FixOverduesOnReturn() not supplied valid itemnumber";
1940         return;
1941     }
1942     my ($exemptfine, $dropbox) = @_;
1943     my $dbh = C4::Context->dbh;
1944
1945     # check for overdue fine
1946     my $sth = $dbh->prepare(
1947 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1948     );
1949     $sth->execute( $borrowernumber, $item );
1950
1951     # alter fine to show that the book has been returned
1952     my $data = $sth->fetchrow_hashref;
1953     return 0 unless $data;    # no warning, there's just nothing to fix
1954
1955     my $uquery;
1956     my @bind = ($data->{'accountlines_id'});
1957     if ($exemptfine) {
1958         $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1959         if (C4::Context->preference("FinesLog")) {
1960             &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1961         }
1962     } elsif ($dropbox && $data->{lastincrement}) {
1963         my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1964         my $amt = $data->{amount} - $data->{lastincrement} ;
1965         if (C4::Context->preference("FinesLog")) {
1966             &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1967         }
1968          $uquery = "update accountlines set accounttype='F' ";
1969          if($outstanding  >= 0 && $amt >=0) {
1970             $uquery .= ", amount = ? , amountoutstanding=? ";
1971             unshift @bind, ($amt, $outstanding) ;
1972         }
1973     } else {
1974         $uquery = "update accountlines set accounttype='F' ";
1975     }
1976     $uquery .= " where (accountlines_id = ?)";
1977     my $usth = $dbh->prepare($uquery);
1978     return $usth->execute(@bind);
1979 }
1980
1981 =head2 _FixAccountForLostAndReturned
1982
1983   &_FixAccountForLostAndReturned($itemnumber, [$borrowernumber, $barcode]);
1984
1985 Calculates the charge for a book lost and returned.
1986
1987 Internal function, not exported, called only by AddReturn.
1988
1989 FIXME: This function reflects how inscrutable fines logic is.  Fix both.
1990 FIXME: Give a positive return value on success.  It might be the $borrowernumber who received credit, or the amount forgiven.
1991
1992 =cut
1993
1994 sub _FixAccountForLostAndReturned {
1995     my $itemnumber     = shift or return;
1996     my $borrowernumber = @_ ? shift : undef;
1997     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
1998     my $dbh = C4::Context->dbh;
1999     # check for charge made for lost book
2000     my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
2001     $sth->execute($itemnumber);
2002     my $data = $sth->fetchrow_hashref;
2003     $data or return;    # bail if there is nothing to do
2004     $data->{accounttype} eq 'W' and return;    # Written off
2005
2006     # writeoff this amount
2007     my $offset;
2008     my $amount = $data->{'amount'};
2009     my $acctno = $data->{'accountno'};
2010     my $amountleft;                                             # Starts off undef/zero.
2011     if ($data->{'amountoutstanding'} == $amount) {
2012         $offset     = $data->{'amount'};
2013         $amountleft = 0;                                        # Hey, it's zero here, too.
2014     } else {
2015         $offset     = $amount - $data->{'amountoutstanding'};   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2016         $amountleft = $data->{'amountoutstanding'} - $amount;   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2017     }
2018     my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
2019         WHERE (accountlines_id = ?)");
2020     $usth->execute($data->{'accountlines_id'});      # We might be adjusting an account for some OTHER borrowernumber now.  Not the one we passed in.
2021     #check if any credit is left if so writeoff other accounts
2022     my $nextaccntno = getnextacctno($data->{'borrowernumber'});
2023     $amountleft *= -1 if ($amountleft < 0);
2024     if ($amountleft > 0) {
2025         my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
2026                             AND (amountoutstanding >0) ORDER BY date");     # might want to order by amountoustanding ASC (pay smallest first)
2027         $msth->execute($data->{'borrowernumber'});
2028         # offset transactions
2029         my $newamtos;
2030         my $accdata;
2031         while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
2032             if ($accdata->{'amountoutstanding'} < $amountleft) {
2033                 $newamtos = 0;
2034                 $amountleft -= $accdata->{'amountoutstanding'};
2035             }  else {
2036                 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
2037                 $amountleft = 0;
2038             }
2039             my $thisacct = $accdata->{'accountlines_id'};
2040             # FIXME: move prepares outside while loop!
2041             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
2042                     WHERE (accountlines_id = ?)");
2043             $usth->execute($newamtos,'$thisacct');    # FIXME: '$thisacct' is a string literal!
2044             $usth = $dbh->prepare("INSERT INTO accountoffsets
2045                 (borrowernumber, accountno, offsetaccount,  offsetamount)
2046                 VALUES
2047                 (?,?,?,?)");
2048             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
2049         }
2050         $msth->finish;  # $msth might actually have data left
2051     }
2052     $amountleft *= -1 if ($amountleft > 0);
2053     my $desc = "Item Returned " . $item_id;
2054     $usth = $dbh->prepare("INSERT INTO accountlines
2055         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
2056         VALUES (?,?,now(),?,?,'CR',?)");
2057     $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
2058     if ($borrowernumber) {
2059         # FIXME: same as query above.  use 1 sth for both
2060         $usth = $dbh->prepare("INSERT INTO accountoffsets
2061             (borrowernumber, accountno, offsetaccount,  offsetamount)
2062             VALUES (?,?,?,?)");
2063         $usth->execute($borrowernumber, $data->{'accountno'}, $nextaccntno, $offset);
2064     }
2065     ModItem({ paidfor => '' }, undef, $itemnumber);
2066     return;
2067 }
2068
2069 =head2 _GetCircControlBranch
2070
2071    my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower);
2072
2073 Internal function : 
2074
2075 Return the library code to be used to determine which circulation
2076 policy applies to a transaction.  Looks up the CircControl and
2077 HomeOrHoldingBranch system preferences.
2078
2079 C<$iteminfos> is a hashref to iteminfo. Only {homebranch or holdingbranch} is used.
2080
2081 C<$borrower> is a hashref to borrower. Only {branchcode} is used.
2082
2083 =cut
2084
2085 sub _GetCircControlBranch {
2086     my ($item, $borrower) = @_;
2087     my $circcontrol = C4::Context->preference('CircControl');
2088     my $branch;
2089
2090     if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) {
2091         $branch= C4::Context->userenv->{'branch'};
2092     } elsif ($circcontrol eq 'PatronLibrary') {
2093         $branch=$borrower->{branchcode};
2094     } else {
2095         my $branchfield = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
2096         $branch = $item->{$branchfield};
2097         # default to item home branch if holdingbranch is used
2098         # and is not defined
2099         if (!defined($branch) && $branchfield eq 'holdingbranch') {
2100             $branch = $item->{homebranch};
2101         }
2102     }
2103     return $branch;
2104 }
2105
2106
2107
2108
2109
2110
2111 =head2 GetItemIssue
2112
2113   $issue = &GetItemIssue($itemnumber);
2114
2115 Returns patron currently having a book, or undef if not checked out.
2116
2117 C<$itemnumber> is the itemnumber.
2118
2119 C<$issue> is a hashref of the row from the issues table.
2120
2121 =cut
2122
2123 sub GetItemIssue {
2124     my ($itemnumber) = @_;
2125     return unless $itemnumber;
2126     my $sth = C4::Context->dbh->prepare(
2127         "SELECT *
2128         FROM issues
2129         LEFT JOIN items ON issues.itemnumber=items.itemnumber
2130         WHERE issues.itemnumber=?");
2131     $sth->execute($itemnumber);
2132     my $data = $sth->fetchrow_hashref;
2133     return unless $data;
2134     $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
2135     $data->{issuedate}->truncate(to => 'minute');
2136     $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
2137     $data->{date_due}->truncate(to => 'minute');
2138     my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minute');
2139     $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
2140     return $data;
2141 }
2142
2143 =head2 GetOpenIssue
2144
2145   $issue = GetOpenIssue( $itemnumber );
2146
2147 Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
2148
2149 C<$itemnumber> is the item's itemnumber
2150
2151 Returns a hashref
2152
2153 =cut
2154
2155 sub GetOpenIssue {
2156   my ( $itemnumber ) = @_;
2157
2158   my $dbh = C4::Context->dbh;  
2159   my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
2160   $sth->execute( $itemnumber );
2161   my $issue = $sth->fetchrow_hashref();
2162   return $issue;
2163 }
2164
2165 =head2 GetItemIssues
2166
2167   $issues = &GetItemIssues($itemnumber, $history);
2168
2169 Returns patrons that have issued a book
2170
2171 C<$itemnumber> is the itemnumber
2172 C<$history> is false if you just want the current "issuer" (if any)
2173 and true if you want issues history from old_issues also.
2174
2175 Returns reference to an array of hashes
2176
2177 =cut
2178
2179 sub GetItemIssues {
2180     my ( $itemnumber, $history ) = @_;
2181     
2182     my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
2183     $today->truncate( to => 'minute' );
2184     my $sql = "SELECT * FROM issues
2185               JOIN borrowers USING (borrowernumber)
2186               JOIN items     USING (itemnumber)
2187               WHERE issues.itemnumber = ? ";
2188     if ($history) {
2189         $sql .= "UNION ALL
2190                  SELECT * FROM old_issues
2191                  LEFT JOIN borrowers USING (borrowernumber)
2192                  JOIN items USING (itemnumber)
2193                  WHERE old_issues.itemnumber = ? ";
2194     }
2195     $sql .= "ORDER BY date_due DESC";
2196     my $sth = C4::Context->dbh->prepare($sql);
2197     if ($history) {
2198         $sth->execute($itemnumber, $itemnumber);
2199     } else {
2200         $sth->execute($itemnumber);
2201     }
2202     my $results = $sth->fetchall_arrayref({});
2203     foreach (@$results) {
2204         my $date_due = dt_from_string($_->{date_due},'sql');
2205         $date_due->truncate( to => 'minute' );
2206
2207         $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
2208     }
2209     return $results;
2210 }
2211
2212 =head2 GetBiblioIssues
2213
2214   $issues = GetBiblioIssues($biblionumber);
2215
2216 this function get all issues from a biblionumber.
2217
2218 Return:
2219 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
2220 tables issues and the firstname,surname & cardnumber from borrowers.
2221
2222 =cut
2223
2224 sub GetBiblioIssues {
2225     my $biblionumber = shift;
2226     return undef unless $biblionumber;
2227     my $dbh   = C4::Context->dbh;
2228     my $query = "
2229         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2230         FROM issues
2231             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
2232             LEFT JOIN items ON issues.itemnumber = items.itemnumber
2233             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2234             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2235         WHERE biblio.biblionumber = ?
2236         UNION ALL
2237         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2238         FROM old_issues
2239             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
2240             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
2241             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2242             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2243         WHERE biblio.biblionumber = ?
2244         ORDER BY timestamp
2245     ";
2246     my $sth = $dbh->prepare($query);
2247     $sth->execute($biblionumber, $biblionumber);
2248
2249     my @issues;
2250     while ( my $data = $sth->fetchrow_hashref ) {
2251         push @issues, $data;
2252     }
2253     return \@issues;
2254 }
2255
2256 =head2 GetUpcomingDueIssues
2257
2258   my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
2259
2260 =cut
2261
2262 sub GetUpcomingDueIssues {
2263     my $params = shift;
2264
2265     $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
2266     my $dbh = C4::Context->dbh;
2267
2268     my $statement = <<END_SQL;
2269 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due, branches.branchemail
2270 FROM issues 
2271 LEFT JOIN items USING (itemnumber)
2272 LEFT OUTER JOIN branches USING (branchcode)
2273 WhERE returndate is NULL
2274 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
2275 END_SQL
2276
2277     my @bind_parameters = ( $params->{'days_in_advance'} );
2278     
2279     my $sth = $dbh->prepare( $statement );
2280     $sth->execute( @bind_parameters );
2281     my $upcoming_dues = $sth->fetchall_arrayref({});
2282     $sth->finish;
2283
2284     return $upcoming_dues;
2285 }
2286
2287 =head2 CanBookBeRenewed
2288
2289   ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
2290
2291 Find out whether a borrowed item may be renewed.
2292
2293 C<$dbh> is a DBI handle to the Koha database.
2294
2295 C<$borrowernumber> is the borrower number of the patron who currently
2296 has the item on loan.
2297
2298 C<$itemnumber> is the number of the item to renew.
2299
2300 C<$override_limit>, if supplied with a true value, causes
2301 the limit on the number of times that the loan can be renewed
2302 (as controlled by the item type) to be ignored.
2303
2304 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
2305 item must currently be on loan to the specified borrower; renewals
2306 must be allowed for the item's type; and the borrower must not have
2307 already renewed the loan. $error will contain the reason the renewal can not proceed
2308
2309 =cut
2310
2311 sub CanBookBeRenewed {
2312
2313     # check renewal status
2314     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2315     my $dbh       = C4::Context->dbh;
2316     my $renews    = 1;
2317     my $renewokay = 0;
2318         my $error;
2319
2320     # Look in the issues table for this item, lent to this borrower,
2321     # and not yet returned.
2322
2323     # Look in the issues table for this item, lent to this borrower,
2324     # and not yet returned.
2325     my %branch = (
2326             'ItemHomeLibrary' => 'items.homebranch',
2327             'PickupLibrary'   => 'items.holdingbranch',
2328             'PatronLibrary'   => 'borrowers.branchcode'
2329             );
2330     my $controlbranch = $branch{C4::Context->preference('CircControl')};
2331     my $itype         = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
2332     
2333     my $sthcount = $dbh->prepare("
2334                    SELECT 
2335                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
2336                    FROM  issuingrules, 
2337                    issues
2338                    LEFT JOIN items USING (itemnumber) 
2339                    LEFT JOIN borrowers USING (borrowernumber) 
2340                    LEFT JOIN biblioitems USING (biblioitemnumber)
2341                    
2342                    WHERE
2343                     (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
2344                    AND
2345                     (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
2346                    AND
2347                     (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') 
2348                    AND 
2349                     borrowernumber = ? 
2350                    AND
2351                     itemnumber = ?
2352                    ORDER BY
2353                     issuingrules.categorycode desc,
2354                     issuingrules.itemtype desc,
2355                     issuingrules.branchcode desc
2356                    LIMIT 1;
2357                   ");
2358
2359     $sthcount->execute( $borrowernumber, $itemnumber );
2360     if ( my $data1 = $sthcount->fetchrow_hashref ) {
2361         
2362         if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
2363             $renewokay = 1;
2364         }
2365         else {
2366                         $error="too_many";
2367                 }
2368                 
2369         my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
2370         if ($resfound) {
2371             $renewokay = 0;
2372                         $error="on_reserve"
2373         }
2374
2375     }
2376     return ($renewokay,$error);
2377 }
2378
2379 =head2 AddRenewal
2380
2381   &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
2382
2383 Renews a loan.
2384
2385 C<$borrowernumber> is the borrower number of the patron who currently
2386 has the item.
2387
2388 C<$itemnumber> is the number of the item to renew.
2389
2390 C<$branch> is the library where the renewal took place (if any).
2391            The library that controls the circ policies for the renewal is retrieved from the issues record.
2392
2393 C<$datedue> can be a C4::Dates object used to set the due date.
2394
2395 C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate.  If
2396 this parameter is not supplied, lastreneweddate is set to the current date.
2397
2398 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2399 from the book's item type.
2400
2401 =cut
2402
2403 sub AddRenewal {
2404     my $borrowernumber  = shift or return undef;
2405     my $itemnumber      = shift or return undef;
2406     my $branch          = shift;
2407     my $datedue         = shift;
2408     my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
2409     my $item   = GetItem($itemnumber) or return undef;
2410     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2411
2412     my $dbh = C4::Context->dbh;
2413     # Find the issues record for this book
2414     my $sth =
2415       $dbh->prepare("SELECT * FROM issues
2416                         WHERE borrowernumber=? 
2417                         AND itemnumber=?"
2418       );
2419     $sth->execute( $borrowernumber, $itemnumber );
2420     my $issuedata = $sth->fetchrow_hashref;
2421     $sth->finish;
2422     if(defined $datedue && ref $datedue ne 'DateTime' ) {
2423         carp 'Invalid date passed to AddRenewal.';
2424         return;
2425     }
2426     # If the due date wasn't specified, calculate it by adding the
2427     # book's loan length to today's date or the current due date
2428     # based on the value of the RenewalPeriodBase syspref.
2429     unless ($datedue) {
2430
2431         my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
2432         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
2433
2434         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2435                                         $issuedata->{date_due} :
2436                                         DateTime->now( time_zone => C4::Context->tz());
2437         $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
2438     }
2439
2440     # Update the issues record to have the new due date, and a new count
2441     # of how many times it has been renewed.
2442     my $renews = $issuedata->{'renewals'} + 1;
2443     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
2444                             WHERE borrowernumber=? 
2445                             AND itemnumber=?"
2446     );
2447
2448     $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
2449     $sth->finish;
2450
2451     # Update the renewal count on the item, and tell zebra to reindex
2452     $renews = $biblio->{'renewals'} + 1;
2453     ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
2454
2455     # Charge a new rental fee, if applicable?
2456     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2457     if ( $charge > 0 ) {
2458         my $accountno = getnextacctno( $borrowernumber );
2459         my $item = GetBiblioFromItemNumber($itemnumber);
2460         my $manager_id = 0;
2461         $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
2462         $sth = $dbh->prepare(
2463                 "INSERT INTO accountlines
2464                     (date, borrowernumber, accountno, amount, manager_id,
2465                     description,accounttype, amountoutstanding, itemnumber)
2466                     VALUES (now(),?,?,?,?,?,?,?,?)"
2467         );
2468         $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
2469             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2470             'Rent', $charge, $itemnumber );
2471     }
2472     # Log the renewal
2473     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
2474         return $datedue;
2475 }
2476
2477 sub GetRenewCount {
2478     # check renewal status
2479     my ( $bornum, $itemno ) = @_;
2480     my $dbh           = C4::Context->dbh;
2481     my $renewcount    = 0;
2482     my $renewsallowed = 0;
2483     my $renewsleft    = 0;
2484
2485     my $borrower = C4::Members::GetMember( borrowernumber => $bornum);
2486     my $item     = GetItem($itemno); 
2487
2488     # Look in the issues table for this item, lent to this borrower,
2489     # and not yet returned.
2490
2491     # FIXME - I think this function could be redone to use only one SQL call.
2492     my $sth = $dbh->prepare(
2493         "select * from issues
2494                                 where (borrowernumber = ?)
2495                                 and (itemnumber = ?)"
2496     );
2497     $sth->execute( $bornum, $itemno );
2498     my $data = $sth->fetchrow_hashref;
2499     $renewcount = $data->{'renewals'} if $data->{'renewals'};
2500     $sth->finish;
2501     # $item and $borrower should be calculated
2502     my $branchcode = _GetCircControlBranch($item, $borrower);
2503     
2504     my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
2505     
2506     $renewsallowed = $issuingrule->{'renewalsallowed'};
2507     $renewsleft    = $renewsallowed - $renewcount;
2508     if($renewsleft < 0){ $renewsleft = 0; }
2509     return ( $renewcount, $renewsallowed, $renewsleft );
2510 }
2511
2512 =head2 GetIssuingCharges
2513
2514   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2515
2516 Calculate how much it would cost for a given patron to borrow a given
2517 item, including any applicable discounts.
2518
2519 C<$itemnumber> is the item number of item the patron wishes to borrow.
2520
2521 C<$borrowernumber> is the patron's borrower number.
2522
2523 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2524 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2525 if it's a video).
2526
2527 =cut
2528
2529 sub GetIssuingCharges {
2530
2531     # calculate charges due
2532     my ( $itemnumber, $borrowernumber ) = @_;
2533     my $charge = 0;
2534     my $dbh    = C4::Context->dbh;
2535     my $item_type;
2536
2537     # Get the book's item type and rental charge (via its biblioitem).
2538     my $charge_query = 'SELECT itemtypes.itemtype,rentalcharge FROM items
2539         LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber';
2540     $charge_query .= (C4::Context->preference('item-level_itypes'))
2541         ? ' LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype'
2542         : ' LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype';
2543
2544     $charge_query .= ' WHERE items.itemnumber =?';
2545
2546     my $sth = $dbh->prepare($charge_query);
2547     $sth->execute($itemnumber);
2548     if ( my $item_data = $sth->fetchrow_hashref ) {
2549         $item_type = $item_data->{itemtype};
2550         $charge    = $item_data->{rentalcharge};
2551         my $branch = C4::Branch::mybranch();
2552         my $discount_query = q|SELECT rentaldiscount,
2553             issuingrules.itemtype, issuingrules.branchcode
2554             FROM borrowers
2555             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2556             WHERE borrowers.borrowernumber = ?
2557             AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
2558             AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
2559         my $discount_sth = $dbh->prepare($discount_query);
2560         $discount_sth->execute( $borrowernumber, $item_type, $branch );
2561         my $discount_rules = $discount_sth->fetchall_arrayref({});
2562         if (@{$discount_rules}) {
2563             # We may have multiple rules so get the most specific
2564             my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
2565             $charge = ( $charge * ( 100 - $discount ) ) / 100;
2566         }
2567     }
2568
2569     $sth->finish; # we havent _explicitly_ fetched all rows
2570     return ( $charge, $item_type );
2571 }
2572
2573 # Select most appropriate discount rule from those returned
2574 sub _get_discount_from_rule {
2575     my ($rules_ref, $branch, $itemtype) = @_;
2576     my $discount;
2577
2578     if (@{$rules_ref} == 1) { # only 1 applicable rule use it
2579         $discount = $rules_ref->[0]->{rentaldiscount};
2580         return (defined $discount) ? $discount : 0;
2581     }
2582     # could have up to 4 does one match $branch and $itemtype
2583     my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
2584     if (@d) {
2585         $discount = $d[0]->{rentaldiscount};
2586         return (defined $discount) ? $discount : 0;
2587     }
2588     # do we have item type + all branches
2589     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
2590     if (@d) {
2591         $discount = $d[0]->{rentaldiscount};
2592         return (defined $discount) ? $discount : 0;
2593     }
2594     # do we all item types + this branch
2595     @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
2596     if (@d) {
2597         $discount = $d[0]->{rentaldiscount};
2598         return (defined $discount) ? $discount : 0;
2599     }
2600     # so all and all (surely we wont get here)
2601     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
2602     if (@d) {
2603         $discount = $d[0]->{rentaldiscount};
2604         return (defined $discount) ? $discount : 0;
2605     }
2606     # none of the above
2607     return 0;
2608 }
2609
2610 =head2 AddIssuingCharge
2611
2612   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2613
2614 =cut
2615
2616 sub AddIssuingCharge {
2617     my ( $itemnumber, $borrowernumber, $charge ) = @_;
2618     my $dbh = C4::Context->dbh;
2619     my $nextaccntno = getnextacctno( $borrowernumber );
2620     my $manager_id = 0;
2621     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
2622     my $query ="
2623         INSERT INTO accountlines
2624             (borrowernumber, itemnumber, accountno,
2625             date, amount, description, accounttype,
2626             amountoutstanding, manager_id)
2627         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?,?)
2628     ";
2629     my $sth = $dbh->prepare($query);
2630     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
2631     $sth->finish;
2632 }
2633
2634 =head2 GetTransfers
2635
2636   GetTransfers($itemnumber);
2637
2638 =cut
2639
2640 sub GetTransfers {
2641     my ($itemnumber) = @_;
2642
2643     my $dbh = C4::Context->dbh;
2644
2645     my $query = '
2646         SELECT datesent,
2647                frombranch,
2648                tobranch
2649         FROM branchtransfers
2650         WHERE itemnumber = ?
2651           AND datearrived IS NULL
2652         ';
2653     my $sth = $dbh->prepare($query);
2654     $sth->execute($itemnumber);
2655     my @row = $sth->fetchrow_array();
2656     $sth->finish;
2657     return @row;
2658 }
2659
2660 =head2 GetTransfersFromTo
2661
2662   @results = GetTransfersFromTo($frombranch,$tobranch);
2663
2664 Returns the list of pending transfers between $from and $to branch
2665
2666 =cut
2667
2668 sub GetTransfersFromTo {
2669     my ( $frombranch, $tobranch ) = @_;
2670     return unless ( $frombranch && $tobranch );
2671     my $dbh   = C4::Context->dbh;
2672     my $query = "
2673         SELECT itemnumber,datesent,frombranch
2674         FROM   branchtransfers
2675         WHERE  frombranch=?
2676           AND  tobranch=?
2677           AND datearrived IS NULL
2678     ";
2679     my $sth = $dbh->prepare($query);
2680     $sth->execute( $frombranch, $tobranch );
2681     my @gettransfers;
2682
2683     while ( my $data = $sth->fetchrow_hashref ) {
2684         push @gettransfers, $data;
2685     }
2686     $sth->finish;
2687     return (@gettransfers);
2688 }
2689
2690 =head2 DeleteTransfer
2691
2692   &DeleteTransfer($itemnumber);
2693
2694 =cut
2695
2696 sub DeleteTransfer {
2697     my ($itemnumber) = @_;
2698     my $dbh          = C4::Context->dbh;
2699     my $sth          = $dbh->prepare(
2700         "DELETE FROM branchtransfers
2701          WHERE itemnumber=?
2702          AND datearrived IS NULL "
2703     );
2704     $sth->execute($itemnumber);
2705     $sth->finish;
2706 }
2707
2708 =head2 AnonymiseIssueHistory
2709
2710   $rows = AnonymiseIssueHistory($date,$borrowernumber)
2711
2712 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2713 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2714
2715 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
2716 setting (force delete).
2717
2718 return the number of affected rows.
2719
2720 =cut
2721
2722 sub AnonymiseIssueHistory {
2723     my $date           = shift;
2724     my $borrowernumber = shift;
2725     my $dbh            = C4::Context->dbh;
2726     my $query          = "
2727         UPDATE old_issues
2728         SET    borrowernumber = ?
2729         WHERE  returndate < ?
2730           AND borrowernumber IS NOT NULL
2731     ";
2732
2733     # The default of 0 does not work due to foreign key constraints
2734     # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
2735     my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
2736     my @bind_params = ($anonymouspatron, $date);
2737     if (defined $borrowernumber) {
2738        $query .= " AND borrowernumber = ?";
2739        push @bind_params, $borrowernumber;
2740     } else {
2741        $query .= " AND (SELECT privacy FROM borrowers WHERE borrowers.borrowernumber=old_issues.borrowernumber) <> 0";
2742     }
2743     my $sth = $dbh->prepare($query);
2744     $sth->execute(@bind_params);
2745     my $rows_affected = $sth->rows;  ### doublecheck row count return function
2746     return $rows_affected;
2747 }
2748
2749 =head2 SendCirculationAlert
2750
2751 Send out a C<check-in> or C<checkout> alert using the messaging system.
2752
2753 B<Parameters>:
2754
2755 =over 4
2756
2757 =item type
2758
2759 Valid values for this parameter are: C<CHECKIN> and C<CHECKOUT>.
2760
2761 =item item
2762
2763 Hashref of information about the item being checked in or out.
2764
2765 =item borrower
2766
2767 Hashref of information about the borrower of the item.
2768
2769 =item branch
2770
2771 The branchcode from where the checkout or check-in took place.
2772
2773 =back
2774
2775 B<Example>:
2776
2777     SendCirculationAlert({
2778         type     => 'CHECKOUT',
2779         item     => $item,
2780         borrower => $borrower,
2781         branch   => $branch,
2782     });
2783
2784 =cut
2785
2786 sub SendCirculationAlert {
2787     my ($opts) = @_;
2788     my ($type, $item, $borrower, $branch) =
2789         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
2790     my %message_name = (
2791         CHECKIN  => 'Item_Check_in',
2792         CHECKOUT => 'Item_Checkout',
2793     );
2794     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
2795         borrowernumber => $borrower->{borrowernumber},
2796         message_name   => $message_name{$type},
2797     });
2798     my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
2799     my $letter =  C4::Letters::GetPreparedLetter (
2800         module => 'circulation',
2801         letter_code => $type,
2802         branchcode => $branch,
2803         tables => {
2804             $issues_table => $item->{itemnumber},
2805             'items'       => $item->{itemnumber},
2806             'biblio'      => $item->{biblionumber},
2807             'biblioitems' => $item->{biblionumber},
2808             'borrowers'   => $borrower,
2809             'branches'    => $branch,
2810         }
2811     ) or return;
2812
2813     my @transports = keys %{ $borrower_preferences->{transports} };
2814     # warn "no transports" unless @transports;
2815     for (@transports) {
2816         # warn "transport: $_";
2817         my $message = C4::Message->find_last_message($borrower, $type, $_);
2818         if (!$message) {
2819             #warn "create new message";
2820             C4::Message->enqueue($letter, $borrower, $_);
2821         } else {
2822             #warn "append to old message";
2823             $message->append($letter);
2824             $message->update;
2825         }
2826     }
2827
2828     return $letter;
2829 }
2830
2831 =head2 updateWrongTransfer
2832
2833   $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2834
2835 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 
2836
2837 =cut
2838
2839 sub updateWrongTransfer {
2840         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2841         my $dbh = C4::Context->dbh;     
2842 # first step validate the actual line of transfert .
2843         my $sth =
2844                 $dbh->prepare(
2845                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2846                 );
2847                 $sth->execute($FromLibrary,$itemNumber);
2848                 $sth->finish;
2849
2850 # second step create a new line of branchtransfer to the right location .
2851         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2852
2853 #third step changing holdingbranch of item
2854         UpdateHoldingbranch($FromLibrary,$itemNumber);
2855 }
2856
2857 =head2 UpdateHoldingbranch
2858
2859   $items = UpdateHoldingbranch($branch,$itmenumber);
2860
2861 Simple methode for updating hodlingbranch in items BDD line
2862
2863 =cut
2864
2865 sub UpdateHoldingbranch {
2866         my ( $branch,$itemnumber ) = @_;
2867     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2868 }
2869
2870 =head2 CalcDateDue
2871
2872 $newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower);
2873
2874 this function calculates the due date given the start date and configured circulation rules,
2875 checking against the holidays calendar as per the 'useDaysMode' syspref.
2876 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2877 C<$itemtype>  = itemtype code of item in question
2878 C<$branch>  = location whose calendar to use
2879 C<$borrower> = Borrower object
2880
2881 =cut
2882
2883 sub CalcDateDue {
2884     my ( $startdate, $itemtype, $branch, $borrower ) = @_;
2885
2886     # loanlength now a href
2887     my $loanlength =
2888       GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
2889
2890     my $datedue;
2891
2892     # if globalDueDate ON the datedue is set to that date
2893     if (C4::Context->preference('globalDueDate')
2894         && ( C4::Context->preference('globalDueDate') =~
2895             C4::Dates->regexp('syspref') )
2896       ) {
2897         $datedue = dt_from_string(
2898             C4::Context->preference('globalDueDate'),
2899             C4::Context->preference('dateformat')
2900         );
2901     } else {
2902
2903         # otherwise, calculate the datedue as normal
2904         if ( C4::Context->preference('useDaysMode') eq 'Days' )
2905         {    # ignoring calendar
2906             my $dt =
2907               DateTime->now( time_zone => C4::Context->tz() )
2908               ->truncate( to => 'minute' );
2909             if ( $loanlength->{lengthunit} eq 'hours' ) {
2910                 $dt->add( hours => $loanlength->{issuelength} );
2911                 return $dt;
2912             } else {    # days
2913                 $dt->add( days => $loanlength->{issuelength} );
2914                 $dt->set_hour(23);
2915                 $dt->set_minute(59);
2916                 return $dt;
2917             }
2918         } else {
2919             my $dur;
2920             if ($loanlength->{lengthunit} eq 'hours') {
2921                 $dur = DateTime::Duration->new( hours => $loanlength->{issuelength});
2922             }
2923             else { # days
2924                 $dur = DateTime::Duration->new( days => $loanlength->{issuelength});
2925             }
2926             if (ref $startdate ne 'DateTime' ) {
2927                 $startdate = dt_from_string($startdate);
2928             }
2929             my $calendar = Koha::Calendar->new( branchcode => $branch );
2930             $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} );
2931             if ($loanlength->{lengthunit} eq 'days') {
2932                 $datedue->set_hour(23);
2933                 $datedue->set_minute(59);
2934             }
2935         }
2936     }
2937
2938     # if Hard Due Dates are used, retreive them and apply as necessary
2939     my ( $hardduedate, $hardduedatecompare ) =
2940       GetHardDueDate( $borrower->{'categorycode'}, $itemtype, $branch );
2941     if ($hardduedate) {    # hardduedates are currently dates
2942         $hardduedate->truncate( to => 'minute' );
2943         $hardduedate->set_hour(23);
2944         $hardduedate->set_minute(59);
2945         my $cmp = DateTime->compare( $hardduedate, $datedue );
2946
2947 # if the calculated due date is after the 'before' Hard Due Date (ceiling), override
2948 # if the calculated date is before the 'after' Hard Due Date (floor), override
2949 # if the hard due date is set to 'exactly', overrride
2950         if ( $hardduedatecompare == 0 || $hardduedatecompare == $cmp ) {
2951             $datedue = $hardduedate->clone;
2952         }
2953
2954         # in all other cases, keep the date due as it is
2955     }
2956
2957     # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
2958     if ( C4::Context->preference('ReturnBeforeExpiry') ) {
2959         my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' );
2960         if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) {
2961             $datedue = $expiry_dt->clone;
2962         }
2963     }
2964
2965     return $datedue;
2966 }
2967
2968
2969 =head2 CheckRepeatableHolidays
2970
2971   $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2972
2973 This function checks if the date due is a repeatable holiday
2974
2975 C<$date_due>   = returndate calculate with no day check
2976 C<$itemnumber>  = itemnumber
2977 C<$branchcode>  = localisation of issue 
2978
2979 =cut
2980
2981 sub CheckRepeatableHolidays{
2982 my($itemnumber,$week_day,$branchcode)=@_;
2983 my $dbh = C4::Context->dbh;
2984 my $query = qq|SELECT count(*)  
2985         FROM repeatable_holidays 
2986         WHERE branchcode=?
2987         AND weekday=?|;
2988 my $sth = $dbh->prepare($query);
2989 $sth->execute($branchcode,$week_day);
2990 my $result=$sth->fetchrow;
2991 $sth->finish;
2992 return $result;
2993 }
2994
2995
2996 =head2 CheckSpecialHolidays
2997
2998   $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2999
3000 This function check if the date is a special holiday
3001
3002 C<$years>   = the years of datedue
3003 C<$month>   = the month of datedue
3004 C<$day>     = the day of datedue
3005 C<$itemnumber>  = itemnumber
3006 C<$branchcode>  = localisation of issue 
3007
3008 =cut
3009
3010 sub CheckSpecialHolidays{
3011 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
3012 my $dbh = C4::Context->dbh;
3013 my $query=qq|SELECT count(*) 
3014              FROM `special_holidays`
3015              WHERE year=?
3016              AND month=?
3017              AND day=?
3018              AND branchcode=?
3019             |;
3020 my $sth = $dbh->prepare($query);
3021 $sth->execute($years,$month,$day,$branchcode);
3022 my $countspecial=$sth->fetchrow ;
3023 $sth->finish;
3024 return $countspecial;
3025 }
3026
3027 =head2 CheckRepeatableSpecialHolidays
3028
3029   $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
3030
3031 This function check if the date is a repeatble special holidays
3032
3033 C<$month>   = the month of datedue
3034 C<$day>     = the day of datedue
3035 C<$itemnumber>  = itemnumber
3036 C<$branchcode>  = localisation of issue 
3037
3038 =cut
3039
3040 sub CheckRepeatableSpecialHolidays{
3041 my ($month,$day,$itemnumber,$branchcode) = @_;
3042 my $dbh = C4::Context->dbh;
3043 my $query=qq|SELECT count(*) 
3044              FROM `repeatable_holidays`
3045              WHERE month=?
3046              AND day=?
3047              AND branchcode=?
3048             |;
3049 my $sth = $dbh->prepare($query);
3050 $sth->execute($month,$day,$branchcode);
3051 my $countspecial=$sth->fetchrow ;
3052 $sth->finish;
3053 return $countspecial;
3054 }
3055
3056
3057
3058 sub CheckValidBarcode{
3059 my ($barcode) = @_;
3060 my $dbh = C4::Context->dbh;
3061 my $query=qq|SELECT count(*) 
3062              FROM items 
3063              WHERE barcode=?
3064             |;
3065 my $sth = $dbh->prepare($query);
3066 $sth->execute($barcode);
3067 my $exist=$sth->fetchrow ;
3068 $sth->finish;
3069 return $exist;
3070 }
3071
3072 =head2 IsBranchTransferAllowed
3073
3074   $allowed = IsBranchTransferAllowed( $toBranch, $fromBranch, $code );
3075
3076 Code is either an itemtype or collection doe depending on the pref BranchTransferLimitsType
3077
3078 =cut
3079
3080 sub IsBranchTransferAllowed {
3081         my ( $toBranch, $fromBranch, $code ) = @_;
3082
3083         if ( $toBranch eq $fromBranch ) { return 1; } ## Short circuit for speed.
3084         
3085         my $limitType = C4::Context->preference("BranchTransferLimitsType");   
3086         my $dbh = C4::Context->dbh;
3087             
3088         my $sth = $dbh->prepare("SELECT * FROM branch_transfer_limits WHERE toBranch = ? AND fromBranch = ? AND $limitType = ?");
3089         $sth->execute( $toBranch, $fromBranch, $code );
3090         my $limit = $sth->fetchrow_hashref();
3091                         
3092         ## If a row is found, then that combination is not allowed, if no matching row is found, then the combination *is allowed*
3093         if ( $limit->{'limitId'} ) {
3094                 return 0;
3095         } else {
3096                 return 1;
3097         }
3098 }                                                        
3099
3100 =head2 CreateBranchTransferLimit
3101
3102   CreateBranchTransferLimit( $toBranch, $fromBranch, $code );
3103
3104 $code is either itemtype or collection code depending on what the pref BranchTransferLimitsType is set to.
3105
3106 =cut
3107
3108 sub CreateBranchTransferLimit {
3109    my ( $toBranch, $fromBranch, $code ) = @_;
3110
3111    my $limitType = C4::Context->preference("BranchTransferLimitsType");
3112    
3113    my $dbh = C4::Context->dbh;
3114    
3115    my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
3116    $sth->execute( $code, $toBranch, $fromBranch );
3117 }
3118
3119 =head2 DeleteBranchTransferLimits
3120
3121 DeleteBranchTransferLimits($frombranch);
3122
3123 Deletes all the branch transfer limits for one branch
3124
3125 =cut
3126
3127 sub DeleteBranchTransferLimits {
3128     my $branch = shift;
3129     my $dbh    = C4::Context->dbh;
3130     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
3131     $sth->execute($branch);
3132 }
3133
3134 sub ReturnLostItem{
3135     my ( $borrowernumber, $itemnum ) = @_;
3136
3137     MarkIssueReturned( $borrowernumber, $itemnum );
3138     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
3139     my $item = C4::Items::GetItem( $itemnum );
3140     my $old_note = ($item->{'paidfor'} && ($item->{'paidfor'} ne q{})) ? $item->{'paidfor'}.' / ' : q{};
3141     my @datearr = localtime(time);
3142     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
3143     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
3144     ModItem({ paidfor =>  $old_note."Paid for by $bor $date" }, undef, $itemnum);
3145 }
3146
3147
3148 sub LostItem{
3149     my ($itemnumber, $mark_returned, $charge_fee) = @_;
3150
3151     my $dbh = C4::Context->dbh();
3152     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
3153                            FROM issues 
3154                            JOIN items USING (itemnumber) 
3155                            JOIN biblio USING (biblionumber)
3156                            WHERE issues.itemnumber=?");
3157     $sth->execute($itemnumber);
3158     my $issues=$sth->fetchrow_hashref();
3159     $sth->finish;
3160
3161     # if a borrower lost the item, add a replacement cost to the their record
3162     if ( my $borrowernumber = $issues->{borrowernumber} ){
3163         my $borrower = C4::Members::GetMemberDetails( $borrowernumber );
3164
3165         C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
3166           if $charge_fee;
3167         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
3168         #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
3169         MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
3170     }
3171 }
3172
3173 sub GetOfflineOperations {
3174     my $dbh = C4::Context->dbh;
3175     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
3176     $sth->execute(C4::Context->userenv->{'branch'});
3177     my $results = $sth->fetchall_arrayref({});
3178     $sth->finish;
3179     return $results;
3180 }
3181
3182 sub GetOfflineOperation {
3183     my $dbh = C4::Context->dbh;
3184     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
3185     $sth->execute( shift );
3186     my $result = $sth->fetchrow_hashref;
3187     $sth->finish;
3188     return $result;
3189 }
3190
3191 sub AddOfflineOperation {
3192     my $dbh = C4::Context->dbh;
3193     my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
3194     $sth->execute( @_ );
3195     return "Added.";
3196 }
3197
3198 sub DeleteOfflineOperation {
3199     my $dbh = C4::Context->dbh;
3200     my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?");
3201     $sth->execute( shift );
3202     return "Deleted.";
3203 }
3204
3205 sub ProcessOfflineOperation {
3206     my $operation = shift;
3207
3208     my $report;
3209     if ( $operation->{action} eq 'return' ) {
3210         $report = ProcessOfflineReturn( $operation );
3211     } elsif ( $operation->{action} eq 'issue' ) {
3212         $report = ProcessOfflineIssue( $operation );
3213     }
3214
3215     DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
3216
3217     return $report;
3218 }
3219
3220 sub ProcessOfflineReturn {
3221     my $operation = shift;
3222
3223     my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3224
3225     if ( $itemnumber ) {
3226         my $issue = GetOpenIssue( $itemnumber );
3227         if ( $issue ) {
3228             MarkIssueReturned(
3229                 $issue->{borrowernumber},
3230                 $itemnumber,
3231                 undef,
3232                 $operation->{timestamp},
3233             );
3234             ModItem(
3235                 { renewals => 0, onloan => undef },
3236                 $issue->{'biblionumber'},
3237                 $itemnumber
3238             );
3239             return "Success.";
3240         } else {
3241             return "Item not issued.";
3242         }
3243     } else {
3244         return "Item not found.";
3245     }
3246 }
3247
3248 sub ProcessOfflineIssue {
3249     my $operation = shift;
3250
3251     my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
3252
3253     if ( $borrower->{borrowernumber} ) {
3254         my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3255         unless ($itemnumber) {
3256             return "Barcode not found.";
3257         }
3258         my $issue = GetOpenIssue( $itemnumber );
3259
3260         if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned
3261             MarkIssueReturned(
3262                 $issue->{borrowernumber},
3263                 $itemnumber,
3264                 undef,
3265                 $operation->{timestamp},
3266             );
3267         }
3268         AddIssue(
3269             $borrower,
3270             $operation->{'barcode'},
3271             undef,
3272             1,
3273             $operation->{timestamp},
3274             undef,
3275         );
3276         return "Success.";
3277     } else {
3278         return "Borrower not found.";
3279     }
3280 }
3281
3282
3283
3284 =head2 TransferSlip
3285
3286   TransferSlip($user_branch, $itemnumber, $to_branch)
3287
3288   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
3289
3290 =cut
3291
3292 sub TransferSlip {
3293     my ($branch, $itemnumber, $to_branch) = @_;
3294
3295     my $item =  GetItem( $itemnumber )
3296       or return;
3297
3298     my $pulldate = C4::Dates->new();
3299
3300     return C4::Letters::GetPreparedLetter (
3301         module => 'circulation',
3302         letter_code => 'TRANSFERSLIP',
3303         branchcode => $branch,
3304         tables => {
3305             'branches'    => $to_branch,
3306             'biblio'      => $item->{biblionumber},
3307             'items'       => $item,
3308         },
3309     );
3310 }
3311
3312
3313 1;
3314
3315 __END__
3316
3317 =head1 AUTHOR
3318
3319 Koha Development Team <http://koha-community.org/>
3320
3321 =cut
3322