clean up old-style calls to GetMemberDetails
[koha.git] / C4 / Circulation.pm
blob826efd5ba76cc27052e1a0c7eacd85f0f609cb92
1 package C4::Circulation;
3 # Copyright 2000-2002 Katipo Communications
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
21 use strict;
22 require Exporter;
23 use C4::Context;
24 use C4::Stats;
25 use C4::Reserves;
26 use C4::Koha;
27 use C4::Biblio;
28 use C4::Items;
29 use C4::Members;
30 use C4::Dates;
31 use C4::Calendar;
32 use C4::Accounts;
33 use Date::Calc qw(
34 Today
35 Today_and_Now
36 Add_Delta_YM
37 Add_Delta_DHMS
38 Date_to_Days
39 Day_of_Week
40 Add_Delta_Days
42 use POSIX qw(strftime);
43 use C4::Branch; # GetBranches
44 use C4::Log; # logaction
46 use Data::Dumper;
48 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
50 BEGIN {
51 # set the version for version checking
52 $VERSION = 3.01;
53 @ISA = qw(Exporter);
55 # FIXME subs that should probably be elsewhere
56 push @EXPORT, qw(
57 &FixOverduesOnReturn
58 &barcodedecode
61 # subs to deal with issuing a book
62 push @EXPORT, qw(
63 &CanBookBeIssued
64 &CanBookBeRenewed
65 &AddIssue
66 &AddRenewal
67 &GetRenewCount
68 &GetItemIssue
69 &GetItemIssues
70 &GetBorrowerIssues
71 &GetIssuingCharges
72 &GetIssuingRule
73 &GetBranchBorrowerCircRule
74 &GetBiblioIssues
75 &AnonymiseIssueHistory
78 # subs to deal with returns
79 push @EXPORT, qw(
80 &AddReturn
81 &MarkIssueReturned
84 # subs to deal with transfers
85 push @EXPORT, qw(
86 &transferbook
87 &GetTransfers
88 &GetTransfersFromTo
89 &updateWrongTransfer
90 &DeleteTransfer
94 =head1 NAME
96 C4::Circulation - Koha circulation module
98 =head1 SYNOPSIS
100 use C4::Circulation;
102 =head1 DESCRIPTION
104 The functions in this module deal with circulation, issues, and
105 returns, as well as general information about the library.
106 Also deals with stocktaking.
108 =head1 FUNCTIONS
110 =head2 barcodedecode
112 =head3 $str = &barcodedecode($barcode);
114 =over 4
116 =item Generic filter function for barcode string.
117 Called on every circ if the System Pref itemBarcodeInputFilter is set.
118 Will do some manipulation of the barcode for systems that deliver a barcode
119 to circulation.pl that differs from the barcode stored for the item.
120 For proper functioning of this filter, calling the function on the
121 correct barcode string (items.barcode) should return an unaltered barcode.
123 =back
125 =cut
127 # FIXME -- the &decode fcn below should be wrapped into this one.
128 # FIXME -- these plugins should be moved out of Circulation.pm
130 sub barcodedecode {
131 my ($barcode) = @_;
132 my $filter = C4::Context->preference('itemBarcodeInputFilter');
133 if($filter eq 'whitespace') {
134 $barcode =~ s/\s//g;
135 return $barcode;
136 } elsif($filter eq 'cuecat') {
137 chomp($barcode);
138 my @fields = split( /\./, $barcode );
139 my @results = map( decode($_), @fields[ 1 .. $#fields ] );
140 if ( $#results == 2 ) {
141 return $results[2];
143 else {
144 return $barcode;
146 } elsif($filter eq 'T-prefix') {
147 if ( $barcode =~ /^[Tt]/) {
148 if (substr($barcode,1,1) eq '0') {
149 return $barcode;
150 } else {
151 $barcode = substr($barcode,2) + 0 ;
154 return sprintf( "T%07d",$barcode);
158 =head2 decode
160 =head3 $str = &decode($chunk);
162 =over 4
164 =item Decodes a segment of a string emitted by a CueCat barcode scanner and
165 returns it.
167 =back
169 =cut
171 sub decode {
172 my ($encoded) = @_;
173 my $seq =
174 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-';
175 my @s = map { index( $seq, $_ ); } split( //, $encoded );
176 my $l = ( $#s + 1 ) % 4;
177 if ($l) {
178 if ( $l == 1 ) {
179 warn "Error!";
180 return;
182 $l = 4 - $l;
183 $#s += $l;
185 my $r = '';
186 while ( $#s >= 0 ) {
187 my $n = ( ( $s[0] << 6 | $s[1] ) << 6 | $s[2] ) << 6 | $s[3];
188 $r .=
189 chr( ( $n >> 16 ) ^ 67 )
190 .chr( ( $n >> 8 & 255 ) ^ 67 )
191 .chr( ( $n & 255 ) ^ 67 );
192 @s = @s[ 4 .. $#s ];
194 $r = substr( $r, 0, length($r) - $l );
195 return $r;
198 =head2 transferbook
200 ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, $barcode, $ignore_reserves);
202 Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
204 C<$newbranch> is the code for the branch to which the item should be transferred.
206 C<$barcode> is the barcode of the item to be transferred.
208 If C<$ignore_reserves> is true, C<&transferbook> ignores reserves.
209 Otherwise, if an item is reserved, the transfer fails.
211 Returns three values:
213 =head3 $dotransfer
215 is true if the transfer was successful.
217 =head3 $messages
219 is a reference-to-hash which may have any of the following keys:
221 =over 4
223 =item C<BadBarcode>
225 There is no item in the catalog with the given barcode. The value is C<$barcode>.
227 =item C<IsPermanent>
229 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.
231 =item C<DestinationEqualsHolding>
233 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.
235 =item C<WasReturned>
237 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.
239 =item C<ResFound>
241 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>.
243 =item C<WasTransferred>
245 The item was eligible to be transferred. Barring problems communicating with the database, the transfer should indeed have succeeded. The value should be ignored.
247 =back
249 =cut
251 sub transferbook {
252 my ( $tbr, $barcode, $ignoreRs ) = @_;
253 my $messages;
254 my $dotransfer = 1;
255 my $branches = GetBranches();
256 my $itemnumber = GetItemnumberFromBarcode( $barcode );
257 my $issue = GetItemIssue($itemnumber);
258 my $biblio = GetBiblioFromItemNumber($itemnumber);
260 # bad barcode..
261 if ( not $itemnumber ) {
262 $messages->{'BadBarcode'} = $barcode;
263 $dotransfer = 0;
266 # get branches of book...
267 my $hbr = $biblio->{'homebranch'};
268 my $fbr = $biblio->{'holdingbranch'};
270 # if is permanent...
271 if ( $hbr && $branches->{$hbr}->{'PE'} ) {
272 $messages->{'IsPermanent'} = $hbr;
275 # can't transfer book if is already there....
276 if ( $fbr eq $tbr ) {
277 $messages->{'DestinationEqualsHolding'} = 1;
278 $dotransfer = 0;
281 # check if it is still issued to someone, return it...
282 if ($issue->{borrowernumber}) {
283 AddReturn( $barcode, $fbr );
284 $messages->{'WasReturned'} = $issue->{borrowernumber};
287 # find reserves.....
288 # That'll save a database query.
289 my ( $resfound, $resrec ) =
290 CheckReserves( $itemnumber );
291 if ( $resfound and not $ignoreRs ) {
292 $resrec->{'ResFound'} = $resfound;
294 # $messages->{'ResFound'} = $resrec;
295 $dotransfer = 1;
298 #actually do the transfer....
299 if ($dotransfer) {
300 ModItemTransfer( $itemnumber, $fbr, $tbr );
302 # don't need to update MARC anymore, we do it in batch now
303 $messages->{'WasTransfered'} = 1;
304 ModDateLastSeen( $itemnumber );
306 return ( $dotransfer, $messages, $biblio );
309 =head2 CanBookBeIssued
311 Check if a book can be issued.
313 my ($issuingimpossible,$needsconfirmation) = CanBookBeIssued($borrower,$barcode,$year,$month,$day);
315 =over 4
317 =item C<$borrower> hash with borrower informations (from GetMemberDetails)
319 =item C<$barcode> is the bar code of the book being issued.
321 =item C<$year> C<$month> C<$day> contains the date of the return (in case it's forced by "stickyduedate".
323 =back
325 Returns :
327 =over 4
329 =item C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
330 Possible values are :
332 =back
334 =head3 INVALID_DATE
336 sticky due date is invalid
338 =head3 GNA
340 borrower gone with no address
342 =head3 CARD_LOST
344 borrower declared it's card lost
346 =head3 DEBARRED
348 borrower debarred
350 =head3 UNKNOWN_BARCODE
352 barcode unknown
354 =head3 NOT_FOR_LOAN
356 item is not for loan
358 =head3 WTHDRAWN
360 item withdrawn.
362 =head3 RESTRICTED
364 item is restricted (set by ??)
366 C<$issuingimpossible> a reference to a hash. It contains reasons why issuing is impossible.
367 Possible values are :
369 =head3 DEBT
371 borrower has debts.
373 =head3 RENEW_ISSUE
375 renewing, not issuing
377 =head3 ISSUED_TO_ANOTHER
379 issued to someone else.
381 =head3 RESERVED
383 reserved for someone else.
385 =head3 INVALID_DATE
387 sticky due date is invalid
389 =head3 TOO_MANY
391 if the borrower borrows to much things
393 =cut
395 # check if a book can be issued.
398 sub TooMany {
399 my $borrower = shift;
400 my $biblionumber = shift;
401 my $item = shift;
402 my $cat_borrower = $borrower->{'categorycode'};
403 my $dbh = C4::Context->dbh;
404 my $branch;
405 # Get which branchcode we need
406 if (C4::Context->preference('CircControl') eq 'PickupLibrary'){
407 $branch = C4::Context->userenv->{'branch'};
409 elsif (C4::Context->preference('CircControl') eq 'PatronLibrary'){
410 $branch = $borrower->{'branchcode'};
412 else {
413 # items home library
414 $branch = $item->{'homebranch'};
416 my $type = (C4::Context->preference('item-level_itypes'))
417 ? $item->{'itype'} # item-level
418 : $item->{'itemtype'}; # biblio-level
420 # given branch, patron category, and item type, determine
421 # applicable issuing rule
422 my $issuing_rule = GetIssuingRule($cat_borrower, $type, $branch);
424 # if a rule is found and has a loan limit set, count
425 # how many loans the patron already has that meet that
426 # rule
427 if (defined($issuing_rule) and defined($issuing_rule->{'maxissueqty'})) {
428 my @bind_params;
429 my $count_query = "SELECT COUNT(*) FROM issues
430 JOIN items USING (itemnumber) ";
432 my $rule_itemtype = $issuing_rule->{itemtype};
433 if ($rule_itemtype eq "*") {
434 # matching rule has the default item type, so count only
435 # those existing loans that don't fall under a more
436 # specific rule
437 if (C4::Context->preference('item-level_itypes')) {
438 $count_query .= " WHERE items.itype NOT IN (
439 SELECT itemtype FROM issuingrules
440 WHERE branchcode = ?
441 AND (categorycode = ? OR categorycode = ?)
442 AND itemtype <> '*'
443 ) ";
444 } else {
445 $count_query .= " JOIN biblioitems USING (biblionumber)
446 WHERE biblioitems.itemtype NOT IN (
447 SELECT itemtype FROM issuingrules
448 WHERE branchcode = ?
449 AND (categorycode = ? OR categorycode = ?)
450 AND itemtype <> '*'
451 ) ";
453 push @bind_params, $issuing_rule->{branchcode};
454 push @bind_params, $issuing_rule->{categorycode};
455 push @bind_params, $cat_borrower;
456 } else {
457 # rule has specific item type, so count loans of that
458 # specific item type
459 if (C4::Context->preference('item-level_itypes')) {
460 $count_query .= " WHERE items.itype = ? ";
461 } else {
462 $count_query .= " JOIN biblioitems USING (biblionumber)
463 WHERE biblioitems.itemtype= ? ";
465 push @bind_params, $type;
468 $count_query .= " AND borrowernumber = ? ";
469 push @bind_params, $borrower->{'borrowernumber'};
470 my $rule_branch = $issuing_rule->{branchcode};
471 if ($rule_branch ne "*") {
472 if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
473 $count_query .= " AND issues.branchcode = ? ";
474 push @bind_params, $branch;
475 } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
476 ; # if branch is the patron's home branch, then count all loans by patron
477 } else {
478 $count_query .= " AND items.homebranch = ? ";
479 push @bind_params, $branch;
483 my $count_sth = $dbh->prepare($count_query);
484 $count_sth->execute(@bind_params);
485 my ($current_loan_count) = $count_sth->fetchrow_array;
487 my $max_loans_allowed = $issuing_rule->{'maxissueqty'};
488 if ($current_loan_count >= $max_loans_allowed) {
489 return "$current_loan_count / $max_loans_allowed";
493 # Now count total loans against the limit for the branch
494 my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
495 if (defined($branch_borrower_circ_rule->{maxissueqty})) {
496 my @bind_params = ();
497 my $branch_count_query = "SELECT COUNT(*) FROM issues
498 JOIN items USING (itemnumber)
499 WHERE borrowernumber = ? ";
500 push @bind_params, $borrower->{borrowernumber};
502 if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
503 $branch_count_query .= " AND issues.branchcode = ? ";
504 push @bind_params, $branch;
505 } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
506 ; # if branch is the patron's home branch, then count all loans by patron
507 } else {
508 $branch_count_query .= " AND items.homebranch = ? ";
509 push @bind_params, $branch;
511 my $branch_count_sth = $dbh->prepare($branch_count_query);
512 $branch_count_sth->execute(@bind_params);
513 my ($current_loan_count) = $branch_count_sth->fetchrow_array;
515 my $max_loans_allowed = $branch_borrower_circ_rule->{maxissueqty};
516 if ($current_loan_count >= $max_loans_allowed) {
517 return "$current_loan_count / $max_loans_allowed";
521 # OK, the patron can issue !!!
522 return;
525 =head2 itemissues
527 @issues = &itemissues($biblioitemnumber, $biblio);
529 Looks up information about who has borrowed the bookZ<>(s) with the
530 given biblioitemnumber.
532 C<$biblio> is ignored.
534 C<&itemissues> returns an array of references-to-hash. The keys
535 include the fields from the C<items> table in the Koha database.
536 Additional keys include:
538 =over 4
540 =item C<date_due>
542 If the item is currently on loan, this gives the due date.
544 If the item is not on loan, then this is either "Available" or
545 "Cancelled", if the item has been withdrawn.
547 =item C<card>
549 If the item is currently on loan, this gives the card number of the
550 patron who currently has the item.
552 =item C<timestamp0>, C<timestamp1>, C<timestamp2>
554 These give the timestamp for the last three times the item was
555 borrowed.
557 =item C<card0>, C<card1>, C<card2>
559 The card number of the last three patrons who borrowed this item.
561 =item C<borrower0>, C<borrower1>, C<borrower2>
563 The borrower number of the last three patrons who borrowed this item.
565 =back
567 =cut
570 sub itemissues {
571 my ( $bibitem, $biblio ) = @_;
572 my $dbh = C4::Context->dbh;
573 my $sth =
574 $dbh->prepare("Select * from items where items.biblioitemnumber = ?")
575 || die $dbh->errstr;
576 my $i = 0;
577 my @results;
579 $sth->execute($bibitem) || die $sth->errstr;
581 while ( my $data = $sth->fetchrow_hashref ) {
583 # Find out who currently has this item.
584 # FIXME - Wouldn't it be better to do this as a left join of
585 # some sort? Currently, this code assumes that if
586 # fetchrow_hashref() fails, then the book is on the shelf.
587 # fetchrow_hashref() can fail for any number of reasons (e.g.,
588 # database server crash), not just because no items match the
589 # search criteria.
590 my $sth2 = $dbh->prepare(
591 "SELECT * FROM issues
592 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
593 WHERE itemnumber = ?
597 $sth2->execute( $data->{'itemnumber'} );
598 if ( my $data2 = $sth2->fetchrow_hashref ) {
599 $data->{'date_due'} = $data2->{'date_due'};
600 $data->{'card'} = $data2->{'cardnumber'};
601 $data->{'borrower'} = $data2->{'borrowernumber'};
603 else {
604 $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
607 $sth2->finish;
609 # Find the last 3 people who borrowed this item.
610 $sth2 = $dbh->prepare(
611 "SELECT * FROM old_issues
612 LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
613 WHERE itemnumber = ?
614 ORDER BY returndate DESC,timestamp DESC"
617 $sth2->execute( $data->{'itemnumber'} );
618 for ( my $i2 = 0 ; $i2 < 2 ; $i2++ )
619 { # FIXME : error if there is less than 3 pple borrowing this item
620 if ( my $data2 = $sth2->fetchrow_hashref ) {
621 $data->{"timestamp$i2"} = $data2->{'timestamp'};
622 $data->{"card$i2"} = $data2->{'cardnumber'};
623 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
624 } # if
625 } # for
627 $sth2->finish;
628 $results[$i] = $data;
629 $i++;
632 $sth->finish;
633 return (@results);
636 =head2 CanBookBeIssued
638 ( $issuingimpossible, $needsconfirmation ) =
639 CanBookBeIssued( $borrower, $barcode, $duedatespec, $inprocess );
640 C<$duedatespec> is a C4::Dates object.
641 C<$issuingimpossible> and C<$needsconfirmation> are some hashref.
643 =cut
645 sub CanBookBeIssued {
646 my ( $borrower, $barcode, $duedate, $inprocess ) = @_;
647 my %needsconfirmation; # filled with problems that needs confirmations
648 my %issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE
649 my $item = GetItem(GetItemnumberFromBarcode( $barcode ));
650 my $issue = GetItemIssue($item->{itemnumber});
651 my $biblioitem = GetBiblioItemData($item->{biblioitemnumber});
652 $item->{'itemtype'}=$item->{'itype'};
653 my $dbh = C4::Context->dbh;
656 # DUE DATE is OK ? -- should already have checked.
658 #$issuingimpossible{INVALID_DATE} = 1 unless ($duedate);
661 # BORROWER STATUS
663 if ( $borrower->{'category_type'} eq 'X' && ( $item->{barcode} )) {
664 # stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 .
665 &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'});
666 return( { STATS => 1 }, {});
668 if ( $borrower->{flags}->{GNA} ) {
669 $issuingimpossible{GNA} = 1;
671 if ( $borrower->{flags}->{'LOST'} ) {
672 $issuingimpossible{CARD_LOST} = 1;
674 if ( $borrower->{flags}->{'DBARRED'} ) {
675 $issuingimpossible{DEBARRED} = 1;
677 if ( $borrower->{'dateexpiry'} eq '0000-00-00') {
678 $issuingimpossible{EXPIRED} = 1;
679 } else {
680 my @expirydate= split /-/,$borrower->{'dateexpiry'};
681 if($expirydate[0]==0 || $expirydate[1]==0|| $expirydate[2]==0 ||
682 Date_to_Days(Today) > Date_to_Days( @expirydate )) {
683 $issuingimpossible{EXPIRED} = 1;
687 # BORROWER STATUS
690 # DEBTS
691 my ($amount) =
692 C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->output('iso') );
693 if ( C4::Context->preference("IssuingInProcess") ) {
694 my $amountlimit = C4::Context->preference("noissuescharge");
695 if ( $amount > $amountlimit && !$inprocess ) {
696 $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
698 elsif ( $amount <= $amountlimit && !$inprocess ) {
699 $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
702 else {
703 if ( $amount > 0 ) {
704 $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
709 # JB34 CHECKS IF BORROWERS DONT HAVE ISSUE TOO MANY BOOKS
711 my $toomany = TooMany( $borrower, $item->{biblionumber}, $item );
712 $needsconfirmation{TOO_MANY} = $toomany if $toomany;
715 # ITEM CHECKING
717 unless ( $item->{barcode} ) {
718 $issuingimpossible{UNKNOWN_BARCODE} = 1;
720 if ( $item->{'notforloan'}
721 && $item->{'notforloan'} > 0 )
723 $issuingimpossible{NOT_FOR_LOAN} = 1;
725 elsif ( !$item->{'notforloan'} ){
726 # we have to check itemtypes.notforloan also
727 if (C4::Context->preference('item-level_itypes')){
728 # this should probably be a subroutine
729 my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
730 $sth->execute($item->{'itemtype'});
731 my $notforloan=$sth->fetchrow_hashref();
732 $sth->finish();
733 if ($notforloan->{'notforloan'} == 1){
734 $issuingimpossible{NOT_FOR_LOAN} = 1;
737 elsif ($biblioitem->{'notforloan'} == 1){
738 $issuingimpossible{NOT_FOR_LOAN} = 1;
741 if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} == 1 )
743 $issuingimpossible{WTHDRAWN} = 1;
745 if ( $item->{'restricted'}
746 && $item->{'restricted'} == 1 )
748 $issuingimpossible{RESTRICTED} = 1;
750 if ( C4::Context->preference("IndependantBranches") ) {
751 my $userenv = C4::Context->userenv;
752 if ( ($userenv) && ( $userenv->{flags} != 1 ) ) {
753 $issuingimpossible{NOTSAMEBRANCH} = 1
754 if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
759 # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
761 if ( $issue->{borrowernumber} && $issue->{borrowernumber} eq $borrower->{'borrowernumber'} )
764 # Already issued to current borrower. Ask whether the loan should
765 # be renewed.
766 my ($CanBookBeRenewed,$renewerror) = CanBookBeRenewed(
767 $borrower->{'borrowernumber'},
768 $item->{'itemnumber'}
770 if ( $CanBookBeRenewed == 0 ) { # no more renewals allowed
771 $issuingimpossible{NO_MORE_RENEWALS} = 1;
773 else {
774 $needsconfirmation{RENEW_ISSUE} = 1;
777 elsif ($issue->{borrowernumber}) {
779 # issued to someone else
780 my $currborinfo = GetMemberDetails( $issue->{borrowernumber} );
782 # warn "=>.$currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
783 $needsconfirmation{ISSUED_TO_ANOTHER} =
784 "$currborinfo->{'reservedate'} : $currborinfo->{'firstname'} $currborinfo->{'surname'} ($currborinfo->{'cardnumber'})";
787 # See if the item is on reserve.
788 my ( $restype, $res ) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
789 if ($restype) {
790 my $resbor = $res->{'borrowernumber'};
791 my ( $resborrower ) = GetMemberDetails( $resbor, 0 );
792 my $branches = GetBranches();
793 my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
794 if ( $resbor ne $borrower->{'borrowernumber'} && $restype eq "Waiting" )
796 # The item is on reserve and waiting, but has been
797 # reserved by some other patron.
798 $needsconfirmation{RESERVE_WAITING} =
799 "$resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'}, $branchname)";
801 elsif ( $restype eq "Reserved" ) {
802 # The item is on reserve for someone else.
803 $needsconfirmation{RESERVED} =
804 "$res->{'reservedate'} : $resborrower->{'firstname'} $resborrower->{'surname'} ($resborrower->{'cardnumber'})";
807 if ( C4::Context->preference("LibraryName") eq "Horowhenua Library Trust" ) {
808 if ( $borrower->{'categorycode'} eq 'W' ) {
809 my %emptyhash;
810 return ( \%emptyhash, \%needsconfirmation );
813 return ( \%issuingimpossible, \%needsconfirmation );
816 =head2 AddIssue
818 Issue a book. Does no check, they are done in CanBookBeIssued. If we reach this sub, it means the user confirmed if needed.
820 &AddIssue($borrower,$barcode,$date)
822 =over 4
824 =item C<$borrower> hash with borrower informations (from GetMemberDetails)
826 =item C<$barcode> is the bar code of the book being issued.
828 =item C<$date> contains the max date of return. calculated if empty.
830 AddIssue does the following things :
831 - step 01: check that there is a borrowernumber & a barcode provided
832 - check for RENEWAL (book issued & being issued to the same patron)
833 - renewal YES = Calculate Charge & renew
834 - renewal NO =
835 * BOOK ACTUALLY ISSUED ? do a return if book is actually issued (but to someone else)
836 * RESERVE PLACED ?
837 - fill reserve if reserve to this patron
838 - cancel reserve or not, otherwise
839 * TRANSFERT PENDING ?
840 - complete the transfert
841 * ISSUE THE BOOK
843 =back
845 =cut
847 sub AddIssue {
848 my ( $borrower, $barcode, $date, $cancelreserve ) = @_;
849 my $dbh = C4::Context->dbh;
850 my $barcodecheck=CheckValidBarcode($barcode);
851 if ($borrower and $barcode and $barcodecheck ne '0'){
852 # find which item we issue
853 my $item = GetItem('', $barcode);
854 my $datedue;
856 my $branch;
857 # Get which branchcode we need
858 if (C4::Context->preference('CircControl') eq 'PickupLibrary'){
859 $branch = C4::Context->userenv->{'branch'};
861 elsif (C4::Context->preference('CircControl') eq 'PatronLibrary'){
862 $branch = $borrower->{'branchcode'};
864 else {
865 # items home library
866 $branch = $item->{'homebranch'};
869 # get actual issuing if there is one
870 my $actualissue = GetItemIssue( $item->{itemnumber});
872 # get biblioinformation for this item
873 my $biblio = GetBiblioFromItemNumber($item->{itemnumber});
876 # check if we just renew the issue.
878 if ( $actualissue->{borrowernumber} eq $borrower->{'borrowernumber'} ) {
879 AddRenewal(
880 $borrower->{'borrowernumber'},
881 $item->{'itemnumber'},
882 $branch,
883 $date
887 else {
888 # it's NOT a renewal
889 if ( $actualissue->{borrowernumber}) {
890 # This book is currently on loan, but not to the person
891 # who wants to borrow it now. mark it returned before issuing to the new borrower
892 AddReturn(
893 $item->{'barcode'},
894 C4::Context->userenv->{'branch'}
898 # See if the item is on reserve.
899 my ( $restype, $res ) =
900 C4::Reserves::CheckReserves( $item->{'itemnumber'} );
901 if ($restype) {
902 my $resbor = $res->{'borrowernumber'};
903 if ( $resbor eq $borrower->{'borrowernumber'} ) {
905 # The item is reserved by the current patron
906 ModReserveFill($res);
908 elsif ( $restype eq "Waiting" ) {
910 # warn "Waiting";
911 # The item is on reserve and waiting, but has been
912 # reserved by some other patron.
913 my ( $resborrower ) = GetMemberDetails( $resbor, 0 );
914 my $branches = GetBranches();
915 my $branchname =
916 $branches->{ $res->{'branchcode'} }->{'branchname'};
918 elsif ( $restype eq "Reserved" ) {
920 # warn "Reserved";
921 # The item is reserved by someone else.
922 my ( $resborrower ) = GetMemberDetails( $resbor, 0 );
923 my $branches = GetBranches();
924 my $branchname = $branches->{ $res->{'branchcode'} }->{'branchname'};
925 if ($cancelreserve) { # cancel reserves on this item
926 CancelReserve( 0, $res->{'itemnumber'},
927 $res->{'borrowernumber'} );
930 if ($cancelreserve) {
931 CancelReserve( $res->{'biblionumber'}, 0,
932 $res->{'borrowernumber'} );
934 else {
935 # set waiting reserve to first in reserve queue as book isn't waiting now
936 ModReserve(1,
937 $res->{'biblionumber'},
938 $res->{'borrowernumber'},
939 $res->{'branchcode'}
944 # Starting process for transfer job (checking transfert and validate it if we have one)
945 my ($datesent) = GetTransfers($item->{'itemnumber'});
946 if ($datesent) {
947 # updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for lisibility of this case (maybe for stats ....)
948 my $sth =
949 $dbh->prepare(
950 "UPDATE branchtransfers
951 SET datearrived = now(),
952 tobranch = ?,
953 comments = 'Forced branchtransfer'
954 WHERE itemnumber= ? AND datearrived IS NULL"
956 $sth->execute(C4::Context->userenv->{'branch'},$item->{'itemnumber'});
957 $sth->finish;
960 # Record in the database the fact that the book was issued.
961 my $sth =
962 $dbh->prepare(
963 "INSERT INTO issues
964 (borrowernumber, itemnumber,issuedate, date_due, branchcode)
965 VALUES (?,?,?,?,?)"
967 my $dateduef;
968 if ($date) {
969 $dateduef = $date;
970 } else {
971 my $itype=(C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ;
972 my $loanlength = GetLoanLength(
973 $borrower->{'categorycode'},
974 $itype,
975 $branch
977 $dateduef = CalcDateDue(C4::Dates->new(),$loanlength,$branch);
978 # if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
979 if ( C4::Context->preference('ReturnBeforeExpiry') && $dateduef->output('iso') gt $borrower->{dateexpiry} ) {
980 $dateduef = C4::Dates->new($borrower->{dateexpiry},'iso');
983 $sth->execute(
984 $borrower->{'borrowernumber'},
985 $item->{'itemnumber'},
986 strftime( "%Y-%m-%d", localtime ),$dateduef->output('iso'), C4::Context->userenv->{'branch'}
988 $sth->finish;
989 $item->{'issues'}++;
990 ModItem({ issues => $item->{'issues'},
991 holdingbranch => C4::Context->userenv->{'branch'},
992 itemlost => 0,
993 datelastborrowed => C4::Dates->new()->output('iso'),
994 onloan => $dateduef->output('iso'),
995 }, $item->{'biblionumber'}, $item->{'itemnumber'});
996 ModDateLastSeen( $item->{'itemnumber'} );
998 # If it costs to borrow this book, charge it to the patron's account.
999 my ( $charge, $itemtype ) = GetIssuingCharges(
1000 $item->{'itemnumber'},
1001 $borrower->{'borrowernumber'}
1003 if ( $charge > 0 ) {
1004 AddIssuingCharge(
1005 $item->{'itemnumber'},
1006 $borrower->{'borrowernumber'}, $charge
1008 $item->{'charge'} = $charge;
1011 # Record the fact that this book was issued.
1012 &UpdateStats(
1013 C4::Context->userenv->{'branch'},
1014 'issue', $charge,
1015 '', $item->{'itemnumber'},
1016 $item->{'itype'}, $borrower->{'borrowernumber'}
1020 logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
1021 if C4::Context->preference("IssueLog");
1022 return ($datedue);
1026 =head2 GetLoanLength
1028 Get loan length for an itemtype, a borrower type and a branch
1030 my $loanlength = &GetLoanLength($borrowertype,$itemtype,branchcode)
1032 =cut
1034 sub GetLoanLength {
1035 my ( $borrowertype, $itemtype, $branchcode ) = @_;
1036 my $dbh = C4::Context->dbh;
1037 my $sth =
1038 $dbh->prepare(
1039 "select issuelength from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null"
1041 # warn "in get loan lenght $borrowertype $itemtype $branchcode ";
1042 # try to find issuelength & return the 1st available.
1043 # check with borrowertype, itemtype and branchcode, then without one of those parameters
1044 $sth->execute( $borrowertype, $itemtype, $branchcode );
1045 my $loanlength = $sth->fetchrow_hashref;
1046 return $loanlength->{issuelength}
1047 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1049 $sth->execute( $borrowertype, "*", $branchcode );
1050 $loanlength = $sth->fetchrow_hashref;
1051 return $loanlength->{issuelength}
1052 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1054 $sth->execute( "*", $itemtype, $branchcode );
1055 $loanlength = $sth->fetchrow_hashref;
1056 return $loanlength->{issuelength}
1057 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1059 $sth->execute( "*", "*", $branchcode );
1060 $loanlength = $sth->fetchrow_hashref;
1061 return $loanlength->{issuelength}
1062 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1064 $sth->execute( $borrowertype, $itemtype, "*" );
1065 $loanlength = $sth->fetchrow_hashref;
1066 return $loanlength->{issuelength}
1067 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1069 $sth->execute( $borrowertype, "*", "*" );
1070 $loanlength = $sth->fetchrow_hashref;
1071 return $loanlength->{issuelength}
1072 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1074 $sth->execute( "*", $itemtype, "*" );
1075 $loanlength = $sth->fetchrow_hashref;
1076 return $loanlength->{issuelength}
1077 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1079 $sth->execute( "*", "*", "*" );
1080 $loanlength = $sth->fetchrow_hashref;
1081 return $loanlength->{issuelength}
1082 if defined($loanlength) && $loanlength->{issuelength} ne 'NULL';
1084 # if no rule is set => 21 days (hardcoded)
1085 return 21;
1088 =head2 GetIssuingRule
1090 FIXME - This is a copy-paste of GetLoanLength
1091 as a stop-gap. Do not wish to change API for GetLoanLength
1092 this close to release, however, Overdues::GetIssuingRules is broken.
1094 Get the issuing rule for an itemtype, a borrower type and a branch
1095 Returns a hashref from the issuingrules table.
1097 my $irule = &GetIssuingRule($borrowertype,$itemtype,branchcode)
1099 =cut
1101 sub GetIssuingRule {
1102 my ( $borrowertype, $itemtype, $branchcode ) = @_;
1103 my $dbh = C4::Context->dbh;
1104 my $sth = $dbh->prepare( "select * from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null" );
1105 my $irule;
1107 $sth->execute( $borrowertype, $itemtype, $branchcode );
1108 $irule = $sth->fetchrow_hashref;
1109 return $irule if defined($irule) ;
1111 $sth->execute( $borrowertype, "*", $branchcode );
1112 $irule = $sth->fetchrow_hashref;
1113 return $irule if defined($irule) ;
1115 $sth->execute( "*", $itemtype, $branchcode );
1116 $irule = $sth->fetchrow_hashref;
1117 return $irule if defined($irule) ;
1119 $sth->execute( "*", "*", $branchcode );
1120 $irule = $sth->fetchrow_hashref;
1121 return $irule if defined($irule) ;
1123 $sth->execute( $borrowertype, $itemtype, "*" );
1124 $irule = $sth->fetchrow_hashref;
1125 return $irule if defined($irule) ;
1127 $sth->execute( $borrowertype, "*", "*" );
1128 $irule = $sth->fetchrow_hashref;
1129 return $irule if defined($irule) ;
1131 $sth->execute( "*", $itemtype, "*" );
1132 $irule = $sth->fetchrow_hashref;
1133 return $irule if defined($irule) ;
1135 $sth->execute( "*", "*", "*" );
1136 $irule = $sth->fetchrow_hashref;
1137 return $irule if defined($irule) ;
1139 # if no rule matches,
1140 return undef;
1143 =head2 GetBranchBorrowerCircRule
1145 =over 4
1147 my $branch_cat_rule = GetBranchBorrowerCircRule($branchcode, $categorycode);
1149 =back
1151 Retrieves circulation rule attributes that apply to the given
1152 branch and patron category, regardless of item type.
1153 The return value is a hashref containing the following key:
1155 maxissueqty - maximum number of loans that a
1156 patron of the given category can have at the given
1157 branch. If the value is undef, no limit.
1159 This will first check for a specific branch and
1160 category match from branch_borrower_circ_rules.
1162 If no rule is found, it will then check default_branch_circ_rules
1163 (same branch, default category). If no rule is found,
1164 it will then check default_borrower_circ_rules (default
1165 branch, same category), then failing that, default_circ_rules
1166 (default branch, default category).
1168 If no rule has been found in the database, it will default to
1169 the buillt in rule:
1171 maxissueqty - undef
1173 C<$branchcode> and C<$categorycode> should contain the
1174 literal branch code and patron category code, respectively - no
1175 wildcards.
1177 =cut
1179 sub GetBranchBorrowerCircRule {
1180 my $branchcode = shift;
1181 my $categorycode = shift;
1183 my $branch_cat_query = "SELECT maxissueqty
1184 FROM branch_borrower_circ_rules
1185 WHERE branchcode = ?
1186 AND categorycode = ?";
1187 my $dbh = C4::Context->dbh();
1188 my $sth = $dbh->prepare($branch_cat_query);
1189 $sth->execute($branchcode, $categorycode);
1190 my $result;
1191 if ($result = $sth->fetchrow_hashref()) {
1192 return $result;
1195 # try same branch, default borrower category
1196 my $branch_query = "SELECT maxissueqty
1197 FROM default_branch_circ_rules
1198 WHERE branchcode = ?";
1199 $sth = $dbh->prepare($branch_query);
1200 $sth->execute($branchcode);
1201 if ($result = $sth->fetchrow_hashref()) {
1202 return $result;
1205 # try default branch, same borrower category
1206 my $category_query = "SELECT maxissueqty
1207 FROM default_borrower_circ_rules
1208 WHERE categorycode = ?";
1209 $sth = $dbh->prepare($category_query);
1210 $sth->execute($categorycode);
1211 if ($result = $sth->fetchrow_hashref()) {
1212 return $result;
1215 # try default branch, default borrower category
1216 my $default_query = "SELECT maxissueqty
1217 FROM default_circ_rules";
1218 $sth = $dbh->prepare($default_query);
1219 $sth->execute();
1220 if ($result = $sth->fetchrow_hashref()) {
1221 return $result;
1224 # built-in default circulation rule
1225 return {
1226 maxissueqty => undef,
1230 =head2 AddReturn
1232 ($doreturn, $messages, $iteminformation, $borrower) =
1233 &AddReturn($barcode, $branch, $exemptfine, $dropbox);
1235 Returns a book.
1237 C<$barcode> is the bar code of the book being returned. C<$branch> is
1238 the code of the branch where the book is being returned. C<$exemptfine>
1239 indicates that overdue charges for the item will be removed. C<$dropbox>
1240 indicates that the check-in date is assumed to be yesterday, or the last
1241 non-holiday as defined in C4::Calendar . If overdue
1242 charges are applied and C<$dropbox> is true, the last charge will be removed.
1243 This assumes that the fines accrual script has run for _today_.
1245 C<&AddReturn> returns a list of four items:
1247 C<$doreturn> is true iff the return succeeded.
1249 C<$messages> is a reference-to-hash giving the reason for failure:
1251 =over 4
1253 =item C<BadBarcode>
1255 No item with this barcode exists. The value is C<$barcode>.
1257 =item C<NotIssued>
1259 The book is not currently on loan. The value is C<$barcode>.
1261 =item C<IsPermanent>
1263 The book's home branch is a permanent collection. If you have borrowed
1264 this book, you are not allowed to return it. The value is the code for
1265 the book's home branch.
1267 =item C<wthdrawn>
1269 This book has been withdrawn/cancelled. The value should be ignored.
1271 =item C<ResFound>
1273 The item was reserved. The value is a reference-to-hash whose keys are
1274 fields from the reserves table of the Koha database, and
1275 C<biblioitemnumber>. It also has the key C<ResFound>, whose value is
1276 either C<Waiting>, C<Reserved>, or 0.
1278 =back
1280 C<$borrower> is a reference-to-hash, giving information about the
1281 patron who last borrowed the book.
1283 =cut
1285 sub AddReturn {
1286 my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
1287 my $dbh = C4::Context->dbh;
1288 my $messages;
1289 my $doreturn = 1;
1290 my $borrower;
1291 my $validTransfert = 0;
1292 my $reserveDone = 0;
1294 # get information on item
1295 my $iteminformation = GetItemIssue( GetItemnumberFromBarcode($barcode));
1296 my $biblio = GetBiblioItemData($iteminformation->{'biblioitemnumber'});
1297 # use Data::Dumper;warn Data::Dumper::Dumper($iteminformation);
1298 unless ($iteminformation->{'itemnumber'} ) {
1299 $messages->{'BadBarcode'} = $barcode;
1300 $doreturn = 0;
1301 } else {
1302 # find the borrower
1303 if ( ( not $iteminformation->{borrowernumber} ) && $doreturn ) {
1304 $messages->{'NotIssued'} = $barcode;
1305 # even though item is not on loan, it may still
1306 # be transferred; therefore, get current branch information
1307 my $curr_iteminfo = GetItem($iteminformation->{'itemnumber'});
1308 $iteminformation->{'homebranch'} = $curr_iteminfo->{'homebranch'};
1309 $iteminformation->{'holdingbranch'} = $curr_iteminfo->{'holdingbranch'};
1310 $doreturn = 0;
1313 # check if the book is in a permanent collection....
1314 my $hbr = $iteminformation->{C4::Context->preference("HomeOrHoldingBranch")};
1315 my $branches = GetBranches();
1316 # FIXME -- This 'PE' attribute is largely undocumented. afaict, there's no user interface that reflects this functionality.
1317 if ( $hbr && $branches->{$hbr}->{'PE'} ) {
1318 $messages->{'IsPermanent'} = $hbr;
1321 # if independent branches are on and returning to different branch, refuse the return
1322 if ($hbr ne C4::Context->userenv->{'branch'} && C4::Context->preference("IndependantBranches")){
1323 $messages->{'Wrongbranch'} = 1;
1324 $doreturn=0;
1327 # check that the book has been cancelled
1328 if ( $iteminformation->{'wthdrawn'} ) {
1329 $messages->{'wthdrawn'} = 1;
1330 $doreturn = 0;
1333 # new op dev : if the book returned in an other branch update the holding branch
1335 # update issues, thereby returning book (should push this out into another subroutine
1336 $borrower = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1338 # case of a return of document (deal with issues and holdingbranch)
1340 if ($doreturn) {
1341 my $circControlBranch;
1342 if($dropbox) {
1343 # don't allow dropbox mode to create an invalid entry in issues (issuedate > returndate) FIXME: actually checks eq, not gt
1344 undef($dropbox) if ( $iteminformation->{'issuedate'} eq C4::Dates->today('iso') );
1345 if (C4::Context->preference('CircControl') eq 'ItemHomeBranch' ) {
1346 $circControlBranch = $iteminformation->{homebranch};
1347 } elsif ( C4::Context->preference('CircControl') eq 'PatronLibrary') {
1348 $circControlBranch = $borrower->{branchcode};
1349 } else { # CircControl must be PickupLibrary.
1350 $circControlBranch = $iteminformation->{holdingbranch};
1351 # FIXME - is this right ? are we sure that the holdingbranch is still the pickup branch?
1354 MarkIssueReturned($borrower->{'borrowernumber'}, $iteminformation->{'itemnumber'},$circControlBranch);
1355 $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right?
1358 # continue to deal with returns cases, but not only if we have an issue
1360 # the holdingbranch is updated if the document is returned in an other location .
1361 if ( $iteminformation->{'holdingbranch'} ne C4::Context->userenv->{'branch'} ) {
1362 UpdateHoldingbranch(C4::Context->userenv->{'branch'},$iteminformation->{'itemnumber'});
1363 # reload iteminformation holdingbranch with the userenv value
1364 $iteminformation->{'holdingbranch'} = C4::Context->userenv->{'branch'};
1366 ModDateLastSeen( $iteminformation->{'itemnumber'} );
1367 ModItem({ onloan => undef }, $biblio->{'biblionumber'}, $iteminformation->{'itemnumber'});
1369 if ($iteminformation->{borrowernumber}){
1370 ($borrower) = C4::Members::GetMemberDetails( $iteminformation->{borrowernumber}, 0 );
1372 # fix up the accounts.....
1373 if ( $iteminformation->{'itemlost'} ) {
1374 $messages->{'WasLost'} = 1;
1377 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1378 # check if we have a transfer for this document
1379 my ($datesent,$frombranch,$tobranch) = GetTransfers( $iteminformation->{'itemnumber'} );
1381 # if we have a transfer to do, we update the line of transfers with the datearrived
1382 if ($datesent) {
1383 if ( $tobranch eq C4::Context->userenv->{'branch'} ) {
1384 my $sth =
1385 $dbh->prepare(
1386 "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
1388 $sth->execute( $iteminformation->{'itemnumber'} );
1389 $sth->finish;
1390 # now we check if there is a reservation with the validate of transfer if we have one, we can set it with the status 'W'
1391 C4::Reserves::ModReserveStatus( $iteminformation->{'itemnumber'},'W' );
1393 else {
1394 $messages->{'WrongTransfer'} = $tobranch;
1395 $messages->{'WrongTransferItem'} = $iteminformation->{'itemnumber'};
1397 $validTransfert = 1;
1400 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
1401 # fix up the accounts.....
1402 if ($iteminformation->{'itemlost'}) {
1403 FixAccountForLostAndReturned($iteminformation, $borrower);
1404 $messages->{'WasLost'} = 1;
1406 # fix up the overdues in accounts...
1407 FixOverduesOnReturn( $borrower->{'borrowernumber'},
1408 $iteminformation->{'itemnumber'}, $exemptfine, $dropbox );
1410 # find reserves.....
1411 # if we don't have a reserve with the status W, we launch the Checkreserves routine
1412 my ( $resfound, $resrec ) =
1413 C4::Reserves::CheckReserves( $iteminformation->{'itemnumber'} );
1414 if ($resfound) {
1415 $resrec->{'ResFound'} = $resfound;
1416 $messages->{'ResFound'} = $resrec;
1417 $reserveDone = 1;
1420 # update stats?
1421 # Record the fact that this book was returned.
1422 UpdateStats(
1423 $branch, 'return', '0', '',
1424 $iteminformation->{'itemnumber'},
1425 $biblio->{'itemtype'},
1426 $borrower->{'borrowernumber'}
1429 logaction("CIRCULATION", "RETURN", $iteminformation->{borrowernumber}, $iteminformation->{'biblionumber'})
1430 if C4::Context->preference("ReturnLog");
1432 #adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
1433 #we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
1435 if ( ($iteminformation->{'holdingbranch'} ne $iteminformation->{'homebranch'}) and not $messages->{'WrongTransfer'} and ($validTransfert ne 1) and ($reserveDone ne 1) ){
1436 if (C4::Context->preference("AutomaticItemReturn") == 1) {
1437 ModItemTransfer($iteminformation->{'itemnumber'}, C4::Context->userenv->{'branch'}, $iteminformation->{'homebranch'});
1438 $messages->{'WasTransfered'} = 1;
1440 else {
1441 $messages->{'NeedsTransfer'} = 1;
1445 return ( $doreturn, $messages, $iteminformation, $borrower );
1448 =head2 MarkIssueReturned
1450 =over 4
1452 MarkIssueReturned($borrowernumber, $itemnumber, $dropbox_branch);
1454 =back
1456 Unconditionally marks an issue as being returned by
1457 moving the C<issues> row to C<old_issues> and
1458 setting C<returndate> to the current date, or
1459 the last non-holiday date of the branccode specified in
1460 C<dropbox> . Assumes you've already checked that
1461 it's safe to do this, i.e. last non-holiday > issuedate.
1463 Ideally, this function would be internal to C<C4::Circulation>,
1464 not exported, but it is currently needed by one
1465 routine in C<C4::Accounts>.
1467 =cut
1469 sub MarkIssueReturned {
1470 my ($borrowernumber, $itemnumber, $dropbox_branch ) = @_;
1471 my $dbh = C4::Context->dbh;
1472 my $query = "UPDATE issues SET returndate=";
1473 my @bind = ($borrowernumber,$itemnumber);
1474 if($dropbox_branch) {
1475 my $calendar = C4::Calendar->new( branchcode => $dropbox_branch );
1476 my $dropboxdate = $calendar->addDate(C4::Dates->new(), -1 );
1477 unshift @bind, $dropboxdate->output('iso') ;
1478 $query .= " ? "
1479 } else {
1480 $query .= " now() ";
1482 $query .= " WHERE borrowernumber = ? AND itemnumber = ?";
1483 # FIXME transaction
1484 my $sth_upd = $dbh->prepare($query);
1485 $sth_upd->execute(@bind);
1486 my $sth_copy = $dbh->prepare("INSERT INTO old_issues SELECT * FROM issues
1487 WHERE borrowernumber = ?
1488 AND itemnumber = ?");
1489 $sth_copy->execute($borrowernumber, $itemnumber);
1490 my $sth_del = $dbh->prepare("DELETE FROM issues
1491 WHERE borrowernumber = ?
1492 AND itemnumber = ?");
1493 $sth_del->execute($borrowernumber, $itemnumber);
1496 =head2 FixOverduesOnReturn
1498 &FixOverduesOnReturn($brn,$itm, $exemptfine, $dropboxmode);
1500 C<$brn> borrowernumber
1502 C<$itm> itemnumber
1504 C<$exemptfine> BOOL -- remove overdue charge associated with this issue.
1505 C<$dropboxmode> BOOL -- remove lastincrement on overdue charge associated with this issue.
1507 internal function, called only by AddReturn
1509 =cut
1511 sub FixOverduesOnReturn {
1512 my ( $borrowernumber, $item, $exemptfine, $dropbox ) = @_;
1513 my $dbh = C4::Context->dbh;
1515 # check for overdue fine
1516 my $sth =
1517 $dbh->prepare(
1518 "SELECT * FROM accountlines WHERE (borrowernumber = ?) AND (itemnumber = ?) AND (accounttype='FU' OR accounttype='O')"
1520 $sth->execute( $borrowernumber, $item );
1522 # alter fine to show that the book has been returned
1523 my $data;
1524 if ($data = $sth->fetchrow_hashref) {
1525 my $uquery;
1526 my @bind = ($borrowernumber,$item ,$data->{'accountno'});
1527 if ($exemptfine) {
1528 $uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
1529 if (C4::Context->preference("FinesLog")) {
1530 &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
1532 } elsif ($dropbox && $data->{lastincrement}) {
1533 my $outstanding = $data->{amountoutstanding} - $data->{lastincrement} ;
1534 my $amt = $data->{amount} - $data->{lastincrement} ;
1535 if (C4::Context->preference("FinesLog")) {
1536 &logaction("FINES", 'MODIFY',$borrowernumber,"Dropbox adjustment $amt, item $item");
1538 $uquery = "update accountlines set accounttype='F' ";
1539 if($outstanding >= 0 && $amt >=0) {
1540 $uquery .= ", amount = ? , amountoutstanding=? ";
1541 unshift @bind, ($amt, $outstanding) ;
1543 } else {
1544 $uquery = "update accountlines set accounttype='F' ";
1546 $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
1547 my $usth = $dbh->prepare($uquery);
1548 $usth->execute(@bind);
1549 $usth->finish();
1552 $sth->finish();
1553 return;
1556 =head2 FixAccountForLostAndReturned
1558 &FixAccountForLostAndReturned($iteminfo,$borrower);
1560 Calculates the charge for a book lost and returned (Not exported & used only once)
1562 C<$iteminfo> is a hashref to iteminfo. Only {itemnumber} is used.
1564 C<$borrower> is a hashref to borrower. Only {borrowernumber is used.
1566 Internal function, called by AddReturn
1568 =cut
1570 sub FixAccountForLostAndReturned {
1571 my ($iteminfo, $borrower) = @_;
1572 my $dbh = C4::Context->dbh;
1573 my $itm = $iteminfo->{'itemnumber'};
1574 # check for charge made for lost book
1575 my $sth = $dbh->prepare("SELECT * FROM accountlines WHERE (itemnumber = ?) AND (accounttype='L' OR accounttype='Rep') ORDER BY date DESC");
1576 $sth->execute($itm);
1577 if (my $data = $sth->fetchrow_hashref) {
1578 # writeoff this amount
1579 my $offset;
1580 my $amount = $data->{'amount'};
1581 my $acctno = $data->{'accountno'};
1582 my $amountleft;
1583 if ($data->{'amountoutstanding'} == $amount) {
1584 $offset = $data->{'amount'};
1585 $amountleft = 0;
1586 } else {
1587 $offset = $amount - $data->{'amountoutstanding'};
1588 $amountleft = $data->{'amountoutstanding'} - $amount;
1590 my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
1591 WHERE (borrowernumber = ?)
1592 AND (itemnumber = ?) AND (accountno = ?) ");
1593 $usth->execute($data->{'borrowernumber'},$itm,$acctno);
1594 $usth->finish;
1595 #check if any credit is left if so writeoff other accounts
1596 my $nextaccntno = getnextacctno($data->{'borrowernumber'});
1597 if ($amountleft < 0){
1598 $amountleft*=-1;
1600 if ($amountleft > 0){
1601 my $msth = $dbh->prepare("SELECT * FROM accountlines WHERE (borrowernumber = ?)
1602 AND (amountoutstanding >0) ORDER BY date");
1603 $msth->execute($data->{'borrowernumber'});
1604 # offset transactions
1605 my $newamtos;
1606 my $accdata;
1607 while (($accdata=$msth->fetchrow_hashref) and ($amountleft>0)){
1608 if ($accdata->{'amountoutstanding'} < $amountleft) {
1609 $newamtos = 0;
1610 $amountleft -= $accdata->{'amountoutstanding'};
1611 } else {
1612 $newamtos = $accdata->{'amountoutstanding'} - $amountleft;
1613 $amountleft = 0;
1615 my $thisacct = $accdata->{'accountno'};
1616 my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
1617 WHERE (borrowernumber = ?)
1618 AND (accountno=?)");
1619 $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct');
1620 $usth->finish;
1621 $usth = $dbh->prepare("INSERT INTO accountoffsets
1622 (borrowernumber, accountno, offsetaccount, offsetamount)
1623 VALUES
1624 (?,?,?,?)");
1625 $usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
1626 $usth->finish;
1628 $msth->finish;
1630 if ($amountleft > 0){
1631 $amountleft*=-1;
1633 my $desc="Item Returned ".$iteminfo->{'barcode'};
1634 $usth = $dbh->prepare("INSERT INTO accountlines
1635 (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
1636 VALUES (?,?,now(),?,?,'CR',?)");
1637 $usth->execute($data->{'borrowernumber'},$nextaccntno,0-$amount,$desc,$amountleft);
1638 $usth->finish;
1639 $usth = $dbh->prepare("INSERT INTO accountoffsets
1640 (borrowernumber, accountno, offsetaccount, offsetamount)
1641 VALUES (?,?,?,?)");
1642 $usth->execute($borrower->{'borrowernumber'},$data->{'accountno'},$nextaccntno,$offset);
1643 $usth->finish;
1644 ModItem({ paidfor => '' }, undef, $itm);
1646 $sth->finish;
1647 return;
1650 =head2 GetItemIssue
1652 $issues = &GetItemIssue($itemnumber);
1654 Returns patrons currently having a book. nothing if item is not issued atm
1656 C<$itemnumber> is the itemnumber
1658 Returns an array of hashes
1660 =cut
1662 sub GetItemIssue {
1663 my ( $itemnumber) = @_;
1664 return unless $itemnumber;
1665 my $dbh = C4::Context->dbh;
1666 my @GetItemIssues;
1668 # get today date
1669 my $today = POSIX::strftime("%Y%m%d", localtime);
1671 my $sth = $dbh->prepare(
1672 "SELECT * FROM issues
1673 LEFT JOIN items ON issues.itemnumber=items.itemnumber
1674 WHERE
1675 issues.itemnumber=?");
1676 $sth->execute($itemnumber);
1677 my $data = $sth->fetchrow_hashref;
1678 my $datedue = $data->{'date_due'};
1679 $datedue =~ s/-//g;
1680 if ( $datedue < $today ) {
1681 $data->{'overdue'} = 1;
1683 $data->{'itemnumber'} = $itemnumber; # fill itemnumber, in case item is not on issue
1684 $sth->finish;
1685 return ($data);
1688 =head2 GetItemIssues
1690 $issues = &GetItemIssues($itemnumber, $history);
1692 Returns patrons that have issued a book
1694 C<$itemnumber> is the itemnumber
1695 C<$history> is 0 if you want actuel "issuer" (if it exist) and 1 if you want issues history
1697 Returns an array of hashes
1699 =cut
1701 sub GetItemIssues {
1702 my ( $itemnumber,$history ) = @_;
1703 my $dbh = C4::Context->dbh;
1704 my @GetItemIssues;
1706 # get today date
1707 my $today = POSIX::strftime("%Y%m%d", localtime);
1709 my $sql = "SELECT * FROM issues
1710 JOIN borrowers USING (borrowernumber)
1711 JOIN items USING (itemnumber)
1712 WHERE issues.itemnumber = ? ";
1713 if ($history) {
1714 $sql .= "UNION ALL
1715 SELECT * FROM old_issues
1716 LEFT JOIN borrowers USING (borrowernumber)
1717 JOIN items USING (itemnumber)
1718 WHERE old_issues.itemnumber = ? ";
1720 $sql .= "ORDER BY date_due DESC";
1721 my $sth = $dbh->prepare($sql);
1722 if ($history) {
1723 $sth->execute($itemnumber, $itemnumber);
1724 } else {
1725 $sth->execute($itemnumber);
1727 while ( my $data = $sth->fetchrow_hashref ) {
1728 my $datedue = $data->{'date_due'};
1729 $datedue =~ s/-//g;
1730 if ( $datedue < $today ) {
1731 $data->{'overdue'} = 1;
1733 my $itemnumber = $data->{'itemnumber'};
1734 push @GetItemIssues, $data;
1736 $sth->finish;
1737 return ( \@GetItemIssues );
1740 =head2 GetBiblioIssues
1742 $issues = GetBiblioIssues($biblionumber);
1744 this function get all issues from a biblionumber.
1746 Return:
1747 C<$issues> is a reference to array which each value is ref-to-hash. This ref-to-hash containts all column from
1748 tables issues and the firstname,surname & cardnumber from borrowers.
1750 =cut
1752 sub GetBiblioIssues {
1753 my $biblionumber = shift;
1754 return undef unless $biblionumber;
1755 my $dbh = C4::Context->dbh;
1756 my $query = "
1757 SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1758 FROM issues
1759 LEFT JOIN borrowers ON borrowers.borrowernumber = issues.borrowernumber
1760 LEFT JOIN items ON issues.itemnumber = items.itemnumber
1761 LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1762 LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1763 WHERE biblio.biblionumber = ?
1764 UNION ALL
1765 SELECT old_issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
1766 FROM old_issues
1767 LEFT JOIN borrowers ON borrowers.borrowernumber = old_issues.borrowernumber
1768 LEFT JOIN items ON old_issues.itemnumber = items.itemnumber
1769 LEFT JOIN biblioitems ON items.itemnumber = biblioitems.biblioitemnumber
1770 LEFT JOIN biblio ON biblio.biblionumber = items.biblioitemnumber
1771 WHERE biblio.biblionumber = ?
1772 ORDER BY timestamp
1774 my $sth = $dbh->prepare($query);
1775 $sth->execute($biblionumber, $biblionumber);
1777 my @issues;
1778 while ( my $data = $sth->fetchrow_hashref ) {
1779 push @issues, $data;
1781 return \@issues;
1784 =head2 GetUpcomingDueIssues
1786 =over 4
1788 my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
1790 =back
1792 =cut
1794 sub GetUpcomingDueIssues {
1795 my $params = shift;
1797 $params->{'days_in_advance'} = 7 unless exists $params->{'days_in_advance'};
1798 my $dbh = C4::Context->dbh;
1800 my $statement = <<END_SQL;
1801 SELECT issues.*, items.itype as itemtype, items.homebranch, TO_DAYS( date_due )-TO_DAYS( NOW() ) as days_until_due
1802 FROM issues
1803 LEFT JOIN items USING (itemnumber)
1804 WhERE returndate is NULL
1805 AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
1806 END_SQL
1808 my @bind_parameters = ( $params->{'days_in_advance'} );
1810 my $sth = $dbh->prepare( $statement );
1811 $sth->execute( @bind_parameters );
1812 my $upcoming_dues = $sth->fetchall_arrayref({});
1813 $sth->finish;
1815 return $upcoming_dues;
1818 =head2 CanBookBeRenewed
1820 ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber);
1822 Find out whether a borrowed item may be renewed.
1824 C<$dbh> is a DBI handle to the Koha database.
1826 C<$borrowernumber> is the borrower number of the patron who currently
1827 has the item on loan.
1829 C<$itemnumber> is the number of the item to renew.
1831 C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
1832 item must currently be on loan to the specified borrower; renewals
1833 must be allowed for the item's type; and the borrower must not have
1834 already renewed the loan. $error will contain the reason the renewal can not proceed
1836 =cut
1838 sub CanBookBeRenewed {
1840 # check renewal status
1841 my ( $borrowernumber, $itemnumber ) = @_;
1842 my $dbh = C4::Context->dbh;
1843 my $renews = 1;
1844 my $renewokay = 0;
1845 my $error;
1847 # Look in the issues table for this item, lent to this borrower,
1848 # and not yet returned.
1850 # FIXME - I think this function could be redone to use only one SQL call.
1851 my $sth1 = $dbh->prepare(
1852 "SELECT * FROM issues
1853 WHERE borrowernumber = ?
1854 AND itemnumber = ?"
1856 $sth1->execute( $borrowernumber, $itemnumber );
1857 if ( my $data1 = $sth1->fetchrow_hashref ) {
1859 # Found a matching item
1861 # See if this item may be renewed. This query is convoluted
1862 # because it's a bit messy: given the item number, we need to find
1863 # the biblioitem, which gives us the itemtype, which tells us
1864 # whether it may be renewed.
1865 my $query = "SELECT renewalsallowed FROM items ";
1866 $query .= (C4::Context->preference('item-level_itypes'))
1867 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
1868 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
1869 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
1870 $query .= "WHERE items.itemnumber = ?";
1871 my $sth2 = $dbh->prepare($query);
1872 $sth2->execute($itemnumber);
1873 if ( my $data2 = $sth2->fetchrow_hashref ) {
1874 $renews = $data2->{'renewalsallowed'};
1876 if ( $renews && $renews > $data1->{'renewals'} ) {
1877 $renewokay = 1;
1879 else {
1880 $error="too_many";
1882 $sth2->finish;
1883 my ( $resfound, $resrec ) = C4::Reserves::CheckReserves($itemnumber);
1884 if ($resfound) {
1885 $renewokay = 0;
1886 $error="on_reserve"
1890 $sth1->finish;
1891 return ($renewokay,$error);
1894 =head2 AddRenewal
1896 &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue]);
1898 Renews a loan.
1900 C<$borrowernumber> is the borrower number of the patron who currently
1901 has the item.
1903 C<$itemnumber> is the number of the item to renew.
1905 C<$branch> is the library branch. Defaults to the homebranch of the ITEM.
1907 C<$datedue> can be a C4::Dates object used to set the due date.
1909 If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
1910 from the book's item type.
1912 =cut
1914 sub AddRenewal {
1915 my $borrowernumber = shift or return undef;
1916 my $itemnumber = shift or return undef;
1917 my $item = GetItem($itemnumber) or return undef;
1918 my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
1919 my $branch = (@_) ? shift : $item->{homebranch}; # opac-renew doesn't send branch
1920 my $datedue;
1921 # If the due date wasn't specified, calculate it by adding the
1922 # book's loan length to today's date.
1923 unless (@_ and $datedue = shift and $datedue->output('iso')) {
1925 my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 ) or return undef;
1926 my $loanlength = GetLoanLength(
1927 $borrower->{'categorycode'},
1928 (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'} ,
1929 $item->{homebranch} # item's homebranch determines loanlength OR do we want the branch specified by the AddRenewal argument?
1931 #FIXME -- use circControl?
1932 $datedue = CalcDateDue(C4::Dates->new(),$loanlength,$branch); # this branch is the transactional branch.
1933 # The question of whether to use item's homebranch calendar is open.
1936 my $dbh = C4::Context->dbh;
1937 # Find the issues record for this book
1938 my $sth =
1939 $dbh->prepare("SELECT * FROM issues
1940 WHERE borrowernumber=?
1941 AND itemnumber=?"
1943 $sth->execute( $borrowernumber, $itemnumber );
1944 my $issuedata = $sth->fetchrow_hashref;
1945 $sth->finish;
1947 # Update the issues record to have the new due date, and a new count
1948 # of how many times it has been renewed.
1949 my $renews = $issuedata->{'renewals'} + 1;
1950 $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?
1951 WHERE borrowernumber=?
1952 AND itemnumber=?"
1954 $sth->execute( $datedue->output('iso'), $renews, $borrowernumber, $itemnumber );
1955 $sth->finish;
1957 # Update the renewal count on the item, and tell zebra to reindex
1958 $renews = $biblio->{'renewals'} + 1;
1959 ModItem({ renewals => $renews }, $biblio->{'biblionumber'}, $itemnumber);
1961 # Charge a new rental fee, if applicable?
1962 my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber );
1963 if ( $charge > 0 ) {
1964 my $accountno = getnextacctno( $borrowernumber );
1965 my $item = GetBiblioFromItemNumber($itemnumber);
1966 $sth = $dbh->prepare(
1967 "INSERT INTO accountlines
1968 (date,
1969 borrowernumber, accountno, amount,
1970 description,
1971 accounttype, amountoutstanding, itemnumber
1973 VALUES (now(),?,?,?,?,?,?,?)"
1975 $sth->execute( $borrowernumber, $accountno, $charge,
1976 "Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
1977 'Rent', $charge, $itemnumber );
1978 $sth->finish;
1980 # Log the renewal
1981 UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
1984 sub GetRenewCount {
1985 # check renewal status
1986 my ($bornum,$itemno)=@_;
1987 my $dbh = C4::Context->dbh;
1988 my $renewcount = 0;
1989 my $renewsallowed = 0;
1990 my $renewsleft = 0;
1991 # Look in the issues table for this item, lent to this borrower,
1992 # and not yet returned.
1994 # FIXME - I think this function could be redone to use only one SQL call.
1995 my $sth = $dbh->prepare("select * from issues
1996 where (borrowernumber = ?)
1997 and (itemnumber = ?)");
1998 $sth->execute($bornum,$itemno);
1999 my $data = $sth->fetchrow_hashref;
2000 $renewcount = $data->{'renewals'} if $data->{'renewals'};
2001 $sth->finish;
2002 my $query = "SELECT renewalsallowed FROM items ";
2003 $query .= (C4::Context->preference('item-level_itypes'))
2004 ? "LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2005 : "LEFT JOIN biblioitems on items.biblioitemnumber = biblioitems.biblioitemnumber
2006 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2007 $query .= "WHERE items.itemnumber = ?";
2008 my $sth2 = $dbh->prepare($query);
2009 $sth2->execute($itemno);
2010 my $data2 = $sth2->fetchrow_hashref();
2011 $renewsallowed = $data2->{'renewalsallowed'};
2012 $renewsleft = $renewsallowed - $renewcount;
2013 return ($renewcount,$renewsallowed,$renewsleft);
2016 =head2 GetIssuingCharges
2018 ($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
2020 Calculate how much it would cost for a given patron to borrow a given
2021 item, including any applicable discounts.
2023 C<$itemnumber> is the item number of item the patron wishes to borrow.
2025 C<$borrowernumber> is the patron's borrower number.
2027 C<&GetIssuingCharges> returns two values: C<$charge> is the rental charge,
2028 and C<$item_type> is the code for the item's item type (e.g., C<VID>
2029 if it's a video).
2031 =cut
2033 sub GetIssuingCharges {
2035 # calculate charges due
2036 my ( $itemnumber, $borrowernumber ) = @_;
2037 my $charge = 0;
2038 my $dbh = C4::Context->dbh;
2039 my $item_type;
2041 # Get the book's item type and rental charge (via its biblioitem).
2042 my $qcharge = "SELECT itemtypes.itemtype,rentalcharge FROM items
2043 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
2044 $qcharge .= (C4::Context->preference('item-level_itypes'))
2045 ? " LEFT JOIN itemtypes ON items.itype = itemtypes.itemtype "
2046 : " LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype ";
2048 $qcharge .= "WHERE items.itemnumber =?";
2050 my $sth1 = $dbh->prepare($qcharge);
2051 $sth1->execute($itemnumber);
2052 if ( my $data1 = $sth1->fetchrow_hashref ) {
2053 $item_type = $data1->{'itemtype'};
2054 $charge = $data1->{'rentalcharge'};
2055 my $q2 = "SELECT rentaldiscount FROM borrowers
2056 LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
2057 WHERE borrowers.borrowernumber = ?
2058 AND issuingrules.itemtype = ?";
2059 my $sth2 = $dbh->prepare($q2);
2060 $sth2->execute( $borrowernumber, $item_type );
2061 if ( my $data2 = $sth2->fetchrow_hashref ) {
2062 my $discount = $data2->{'rentaldiscount'};
2063 if ( $discount eq 'NULL' ) {
2064 $discount = 0;
2066 $charge = ( $charge * ( 100 - $discount ) ) / 100;
2068 $sth2->finish;
2071 $sth1->finish;
2072 return ( $charge, $item_type );
2075 =head2 AddIssuingCharge
2077 &AddIssuingCharge( $itemno, $borrowernumber, $charge )
2079 =cut
2081 sub AddIssuingCharge {
2082 my ( $itemnumber, $borrowernumber, $charge ) = @_;
2083 my $dbh = C4::Context->dbh;
2084 my $nextaccntno = getnextacctno( $borrowernumber );
2085 my $query ="
2086 INSERT INTO accountlines
2087 (borrowernumber, itemnumber, accountno,
2088 date, amount, description, accounttype,
2089 amountoutstanding)
2090 VALUES (?, ?, ?,now(), ?, 'Rental', 'Rent',?)
2092 my $sth = $dbh->prepare($query);
2093 $sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge );
2094 $sth->finish;
2097 =head2 GetTransfers
2099 GetTransfers($itemnumber);
2101 =cut
2103 sub GetTransfers {
2104 my ($itemnumber) = @_;
2106 my $dbh = C4::Context->dbh;
2108 my $query = '
2109 SELECT datesent,
2110 frombranch,
2111 tobranch
2112 FROM branchtransfers
2113 WHERE itemnumber = ?
2114 AND datearrived IS NULL
2116 my $sth = $dbh->prepare($query);
2117 $sth->execute($itemnumber);
2118 my @row = $sth->fetchrow_array();
2119 $sth->finish;
2120 return @row;
2124 =head2 GetTransfersFromTo
2126 @results = GetTransfersFromTo($frombranch,$tobranch);
2128 Returns the list of pending transfers between $from and $to branch
2130 =cut
2132 sub GetTransfersFromTo {
2133 my ( $frombranch, $tobranch ) = @_;
2134 return unless ( $frombranch && $tobranch );
2135 my $dbh = C4::Context->dbh;
2136 my $query = "
2137 SELECT itemnumber,datesent,frombranch
2138 FROM branchtransfers
2139 WHERE frombranch=?
2140 AND tobranch=?
2141 AND datearrived IS NULL
2143 my $sth = $dbh->prepare($query);
2144 $sth->execute( $frombranch, $tobranch );
2145 my @gettransfers;
2147 while ( my $data = $sth->fetchrow_hashref ) {
2148 push @gettransfers, $data;
2150 $sth->finish;
2151 return (@gettransfers);
2154 =head2 DeleteTransfer
2156 &DeleteTransfer($itemnumber);
2158 =cut
2160 sub DeleteTransfer {
2161 my ($itemnumber) = @_;
2162 my $dbh = C4::Context->dbh;
2163 my $sth = $dbh->prepare(
2164 "DELETE FROM branchtransfers
2165 WHERE itemnumber=?
2166 AND datearrived IS NULL "
2168 $sth->execute($itemnumber);
2169 $sth->finish;
2172 =head2 AnonymiseIssueHistory
2174 $rows = AnonymiseIssueHistory($borrowernumber,$date)
2176 This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
2177 if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
2179 return the number of affected rows.
2181 =cut
2183 sub AnonymiseIssueHistory {
2184 my $date = shift;
2185 my $borrowernumber = shift;
2186 my $dbh = C4::Context->dbh;
2187 my $query = "
2188 UPDATE old_issues
2189 SET borrowernumber = NULL
2190 WHERE returndate < '".$date."'
2191 AND borrowernumber IS NOT NULL
2193 $query .= " AND borrowernumber = '".$borrowernumber."'" if defined $borrowernumber;
2194 my $rows_affected = $dbh->do($query);
2195 return $rows_affected;
2198 =head2 updateWrongTransfer
2200 $items = updateWrongTransfer($itemNumber,$borrowernumber,$waitingAtLibrary,$FromLibrary);
2202 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
2204 =cut
2206 sub updateWrongTransfer {
2207 my ( $itemNumber,$waitingAtLibrary,$FromLibrary ) = @_;
2208 my $dbh = C4::Context->dbh;
2209 # first step validate the actual line of transfert .
2210 my $sth =
2211 $dbh->prepare(
2212 "update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
2214 $sth->execute($FromLibrary,$itemNumber);
2215 $sth->finish;
2217 # second step create a new line of branchtransfer to the right location .
2218 ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
2220 #third step changing holdingbranch of item
2221 UpdateHoldingbranch($FromLibrary,$itemNumber);
2224 =head2 UpdateHoldingbranch
2226 $items = UpdateHoldingbranch($branch,$itmenumber);
2227 Simple methode for updating hodlingbranch in items BDD line
2229 =cut
2231 sub UpdateHoldingbranch {
2232 my ( $branch,$itemnumber ) = @_;
2233 ModItem({ holdingbranch => $branch }, undef, $itemnumber);
2236 =head2 CalcDateDue
2238 $newdatedue = CalcDateDue($startdate,$loanlength,$branchcode);
2239 this function calculates the due date given the loan length ,
2240 checking against the holidays calendar as per the 'useDaysMode' syspref.
2241 C<$startdate> = C4::Dates object representing start date of loan period (assumed to be today)
2242 C<$branch> = location whose calendar to use
2243 C<$loanlength> = loan length prior to adjustment
2244 =cut
2246 sub CalcDateDue {
2247 my ($startdate,$loanlength,$branch) = @_;
2248 if(C4::Context->preference('useDaysMode') eq 'Days') { # ignoring calendar
2249 my $datedue = time + ($loanlength) * 86400;
2250 #FIXME - assumes now even though we take a startdate
2251 my @datearr = localtime($datedue);
2252 return C4::Dates->new( sprintf("%04d-%02d-%02d", 1900 + $datearr[5], $datearr[4] + 1, $datearr[3]), 'iso');
2253 } else {
2254 my $calendar = C4::Calendar->new( branchcode => $branch );
2255 my $datedue = $calendar->addDate($startdate, $loanlength);
2256 return $datedue;
2260 =head2 CheckValidDatedue
2261 This function does not account for holiday exceptions nor does it handle the 'useDaysMode' syspref .
2262 To be replaced by CalcDateDue() once C4::Calendar use is tested.
2264 $newdatedue = CheckValidDatedue($date_due,$itemnumber,$branchcode);
2265 this function validates the loan length against the holidays calendar, and adjusts the due date as per the 'useDaysMode' syspref.
2266 C<$date_due> = returndate calculate with no day check
2267 C<$itemnumber> = itemnumber
2268 C<$branchcode> = location of issue (affected by 'CircControl' syspref)
2269 C<$loanlength> = loan length prior to adjustment
2270 =cut
2272 sub CheckValidDatedue {
2273 my ($date_due,$itemnumber,$branchcode)=@_;
2274 my @datedue=split('-',$date_due->output('iso'));
2275 my $years=$datedue[0];
2276 my $month=$datedue[1];
2277 my $day=$datedue[2];
2278 # die "Item# $itemnumber ($branchcode) due: " . ${date_due}->output() . "\n(Y,M,D) = ($years,$month,$day)":
2279 my $dow;
2280 for (my $i=0;$i<2;$i++){
2281 $dow=Day_of_Week($years,$month,$day);
2282 ($dow=0) if ($dow>6);
2283 my $result=CheckRepeatableHolidays($itemnumber,$dow,$branchcode);
2284 my $countspecial=CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2285 my $countspecialrepeatable=CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2286 if (($result ne '0') or ($countspecial ne '0') or ($countspecialrepeatable ne '0') ){
2287 $i=0;
2288 (($years,$month,$day) = Add_Delta_Days($years,$month,$day, 1))if ($i ne '1');
2291 my $newdatedue=C4::Dates->new(sprintf("%04d-%02d-%02d",$years,$month,$day),'iso');
2292 return $newdatedue;
2296 =head2 CheckRepeatableHolidays
2298 $countrepeatable = CheckRepeatableHoliday($itemnumber,$week_day,$branchcode);
2299 this function checks if the date due is a repeatable holiday
2300 C<$date_due> = returndate calculate with no day check
2301 C<$itemnumber> = itemnumber
2302 C<$branchcode> = localisation of issue
2304 =cut
2306 sub CheckRepeatableHolidays{
2307 my($itemnumber,$week_day,$branchcode)=@_;
2308 my $dbh = C4::Context->dbh;
2309 my $query = qq|SELECT count(*)
2310 FROM repeatable_holidays
2311 WHERE branchcode=?
2312 AND weekday=?|;
2313 my $sth = $dbh->prepare($query);
2314 $sth->execute($branchcode,$week_day);
2315 my $result=$sth->fetchrow;
2316 $sth->finish;
2317 return $result;
2321 =head2 CheckSpecialHolidays
2323 $countspecial = CheckSpecialHolidays($years,$month,$day,$itemnumber,$branchcode);
2324 this function check if the date is a special holiday
2325 C<$years> = the years of datedue
2326 C<$month> = the month of datedue
2327 C<$day> = the day of datedue
2328 C<$itemnumber> = itemnumber
2329 C<$branchcode> = localisation of issue
2331 =cut
2333 sub CheckSpecialHolidays{
2334 my ($years,$month,$day,$itemnumber,$branchcode) = @_;
2335 my $dbh = C4::Context->dbh;
2336 my $query=qq|SELECT count(*)
2337 FROM `special_holidays`
2338 WHERE year=?
2339 AND month=?
2340 AND day=?
2341 AND branchcode=?
2343 my $sth = $dbh->prepare($query);
2344 $sth->execute($years,$month,$day,$branchcode);
2345 my $countspecial=$sth->fetchrow ;
2346 $sth->finish;
2347 return $countspecial;
2350 =head2 CheckRepeatableSpecialHolidays
2352 $countspecial = CheckRepeatableSpecialHolidays($month,$day,$itemnumber,$branchcode);
2353 this function check if the date is a repeatble special holidays
2354 C<$month> = the month of datedue
2355 C<$day> = the day of datedue
2356 C<$itemnumber> = itemnumber
2357 C<$branchcode> = localisation of issue
2359 =cut
2361 sub CheckRepeatableSpecialHolidays{
2362 my ($month,$day,$itemnumber,$branchcode) = @_;
2363 my $dbh = C4::Context->dbh;
2364 my $query=qq|SELECT count(*)
2365 FROM `repeatable_holidays`
2366 WHERE month=?
2367 AND day=?
2368 AND branchcode=?
2370 my $sth = $dbh->prepare($query);
2371 $sth->execute($month,$day,$branchcode);
2372 my $countspecial=$sth->fetchrow ;
2373 $sth->finish;
2374 return $countspecial;
2379 sub CheckValidBarcode{
2380 my ($barcode) = @_;
2381 my $dbh = C4::Context->dbh;
2382 my $query=qq|SELECT count(*)
2383 FROM items
2384 WHERE barcode=?
2386 my $sth = $dbh->prepare($query);
2387 $sth->execute($barcode);
2388 my $exist=$sth->fetchrow ;
2389 $sth->finish;
2390 return $exist;
2395 __END__
2397 =head1 AUTHOR
2399 Koha Developement team <info@koha.org>
2401 =cut