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