Bug 8597 follow-up translation issues
[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 CanBookBeReturned
1012
1013   ($returnallowed, $message) = CanBookBeReturned($item, $branch)
1014
1015 Check whether the item can be returned to the provided branch
1016
1017 =over 4
1018
1019 =item C<$item> is a hash of item information as returned from GetItem
1020
1021 =item C<$branch> is the branchcode where the return is taking place
1022
1023 =back
1024
1025 Returns:
1026
1027 =over 4
1028
1029 =item C<$returnallowed> is 0 or 1, corresponding to whether the return is allowed (1) or not (0)
1030
1031 =item C<$message> is the branchcode where the item SHOULD be returned, if the return is not allowed
1032
1033 =cut
1034
1035 sub CanBookBeReturned {
1036   my ($item, $branch) = @_;
1037   my $allowreturntobranch = C4::Context->preference("AllowReturnToBranch") || 'anywhere';
1038
1039   # assume return is allowed to start
1040   my $allowed = 1;
1041   my $message;
1042
1043   # identify all cases where return is forbidden
1044   if ($allowreturntobranch eq 'homebranch' && $branch ne $item->{'homebranch'}) {
1045      $allowed = 0;
1046      $message = $item->{'homebranch'};
1047   } elsif ($allowreturntobranch eq 'holdingbranch' && $branch ne $item->{'holdingbranch'}) {
1048      $allowed = 0;
1049      $message = $item->{'holdingbranch'};
1050   } elsif ($allowreturntobranch eq 'homeorholdingbranch' && $branch ne $item->{'homebranch'} && $branch ne $item->{'holdingbranch'}) {
1051      $allowed = 0;
1052      $message = $item->{'homebranch'}; # FIXME: choice of homebranch is arbitrary
1053   }
1054
1055   return ($allowed, $message);
1056 }
1057
1058 =head2 AddIssue
1059
1060   &AddIssue($borrower, $barcode, [$datedue], [$cancelreserve], [$issuedate])
1061
1062 Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
1063
1064 =over 4
1065
1066 =item C<$borrower> is a hash with borrower informations (from GetMember or GetMemberDetails).
1067
1068 =item C<$barcode> is the barcode of the item being issued.
1069
1070 =item C<$datedue> is a C4::Dates object for the max date of return, i.e. the date due (optional).
1071 Calculated if empty.
1072
1073 =item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
1074
1075 =item C<$issuedate> is the date to issue the item in iso (YYYY-MM-DD) format (optional).
1076 Defaults to today.  Unlike C<$datedue>, NOT a C4::Dates object, unfortunately.
1077
1078 AddIssue does the following things :
1079
1080   - step 01: check that there is a borrowernumber & a barcode provided
1081   - check for RENEWAL (book issued & being issued to the same patron)
1082       - renewal YES = Calculate Charge & renew
1083       - renewal NO  =
1084           * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
1085           * RESERVE PLACED ?
1086               - fill reserve if reserve to this patron
1087               - cancel reserve or not, otherwise
1088           * TRANSFERT PENDING ?
1089               - complete the transfert
1090           * ISSUE THE BOOK
1091
1092 =back
1093
1094 =cut
1095
1096 sub AddIssue {
1097     my ( $borrower, $barcode, $datedue, $cancelreserve, $issuedate, $sipmode) = @_;
1098     my $dbh = C4::Context->dbh;
1099         my $barcodecheck=CheckValidBarcode($barcode);
1100     if ($datedue && ref $datedue ne 'DateTime') {
1101         $datedue = dt_from_string($datedue);
1102     }
1103     # $issuedate defaults to today.
1104     if ( ! defined $issuedate ) {
1105         $issuedate = DateTime->now(time_zone => C4::Context->tz());
1106     }
1107     else {
1108         if ( ref $issuedate ne 'DateTime') {
1109             $issuedate = dt_from_string($issuedate);
1110
1111         }
1112     }
1113         if ($borrower and $barcode and $barcodecheck ne '0'){#??? wtf
1114                 # find which item we issue
1115                 my $item = GetItem('', $barcode) or return undef;       # if we don't get an Item, abort.
1116                 my $branch = _GetCircControlBranch($item,$borrower);
1117                 
1118                 # get actual issuing if there is one
1119                 my $actualissue = GetItemIssue( $item->{itemnumber});
1120                 
1121                 # get biblioinformation for this item
1122                 my $biblio = GetBiblioFromItemNumber($item->{itemnumber});
1123                 
1124                 #
1125                 # check if we just renew the issue.
1126                 #
1127                 if ($actualissue->{borrowernumber} eq $borrower->{'borrowernumber'}) {
1128                     $datedue = AddRenewal(
1129                         $borrower->{'borrowernumber'},
1130                         $item->{'itemnumber'},
1131                         $branch,
1132                         $datedue,
1133                         $issuedate, # here interpreted as the renewal date
1134                         );
1135                 }
1136                 else {
1137         # it's NOT a renewal
1138                         if ( $actualissue->{borrowernumber}) {
1139                                 # This book is currently on loan, but not to the person
1140                                 # who wants to borrow it now. mark it returned before issuing to the new borrower
1141                                 AddReturn(
1142                                         $item->{'barcode'},
1143                                         C4::Context->userenv->{'branch'}
1144                                 );
1145                         }
1146
1147             MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve );
1148                         # Starting process for transfer job (checking transfert and validate it if we have one)
1149             my ($datesent) = GetTransfers($item->{'itemnumber'});
1150             if ($datesent) {
1151         #       updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for visibility of this case (maybe for stats ....)
1152                 my $sth =
1153                     $dbh->prepare(
1154                     "UPDATE branchtransfers 
1155                         SET datearrived = now(),
1156                         tobranch = ?,
1157                         comments = 'Forced branchtransfer'
1158                     WHERE itemnumber= ? AND datearrived IS NULL"
1159                     );
1160                 $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
1161             }
1162
1163         # Record in the database the fact that the book was issued.
1164         my $sth =
1165           $dbh->prepare(
1166                 "INSERT INTO issues
1167                     (borrowernumber, itemnumber,issuedate, date_due, branchcode)
1168                 VALUES (?,?,?,?,?)"
1169           );
1170         unless ($datedue) {
1171             my $itype = ( C4::Context->preference('item-level_itypes') ) ? $biblio->{'itype'} : $biblio->{'itemtype'};
1172             $datedue = CalcDateDue( $issuedate, $itype, $branch, $borrower );
1173
1174         }
1175         $datedue->truncate( to => 'minute');
1176         $sth->execute(
1177             $borrower->{'borrowernumber'},      # borrowernumber
1178             $item->{'itemnumber'},              # itemnumber
1179             $issuedate->strftime('%Y-%m-%d %H:%M:00'), # issuedate
1180             $datedue->strftime('%Y-%m-%d %H:%M:00'),   # date_due
1181             C4::Context->userenv->{'branch'}    # branchcode
1182         );
1183         if ( C4::Context->preference('ReturnToShelvingCart') ) { ## ReturnToShelvingCart is on, anything issued should be taken off the cart.
1184           CartToShelf( $item->{'itemnumber'} );
1185         }
1186         $item->{'issues'}++;
1187         if ( C4::Context->preference('UpdateTotalIssuesOnCirc') ) {
1188             UpdateTotalIssues($item->{'biblionumber'}, 1);
1189         }
1190
1191         ## If item was lost, it has now been found, reverse any list item charges if neccessary.
1192         if ( $item->{'itemlost'} ) {
1193             _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
1194         }
1195
1196         ModItem({ issues           => $item->{'issues'},
1197                   holdingbranch    => C4::Context->userenv->{'branch'},
1198                   itemlost         => 0,
1199                   datelastborrowed => DateTime->now(time_zone => C4::Context->tz())->ymd(),
1200                   onloan           => $datedue->ymd(),
1201                 }, $item->{'biblionumber'}, $item->{'itemnumber'});
1202         ModDateLastSeen( $item->{'itemnumber'} );
1203
1204         # If it costs to borrow this book, charge it to the patron's account.
1205         my ( $charge, $itemtype ) = GetIssuingCharges(
1206             $item->{'itemnumber'},
1207             $borrower->{'borrowernumber'}
1208         );
1209         if ( $charge > 0 ) {
1210             AddIssuingCharge(
1211                 $item->{'itemnumber'},
1212                 $borrower->{'borrowernumber'}, $charge
1213             );
1214             $item->{'charge'} = $charge;
1215         }
1216
1217         # Record the fact that this book was issued.
1218         &UpdateStats(
1219             C4::Context->userenv->{'branch'},
1220             'issue', $charge,
1221             ($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
1222             $item->{'itype'}, $borrower->{'borrowernumber'}, undef, $item->{'ccode'}
1223         );
1224
1225         # Send a checkout slip.
1226         my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1227         my %conditions = (
1228             branchcode   => $branch,
1229             categorycode => $borrower->{categorycode},
1230             item_type    => $item->{itype},
1231             notification => 'CHECKOUT',
1232         );
1233         if ($circulation_alert->is_enabled_for(\%conditions)) {
1234             SendCirculationAlert({
1235                 type     => 'CHECKOUT',
1236                 item     => $item,
1237                 borrower => $borrower,
1238                 branch   => $branch,
1239             });
1240         }
1241     }
1242
1243     logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
1244         if C4::Context->preference("IssueLog");
1245   }
1246   return ($datedue);    # not necessarily the same as when it came in!
1247 }
1248
1249 =head2 GetLoanLength
1250
1251   my $loanlength = &GetLoanLength($borrowertype,$itemtype,branchcode)
1252
1253 Get loan length for an itemtype, a borrower type and a branch
1254
1255 =cut
1256
1257 sub GetLoanLength {
1258     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1259     my $dbh = C4::Context->dbh;
1260     my $sth =
1261       $dbh->prepare(
1262 'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null'
1263       );
1264 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
1265 # try to find issuelength & return the 1st available.
1266 # check with borrowertype, itemtype and branchcode, then without one of those parameters
1267     $sth->execute( $borrowertype, $itemtype, $branchcode );
1268     my $loanlength = $sth->fetchrow_hashref;
1269     return $loanlength
1270       if defined($loanlength) && $loanlength->{issuelength};
1271
1272     $sth->execute( $borrowertype, '*', $branchcode );
1273     $loanlength = $sth->fetchrow_hashref;
1274     return $loanlength
1275       if defined($loanlength) && $loanlength->{issuelength};
1276
1277     $sth->execute( '*', $itemtype, $branchcode );
1278     $loanlength = $sth->fetchrow_hashref;
1279     return $loanlength
1280       if defined($loanlength) && $loanlength->{issuelength};
1281
1282     $sth->execute( '*', '*', $branchcode );
1283     $loanlength = $sth->fetchrow_hashref;
1284     return $loanlength
1285       if defined($loanlength) && $loanlength->{issuelength};
1286
1287     $sth->execute( $borrowertype, $itemtype, '*' );
1288     $loanlength = $sth->fetchrow_hashref;
1289     return $loanlength
1290       if defined($loanlength) && $loanlength->{issuelength};
1291
1292     $sth->execute( $borrowertype, '*', '*' );
1293     $loanlength = $sth->fetchrow_hashref;
1294     return $loanlength
1295       if defined($loanlength) && $loanlength->{issuelength};
1296
1297     $sth->execute( '*', $itemtype, '*' );
1298     $loanlength = $sth->fetchrow_hashref;
1299     return $loanlength
1300       if defined($loanlength) && $loanlength->{issuelength};
1301
1302     $sth->execute( '*', '*', '*' );
1303     $loanlength = $sth->fetchrow_hashref;
1304     return $loanlength
1305       if defined($loanlength) && $loanlength->{issuelength};
1306
1307     # if no rule is set => 21 days (hardcoded)
1308     return {
1309         issuelength => 21,
1310         lengthunit => 'days',
1311     };
1312
1313 }
1314
1315
1316 =head2 GetHardDueDate
1317
1318   my ($hardduedate,$hardduedatecompare) = &GetHardDueDate($borrowertype,$itemtype,branchcode)
1319
1320 Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a branch
1321
1322 =cut
1323
1324 sub GetHardDueDate {
1325     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1326
1327     my $rule = GetIssuingRule( $borrowertype, $itemtype, $branchcode );
1328
1329     if ( defined( $rule ) ) {
1330         if ( $rule->{hardduedate} ) {
1331             return (dt_from_string($rule->{hardduedate}, 'iso'),$rule->{hardduedatecompare});
1332         } else {
1333             return (undef, undef);
1334         }
1335     }
1336 }
1337
1338 =head2 GetIssuingRule
1339
1340   my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode)
1341
1342 FIXME - This is a copy-paste of GetLoanLength
1343 as a stop-gap.  Do not wish to change API for GetLoanLength 
1344 this close to release, however, Overdues::GetIssuingRules is broken.
1345
1346 Get the issuing rule for an itemtype, a borrower type and a branch
1347 Returns a hashref from the issuingrules table.
1348
1349 =cut
1350
1351 sub GetIssuingRule {
1352     my ( $borrowertype, $itemtype, $branchcode ) = @_;
1353     my $dbh = C4::Context->dbh;
1354     my $sth =  $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"  );
1355     my $irule;
1356
1357         $sth->execute( $borrowertype, $itemtype, $branchcode );
1358     $irule = $sth->fetchrow_hashref;
1359     return $irule if defined($irule) ;
1360
1361     $sth->execute( $borrowertype, "*", $branchcode );
1362     $irule = $sth->fetchrow_hashref;
1363     return $irule if defined($irule) ;
1364
1365     $sth->execute( "*", $itemtype, $branchcode );
1366     $irule = $sth->fetchrow_hashref;
1367     return $irule if defined($irule) ;
1368
1369     $sth->execute( "*", "*", $branchcode );
1370     $irule = $sth->fetchrow_hashref;
1371     return $irule if defined($irule) ;
1372
1373     $sth->execute( $borrowertype, $itemtype, "*" );
1374     $irule = $sth->fetchrow_hashref;
1375     return $irule if defined($irule) ;
1376
1377     $sth->execute( $borrowertype, "*", "*" );
1378     $irule = $sth->fetchrow_hashref;
1379     return $irule if defined($irule) ;
1380
1381     $sth->execute( "*", $itemtype, "*" );
1382     $irule = $sth->fetchrow_hashref;
1383     return $irule if defined($irule) ;
1384
1385     $sth->execute( "*", "*", "*" );
1386     $irule = $sth->fetchrow_hashref;
1387     return $irule if defined($irule) ;
1388
1389     # if no rule matches,
1390     return undef;
1391 }
1392
1393 =head2 GetBranchBorrowerCircRule
1394
1395   my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode);
1396
1397 Retrieves circulation rule attributes that apply to the given
1398 branch and patron category, regardless of item type.  
1399 The return value is a hashref containing the following key:
1400
1401 maxissueqty - maximum number of loans that a
1402 patron of the given category can have at the given
1403 branch.  If the value is undef, no limit.
1404
1405 This will first check for a specific branch and
1406 category match from branch_borrower_circ_rules. 
1407
1408 If no rule is found, it will then check default_branch_circ_rules
1409 (same branch, default category).  If no rule is found,
1410 it will then check default_borrower_circ_rules (default 
1411 branch, same category), then failing that, default_circ_rules
1412 (default branch, default category).
1413
1414 If no rule has been found in the database, it will default to
1415 the buillt in rule:
1416
1417 maxissueqty - undef
1418
1419 C<$branchcode> and C<$categorycode> should contain the
1420 literal branch code and patron category code, respectively - no
1421 wildcards.
1422
1423 =cut
1424
1425 sub GetBranchBorrowerCircRule {
1426     my $branchcode = shift;
1427     my $categorycode = shift;
1428
1429     my $branch_cat_query = "SELECT maxissueqty
1430                             FROM branch_borrower_circ_rules
1431                             WHERE branchcode = ?
1432                             AND   categorycode = ?";
1433     my $dbh = C4::Context->dbh();
1434     my $sth = $dbh->prepare($branch_cat_query);
1435     $sth->execute($branchcode, $categorycode);
1436     my $result;
1437     if ($result = $sth->fetchrow_hashref()) {
1438         return $result;
1439     }
1440
1441     # try same branch, default borrower category
1442     my $branch_query = "SELECT maxissueqty
1443                         FROM default_branch_circ_rules
1444                         WHERE branchcode = ?";
1445     $sth = $dbh->prepare($branch_query);
1446     $sth->execute($branchcode);
1447     if ($result = $sth->fetchrow_hashref()) {
1448         return $result;
1449     }
1450
1451     # try default branch, same borrower category
1452     my $category_query = "SELECT maxissueqty
1453                           FROM default_borrower_circ_rules
1454                           WHERE categorycode = ?";
1455     $sth = $dbh->prepare($category_query);
1456     $sth->execute($categorycode);
1457     if ($result = $sth->fetchrow_hashref()) {
1458         return $result;
1459     }
1460   
1461     # try default branch, default borrower category
1462     my $default_query = "SELECT maxissueqty
1463                           FROM default_circ_rules";
1464     $sth = $dbh->prepare($default_query);
1465     $sth->execute();
1466     if ($result = $sth->fetchrow_hashref()) {
1467         return $result;
1468     }
1469     
1470     # built-in default circulation rule
1471     return {
1472         maxissueqty => undef,
1473     };
1474 }
1475
1476 =head2 GetBranchItemRule
1477
1478   my $branch_item_rule = GetBranchItemRule($branchcode, $itemtype);
1479
1480 Retrieves circulation rule attributes that apply to the given
1481 branch and item type, regardless of patron category.
1482
1483 The return value is a hashref containing the following keys:
1484
1485 holdallowed => Hold policy for this branch and itemtype. Possible values:
1486   0: No holds allowed.
1487   1: Holds allowed only by patrons that have the same homebranch as the item.
1488   2: Holds allowed from any patron.
1489
1490 returnbranch => branch to which to return item.  Possible values:
1491   noreturn: do not return, let item remain where checked in (floating collections)
1492   homebranch: return to item's home branch
1493
1494 This searches branchitemrules in the following order:
1495
1496   * Same branchcode and itemtype
1497   * Same branchcode, itemtype '*'
1498   * branchcode '*', same itemtype
1499   * branchcode and itemtype '*'
1500
1501 Neither C<$branchcode> nor C<$itemtype> should be '*'.
1502
1503 =cut
1504
1505 sub GetBranchItemRule {
1506     my ( $branchcode, $itemtype ) = @_;
1507     my $dbh = C4::Context->dbh();
1508     my $result = {};
1509
1510     my @attempts = (
1511         ['SELECT holdallowed, returnbranch
1512             FROM branch_item_rules
1513             WHERE branchcode = ?
1514               AND itemtype = ?', $branchcode, $itemtype],
1515         ['SELECT holdallowed, returnbranch
1516             FROM default_branch_circ_rules
1517             WHERE branchcode = ?', $branchcode],
1518         ['SELECT holdallowed, returnbranch
1519             FROM default_branch_item_rules
1520             WHERE itemtype = ?', $itemtype],
1521         ['SELECT holdallowed, returnbranch
1522             FROM default_circ_rules'],
1523     );
1524
1525     foreach my $attempt (@attempts) {
1526         my ($query, @bind_params) = @{$attempt};
1527         my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params )
1528           or next;
1529
1530         # Since branch/category and branch/itemtype use the same per-branch
1531         # defaults tables, we have to check that the key we want is set, not
1532         # just that a row was returned
1533         $result->{'holdallowed'}  = $search_result->{'holdallowed'}  unless ( defined $result->{'holdallowed'} );
1534         $result->{'returnbranch'} = $search_result->{'returnbranch'} unless ( defined $result->{'returnbranch'} );
1535     }
1536     
1537     # built-in default circulation rule
1538     $result->{'holdallowed'} = 2 unless ( defined $result->{'holdallowed'} );
1539     $result->{'returnbranch'} = 'homebranch' unless ( defined $result->{'returnbranch'} );
1540
1541     return $result;
1542 }
1543
1544 =head2 AddReturn
1545
1546   ($doreturn, $messages, $iteminformation, $borrower) =
1547       &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1548
1549 Returns a book.
1550
1551 =over 4
1552
1553 =item C<$barcode> is the bar code of the book being returned.
1554
1555 =item C<$branch> is the code of the branch where the book is being returned.
1556
1557 =item C<$exemptfine> indicates that overdue charges for the item will be
1558 removed.
1559
1560 =item C<$dropbox> indicates that the check-in date is assumed to be
1561 yesterday, or the last non-holiday as defined in C4::Calendar .  If
1562 overdue charges are applied and C<$dropbox> is true, the last charge
1563 will be removed.  This assumes that the fines accrual script has run
1564 for _today_.
1565
1566 =back
1567
1568 C<&AddReturn> returns a list of four items:
1569
1570 C<$doreturn> is true iff the return succeeded.
1571
1572 C<$messages> is a reference-to-hash giving feedback on the operation.
1573 The keys of the hash are:
1574
1575 =over 4
1576
1577 =item C<BadBarcode>
1578
1579 No item with this barcode exists. The value is C<$barcode>.
1580
1581 =item C<NotIssued>
1582
1583 The book is not currently on loan. The value is C<$barcode>.
1584
1585 =item C<IsPermanent>
1586
1587 The book's home branch is a permanent collection. If you have borrowed
1588 this book, you are not allowed to return it. The value is the code for
1589 the book's home branch.
1590
1591 =item C<wthdrawn>
1592
1593 This book has been withdrawn/cancelled. The value should be ignored.
1594
1595 =item C<Wrongbranch>
1596
1597 This book has was returned to the wrong branch.  The value is a hashref
1598 so that C<$messages->{Wrongbranch}->{Wrongbranch}> and C<$messages->{Wrongbranch}->{Rightbranch}>
1599 contain the branchcode of the incorrect and correct return library, respectively.
1600
1601 =item C<ResFound>
1602
1603 The item was reserved. The value is a reference-to-hash whose keys are
1604 fields from the reserves table of the Koha database, and
1605 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1606 either C<Waiting>, C<Reserved>, or 0.
1607
1608 =back
1609
1610 C<$iteminformation> is a reference-to-hash, giving information about the
1611 returned item from the issues table.
1612
1613 C<$borrower> is a reference-to-hash, giving information about the
1614 patron who last borrowed the book.
1615
1616 =cut
1617
1618 sub AddReturn {
1619     my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1620
1621     if ($branch and not GetBranchDetail($branch)) {
1622         warn "AddReturn error: branch '$branch' not found.  Reverting to " . C4::Context->userenv->{'branch'};
1623         undef $branch;
1624     }
1625     $branch = C4::Context->userenv->{'branch'} unless $branch;  # we trust userenv to be a safe fallback/default
1626     my $messages;
1627     my $borrower;
1628     my $biblio;
1629     my $doreturn       = 1;
1630     my $validTransfert = 0;
1631     my $stat_type = 'return';    
1632
1633     # get information on item
1634     my $itemnumber = GetItemnumberFromBarcode( $barcode );
1635     unless ($itemnumber) {
1636         return (0, { BadBarcode => $barcode }); # no barcode means no item or borrower.  bail out.
1637     }
1638     my $issue  = GetItemIssue($itemnumber);
1639 #   warn Dumper($iteminformation);
1640     if ($issue and $issue->{borrowernumber}) {
1641         $borrower = C4::Members::GetMemberDetails($issue->{borrowernumber})
1642             or die "Data inconsistency: barcode $barcode (itemnumber:$itemnumber) claims to be issued to non-existant borrowernumber '$issue->{borrowernumber}'\n"
1643                 . Dumper($issue) . "\n";
1644     } else {
1645         $messages->{'NotIssued'} = $barcode;
1646         # even though item is not on loan, it may still be transferred;  therefore, get current branch info
1647         $doreturn = 0;
1648         # No issue, no borrowernumber.  ONLY if $doreturn, *might* you have a $borrower later.
1649         # Record this as a local use, instead of a return, if the RecordLocalUseOnReturn is on
1650         if (C4::Context->preference("RecordLocalUseOnReturn")) {
1651            $messages->{'LocalUse'} = 1;
1652            $stat_type = 'localuse';
1653         }
1654     }
1655
1656     my $item = GetItem($itemnumber) or die "GetItem($itemnumber) failed";
1657         # full item data, but no borrowernumber or checkout info (no issue)
1658         # we know GetItem should work because GetItemnumberFromBarcode worked
1659     my $hbr      = GetBranchItemRule($item->{'homebranch'}, $item->{'itype'})->{'returnbranch'} || "homebranch";
1660         # get the proper branch to which to return the item
1661     $hbr = $item->{$hbr} || $branch ;
1662         # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch)
1663
1664     my $borrowernumber = $borrower->{'borrowernumber'} || undef;    # we don't know if we had a borrower or not
1665
1666     # check if the book is in a permanent collection....
1667     # FIXME -- This 'PE' attribute is largely undocumented.  afaict, there's no user interface that reflects this functionality.
1668     if ( $hbr ) {
1669         my $branches = GetBranches();    # a potentially expensive call for a non-feature.
1670         $branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
1671     }
1672
1673     # check if the return is allowed at this branch
1674     my ($returnallowed, $message) = CanBookBeReturned($item, $branch);
1675     unless ($returnallowed){
1676         $messages->{'Wrongbranch'} = {
1677             Wrongbranch => $branch,
1678             Rightbranch => $message
1679         };
1680         $doreturn = 0;
1681         return ( $doreturn, $messages, $issue, $borrower );
1682     }
1683
1684     if ( $item->{'wthdrawn'} ) { # book has been cancelled
1685         $messages->{'wthdrawn'} = 1;
1686         $doreturn = 0;
1687     }
1688
1689     # case of a return of document (deal with issues and holdingbranch)
1690     my $today = DateTime->now( time_zone => C4::Context->tz() );
1691     if ($doreturn) {
1692     my $datedue = $issue->{date_due};
1693         $borrower or warn "AddReturn without current borrower";
1694                 my $circControlBranch;
1695         if ($dropbox) {
1696             # define circControlBranch only if dropbox mode is set
1697             # don't allow dropbox mode to create an invalid entry in issues (issuedate > today)
1698             # FIXME: check issuedate > returndate, factoring in holidays
1699             #$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
1700             $circControlBranch = _GetCircControlBranch($item,$borrower);
1701         $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
1702         }
1703
1704         if ($borrowernumber) {
1705         if($issue->{'overdue'}){
1706                 my ( $amount, $type, $unitcounttotal ) = C4::Overdues::CalcFine( $item, $borrower->{categorycode},$branch, $datedue, $today );
1707                 $type ||= q{};
1708         if ( $amount > 0 && ( C4::Context->preference('finesMode') eq 'production' )) {
1709           C4::Overdues::UpdateFine(
1710               $issue->{itemnumber},
1711               $issue->{borrowernumber},
1712                       $amount, $type, output_pref($datedue)
1713               );
1714         }
1715             }
1716             MarkIssueReturned($borrowernumber, $item->{'itemnumber'}, $circControlBranch, '', $borrower->{'privacy'});
1717             $messages->{'WasReturned'} = 1;    # FIXME is the "= 1" right?  This could be the borrower hash.
1718         }
1719
1720         ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
1721     }
1722
1723     # the holdingbranch is updated if the document is returned to another location.
1724     # this is always done regardless of whether the item was on loan or not
1725     if ($item->{'holdingbranch'} ne $branch) {
1726         UpdateHoldingbranch($branch, $item->{'itemnumber'});
1727         $item->{'holdingbranch'} = $branch; # update item data holdingbranch too
1728     }
1729     ModDateLastSeen( $item->{'itemnumber'} );
1730
1731     # check if we have a transfer for this document
1732     my ($datesent,$frombranch,$tobranch) = GetTransfers( $item->{'itemnumber'} );
1733
1734     # if we have a transfer to do, we update the line of transfers with the datearrived
1735     if ($datesent) {
1736         if ( $tobranch eq $branch ) {
1737             my $sth = C4::Context->dbh->prepare(
1738                 "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1739             );
1740             $sth->execute( $item->{'itemnumber'} );
1741             # if we have a reservation with valid transfer, we can set it's status to 'W'
1742             ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
1743             C4::Reserves::ModReserveStatus($item->{'itemnumber'}, 'W');
1744         } else {
1745             $messages->{'WrongTransfer'}     = $tobranch;
1746             $messages->{'WrongTransferItem'} = $item->{'itemnumber'};
1747         }
1748         $validTransfert = 1;
1749     } else {
1750         ShelfToCart( $item->{'itemnumber'} ) if ( C4::Context->preference("ReturnToShelvingCart") );
1751     }
1752
1753     # fix up the accounts.....
1754     if ($item->{'itemlost'}) {
1755         _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode);    # can tolerate undef $borrowernumber
1756         $messages->{'WasLost'} = 1;
1757     }
1758
1759     # fix up the overdues in accounts...
1760     if ($borrowernumber) {
1761         my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
1762         defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!";  # zero is OK, check defined
1763         
1764         if ( $issue->{overdue} && $issue->{date_due} ) {
1765 # fix fine days
1766             my $debardate =
1767               _debar_user_on_return( $borrower, $item, $issue->{date_due}, $today );
1768             $messages->{Debarred} = $debardate if ($debardate);
1769         }
1770     }
1771
1772     # find reserves.....
1773     # if we don't have a reserve with the status W, we launch the Checkreserves routine
1774     my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
1775     if ($resfound) {
1776           $resrec->{'ResFound'} = $resfound;
1777         $messages->{'ResFound'} = $resrec;
1778     }
1779
1780     # update stats?
1781     # Record the fact that this book was returned.
1782     UpdateStats(
1783         $branch, $stat_type, '0', '',
1784         $item->{'itemnumber'},
1785         $biblio->{'itemtype'},
1786         $borrowernumber, undef, $item->{'ccode'}
1787     );
1788
1789     # Send a check-in slip. # NOTE: borrower may be undef.  probably shouldn't try to send messages then.
1790     my $circulation_alert = 'C4::ItemCirculationAlertPreference';
1791     my %conditions = (
1792         branchcode   => $branch,
1793         categorycode => $borrower->{categorycode},
1794         item_type    => $item->{itype},
1795         notification => 'CHECKIN',
1796     );
1797     if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) {
1798         SendCirculationAlert({
1799             type     => 'CHECKIN',
1800             item     => $item,
1801             borrower => $borrower,
1802             branch   => $branch,
1803         });
1804     }
1805     
1806     logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'})
1807         if C4::Context->preference("ReturnLog");
1808     
1809     # FIXME: make this comment intelligible.
1810     #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1811     #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1812
1813     if (($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $hbr) and not $messages->{'WrongTransfer'}){
1814         if ( C4::Context->preference("AutomaticItemReturn"    ) or
1815             (C4::Context->preference("UseBranchTransferLimits") and
1816              ! IsBranchTransferAllowed($branch, $hbr, $item->{C4::Context->preference("BranchTransferLimitsType")} )
1817            )) {
1818             $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->{'itemnumber'},$branch, $hbr;
1819             $debug and warn "item: " . Dumper($item);
1820             ModItemTransfer($item->{'itemnumber'}, $branch, $hbr);
1821             $messages->{'WasTransfered'} = 1;
1822         } else {
1823             $messages->{'NeedsTransfer'} = 1;   # TODO: instead of 1, specify branchcode that the transfer SHOULD go to, $item->{homebranch}
1824         }
1825     }
1826     return ( $doreturn, $messages, $issue, $borrower );
1827 }
1828
1829 =head2 MarkIssueReturned
1830
1831   MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy);
1832
1833 Unconditionally marks an issue as being returned by
1834 moving the C<issues> row to C<old_issues> and
1835 setting C<returndate> to the current date, or
1836 the last non-holiday date of the branccode specified in
1837 C<dropbox_branch> .  Assumes you've already checked that 
1838 it's safe to do this, i.e. last non-holiday > issuedate.
1839
1840 if C<$returndate> is specified (in iso format), it is used as the date
1841 of the return. It is ignored when a dropbox_branch is passed in.
1842
1843 C<$privacy> contains the privacy parameter. If the patron has set privacy to 2,
1844 the old_issue is immediately anonymised
1845
1846 Ideally, this function would be internal to C<C4::Circulation>,
1847 not exported, but it is currently needed by one 
1848 routine in C<C4::Accounts>.
1849
1850 =cut
1851
1852 sub MarkIssueReturned {
1853     my ( $borrowernumber, $itemnumber, $dropbox_branch, $returndate, $privacy ) = @_;
1854
1855     my $dbh   = C4::Context->dbh;
1856     my $query = 'UPDATE issues SET returndate=';
1857     my @bind;
1858     if ($dropbox_branch) {
1859         my $calendar = Koha::Calendar->new( branchcode => $dropbox_branch );
1860         my $dropboxdate = $calendar->addDate( DateTime->now( time_zone => C4::Context->tz), -1 );
1861         $query .= ' ? ';
1862         push @bind, $dropboxdate->strftime('%Y-%m-%d %H:%M');
1863     } elsif ($returndate) {
1864         $query .= ' ? ';
1865         push @bind, $returndate;
1866     } else {
1867         $query .= ' now() ';
1868     }
1869     $query .= ' WHERE  borrowernumber = ?  AND itemnumber = ?';
1870     push @bind, $borrowernumber, $itemnumber;
1871     # FIXME transaction
1872     my $sth_upd  = $dbh->prepare($query);
1873     $sth_upd->execute(@bind);
1874     my $sth_copy = $dbh->prepare('INSERT INTO old_issues SELECT * FROM issues
1875                                   WHERE borrowernumber = ?
1876                                   AND itemnumber = ?');
1877     $sth_copy->execute($borrowernumber, $itemnumber);
1878     # anonymise patron checkout immediately if $privacy set to 2 and AnonymousPatron is set to a valid borrowernumber
1879     if ( $privacy == 2) {
1880         # The default of 0 does not work due to foreign key constraints
1881         # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
1882         my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
1883         my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
1884                                   WHERE borrowernumber = ?
1885                                   AND itemnumber = ?");
1886        $sth_ano->execute($anonymouspatron, $borrowernumber, $itemnumber);
1887     }
1888     my $sth_del  = $dbh->prepare("DELETE FROM issues
1889                                   WHERE borrowernumber = ?
1890                                   AND itemnumber = ?");
1891     $sth_del->execute($borrowernumber, $itemnumber);
1892 }
1893
1894 =head2 _debar_user_on_return
1895
1896     _debar_user_on_return($borrower, $item, $datedue, today);
1897
1898 C<$borrower> borrower hashref
1899
1900 C<$item> item hashref
1901
1902 C<$datedue> date due DateTime object
1903
1904 C<$today> DateTime object representing the return time
1905
1906 Internal function, called only by AddReturn that calculates and updates
1907  the user fine days, and debars him if necessary.
1908
1909 Should only be called for overdue returns
1910
1911 =cut
1912
1913 sub _debar_user_on_return {
1914     my ( $borrower, $item, $dt_due, $dt_today ) = @_;
1915
1916     my $branchcode = _GetCircControlBranch( $item, $borrower );
1917     my $calendar = Koha::Calendar->new( branchcode => $branchcode );
1918
1919     # $deltadays is a DateTime::Duration object
1920     my $deltadays = $calendar->days_between( $dt_due, $dt_today );
1921
1922     my $circcontrol = C4::Context::preference('CircControl');
1923     my $issuingrule =
1924       GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
1925     my $finedays = $issuingrule->{finedays};
1926     my $unit     = $issuingrule->{lengthunit};
1927
1928     if ($finedays) {
1929
1930         # finedays is in days, so hourly loans must multiply by 24
1931         # thus 1 hour late equals 1 day suspension * finedays rate
1932         $finedays = $finedays * 24 if ( $unit eq 'hours' );
1933
1934         # grace period is measured in the same units as the loan
1935         my $grace =
1936           DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
1937         if ( $deltadays->subtract($grace)->is_positive() ) {
1938
1939             my $new_debar_dt =
1940               $dt_today->clone()->add_duration( $deltadays * $finedays );
1941             if ( $borrower->{debarred} ) {
1942                 my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
1943
1944                 # Update patron only if new date > old
1945                 if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) !=
1946                     -1 )
1947                 {
1948                     return;
1949                 }
1950
1951             }
1952             C4::Members::DebarMember( $borrower->{borrowernumber},
1953                 $new_debar_dt->ymd() );
1954             return $new_debar_dt->ymd();
1955         }
1956     }
1957     return;
1958 }
1959
1960 =head2 _FixOverduesOnReturn
1961
1962    &_FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1963
1964 C<$brn> borrowernumber
1965
1966 C<$itm> itemnumber
1967
1968 C<$exemptfine> BOOL -- remove overdue charge associated with this issue. 
1969 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1970
1971 Internal function, called only by AddReturn
1972
1973 =cut
1974
1975 sub _FixOverduesOnReturn {
1976     my ($borrowernumber, $item);
1977     unless ($borrowernumber = shift) {
1978         warn "_FixOverduesOnReturn() not supplied valid borrowernumber";
1979         return;
1980     }
1981     unless ($item = shift) {
1982         warn "_FixOverduesOnReturn() not supplied valid itemnumber";
1983         return;
1984     }
1985     my ($exemptfine, $dropbox) = @_;
1986     my $dbh = C4::Context->dbh;
1987
1988     # check for overdue fine
1989     my $sth = $dbh->prepare(
1990 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1991     );
1992     $sth->execute( $borrowernumber, $item );
1993
1994     # alter fine to show that the book has been returned
1995     my $data = $sth->fetchrow_hashref;
1996     return 0 unless $data;    # no warning, there's just nothing to fix
1997
1998     my $uquery;
1999     my @bind = ($data->{'accountlines_id'});
2000     if ($exemptfine) {
2001         $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
2002         if (C4::Context->preference("FinesLog")) {
2003             &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
2004         }
2005     } elsif ($dropbox && $data->{lastincrement}) {
2006         my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
2007         my $amt = $data->{amount} - $data->{lastincrement} ;
2008         if (C4::Context->preference("FinesLog")) {
2009             &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
2010         }
2011          $uquery = "update accountlines set accounttype='F' ";
2012          if($outstanding  >= 0 && $amt >=0) {
2013             $uquery .= ", amount = ? , amountoutstanding=? ";
2014             unshift @bind, ($amt, $outstanding) ;
2015         }
2016     } else {
2017         $uquery = "update accountlines set accounttype='F' ";
2018     }
2019     $uquery .= " where (accountlines_id = ?)";
2020     my $usth = $dbh->prepare($uquery);
2021     return $usth->execute(@bind);
2022 }
2023
2024 =head2 _FixAccountForLostAndReturned
2025
2026   &_FixAccountForLostAndReturned($itemnumber, [$borrowernumber, $barcode]);
2027
2028 Calculates the charge for a book lost and returned.
2029
2030 Internal function, not exported, called only by AddReturn.
2031
2032 FIXME: This function reflects how inscrutable fines logic is.  Fix both.
2033 FIXME: Give a positive return value on success.  It might be the $borrowernumber who received credit, or the amount forgiven.
2034
2035 =cut
2036
2037 sub _FixAccountForLostAndReturned {
2038     my $itemnumber     = shift or return;
2039     my $borrowernumber = @_ ? shift : undef;
2040     my $item_id        = @_ ? shift : $itemnumber;  # Send the barcode if you want that logged in the description
2041     my $dbh = C4::Context->dbh;
2042     # check for charge made for lost book
2043     my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE itemnumber = ? AND accounttype IN ('L', 'Rep', 'W') ORDER BY date DESC, accountno DESC");
2044     $sth->execute($itemnumber);
2045     my $data = $sth->fetchrow_hashref;
2046     $data or return;    # bail if there is nothing to do
2047     $data->{accounttype} eq 'W' and return;    # Written off
2048
2049     # writeoff this amount
2050     my $offset;
2051     my $amount = $data->{'amount'};
2052     my $acctno = $data->{'accountno'};
2053     my $amountleft;                                             # Starts off undef/zero.
2054     if ($data->{'amountoutstanding'} == $amount) {
2055         $offset     = $data->{'amount'};
2056         $amountleft = 0;                                        # Hey, it's zero here, too.
2057     } else {
2058         $offset     = $amount - $data->{'amountoutstanding'};   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2059         $amountleft = $data->{'amountoutstanding'} - $amount;   # Um, isn't this the same as ZERO?  We just tested those two things are ==
2060     }
2061     my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
2062         WHERE (accountlines_id = ?)");
2063     $usth->execute($data->{'accountlines_id'});      # We might be adjusting an account for some OTHER borrowernumber now.  Not the one we passed in.
2064     #check if any credit is left if so writeoff other accounts
2065     my $nextaccntno = getnextacctno($data->{'borrowernumber'});
2066     $amountleft *= -1 if ($amountleft < 0);
2067     if ($amountleft > 0) {
2068         my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
2069                             AND (amountoutstanding >0) ORDER BY date");     # might want to order by amountoustanding ASC (pay smallest first)
2070         $msth->execute($data->{'borrowernumber'});
2071         # offset transactions
2072         my $newamtos;
2073         my $accdata;
2074         while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
2075             if ($accdata->{'amountoutstanding'} < $amountleft) {
2076                 $newamtos = 0;
2077                 $amountleft -= $accdata->{'amountoutstanding'};
2078             }  else {
2079                 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
2080                 $amountleft = 0;
2081             }
2082             my $thisacct = $accdata->{'accountlines_id'};
2083             # FIXME: move prepares outside while loop!
2084             my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
2085                     WHERE (accountlines_id = ?)");
2086             $usth->execute($newamtos,'$thisacct');    # FIXME: '$thisacct' is a string literal!
2087             $usth = $dbh->prepare("INSERT INTO accountoffsets
2088                 (borrowernumber, accountno, offsetaccount,  offsetamount)
2089                 VALUES
2090                 (?,?,?,?)");
2091             $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
2092         }
2093         $msth->finish;  # $msth might actually have data left
2094     }
2095     $amountleft *= -1 if ($amountleft > 0);
2096     my $desc = "Item Returned " . $item_id;
2097     $usth = $dbh->prepare("INSERT INTO accountlines
2098         (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
2099         VALUES (?,?,now(),?,?,'CR',?)");
2100     $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
2101     if ($borrowernumber) {
2102         # FIXME: same as query above.  use 1 sth for both
2103         $usth = $dbh->prepare("INSERT INTO accountoffsets
2104             (borrowernumber, accountno, offsetaccount,  offsetamount)
2105             VALUES (?,?,?,?)");
2106         $usth->execute($borrowernumber, $data->{'accountno'}, $nextaccntno, $offset);
2107     }
2108     ModItem({ paidfor => '' }, undef, $itemnumber);
2109     return;
2110 }
2111
2112 =head2 _GetCircControlBranch
2113
2114    my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower);
2115
2116 Internal function : 
2117
2118 Return the library code to be used to determine which circulation
2119 policy applies to a transaction.  Looks up the CircControl and
2120 HomeOrHoldingBranch system preferences.
2121
2122 C<$iteminfos> is a hashref to iteminfo. Only {homebranch or holdingbranch} is used.
2123
2124 C<$borrower> is a hashref to borrower. Only {branchcode} is used.
2125
2126 =cut
2127
2128 sub _GetCircControlBranch {
2129     my ($item, $borrower) = @_;
2130     my $circcontrol = C4::Context->preference('CircControl');
2131     my $branch;
2132
2133     if ($circcontrol eq 'PickupLibrary' and (C4::Context->userenv and C4::Context->userenv->{'branch'}) ) {
2134         $branch= C4::Context->userenv->{'branch'};
2135     } elsif ($circcontrol eq 'PatronLibrary') {
2136         $branch=$borrower->{branchcode};
2137     } else {
2138         my $branchfield = C4::Context->preference('HomeOrHoldingBranch') || 'homebranch';
2139         $branch = $item->{$branchfield};
2140         # default to item home branch if holdingbranch is used
2141         # and is not defined
2142         if (!defined($branch) && $branchfield eq 'holdingbranch') {
2143             $branch = $item->{homebranch};
2144         }
2145     }
2146     return $branch;
2147 }
2148
2149
2150
2151
2152
2153
2154 =head2 GetItemIssue
2155
2156   $issue = &GetItemIssue($itemnumber);
2157
2158 Returns patron currently having a book, or undef if not checked out.
2159
2160 C<$itemnumber> is the itemnumber.
2161
2162 C<$issue> is a hashref of the row from the issues table.
2163
2164 =cut
2165
2166 sub GetItemIssue {
2167     my ($itemnumber) = @_;
2168     return unless $itemnumber;
2169     my $sth = C4::Context->dbh->prepare(
2170         "SELECT *
2171         FROM issues
2172         LEFT JOIN items ON issues.itemnumber=items.itemnumber
2173         WHERE issues.itemnumber=?");
2174     $sth->execute($itemnumber);
2175     my $data = $sth->fetchrow_hashref;
2176     return unless $data;
2177     $data->{issuedate} = dt_from_string($data->{issuedate}, 'sql');
2178     $data->{issuedate}->truncate(to => 'minute');
2179     $data->{date_due} = dt_from_string($data->{date_due}, 'sql');
2180     $data->{date_due}->truncate(to => 'minute');
2181     my $dt = DateTime->now( time_zone => C4::Context->tz)->truncate( to => 'minute');
2182     $data->{'overdue'} = DateTime->compare($data->{'date_due'}, $dt ) == -1 ? 1 : 0;
2183     return $data;
2184 }
2185
2186 =head2 GetOpenIssue
2187
2188   $issue = GetOpenIssue( $itemnumber );
2189
2190 Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
2191
2192 C<$itemnumber> is the item's itemnumber
2193
2194 Returns a hashref
2195
2196 =cut
2197
2198 sub GetOpenIssue {
2199   my ( $itemnumber ) = @_;
2200
2201   my $dbh = C4::Context->dbh;  
2202   my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
2203   $sth->execute( $itemnumber );
2204   my $issue = $sth->fetchrow_hashref();
2205   return $issue;
2206 }
2207
2208 =head2 GetItemIssues
2209
2210   $issues = &GetItemIssues($itemnumber, $history);
2211
2212 Returns patrons that have issued a book
2213
2214 C<$itemnumber> is the itemnumber
2215 C<$history> is false if you just want the current "issuer" (if any)
2216 and true if you want issues history from old_issues also.
2217
2218 Returns reference to an array of hashes
2219
2220 =cut
2221
2222 sub GetItemIssues {
2223     my ( $itemnumber, $history ) = @_;
2224     
2225     my $today = DateTime->now( time_zome => C4::Context->tz);  # get today date
2226     $today->truncate( to => 'minute' );
2227     my $sql = "SELECT * FROM issues
2228               JOIN borrowers USING (borrowernumber)
2229               JOIN items     USING (itemnumber)
2230               WHERE issues.itemnumber = ? ";
2231     if ($history) {
2232         $sql .= "UNION ALL
2233                  SELECT * FROM old_issues
2234                  LEFT JOIN borrowers USING (borrowernumber)
2235                  JOIN items USING (itemnumber)
2236                  WHERE old_issues.itemnumber = ? ";
2237     }
2238     $sql .= "ORDER BY date_due DESC";
2239     my $sth = C4::Context->dbh->prepare($sql);
2240     if ($history) {
2241         $sth->execute($itemnumber, $itemnumber);
2242     } else {
2243         $sth->execute($itemnumber);
2244     }
2245     my $results = $sth->fetchall_arrayref({});
2246     foreach (@$results) {
2247         my $date_due = dt_from_string($_->{date_due},'sql');
2248         $date_due->truncate( to => 'minute' );
2249
2250         $_->{overdue} = (DateTime->compare($date_due, $today) == -1) ? 1 : 0;
2251     }
2252     return $results;
2253 }
2254
2255 =head2 GetBiblioIssues
2256
2257   $issues = GetBiblioIssues($biblionumber);
2258
2259 this function get all issues from a biblionumber.
2260
2261 Return:
2262 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
2263 tables issues and the firstname,surname & cardnumber from borrowers.
2264
2265 =cut
2266
2267 sub GetBiblioIssues {
2268     my $biblionumber = shift;
2269     return undef unless $biblionumber;
2270     my $dbh   = C4::Context->dbh;
2271     my $query = "
2272         SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2273         FROM issues
2274             LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
2275             LEFT JOIN items ON issues.itemnumber = items.itemnumber
2276             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2277             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2278         WHERE biblio.biblionumber = ?
2279         UNION ALL
2280         SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
2281         FROM old_issues
2282             LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
2283             LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
2284             LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
2285             LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2286         WHERE biblio.biblionumber = ?
2287         ORDER BY timestamp
2288     ";
2289     my $sth = $dbh->prepare($query);
2290     $sth->execute($biblionumber, $biblionumber);
2291
2292     my @issues;
2293     while ( my $data = $sth->fetchrow_hashref ) {
2294         push @issues, $data;
2295     }
2296     return \@issues;
2297 }
2298
2299 =head2 GetUpcomingDueIssues
2300
2301   my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
2302
2303 =cut
2304
2305 sub GetUpcomingDueIssues {
2306     my $params = shift;
2307
2308     $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
2309     my $dbh = C4::Context->dbh;
2310
2311     my $statement = <<END_SQL;
2312 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due, branches.branchemail
2313 FROM issues 
2314 LEFT JOIN items USING (itemnumber)
2315 LEFT OUTER JOIN branches USING (branchcode)
2316 WhERE returndate is NULL
2317 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
2318 END_SQL
2319
2320     my @bind_parameters = ( $params->{'days_in_advance'} );
2321     
2322     my $sth = $dbh->prepare( $statement );
2323     $sth->execute( @bind_parameters );
2324     my $upcoming_dues = $sth->fetchall_arrayref({});
2325     $sth->finish;
2326
2327     return $upcoming_dues;
2328 }
2329
2330 =head2 CanBookBeRenewed
2331
2332   ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
2333
2334 Find out whether a borrowed item may be renewed.
2335
2336 C<$dbh> is a DBI handle to the Koha database.
2337
2338 C<$borrowernumber> is the borrower number of the patron who currently
2339 has the item on loan.
2340
2341 C<$itemnumber> is the number of the item to renew.
2342
2343 C<$override_limit>, if supplied with a true value, causes
2344 the limit on the number of times that the loan can be renewed
2345 (as controlled by the item type) to be ignored.
2346
2347 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
2348 item must currently be on loan to the specified borrower; renewals
2349 must be allowed for the item's type; and the borrower must not have
2350 already renewed the loan. $error will contain the reason the renewal can not proceed
2351
2352 =cut
2353
2354 sub CanBookBeRenewed {
2355
2356     # check renewal status
2357     my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
2358     my $dbh       = C4::Context->dbh;
2359     my $renews    = 1;
2360     my $renewokay = 0;
2361         my $error;
2362
2363     # Look in the issues table for this item, lent to this borrower,
2364     # and not yet returned.
2365
2366     # Look in the issues table for this item, lent to this borrower,
2367     # and not yet returned.
2368     my %branch = (
2369             'ItemHomeLibrary' => 'items.homebranch',
2370             'PickupLibrary'   => 'items.holdingbranch',
2371             'PatronLibrary'   => 'borrowers.branchcode'
2372             );
2373     my $controlbranch = $branch{C4::Context->preference('CircControl')};
2374     my $itype         = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
2375     
2376     my $sthcount = $dbh->prepare("
2377                    SELECT 
2378                     borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
2379                    FROM  issuingrules, 
2380                    issues
2381                    LEFT JOIN items USING (itemnumber) 
2382                    LEFT JOIN borrowers USING (borrowernumber) 
2383                    LEFT JOIN biblioitems USING (biblioitemnumber)
2384                    
2385                    WHERE
2386                     (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
2387                    AND
2388                     (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
2389                    AND
2390                     (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*') 
2391                    AND 
2392                     borrowernumber = ? 
2393                    AND
2394                     itemnumber = ?
2395                    ORDER BY
2396                     issuingrules.categorycode desc,
2397                     issuingrules.itemtype desc,
2398                     issuingrules.branchcode desc
2399                    LIMIT 1;
2400                   ");
2401
2402     $sthcount->execute( $borrowernumber, $itemnumber );
2403     if ( my $data1 = $sthcount->fetchrow_hashref ) {
2404         
2405         if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
2406             $renewokay = 1;
2407         }
2408         else {
2409                         $error="too_many";
2410                 }
2411                 
2412         my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
2413         if ($resfound) {
2414             $renewokay = 0;
2415                         $error="on_reserve"
2416         }
2417
2418     }
2419     return ($renewokay,$error);
2420 }
2421
2422 =head2 AddRenewal
2423
2424   &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
2425
2426 Renews a loan.
2427
2428 C<$borrowernumber> is the borrower number of the patron who currently
2429 has the item.
2430
2431 C<$itemnumber> is the number of the item to renew.
2432
2433 C<$branch> is the library where the renewal took place (if any).
2434            The library that controls the circ policies for the renewal is retrieved from the issues record.
2435
2436 C<$datedue> can be a C4::Dates object used to set the due date.
2437
2438 C<$lastreneweddate> is an optional ISO-formatted date used to set issues.lastreneweddate.  If
2439 this parameter is not supplied, lastreneweddate is set to the current date.
2440
2441 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
2442 from the book's item type.
2443
2444 =cut
2445
2446 sub AddRenewal {
2447     my $borrowernumber  = shift or return undef;
2448     my $itemnumber      = shift or return undef;
2449     my $branch          = shift;
2450     my $datedue         = shift;
2451     my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
2452     my $item   = GetItem($itemnumber) or return undef;
2453     my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
2454
2455     my $dbh = C4::Context->dbh;
2456     # Find the issues record for this book
2457     my $sth =
2458       $dbh->prepare("SELECT * FROM issues
2459                         WHERE borrowernumber=? 
2460                         AND itemnumber=?"
2461       );
2462     $sth->execute( $borrowernumber, $itemnumber );
2463     my $issuedata = $sth->fetchrow_hashref;
2464     $sth->finish;
2465     if(defined $datedue && ref $datedue ne 'DateTime' ) {
2466         carp 'Invalid date passed to AddRenewal.';
2467         return;
2468     }
2469     # If the due date wasn't specified, calculate it by adding the
2470     # book's loan length to today's date or the current due date
2471     # based on the value of the RenewalPeriodBase syspref.
2472     unless ($datedue) {
2473
2474         my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
2475         my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
2476
2477         $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
2478                                         $issuedata->{date_due} :
2479                                         DateTime->now( time_zone => C4::Context->tz());
2480         $datedue =  CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
2481     }
2482
2483     # Update the issues record to have the new due date, and a new count
2484     # of how many times it has been renewed.
2485     my $renews = $issuedata->{'renewals'} + 1;
2486     $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
2487                             WHERE borrowernumber=? 
2488                             AND itemnumber=?"
2489     );
2490
2491     $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
2492     $sth->finish;
2493
2494     # Update the renewal count on the item, and tell zebra to reindex
2495     $renews = $biblio->{'renewals'} + 1;
2496     ModItem({ renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $biblio->{'biblionumber'}, $itemnumber);
2497
2498     # Charge a new rental fee, if applicable?
2499     my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
2500     if ( $charge > 0 ) {
2501         my $accountno = getnextacctno( $borrowernumber );
2502         my $item = GetBiblioFromItemNumber($itemnumber);
2503         my $manager_id = 0;
2504         $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; 
2505         $sth = $dbh->prepare(
2506                 "INSERT INTO accountlines
2507                     (date, borrowernumber, accountno, amount, manager_id,
2508                     description,accounttype, amountoutstanding, itemnumber)
2509                     VALUES (now(),?,?,?,?,?,?,?,?)"
2510         );
2511         $sth->execute( $borrowernumber, $accountno, $charge, $manager_id,
2512             "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
2513             'Rent', $charge, $itemnumber );
2514     }
2515     # Log the renewal
2516     UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
2517         return $datedue;
2518 }
2519
2520 sub GetRenewCount {
2521     # check renewal status
2522     my ( $bornum, $itemno ) = @_;
2523     my $dbh           = C4::Context->dbh;
2524     my $renewcount    = 0;
2525     my $renewsallowed = 0;
2526     my $renewsleft    = 0;
2527
2528     my $borrower = C4::Members::GetMember( borrowernumber => $bornum);
2529     my $item     = GetItem($itemno); 
2530
2531     # Look in the issues table for this item, lent to this borrower,
2532     # and not yet returned.
2533
2534     # FIXME - I think this function could be redone to use only one SQL call.
2535     my $sth = $dbh->prepare(
2536         "select * from issues
2537                                 where (borrowernumber = ?)
2538                                 and (itemnumber = ?)"
2539     );
2540     $sth->execute( $bornum, $itemno );
2541     my $data = $sth->fetchrow_hashref;
2542     $renewcount = $data->{'renewals'} if $data->{'renewals'};
2543     $sth->finish;
2544     # $item and $borrower should be calculated
2545     my $branchcode = _GetCircControlBranch($item, $borrower);
2546     
2547     my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
2548     
2549     $renewsallowed = $issuingrule->{'renewalsallowed'};
2550     $renewsleft    = $renewsallowed - $renewcount;
2551     if($renewsleft < 0){ $renewsleft = 0; }
2552     return ( $renewcount, $renewsallowed, $renewsleft );
2553 }
2554
2555 =head2 GetIssuingCharges
2556
2557   ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2558
2559 Calculate how much it would cost for a given patron to borrow a given
2560 item, including any applicable discounts.
2561
2562 C<$itemnumber> is the item number of item the patron wishes to borrow.
2563
2564 C<$borrowernumber> is the patron's borrower number.
2565
2566 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2567 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2568 if it's a video).
2569
2570 =cut
2571
2572 sub GetIssuingCharges {
2573
2574     # calculate charges due
2575     my ( $itemnumber, $borrowernumber ) = @_;
2576     my $charge = 0;
2577     my $dbh    = C4::Context->dbh;
2578     my $item_type;
2579
2580     # Get the book's item type and rental charge (via its biblioitem).
2581     my $charge_query = 'SELECT itemtypes.itemtype,rentalcharge FROM items
2582         LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber';
2583     $charge_query .= (C4::Context->preference('item-level_itypes'))
2584         ? ' LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype'
2585         : ' LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype';
2586
2587     $charge_query .= ' WHERE items.itemnumber =?';
2588
2589     my $sth = $dbh->prepare($charge_query);
2590     $sth->execute($itemnumber);
2591     if ( my $item_data = $sth->fetchrow_hashref ) {
2592         $item_type = $item_data->{itemtype};
2593         $charge    = $item_data->{rentalcharge};
2594         my $branch = C4::Branch::mybranch();
2595         my $discount_query = q|SELECT rentaldiscount,
2596             issuingrules.itemtype, issuingrules.branchcode
2597             FROM borrowers
2598             LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2599             WHERE borrowers.borrowernumber = ?
2600             AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
2601             AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
2602         my $discount_sth = $dbh->prepare($discount_query);
2603         $discount_sth->execute( $borrowernumber, $item_type, $branch );
2604         my $discount_rules = $discount_sth->fetchall_arrayref({});
2605         if (@{$discount_rules}) {
2606             # We may have multiple rules so get the most specific
2607             my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
2608             $charge = ( $charge * ( 100 - $discount ) ) / 100;
2609         }
2610     }
2611
2612     $sth->finish; # we havent _explicitly_ fetched all rows
2613     return ( $charge, $item_type );
2614 }
2615
2616 # Select most appropriate discount rule from those returned
2617 sub _get_discount_from_rule {
2618     my ($rules_ref, $branch, $itemtype) = @_;
2619     my $discount;
2620
2621     if (@{$rules_ref} == 1) { # only 1 applicable rule use it
2622         $discount = $rules_ref->[0]->{rentaldiscount};
2623         return (defined $discount) ? $discount : 0;
2624     }
2625     # could have up to 4 does one match $branch and $itemtype
2626     my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
2627     if (@d) {
2628         $discount = $d[0]->{rentaldiscount};
2629         return (defined $discount) ? $discount : 0;
2630     }
2631     # do we have item type + all branches
2632     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
2633     if (@d) {
2634         $discount = $d[0]->{rentaldiscount};
2635         return (defined $discount) ? $discount : 0;
2636     }
2637     # do we all item types + this branch
2638     @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
2639     if (@d) {
2640         $discount = $d[0]->{rentaldiscount};
2641         return (defined $discount) ? $discount : 0;
2642     }
2643     # so all and all (surely we wont get here)
2644     @d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
2645     if (@d) {
2646         $discount = $d[0]->{rentaldiscount};
2647         return (defined $discount) ? $discount : 0;
2648     }
2649     # none of the above
2650     return 0;
2651 }
2652
2653 =head2 AddIssuingCharge
2654
2655   &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2656
2657 =cut
2658
2659 sub AddIssuingCharge {
2660     my ( $itemnumber, $borrowernumber, $charge ) = @_;
2661     my $dbh = C4::Context->dbh;
2662     my $nextaccntno = getnextacctno( $borrowernumber );
2663     my $manager_id = 0;
2664     $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv;
2665     my $query ="
2666         INSERT INTO accountlines
2667             (borrowernumber, itemnumber, accountno,
2668             date, amount, description, accounttype,
2669             amountoutstanding, manager_id)
2670         VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?,?)
2671     ";
2672     my $sth = $dbh->prepare($query);
2673     $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
2674     $sth->finish;
2675 }
2676
2677 =head2 GetTransfers
2678
2679   GetTransfers($itemnumber);
2680
2681 =cut
2682
2683 sub GetTransfers {
2684     my ($itemnumber) = @_;
2685
2686     my $dbh = C4::Context->dbh;
2687
2688     my $query = '
2689         SELECT datesent,
2690                frombranch,
2691                tobranch
2692         FROM branchtransfers
2693         WHERE itemnumber = ?
2694           AND datearrived IS NULL
2695         ';
2696     my $sth = $dbh->prepare($query);
2697     $sth->execute($itemnumber);
2698     my @row = $sth->fetchrow_array();
2699     $sth->finish;
2700     return @row;
2701 }
2702
2703 =head2 GetTransfersFromTo
2704
2705   @results = GetTransfersFromTo($frombranch,$tobranch);
2706
2707 Returns the list of pending transfers between $from and $to branch
2708
2709 =cut
2710
2711 sub GetTransfersFromTo {
2712     my ( $frombranch, $tobranch ) = @_;
2713     return unless ( $frombranch && $tobranch );
2714     my $dbh   = C4::Context->dbh;
2715     my $query = "
2716         SELECT itemnumber,datesent,frombranch
2717         FROM   branchtransfers
2718         WHERE  frombranch=?
2719           AND  tobranch=?
2720           AND datearrived IS NULL
2721     ";
2722     my $sth = $dbh->prepare($query);
2723     $sth->execute( $frombranch, $tobranch );
2724     my @gettransfers;
2725
2726     while ( my $data = $sth->fetchrow_hashref ) {
2727         push @gettransfers, $data;
2728     }
2729     $sth->finish;
2730     return (@gettransfers);
2731 }
2732
2733 =head2 DeleteTransfer
2734
2735   &DeleteTransfer($itemnumber);
2736
2737 =cut
2738
2739 sub DeleteTransfer {
2740     my ($itemnumber) = @_;
2741     my $dbh          = C4::Context->dbh;
2742     my $sth          = $dbh->prepare(
2743         "DELETE FROM branchtransfers
2744          WHERE itemnumber=?
2745          AND datearrived IS NULL "
2746     );
2747     $sth->execute($itemnumber);
2748     $sth->finish;
2749 }
2750
2751 =head2 AnonymiseIssueHistory
2752
2753   $rows = AnonymiseIssueHistory($date,$borrowernumber)
2754
2755 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2756 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2757
2758 If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
2759 setting (force delete).
2760
2761 return the number of affected rows.
2762
2763 =cut
2764
2765 sub AnonymiseIssueHistory {
2766     my $date           = shift;
2767     my $borrowernumber = shift;
2768     my $dbh            = C4::Context->dbh;
2769     my $query          = "
2770         UPDATE old_issues
2771         SET    borrowernumber = ?
2772         WHERE  returndate < ?
2773           AND borrowernumber IS NOT NULL
2774     ";
2775
2776     # The default of 0 does not work due to foreign key constraints
2777     # The anonymisation will fail quietly if AnonymousPatron is not a valid entry
2778     my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
2779     my @bind_params = ($anonymouspatron, $date);
2780     if (defined $borrowernumber) {
2781        $query .= " AND borrowernumber = ?";
2782        push @bind_params, $borrowernumber;
2783     } else {
2784        $query .= " AND (SELECT privacy FROM borrowers WHERE borrowers.borrowernumber=old_issues.borrowernumber) <> 0";
2785     }
2786     my $sth = $dbh->prepare($query);
2787     $sth->execute(@bind_params);
2788     my $rows_affected = $sth->rows;  ### doublecheck row count return function
2789     return $rows_affected;
2790 }
2791
2792 =head2 SendCirculationAlert
2793
2794 Send out a C<check-in> or C<checkout> alert using the messaging system.
2795
2796 B<Parameters>:
2797
2798 =over 4
2799
2800 =item type
2801
2802 Valid values for this parameter are: C<CHECKIN> and C<CHECKOUT>.
2803
2804 =item item
2805
2806 Hashref of information about the item being checked in or out.
2807
2808 =item borrower
2809
2810 Hashref of information about the borrower of the item.
2811
2812 =item branch
2813
2814 The branchcode from where the checkout or check-in took place.
2815
2816 =back
2817
2818 B<Example>:
2819
2820     SendCirculationAlert({
2821         type     => 'CHECKOUT',
2822         item     => $item,
2823         borrower => $borrower,
2824         branch   => $branch,
2825     });
2826
2827 =cut
2828
2829 sub SendCirculationAlert {
2830     my ($opts) = @_;
2831     my ($type, $item, $borrower, $branch) =
2832         ($opts->{type}, $opts->{item}, $opts->{borrower}, $opts->{branch});
2833     my %message_name = (
2834         CHECKIN  => 'Item_Check_in',
2835         CHECKOUT => 'Item_Checkout',
2836     );
2837     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
2838         borrowernumber => $borrower->{borrowernumber},
2839         message_name   => $message_name{$type},
2840     });
2841     my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
2842     my $letter =  C4::Letters::GetPreparedLetter (
2843         module => 'circulation',
2844         letter_code => $type,
2845         branchcode => $branch,
2846         tables => {
2847             $issues_table => $item->{itemnumber},
2848             'items'       => $item->{itemnumber},
2849             'biblio'      => $item->{biblionumber},
2850             'biblioitems' => $item->{biblionumber},
2851             'borrowers'   => $borrower,
2852             'branches'    => $branch,
2853         }
2854     ) or return;
2855
2856     my @transports = keys %{ $borrower_preferences->{transports} };
2857     # warn "no transports" unless @transports;
2858     for (@transports) {
2859         # warn "transport: $_";
2860         my $message = C4::Message->find_last_message($borrower, $type, $_);
2861         if (!$message) {
2862             #warn "create new message";
2863             C4::Message->enqueue($letter, $borrower, $_);
2864         } else {
2865             #warn "append to old message";
2866             $message->append($letter);
2867             $message->update;
2868         }
2869     }
2870
2871     return $letter;
2872 }
2873
2874 =head2 updateWrongTransfer
2875
2876   $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2877
2878 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 
2879
2880 =cut
2881
2882 sub updateWrongTransfer {
2883         my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2884         my $dbh = C4::Context->dbh;     
2885 # first step validate the actual line of transfert .
2886         my $sth =
2887                 $dbh->prepare(
2888                         "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2889                 );
2890                 $sth->execute($FromLibrary,$itemNumber);
2891                 $sth->finish;
2892
2893 # second step create a new line of branchtransfer to the right location .
2894         ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2895
2896 #third step changing holdingbranch of item
2897         UpdateHoldingbranch($FromLibrary,$itemNumber);
2898 }
2899
2900 =head2 UpdateHoldingbranch
2901
2902   $items = UpdateHoldingbranch($branch,$itmenumber);
2903
2904 Simple methode for updating hodlingbranch in items BDD line
2905
2906 =cut
2907
2908 sub UpdateHoldingbranch {
2909         my ( $branch,$itemnumber ) = @_;
2910     ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2911 }
2912
2913 =head2 CalcDateDue
2914
2915 $newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower);
2916
2917 this function calculates the due date given the start date and configured circulation rules,
2918 checking against the holidays calendar as per the 'useDaysMode' syspref.
2919 C<$startdate>   = C4::Dates object representing start date of loan period (assumed to be today)
2920 C<$itemtype>  = itemtype code of item in question
2921 C<$branch>  = location whose calendar to use
2922 C<$borrower> = Borrower object
2923
2924 =cut
2925
2926 sub CalcDateDue {
2927     my ( $startdate, $itemtype, $branch, $borrower ) = @_;
2928
2929     # loanlength now a href
2930     my $loanlength =
2931       GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
2932
2933     my $datedue;
2934
2935     # if globalDueDate ON the datedue is set to that date
2936     if (C4::Context->preference('globalDueDate')
2937         && ( C4::Context->preference('globalDueDate') =~
2938             C4::Dates->regexp('syspref') )
2939       ) {
2940         $datedue = dt_from_string(
2941             C4::Context->preference('globalDueDate'),
2942             C4::Context->preference('dateformat')
2943         );
2944     } else {
2945
2946         # otherwise, calculate the datedue as normal
2947         if ( C4::Context->preference('useDaysMode') eq 'Days' )
2948         {    # ignoring calendar
2949             my $dt =
2950               DateTime->now( time_zone => C4::Context->tz() )
2951               ->truncate( to => 'minute' );
2952             if ( $loanlength->{lengthunit} eq 'hours' ) {
2953                 $dt->add( hours => $loanlength->{issuelength} );
2954                 return $dt;
2955             } else {    # days
2956                 $dt->add( days => $loanlength->{issuelength} );
2957                 $dt->set_hour(23);
2958                 $dt->set_minute(59);
2959                 return $dt;
2960             }
2961         } else {
2962             my $dur;
2963             if ($loanlength->{lengthunit} eq 'hours') {
2964                 $dur = DateTime::Duration->new( hours => $loanlength->{issuelength});
2965             }
2966             else { # days
2967                 $dur = DateTime::Duration->new( days => $loanlength->{issuelength});
2968             }
2969             if (ref $startdate ne 'DateTime' ) {
2970                 $startdate = dt_from_string($startdate);
2971             }
2972             my $calendar = Koha::Calendar->new( branchcode => $branch );
2973             $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} );
2974             if ($loanlength->{lengthunit} eq 'days') {
2975                 $datedue->set_hour(23);
2976                 $datedue->set_minute(59);
2977             }
2978         }
2979     }
2980
2981     # if Hard Due Dates are used, retreive them and apply as necessary
2982     my ( $hardduedate, $hardduedatecompare ) =
2983       GetHardDueDate( $borrower->{'categorycode'}, $itemtype, $branch );
2984     if ($hardduedate) {    # hardduedates are currently dates
2985         $hardduedate->truncate( to => 'minute' );
2986         $hardduedate->set_hour(23);
2987         $hardduedate->set_minute(59);
2988         my $cmp = DateTime->compare( $hardduedate, $datedue );
2989
2990 # if the calculated due date is after the 'before' Hard Due Date (ceiling), override
2991 # if the calculated date is before the 'after' Hard Due Date (floor), override
2992 # if the hard due date is set to 'exactly', overrride
2993         if ( $hardduedatecompare == 0 || $hardduedatecompare == $cmp ) {
2994             $datedue = $hardduedate->clone;
2995         }
2996
2997         # in all other cases, keep the date due as it is
2998     }
2999
3000     # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
3001     if ( C4::Context->preference('ReturnBeforeExpiry') ) {
3002         my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' );
3003         if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) {
3004             $datedue = $expiry_dt->clone;
3005         }
3006     }
3007
3008     return $datedue;
3009 }
3010
3011
3012 =head2 CheckRepeatableHolidays
3013
3014   $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
3015
3016 This function checks if the date due is a repeatable holiday
3017
3018 C<$date_due>   = returndate calculate with no day check
3019 C<$itemnumber>  = itemnumber
3020 C<$branchcode>  = localisation of issue 
3021
3022 =cut
3023
3024 sub CheckRepeatableHolidays{
3025 my($itemnumber,$week_day,$branchcode)=@_;
3026 my $dbh = C4::Context->dbh;
3027 my $query = qq|SELECT count(*)  
3028         FROM repeatable_holidays 
3029         WHERE branchcode=?
3030         AND weekday=?|;
3031 my $sth = $dbh->prepare($query);
3032 $sth->execute($branchcode,$week_day);
3033 my $result=$sth->fetchrow;
3034 $sth->finish;
3035 return $result;
3036 }
3037
3038
3039 =head2 CheckSpecialHolidays
3040
3041   $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
3042
3043 This function check if the date is a special holiday
3044
3045 C<$years>   = the years of datedue
3046 C<$month>   = the month of datedue
3047 C<$day>     = the day of datedue
3048 C<$itemnumber>  = itemnumber
3049 C<$branchcode>  = localisation of issue 
3050
3051 =cut
3052
3053 sub CheckSpecialHolidays{
3054 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
3055 my $dbh = C4::Context->dbh;
3056 my $query=qq|SELECT count(*) 
3057              FROM `special_holidays`
3058              WHERE year=?
3059              AND month=?
3060              AND day=?
3061              AND branchcode=?
3062             |;
3063 my $sth = $dbh->prepare($query);
3064 $sth->execute($years,$month,$day,$branchcode);
3065 my $countspecial=$sth->fetchrow ;
3066 $sth->finish;
3067 return $countspecial;
3068 }
3069
3070 =head2 CheckRepeatableSpecialHolidays
3071
3072   $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
3073
3074 This function check if the date is a repeatble special holidays
3075
3076 C<$month>   = the month of datedue
3077 C<$day>     = the day of datedue
3078 C<$itemnumber>  = itemnumber
3079 C<$branchcode>  = localisation of issue 
3080
3081 =cut
3082
3083 sub CheckRepeatableSpecialHolidays{
3084 my ($month,$day,$itemnumber,$branchcode) = @_;
3085 my $dbh = C4::Context->dbh;
3086 my $query=qq|SELECT count(*) 
3087              FROM `repeatable_holidays`
3088              WHERE month=?
3089              AND day=?
3090              AND branchcode=?
3091             |;
3092 my $sth = $dbh->prepare($query);
3093 $sth->execute($month,$day,$branchcode);
3094 my $countspecial=$sth->fetchrow ;
3095 $sth->finish;
3096 return $countspecial;
3097 }
3098
3099
3100
3101 sub CheckValidBarcode{
3102 my ($barcode) = @_;
3103 my $dbh = C4::Context->dbh;
3104 my $query=qq|SELECT count(*) 
3105              FROM items 
3106              WHERE barcode=?
3107             |;
3108 my $sth = $dbh->prepare($query);
3109 $sth->execute($barcode);
3110 my $exist=$sth->fetchrow ;
3111 $sth->finish;
3112 return $exist;
3113 }
3114
3115 =head2 IsBranchTransferAllowed
3116
3117   $allowed = IsBranchTransferAllowed( $toBranch, $fromBranch, $code );
3118
3119 Code is either an itemtype or collection doe depending on the pref BranchTransferLimitsType
3120
3121 =cut
3122
3123 sub IsBranchTransferAllowed {
3124         my ( $toBranch, $fromBranch, $code ) = @_;
3125
3126         if ( $toBranch eq $fromBranch ) { return 1; } ## Short circuit for speed.
3127         
3128         my $limitType = C4::Context->preference("BranchTransferLimitsType");   
3129         my $dbh = C4::Context->dbh;
3130             
3131         my $sth = $dbh->prepare("SELECT * FROM branch_transfer_limits WHERE toBranch = ? AND fromBranch = ? AND $limitType = ?");
3132         $sth->execute( $toBranch, $fromBranch, $code );
3133         my $limit = $sth->fetchrow_hashref();
3134                         
3135         ## If a row is found, then that combination is not allowed, if no matching row is found, then the combination *is allowed*
3136         if ( $limit->{'limitId'} ) {
3137                 return 0;
3138         } else {
3139                 return 1;
3140         }
3141 }                                                        
3142
3143 =head2 CreateBranchTransferLimit
3144
3145   CreateBranchTransferLimit( $toBranch, $fromBranch, $code );
3146
3147 $code is either itemtype or collection code depending on what the pref BranchTransferLimitsType is set to.
3148
3149 =cut
3150
3151 sub CreateBranchTransferLimit {
3152    my ( $toBranch, $fromBranch, $code ) = @_;
3153
3154    my $limitType = C4::Context->preference("BranchTransferLimitsType");
3155    
3156    my $dbh = C4::Context->dbh;
3157    
3158    my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
3159    $sth->execute( $code, $toBranch, $fromBranch );
3160 }
3161
3162 =head2 DeleteBranchTransferLimits
3163
3164 DeleteBranchTransferLimits($frombranch);
3165
3166 Deletes all the branch transfer limits for one branch
3167
3168 =cut
3169
3170 sub DeleteBranchTransferLimits {
3171     my $branch = shift;
3172     my $dbh    = C4::Context->dbh;
3173     my $sth    = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
3174     $sth->execute($branch);
3175 }
3176
3177 sub ReturnLostItem{
3178     my ( $borrowernumber, $itemnum ) = @_;
3179
3180     MarkIssueReturned( $borrowernumber, $itemnum );
3181     my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
3182     my $item = C4::Items::GetItem( $itemnum );
3183     my $old_note = ($item->{'paidfor'} && ($item->{'paidfor'} ne q{})) ? $item->{'paidfor'}.' / ' : q{};
3184     my @datearr = localtime(time);
3185     my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
3186     my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
3187     ModItem({ paidfor =>  $old_note."Paid for by $bor $date" }, undef, $itemnum);
3188 }
3189
3190
3191 sub LostItem{
3192     my ($itemnumber, $mark_returned, $charge_fee) = @_;
3193
3194     my $dbh = C4::Context->dbh();
3195     my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title 
3196                            FROM issues 
3197                            JOIN items USING (itemnumber) 
3198                            JOIN biblio USING (biblionumber)
3199                            WHERE issues.itemnumber=?");
3200     $sth->execute($itemnumber);
3201     my $issues=$sth->fetchrow_hashref();
3202     $sth->finish;
3203
3204     # if a borrower lost the item, add a replacement cost to the their record
3205     if ( my $borrowernumber = $issues->{borrowernumber} ){
3206         my $borrower = C4::Members::GetMemberDetails( $borrowernumber );
3207
3208         C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
3209           if $charge_fee;
3210         #FIXME : Should probably have a way to distinguish this from an item that really was returned.
3211         #warn " $issues->{'borrowernumber'}  /  $itemnumber ";
3212         MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
3213     }
3214 }
3215
3216 sub GetOfflineOperations {
3217     my $dbh = C4::Context->dbh;
3218     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
3219     $sth->execute(C4::Context->userenv->{'branch'});
3220     my $results = $sth->fetchall_arrayref({});
3221     $sth->finish;
3222     return $results;
3223 }
3224
3225 sub GetOfflineOperation {
3226     my $dbh = C4::Context->dbh;
3227     my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
3228     $sth->execute( shift );
3229     my $result = $sth->fetchrow_hashref;
3230     $sth->finish;
3231     return $result;
3232 }
3233
3234 sub AddOfflineOperation {
3235     my $dbh = C4::Context->dbh;
3236     my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
3237     $sth->execute( @_ );
3238     return "Added.";
3239 }
3240
3241 sub DeleteOfflineOperation {
3242     my $dbh = C4::Context->dbh;
3243     my $sth = $dbh->prepare("DELETE FROM pending_offline_operations WHERE operationid=?");
3244     $sth->execute( shift );
3245     return "Deleted.";
3246 }
3247
3248 sub ProcessOfflineOperation {
3249     my $operation = shift;
3250
3251     my $report;
3252     if ( $operation->{action} eq 'return' ) {
3253         $report = ProcessOfflineReturn( $operation );
3254     } elsif ( $operation->{action} eq 'issue' ) {
3255         $report = ProcessOfflineIssue( $operation );
3256     }
3257
3258     DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
3259
3260     return $report;
3261 }
3262
3263 sub ProcessOfflineReturn {
3264     my $operation = shift;
3265
3266     my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3267
3268     if ( $itemnumber ) {
3269         my $issue = GetOpenIssue( $itemnumber );
3270         if ( $issue ) {
3271             MarkIssueReturned(
3272                 $issue->{borrowernumber},
3273                 $itemnumber,
3274                 undef,
3275                 $operation->{timestamp},
3276             );
3277             ModItem(
3278                 { renewals => 0, onloan => undef },
3279                 $issue->{'biblionumber'},
3280                 $itemnumber
3281             );
3282             return "Success.";
3283         } else {
3284             return "Item not issued.";
3285         }
3286     } else {
3287         return "Item not found.";
3288     }
3289 }
3290
3291 sub ProcessOfflineIssue {
3292     my $operation = shift;
3293
3294     my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
3295
3296     if ( $borrower->{borrowernumber} ) {
3297         my $itemnumber = C4::Items::GetItemnumberFromBarcode( $operation->{barcode} );
3298         unless ($itemnumber) {
3299             return "Barcode not found.";
3300         }
3301         my $issue = GetOpenIssue( $itemnumber );
3302
3303         if ( $issue and ( $issue->{borrowernumber} ne $borrower->{borrowernumber} ) ) { # Item already issued to another borrower, mark it returned
3304             MarkIssueReturned(
3305                 $issue->{borrowernumber},
3306                 $itemnumber,
3307                 undef,
3308                 $operation->{timestamp},
3309             );
3310         }
3311         AddIssue(
3312             $borrower,
3313             $operation->{'barcode'},
3314             undef,
3315             1,
3316             $operation->{timestamp},
3317             undef,
3318         );
3319         return "Success.";
3320     } else {
3321         return "Borrower not found.";
3322     }
3323 }
3324
3325
3326
3327 =head2 TransferSlip
3328
3329   TransferSlip($user_branch, $itemnumber, $to_branch)
3330
3331   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
3332
3333 =cut
3334
3335 sub TransferSlip {
3336     my ($branch, $itemnumber, $to_branch) = @_;
3337
3338     my $item =  GetItem( $itemnumber )
3339       or return;
3340
3341     my $pulldate = C4::Dates->new();
3342
3343     return C4::Letters::GetPreparedLetter (
3344         module => 'circulation',
3345         letter_code => 'TRANSFERSLIP',
3346         branchcode => $branch,
3347         tables => {
3348             'branches'    => $to_branch,
3349             'biblio'      => $item->{biblionumber},
3350             'items'       => $item,
3351         },
3352     );
3353 }
3354
3355
3356 1;
3357
3358 __END__
3359
3360 =head1 AUTHOR
3361
3362 Koha Development Team <http://koha-community.org/>
3363
3364 =cut
3365