use C4::Debug;
use C4::Branch; # GetBranches
use C4::Log; # logaction
-use C4::Koha qw(GetAuthorisedValueByCode);
+use C4::Koha qw(
+ GetAuthorisedValueByCode
+ GetAuthValCode
+ GetKohaAuthorisedValueLib
+);
use C4::Overdues qw(CalcFine UpdateFine);
+use Algorithm::CheckDigits;
+
use Data::Dumper;
use Koha::DateUtils;
use Koha::Calendar;
+use Koha::Borrower::Debarments;
use Carp;
-
+use Date::Calc qw(
+ Today
+ Today_and_Now
+ Add_Delta_YM
+ Add_Delta_DHMS
+ Date_to_Days
+ Day_of_Week
+ Add_Delta_Days
+);
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
BEGIN {
&AddIssue
&AddRenewal
&GetRenewCount
+ &GetSoonestRenewDate
&GetItemIssue
&GetItemIssues
&GetIssuingCharges
&GetBiblioIssues
&GetOpenIssue
&AnonymiseIssueHistory
+ &CheckIfIssuedToPatron
+ &IsItemIssued
);
# subs to deal with returns
$barcode =~ s/^(\D+)[0]*(\d+)$/$branch-$1-$2/i;
}
}
+ } elsif ($filter eq 'EAN13') {
+ my $ean = CheckDigits('ean');
+ if ( $ean->is_valid($barcode) ) {
+ #$barcode = sprintf('%013d',$barcode); # this doesn't work on 32-bit systems
+ $barcode = '0' x ( 13 - length($barcode) ) . $barcode;
+ } else {
+ warn "# [$barcode] not valid EAN-13/UPC-A\n";
+ }
}
return $barcode; # return barcode, modified or not
}
$data->{'borrower'} = $data2->{'borrowernumber'};
}
else {
- $data->{'date_due'} = ($data->{'wthdrawn'} eq '1') ? 'Cancelled' : 'Available';
+ $data->{'date_due'} = ($data->{'withdrawn'} eq '1') ? 'Cancelled' : 'Available';
}
#
if ( $borrower->{'category_type'} eq 'X' && ( $item->{barcode} )) {
# stats only borrower -- add entry to statistics table, and return issuingimpossible{STATS} = 1 .
- &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'});
+ &UpdateStats(C4::Context->userenv->{'branch'},'localuse','','',$item->{'itemnumber'},$item->{'itemtype'},$borrower->{'borrowernumber'}, undef, $item->{'ccode'});
ModDateLastSeen( $item->{'itemnumber'} );
return( { STATS => 1 }, {});
}
#
# DEBTS
- my ($amount) =
- C4::Members::GetMemberAccountRecords( $borrower->{'borrowernumber'}, '' && $duedate->ymd() );
+ my ($balance, $non_issue_charges, $other_charges) =
+ C4::Members::GetMemberAccountBalance( $borrower->{'borrowernumber'} );
my $amountlimit = C4::Context->preference("noissuescharge");
my $allowfineoverride = C4::Context->preference("AllowFineOverride");
my $allfinesneedoverride = C4::Context->preference("AllFinesNeedOverride");
if ( C4::Context->preference("IssuingInProcess") ) {
- if ( $amount > $amountlimit && !$inprocess && !$allowfineoverride) {
- $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
- } elsif ( $amount > $amountlimit && !$inprocess && $allowfineoverride) {
- $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
- } elsif ( $allfinesneedoverride && $amount > 0 && $amount <= $amountlimit && !$inprocess ) {
- $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
+ if ( $non_issue_charges > $amountlimit && !$inprocess && !$allowfineoverride) {
+ $issuingimpossible{DEBT} = sprintf( "%.2f", $non_issue_charges );
+ } elsif ( $non_issue_charges > $amountlimit && !$inprocess && $allowfineoverride) {
+ $needsconfirmation{DEBT} = sprintf( "%.2f", $non_issue_charges );
+ } elsif ( $allfinesneedoverride && $non_issue_charges > 0 && $non_issue_charges <= $amountlimit && !$inprocess ) {
+ $needsconfirmation{DEBT} = sprintf( "%.2f", $non_issue_charges );
}
}
else {
- if ( $amount > $amountlimit && $allowfineoverride ) {
- $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
- } elsif ( $amount > $amountlimit && !$allowfineoverride) {
- $issuingimpossible{DEBT} = sprintf( "%.2f", $amount );
- } elsif ( $amount > 0 && $allfinesneedoverride ) {
- $needsconfirmation{DEBT} = sprintf( "%.2f", $amount );
+ if ( $non_issue_charges > $amountlimit && $allowfineoverride ) {
+ $needsconfirmation{DEBT} = sprintf( "%.2f", $non_issue_charges );
+ } elsif ( $non_issue_charges > $amountlimit && !$allowfineoverride) {
+ $issuingimpossible{DEBT} = sprintf( "%.2f", $non_issue_charges );
+ } elsif ( $non_issue_charges > 0 && $allfinesneedoverride ) {
+ $needsconfirmation{DEBT} = sprintf( "%.2f", $non_issue_charges );
}
}
+ if ($balance > 0 && $other_charges > 0) {
+ $alerts{OTHER_CHARGES} = sprintf( "%.2f", $other_charges );
+ }
my ($blocktype, $count) = C4::Members::IsMemberBlocked($borrower->{'borrowernumber'});
if ($blocktype == -1) {
$needsconfirmation{PATRON_CANT} = 1;
} else {
if($max_loans_allowed){
- $needsconfirmation{TOO_MANY} = 1;
- $needsconfirmation{current_loan_count} = $current_loan_count;
- $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
+ if ( C4::Context->preference("AllowTooManyOverride") ) {
+ $needsconfirmation{TOO_MANY} = 1;
+ $needsconfirmation{current_loan_count} = $current_loan_count;
+ $needsconfirmation{max_loans_allowed} = $max_loans_allowed;
+ } else {
+ $issuingimpossible{TOO_MANY} = 1;
+ $issuingimpossible{current_loan_count} = $current_loan_count;
+ $issuingimpossible{max_loans_allowed} = $max_loans_allowed;
+ }
}
}
#
# ITEM CHECKING
#
- if ( $item->{'notforloan'}
- && $item->{'notforloan'} > 0 )
+ if ( $item->{'notforloan'} )
{
if(!C4::Context->preference("AllowNotForLoanOverride")){
$issuingimpossible{NOT_FOR_LOAN} = 1;
+ $issuingimpossible{item_notforloan} = $item->{'notforloan'};
}else{
$needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ $needsconfirmation{item_notforloan} = $item->{'notforloan'};
}
}
- elsif ( !$item->{'notforloan'} ){
+ else {
# we have to check itemtypes.notforloan also
if (C4::Context->preference('item-level_itypes')){
# this should probably be a subroutine
my $sth = $dbh->prepare("SELECT notforloan FROM itemtypes WHERE itemtype = ?");
$sth->execute($item->{'itemtype'});
my $notforloan=$sth->fetchrow_hashref();
- $sth->finish();
if ($notforloan->{'notforloan'}) {
if (!C4::Context->preference("AllowNotForLoanOverride")) {
$issuingimpossible{NOT_FOR_LOAN} = 1;
+ $issuingimpossible{itemtype_notforloan} = $item->{'itype'};
} else {
$needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ $needsconfirmation{itemtype_notforloan} = $item->{'itype'};
}
}
}
elsif ($biblioitem->{'notforloan'} == 1){
if (!C4::Context->preference("AllowNotForLoanOverride")) {
$issuingimpossible{NOT_FOR_LOAN} = 1;
+ $issuingimpossible{itemtype_notforloan} = $biblioitem->{'itemtype'};
} else {
$needsconfirmation{NOT_FOR_LOAN_FORCING} = 1;
+ $needsconfirmation{itemtype_notforloan} = $biblioitem->{'itemtype'};
}
}
}
- if ( $item->{'wthdrawn'} && $item->{'wthdrawn'} > 0 )
+ if ( $item->{'withdrawn'} && $item->{'withdrawn'} > 0 )
{
$issuingimpossible{WTHDRAWN} = 1;
}
$needsconfirmation{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'confirm' );
$alerts{ITEM_LOST} = $code if ( C4::Context->preference("IssueLostItem") eq 'alert' );
}
- if ( C4::Context->preference("IndependantBranches") ) {
+ if ( C4::Context->preference("IndependentBranches") ) {
my $userenv = C4::Context->userenv;
- if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
- $issuingimpossible{ITEMNOTSAMEBRANCH} = 1
- if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} );
+ unless ( C4::Context->IsSuperLibrarian() ) {
+ if ( $item->{C4::Context->preference("HomeOrHoldingBranch")} ne $userenv->{branch} ){
+ $issuingimpossible{ITEMNOTSAMEBRANCH} = 1;
+ $issuingimpossible{'itemhomebranch'} = $item->{C4::Context->preference("HomeOrHoldingBranch")};
+ }
$needsconfirmation{BORRNOTSAMEBRANCH} = GetBranchName( $borrower->{'branchcode'} )
if ( $borrower->{'branchcode'} ne $userenv->{branch} );
}
}
}
}
+
+ ## CHECK AGE RESTRICTION
+ # get $marker from preferences. Could be something like "FSK|PEGI|Alter|Age:"
+ my $markers = C4::Context->preference('AgeRestrictionMarker');
+ my $bibvalues = $biblioitem->{'agerestriction'};
+ my $restriction_age = GetAgeRestriction( $bibvalues );
+
+ if ( $restriction_age > 0 ) {
+ if ( $borrower->{'dateofbirth'} ) {
+ my @alloweddate = split /-/, $borrower->{'dateofbirth'};
+ $alloweddate[0] += $restriction_age;
+
+ #Prevent runime eror on leap year (invalid date)
+ if ( ( $alloweddate[1] == 2 ) && ( $alloweddate[2] == 29 ) ) {
+ $alloweddate[2] = 28;
+ }
+
+ if ( Date_to_Days(Today) < Date_to_Days(@alloweddate) - 1 ) {
+ if ( C4::Context->preference('AgeRestrictionOverride') ) {
+ $needsconfirmation{AGE_RESTRICTION} = "$bibvalues";
+ }
+ else {
+ $issuingimpossible{AGE_RESTRICTION} = "$bibvalues";
+ }
+ }
+ }
+ }
+
+ ## check for high holds decreasing loan period
+ my $decrease_loan = C4::Context->preference('decreaseLoanHighHolds');
+ if ( $decrease_loan && $decrease_loan == 1 ) {
+ my ( $reserved, $num, $duration, $returndate ) =
+ checkHighHolds( $item, $borrower );
+
+ if ( $num >= C4::Context->preference('decreaseLoanHighHoldsValue') ) {
+ $needsconfirmation{HIGHHOLDS} = {
+ num_holds => $num,
+ duration => $duration,
+ returndate => output_pref($returndate),
+ };
+ }
+ }
+
+ if (
+ !C4::Context->preference('AllowMultipleIssuesOnABiblio') &&
+ # don't do the multiple loans per bib check if we've
+ # already determined that we've got a loan on the same item
+ !$issuingimpossible{NO_MORE_RENEWALS} &&
+ !$needsconfirmation{RENEW_ISSUE}
+ ) {
+ # Check if borrower has already issued an item from the same biblio
+ # Only if it's not a subscription
+ my $biblionumber = $item->{biblionumber};
+ require C4::Serials;
+ my $is_a_subscription = C4::Serials::CountSubscriptionFromBiblionumber($biblionumber);
+ unless ($is_a_subscription) {
+ my $issues = GetIssues( {
+ borrowernumber => $borrower->{borrowernumber},
+ biblionumber => $biblionumber,
+ } );
+ my @issues = $issues ? @$issues : ();
+ # if we get here, we don't already have a loan on this item,
+ # so if there are any loans on this bib, ask for confirmation
+ if (scalar @issues > 0) {
+ $needsconfirmation{BIBLIO_ALREADY_ISSUED} = 1;
+ }
+ }
+ }
+
return ( \%issuingimpossible, \%needsconfirmation, \%alerts );
}
+=head2 CanBookBeReturned
+
+ ($returnallowed, $message) = CanBookBeReturned($item, $branch)
+
+Check whether the item can be returned to the provided branch
+
+=over 4
+
+=item C<$item> is a hash of item information as returned from GetItem
+
+=item C<$branch> is the branchcode where the return is taking place
+
+=back
+
+Returns:
+
+=over 4
+
+=item C<$returnallowed> is 0 or 1, corresponding to whether the return is allowed (1) or not (0)
+
+=item C<$message> is the branchcode where the item SHOULD be returned, if the return is not allowed
+
+=back
+
+=cut
+
+sub CanBookBeReturned {
+ my ($item, $branch) = @_;
+ my $allowreturntobranch = C4::Context->preference("AllowReturnToBranch") || 'anywhere';
+
+ # assume return is allowed to start
+ my $allowed = 1;
+ my $message;
+
+ # identify all cases where return is forbidden
+ if ($allowreturntobranch eq 'homebranch' && $branch ne $item->{'homebranch'}) {
+ $allowed = 0;
+ $message = $item->{'homebranch'};
+ } elsif ($allowreturntobranch eq 'holdingbranch' && $branch ne $item->{'holdingbranch'}) {
+ $allowed = 0;
+ $message = $item->{'holdingbranch'};
+ } elsif ($allowreturntobranch eq 'homeorholdingbranch' && $branch ne $item->{'homebranch'} && $branch ne $item->{'holdingbranch'}) {
+ $allowed = 0;
+ $message = $item->{'homebranch'}; # FIXME: choice of homebranch is arbitrary
+ }
+
+ return ($allowed, $message);
+}
+
+=head2 CheckHighHolds
+
+ used when syspref decreaseLoanHighHolds is active. Returns 1 or 0 to define whether the minimum value held in
+ decreaseLoanHighHoldsValue is exceeded, the total number of outstanding holds, the number of days the loan
+ has been decreased to (held in syspref decreaseLoanHighHoldsValue), and the new due date
+
+=cut
+
+sub checkHighHolds {
+ my ( $item, $borrower ) = @_;
+ my $biblio = GetBiblioFromItemNumber( $item->{itemnumber} );
+ my $branch = _GetCircControlBranch( $item, $borrower );
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare(
+'select count(borrowernumber) as num_holds from reserves where biblionumber=?'
+ );
+ $sth->execute( $item->{'biblionumber'} );
+ my ($holds) = $sth->fetchrow_array;
+ if ($holds) {
+ my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
+
+ my $calendar = Koha::Calendar->new( branchcode => $branch );
+
+ my $itype =
+ ( C4::Context->preference('item-level_itypes') )
+ ? $biblio->{'itype'}
+ : $biblio->{'itemtype'};
+ my $orig_due =
+ C4::Circulation::CalcDateDue( $issuedate, $itype, $branch,
+ $borrower );
+
+ my $reduced_datedue =
+ $calendar->addDate( $issuedate,
+ C4::Context->preference('decreaseLoanHighHoldsDuration') );
+
+ if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
+ return ( 1, $holds,
+ C4::Context->preference('decreaseLoanHighHoldsDuration'),
+ $reduced_datedue );
+ }
+ }
+ return ( 0, 0, 0, undef );
+}
+
=head2 AddIssue
&AddIssue($borrower, $barcode, [$datedue], [$cancelreserve], [$issuedate])
}
if ($borrower and $barcode and $barcodecheck ne '0'){#??? wtf
# find which item we issue
- my $item = GetItem('', $barcode) or return undef; # if we don't get an Item, abort.
+ my $item = GetItem('', $barcode) or return; # if we don't get an Item, abort.
my $branch = _GetCircControlBranch($item,$borrower);
# get actual issuing if there is one
## If item was lost, it has now been found, reverse any list item charges if neccessary.
if ( $item->{'itemlost'} ) {
- _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
+ if ( C4::Context->preference('RefundLostItemFeeOnReturn' ) ) {
+ _FixAccountForLostAndReturned( $item->{'itemnumber'}, undef, $item->{'barcode'} );
+ }
}
ModItem({ issues => $item->{'issues'},
C4::Context->userenv->{'branch'},
'issue', $charge,
($sipmode ? "SIP-$sipmode" : ''), $item->{'itemnumber'},
- $item->{'itype'}, $borrower->{'borrowernumber'}
+ $item->{'itype'}, $borrower->{'borrowernumber'}, undef, $item->{'ccode'}
);
# Send a checkout slip.
}
}
- logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'biblionumber'})
+ logaction("CIRCULATION", "ISSUE", $borrower->{'borrowernumber'}, $biblio->{'itemnumber'})
if C4::Context->preference("IssueLog");
}
return ($datedue); # not necessarily the same as when it came in!
sub GetLoanLength {
my ( $borrowertype, $itemtype, $branchcode ) = @_;
my $dbh = C4::Context->dbh;
- my $sth =
- $dbh->prepare(
-'select issuelength, lengthunit from issuingrules where categorycode=? and itemtype=? and branchcode=? and issuelength is not null'
- );
-# warn "in get loan lenght $borrowertype $itemtype $branchcode ";
-# try to find issuelength & return the 1st available.
-# check with borrowertype, itemtype and branchcode, then without one of those parameters
+ my $sth = $dbh->prepare(qq{
+ SELECT issuelength, lengthunit, renewalperiod
+ FROM issuingrules
+ WHERE categorycode=?
+ AND itemtype=?
+ AND branchcode=?
+ AND issuelength IS NOT NULL
+ });
+
+ # try to find issuelength & return the 1st available.
+ # check with borrowertype, itemtype and branchcode, then without one of those parameters
$sth->execute( $borrowertype, $itemtype, $branchcode );
my $loanlength = $sth->fetchrow_hashref;
+
return $loanlength
if defined($loanlength) && $loanlength->{issuelength};
# if no rule is set => 21 days (hardcoded)
return {
issuelength => 21,
+ renewalperiod => 21,
lengthunit => 'days',
};
sub GetHardDueDate {
my ( $borrowertype, $itemtype, $branchcode ) = @_;
- my $dbh = C4::Context->dbh;
- my $sth =
- $dbh->prepare(
-"select hardduedate, hardduedatecompare from issuingrules where categorycode=? and itemtype=? and branchcode=?"
- );
- $sth->execute( $borrowertype, $itemtype, $branchcode );
- my $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
- $sth->execute( $borrowertype, "*", $branchcode );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- $sth->execute( "*", $itemtype, $branchcode );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
+ my $rule = GetIssuingRule( $borrowertype, $itemtype, $branchcode );
- $sth->execute( "*", "*", $branchcode );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- $sth->execute( $borrowertype, $itemtype, "*" );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- $sth->execute( $borrowertype, "*", "*" );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- $sth->execute( "*", $itemtype, "*" );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- $sth->execute( "*", "*", "*" );
- $results = $sth->fetchrow_hashref;
- return (dt_from_string($results->{hardduedate}, 'iso'),$results->{hardduedatecompare})
- if defined($results) && $results->{hardduedate};
-
- # if no rule is set => return undefined
- return (undef, undef);
+ if ( defined( $rule ) ) {
+ if ( $rule->{hardduedate} ) {
+ return (dt_from_string($rule->{hardduedate}, 'iso'),$rule->{hardduedatecompare});
+ } else {
+ return (undef, undef);
+ }
+ }
}
=head2 GetIssuingRule
FIXME - This is a copy-paste of GetLoanLength
as a stop-gap. Do not wish to change API for GetLoanLength
-this close to release, however, Overdues::GetIssuingRules is broken.
+this close to release.
Get the issuing rule for an itemtype, a borrower type and a branch
Returns a hashref from the issuingrules table.
return $irule if defined($irule) ;
# if no rule matches,
- return undef;
+ return;
}
=head2 GetBranchBorrowerCircRule
foreach my $attempt (@attempts) {
my ($query, @bind_params) = @{$attempt};
- my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params );
+ my $search_result = $dbh->selectrow_hashref ( $query , {}, @bind_params )
+ or next;
# Since branch/category and branch/itemtype use the same per-branch
# defaults tables, we have to check that the key we want is set, not
=head2 AddReturn
($doreturn, $messages, $iteminformation, $borrower) =
- &AddReturn($barcode, $branch, $exemptfine, $dropbox);
+ &AddReturn( $barcode, $branch [,$exemptfine] [,$dropbox] [,$returndate] );
Returns a book.
=item C<$branch> is the code of the branch where the book is being returned.
=item C<$exemptfine> indicates that overdue charges for the item will be
-removed.
+removed. Optional.
=item C<$dropbox> indicates that the check-in date is assumed to be
yesterday, or the last non-holiday as defined in C4::Calendar . If
overdue charges are applied and C<$dropbox> is true, the last charge
will be removed. This assumes that the fines accrual script has run
-for _today_.
+for _today_. Optional.
+
+=item C<$return_date> allows the default return date to be overridden
+by the given return date. Optional.
=back
this book, you are not allowed to return it. The value is the code for
the book's home branch.
-=item C<wthdrawn>
+=item C<withdrawn>
This book has been withdrawn/cancelled. The value should be ignored.
=cut
sub AddReturn {
- my ( $barcode, $branch, $exemptfine, $dropbox ) = @_;
+ my ( $barcode, $branch, $exemptfine, $dropbox, $return_date ) = @_;
if ($branch and not GetBranchDetail($branch)) {
warn "AddReturn error: branch '$branch' not found. Reverting to " . C4::Context->userenv->{'branch'};
my $borrowernumber = $borrower->{'borrowernumber'} || undef; # we don't know if we had a borrower or not
+ my $yaml = C4::Context->preference('UpdateNotForLoanStatusOnCheckin');
+ if ($yaml) {
+ $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
+ my $rules;
+ eval { $rules = YAML::Load($yaml); };
+ if ($@) {
+ warn "Unable to parse UpdateNotForLoanStatusOnCheckin syspref : $@";
+ }
+ else {
+ foreach my $key ( keys %$rules ) {
+ if ( $item->{notforloan} eq $key ) {
+ $messages->{'NotForLoanStatusUpdated'} = { from => $item->{notforloan}, to => $rules->{$key} };
+ ModItem( { notforloan => $rules->{$key} }, undef, $itemnumber );
+ last;
+ }
+ }
+ }
+ }
+
+
# check if the book is in a permanent collection....
# FIXME -- This 'PE' attribute is largely undocumented. afaict, there's no user interface that reflects this functionality.
if ( $hbr ) {
$branches->{$hbr}->{PE} and $messages->{'IsPermanent'} = $hbr;
}
- # if indy branches and returning to different branch, refuse the return unless canreservefromotherbranches is turned on
- if ($hbr ne $branch && C4::Context->preference("IndependantBranches") && !(C4::Context->preference("canreservefromotherbranches"))){
+ # check if the return is allowed at this branch
+ my ($returnallowed, $message) = CanBookBeReturned($item, $branch);
+ unless ($returnallowed){
$messages->{'Wrongbranch'} = {
Wrongbranch => $branch,
- Rightbranch => $hbr,
+ Rightbranch => $message
};
$doreturn = 0;
- # bailing out here - in this case, current desired behavior
- # is to act as if no return ever happened at all.
- # FIXME - even in an indy branches situation, there should
- # still be an option for the library to accept the item
- # and transfer it to its owning library.
return ( $doreturn, $messages, $issue, $borrower );
}
- if ( $item->{'wthdrawn'} ) { # book has been cancelled
- $messages->{'wthdrawn'} = 1;
- $doreturn = 0;
+ if ( $item->{'withdrawn'} ) { # book has been cancelled
+ $messages->{'withdrawn'} = 1;
+ $doreturn = 0 if C4::Context->preference("BlockReturnOfWithdrawnItems");
}
# case of a return of document (deal with issues and holdingbranch)
- if ($doreturn) {
my $today = DateTime->now( time_zone => C4::Context->tz() );
- my $datedue = $issue->{date_due};
+
+ if ($doreturn) {
+ my $datedue = $issue->{date_due};
$borrower or warn "AddReturn without current borrower";
my $circControlBranch;
if ($dropbox) {
# FIXME: check issuedate > returndate, factoring in holidays
#$circControlBranch = _GetCircControlBranch($item,$borrower) unless ( $item->{'issuedate'} eq C4::Dates->today('iso') );;
$circControlBranch = _GetCircControlBranch($item,$borrower);
- my $datedue = $issue->{date_due};
- $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
+ $issue->{'overdue'} = DateTime->compare($issue->{'date_due'}, $today ) == -1 ? 1 : 0;
}
if ($borrowernumber) {
- if($issue->{'overdue'}){
- my ( $amount, $type, $daycounttotal ) = C4::Overdues::CalcFine( $item, $borrower->{categorycode},$branch, $datedue, $today );
+ if ( ( C4::Context->preference('CalculateFinesOnReturn') && $issue->{'overdue'} ) || $return_date ) {
+ # we only need to calculate and change the fines if we want to do that on return
+ # Should be on for hourly loans
+ my $control = C4::Context->preference('CircControl');
+ my $control_branchcode =
+ ( $control eq 'ItemHomeLibrary' ) ? $item->{homebranch}
+ : ( $control eq 'PatronLibrary' ) ? $borrower->{branchcode}
+ : $issue->{branchcode};
+
+ my $date_returned =
+ $return_date ? dt_from_string($return_date) : $today;
+
+ my ( $amount, $type, $unitcounttotal ) =
+ C4::Overdues::CalcFine( $item, $borrower->{categorycode},
+ $control_branchcode, $datedue, $date_returned );
+
$type ||= q{};
- if ( $amount > 0 && ( C4::Context->preference('finesMode') eq 'production' )) {
- C4::Overdues::UpdateFine(
- $issue->{itemnumber},
- $issue->{borrowernumber},
- $amount, $type, output_pref($datedue)
- );
- }
+
+ if ( C4::Context->preference('finesMode') eq 'production' ) {
+ if ( $amount > 0 ) {
+ C4::Overdues::UpdateFine( $issue->{itemnumber},
+ $issue->{borrowernumber},
+ $amount, $type, output_pref($datedue) );
+ }
+ elsif ($return_date) {
+
+ # Backdated returns may have fines that shouldn't exist,
+ # so in this case, we need to drop those fines to 0
+
+ C4::Overdues::UpdateFine( $issue->{itemnumber},
+ $issue->{borrowernumber},
+ 0, $type, output_pref($datedue) );
+ }
+ }
}
- MarkIssueReturned($borrowernumber, $item->{'itemnumber'}, $circControlBranch, '', $borrower->{'privacy'});
- $messages->{'WasReturned'} = 1; # FIXME is the "= 1" right? This could be the borrower hash.
+
+ MarkIssueReturned( $borrowernumber, $item->{'itemnumber'},
+ $circControlBranch, $return_date, $borrower->{'privacy'} );
+
+ # FIXME is the "= 1" right? This could be the borrower hash.
+ $messages->{'WasReturned'} = 1;
+
}
ModItem({ onloan => undef }, $issue->{'biblionumber'}, $item->{'itemnumber'});
}
# fix up the accounts.....
- if ($item->{'itemlost'}) {
- _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode); # can tolerate undef $borrowernumber
+ if ( $item->{'itemlost'} ) {
$messages->{'WasLost'} = 1;
+
+ if ( C4::Context->preference('RefundLostItemFeeOnReturn' ) ) {
+ _FixAccountForLostAndReturned($item->{'itemnumber'}, $borrowernumber, $barcode); # can tolerate undef $borrowernumber
+ $messages->{'LostItemFeeRefunded'} = 1;
+ }
}
# fix up the overdues in accounts...
my $fix = _FixOverduesOnReturn($borrowernumber, $item->{itemnumber}, $exemptfine, $dropbox);
defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->{itemnumber}...) failed!"; # zero is OK, check defined
+ if ( $issue->{overdue} && $issue->{date_due} ) {
# fix fine days
- my $debardate = _FixFineDaysOnReturn( $borrower, $item, $issue->{date_due} );
- $messages->{'Debarred'} = $debardate if ($debardate);
+ my ($debardate,$reminder) = _debar_user_on_return( $borrower, $item, $issue->{date_due}, $today );
+ if ($reminder){
+ $messages->{'PrevDebarred'} = $debardate;
+ } else {
+ $messages->{'Debarred'} = $debardate if $debardate;
+ }
+ # there's no overdue on the item but borrower had been previously debarred
+ } elsif ( $issue->{date_due} and $borrower->{'debarred'} ) {
+ my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
+ $borrower_debar_dt->truncate(to => 'day');
+ my $today_dt = $today->clone()->truncate(to => 'day');
+ if ( DateTime->compare( $borrower_debar_dt, $today_dt ) != -1 ) {
+ $messages->{'PrevDebarred'} = $borrower->{'debarred'};
+ }
+ }
}
# find reserves.....
# if we don't have a reserve with the status W, we launch the Checkreserves routine
- my ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'} );
+ my ($resfound, $resrec);
+ my $lookahead= C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds
+ ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->{'itemnumber'}, undef, $lookahead ) unless ( $item->{'withdrawn'} );
if ($resfound) {
$resrec->{'ResFound'} = $resfound;
$messages->{'ResFound'} = $resrec;
$branch, $stat_type, '0', '',
$item->{'itemnumber'},
$biblio->{'itemtype'},
- $borrowernumber
+ $borrowernumber, undef, $item->{'ccode'}
);
# Send a check-in slip. # NOTE: borrower may be undef. probably shouldn't try to send messages then.
});
}
- logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'biblionumber'})
+ logaction("CIRCULATION", "RETURN", $borrowernumber, $item->{'itemnumber'})
if C4::Context->preference("ReturnLog");
+ # Remove any OVERDUES related debarment if the borrower has no overdues
+ if ( $borrowernumber
+ && $borrower->{'debarred'}
+ && C4::Context->preference('AutoRemoveOverduesRestrictions')
+ && !HasOverdues( $borrowernumber )
+ && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) }
+ ) {
+ DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
+ }
+
# FIXME: make this comment intelligible.
#adding message if holdingbranch is non equal a userenv branch to return the document to homebranch
#we check, if we don't have reserv or transfert for this document, if not, return it to homebranch .
if ( $privacy == 2) {
# The default of 0 does not work due to foreign key constraints
# The anonymisation will fail quietly if AnonymousPatron is not a valid entry
+ # FIXME the above is unacceptable - bug 9942 relates
my $anonymouspatron = (C4::Context->preference('AnonymousPatron')) ? C4::Context->preference('AnonymousPatron') : 0;
my $sth_ano = $dbh->prepare("UPDATE old_issues SET borrowernumber=?
WHERE borrowernumber = ?
$sth_del->execute($borrowernumber, $itemnumber);
}
-=head2 _FixFineDaysOnReturn
+=head2 _debar_user_on_return
- &_FixFineDaysOnReturn($borrower, $item, $datedue);
+ _debar_user_on_return($borrower, $item, $datedue, today);
C<$borrower> borrower hashref
C<$item> item hashref
-C<$datedue> date due
+C<$datedue> date due DateTime object
+
+C<$today> DateTime object representing the return time
-Internal function, called only by AddReturn that calculate and update the user fine days, and debars him
+Internal function, called only by AddReturn that calculates and updates
+ the user fine days, and debars him if necessary.
+
+Should only be called for overdue returns
=cut
-sub _FixFineDaysOnReturn {
- my ( $borrower, $item, $datedue ) = @_;
- return unless ($datedue);
-
- my $dt_due = dt_from_string( $datedue );
- my $dt_today = DateTime->now( time_zone => C4::Context->tz() );
+sub _debar_user_on_return {
+ my ( $borrower, $item, $dt_due, $dt_today ) = @_;
my $branchcode = _GetCircControlBranch( $item, $borrower );
my $calendar = Koha::Calendar->new( branchcode => $branchcode );
# $deltadays is a DateTime::Duration object
my $deltadays = $calendar->days_between( $dt_due, $dt_today );
- my $circcontrol = C4::Context::preference('CircControl');
- my $issuingrule = GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
- my $finedays = $issuingrule->{finedays};
- my $unit = $issuingrule->{lengthunit};
-
- # exit if no finedays defined
- return unless $finedays;
- # finedays is in days, so hourly loans must multiply by 24
- # thus 1 hour late equals 1 day suspension * finedays rate
- $finedays = $finedays * 24 if ($unit eq 'hours');
-
- # grace period is measured in the same units as the loan
- my $grace = DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
-
- if ( ( $deltadays - $grace )->is_positive ) { # you can't compare DateTime::Durations with logical operators
- my $new_debar_dt = $dt_today->clone()->add_duration( $deltadays * $finedays );
- my $borrower_debar_dt = dt_from_string( $borrower->{debarred} );
- # check to see if the current debar date is a valid date
- if ( $borrower->{debarred} && $borrower_debar_dt ) {
- # if so, is it before the new date? update only if true
- if ( DateTime->compare( $borrower_debar_dt, $new_debar_dt ) == -1 ) {
- C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
- return $new_debar_dt->ymd();
+ my $circcontrol = C4::Context->preference('CircControl');
+ my $issuingrule =
+ GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
+ my $finedays = $issuingrule->{finedays};
+ my $unit = $issuingrule->{lengthunit};
+
+ if ($finedays) {
+
+ # finedays is in days, so hourly loans must multiply by 24
+ # thus 1 hour late equals 1 day suspension * finedays rate
+ $finedays = $finedays * 24 if ( $unit eq 'hours' );
+
+ # grace period is measured in the same units as the loan
+ my $grace =
+ DateTime::Duration->new( $unit => $issuingrule->{firstremind} );
+
+ if ( $deltadays->subtract($grace)->is_positive() ) {
+ my $suspension_days = $deltadays * $finedays;
+
+ # If the max suspension days is < than the suspension days
+ # the suspension days is limited to this maximum period.
+ my $max_sd = $issuingrule->{maxsuspensiondays};
+ if ( defined $max_sd ) {
+ $max_sd = DateTime::Duration->new( days => $max_sd );
+ $suspension_days = $max_sd
+ if DateTime::Duration->compare( $max_sd, $suspension_days ) < 0;
+ }
+
+ my $new_debar_dt =
+ $dt_today->clone()->add_duration( $suspension_days );
+
+ Koha::Borrower::Debarments::AddUniqueDebarment({
+ borrowernumber => $borrower->{borrowernumber},
+ expiration => $new_debar_dt->ymd(),
+ type => 'SUSPENSION',
+ });
+ # if borrower was already debarred but does not get an extra debarment
+ if ( $borrower->{debarred} eq Koha::Borrower::Debarments::IsDebarred($borrower->{borrowernumber}) ) {
+ return ($borrower->{debarred},1);
}
- # if the borrower's debar date is not set or valid, debar them
- } else {
- C4::Members::DebarMember( $borrower->{borrowernumber}, $new_debar_dt->ymd() );
return $new_debar_dt->ymd();
}
}
+ return;
}
=head2 _FixOverduesOnReturn
return 0 unless $data; # no warning, there's just nothing to fix
my $uquery;
- my @bind = ($borrowernumber, $item, $data->{'accountno'});
+ my @bind = ($data->{'accountlines_id'});
if ($exemptfine) {
$uquery = "update accountlines set accounttype='FFOR', amountoutstanding=0";
if (C4::Context->preference("FinesLog")) {
} else {
$uquery = "update accountlines set accounttype='F' ";
}
- $uquery .= " where (borrowernumber = ?) and (itemnumber = ?) and (accountno = ?)";
+ $uquery .= " where (accountlines_id = ?)";
my $usth = $dbh->prepare($uquery);
return $usth->execute(@bind);
}
$amountleft = $data->{'amountoutstanding'} - $amount; # Um, isn't this the same as ZERO? We just tested those two things are ==
}
my $usth = $dbh->prepare("UPDATE accountlines SET accounttype = 'LR',amountoutstanding='0'
- WHERE (borrowernumber = ?)
- AND (itemnumber = ?) AND (accountno = ?) ");
- $usth->execute($data->{'borrowernumber'},$itemnumber,$acctno); # We might be adjusting an account for some OTHER borrowernumber now. Not the one we passed in.
+ WHERE (accountlines_id = ?)");
+ $usth->execute($data->{'accountlines_id'}); # We might be adjusting an account for some OTHER borrowernumber now. Not the one we passed in.
#check if any credit is left if so writeoff other accounts
my $nextaccntno = getnextacctno($data->{'borrowernumber'});
$amountleft *= -1 if ($amountleft < 0);
$newamtos = $accdata->{'amountoutstanding'} - $amountleft;
$amountleft = 0;
}
- my $thisacct = $accdata->{'accountno'};
+ my $thisacct = $accdata->{'accountlines_id'};
# FIXME: move prepares outside while loop!
my $usth = $dbh->prepare("UPDATE accountlines SET amountoutstanding= ?
- WHERE (borrowernumber = ?)
- AND (accountno=?)");
- $usth->execute($newamtos,$data->{'borrowernumber'},'$thisacct'); # FIXME: '$thisacct' is a string literal!
+ WHERE (accountlines_id = ?)");
+ $usth->execute($newamtos,$thisacct);
$usth = $dbh->prepare("INSERT INTO accountoffsets
(borrowernumber, accountno, offsetaccount, offsetamount)
VALUES
(?,?,?,?)");
$usth->execute($data->{'borrowernumber'},$accdata->{'accountno'},$nextaccntno,$newamtos);
}
- $msth->finish; # $msth might actually have data left
}
$amountleft *= -1 if ($amountleft > 0);
my $desc = "Item Returned " . $item_id;
my ($itemnumber) = @_;
return unless $itemnumber;
my $sth = C4::Context->dbh->prepare(
- "SELECT *
+ "SELECT items.*, issues.*
FROM issues
LEFT JOIN items ON issues.itemnumber=items.itemnumber
WHERE issues.itemnumber=?");
sub GetOpenIssue {
my ( $itemnumber ) = @_;
-
+ return unless $itemnumber;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare( "SELECT * FROM issues WHERE itemnumber = ? AND returndate IS NULL" );
$sth->execute( $itemnumber );
- my $issue = $sth->fetchrow_hashref();
- return $issue;
+ return $sth->fetchrow_hashref();
+
+}
+
+=head2 GetIssues
+
+ $issues = GetIssues({}); # return all issues!
+ $issues = GetIssues({ borrowernumber => $borrowernumber, biblionumber => $biblionumber });
+
+Returns all pending issues that match given criteria.
+Returns a arrayref or undef if an error occurs.
+
+Allowed criteria are:
+
+=over 2
+
+=item * borrowernumber
+
+=item * biblionumber
+
+=item * itemnumber
+
+=back
+
+=cut
+
+sub GetIssues {
+ my ($criteria) = @_;
+
+ # Build filters
+ my @filters;
+ my @allowed = qw(borrowernumber biblionumber itemnumber);
+ foreach (@allowed) {
+ if (defined $criteria->{$_}) {
+ push @filters, {
+ field => $_,
+ value => $criteria->{$_},
+ };
+ }
+ }
+
+ # Do we need to join other tables ?
+ my %join;
+ if (defined $criteria->{biblionumber}) {
+ $join{items} = 1;
+ }
+
+ # Build SQL query
+ my $where = '';
+ if (@filters) {
+ $where = "WHERE " . join(' AND ', map { "$_->{field} = ?" } @filters);
+ }
+ my $query = q{
+ SELECT issues.*
+ FROM issues
+ };
+ if (defined $join{items}) {
+ $query .= q{
+ LEFT JOIN items ON (issues.itemnumber = items.itemnumber)
+ };
+ }
+ $query .= $where;
+
+ # Execute SQL query
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare($query);
+ my $rv = $sth->execute(map { $_->{value} } @filters);
+
+ return $rv ? $sth->fetchall_arrayref({}) : undef;
}
=head2 GetItemIssues
sub GetBiblioIssues {
my $biblionumber = shift;
- return undef unless $biblionumber;
+ return unless $biblionumber;
my $dbh = C4::Context->dbh;
my $query = "
SELECT issues.*,items.barcode,biblio.biblionumber,biblio.title, biblio.author,borrowers.cardnumber,borrowers.surname,borrowers.firstname
FROM issues
LEFT JOIN items USING (itemnumber)
LEFT OUTER JOIN branches USING (branchcode)
-WhERE returndate is NULL
-AND ( TO_DAYS( NOW() )-TO_DAYS( date_due ) ) < ?
+WHERE returndate is NULL
+HAVING days_until_due >= 0 AND days_until_due <= ?
END_SQL
my @bind_parameters = ( $params->{'days_in_advance'} );
my $sth = $dbh->prepare( $statement );
$sth->execute( @bind_parameters );
my $upcoming_dues = $sth->fetchall_arrayref({});
- $sth->finish;
return $upcoming_dues;
}
Find out whether a borrowed item may be renewed.
-C<$dbh> is a DBI handle to the Koha database.
-
C<$borrowernumber> is the borrower number of the patron who currently
has the item on loan.
the limit on the number of times that the loan can be renewed
(as controlled by the item type) to be ignored.
-C<$CanBookBeRenewed> returns a true value iff the item may be renewed. The
+C<$CanBookBeRenewed> returns a true value if the item may be renewed. The
item must currently be on loan to the specified borrower; renewals
must be allowed for the item's type; and the borrower must not have
already renewed the loan. $error will contain the reason the renewal can not proceed
=cut
sub CanBookBeRenewed {
-
- # check renewal status
my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
+
my $dbh = C4::Context->dbh;
my $renews = 1;
- my $renewokay = 0;
- my $error;
+ my $renewokay = 1;
+ my $error;
- # Look in the issues table for this item, lent to this borrower,
- # and not yet returned.
+ my $item = GetItem($itemnumber) or return ( 0, 'no_item' );
+ my $itemissue = GetItemIssue($itemnumber) or return ( 0, 'no_checkout' );
- # Look in the issues table for this item, lent to this borrower,
- # and not yet returned.
- my %branch = (
- 'ItemHomeLibrary' => 'items.homebranch',
- 'PickupLibrary' => 'items.holdingbranch',
- 'PatronLibrary' => 'borrowers.branchcode'
- );
- my $controlbranch = $branch{C4::Context->preference('CircControl')};
- my $itype = C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype';
-
- my $sthcount = $dbh->prepare("
- SELECT
- borrowers.categorycode, biblioitems.itemtype, issues.renewals, renewalsallowed, $controlbranch
- FROM issuingrules,
- issues
- LEFT JOIN items USING (itemnumber)
- LEFT JOIN borrowers USING (borrowernumber)
- LEFT JOIN biblioitems USING (biblioitemnumber)
-
- WHERE
- (issuingrules.categorycode = borrowers.categorycode OR issuingrules.categorycode = '*')
- AND
- (issuingrules.itemtype = $itype OR issuingrules.itemtype = '*')
- AND
- (issuingrules.branchcode = $controlbranch OR issuingrules.branchcode = '*')
- AND
- borrowernumber = ?
- AND
- itemnumber = ?
- ORDER BY
- issuingrules.categorycode desc,
- issuingrules.itemtype desc,
- issuingrules.branchcode desc
- LIMIT 1;
- ");
-
- $sthcount->execute( $borrowernumber, $itemnumber );
- if ( my $data1 = $sthcount->fetchrow_hashref ) {
-
- if ( ( $data1->{renewalsallowed} && $data1->{renewalsallowed} > $data1->{renewals} ) || $override_limit ) {
- $renewokay = 1;
- }
- else {
- $error="too_many";
- }
-
- my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
- if ($resfound) {
+ $borrowernumber ||= $itemissue->{borrowernumber};
+ my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber )
+ or return;
+
+ my $branchcode = _GetCircControlBranch($item, $borrower);
+
+ my $issuingrule = GetIssuingRule($borrower->{categorycode}, $item->{itype}, $branchcode);
+
+ if ( $issuingrule->{norenewalbefore} ) {
+
+ # Get current time and add norenewalbefore. If this is smaller than date_due, it's too soon for renewal.
+ if (
+ DateTime->now( time_zone => C4::Context->tz() )->add(
+ $issuingrule->{lengthunit} => $issuingrule->{norenewalbefore}
+ ) < $itemissue->{date_due}
+ )
+ {
$renewokay = 0;
- $error="on_reserve"
+ $error = "too_soon";
}
+ }
+
+ if ( $issuingrule->{renewalsallowed} <= $itemissue->{renewals} ) {
+ $renewokay = 0;
+ $error = "too_many";
+ }
+ if ( $override_limit ) {
+ $renewokay = 1;
+ $error = undef;
}
- return ($renewokay,$error);
+
+ my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves( $itemnumber );
+
+ if ( $resfound ) { # '' when no hold was found
+ $renewokay = 0;
+ $error = "on_reserve";
+ }
+
+ return ( $renewokay, $error );
}
=head2 AddRenewal
=cut
sub AddRenewal {
- my $borrowernumber = shift or return undef;
- my $itemnumber = shift or return undef;
+ my $borrowernumber = shift;
+ my $itemnumber = shift or return;
my $branch = shift;
my $datedue = shift;
my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz)->ymd();
- my $item = GetItem($itemnumber) or return undef;
- my $biblio = GetBiblioFromItemNumber($itemnumber) or return undef;
+
+ my $item = GetItem($itemnumber) or return;
+ my $biblio = GetBiblioFromItemNumber($itemnumber) or return;
my $dbh = C4::Context->dbh;
+
# Find the issues record for this book
my $sth =
- $dbh->prepare("SELECT * FROM issues
- WHERE borrowernumber=?
- AND itemnumber=?"
- );
- $sth->execute( $borrowernumber, $itemnumber );
+ $dbh->prepare("SELECT * FROM issues WHERE itemnumber = ?");
+ $sth->execute( $itemnumber );
my $issuedata = $sth->fetchrow_hashref;
- $sth->finish;
- if(defined $datedue && ref $datedue ne 'DateTime' ) {
+
+ return unless ( $issuedata );
+
+ $borrowernumber ||= $issuedata->{borrowernumber};
+
+ if ( defined $datedue && ref $datedue ne 'DateTime' ) {
carp 'Invalid date passed to AddRenewal.';
return;
}
+
# If the due date wasn't specified, calculate it by adding the
# book's loan length to today's date or the current due date
# based on the value of the RenewalPeriodBase syspref.
unless ($datedue) {
- my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return undef;
+ my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) or return;
my $itemtype = (C4::Context->preference('item-level_itypes')) ? $biblio->{'itype'} : $biblio->{'itemtype'};
$datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ?
- $issuedata->{date_due} :
+ dt_from_string( $issuedata->{date_due} ) :
DateTime->now( time_zone => C4::Context->tz());
- $datedue = CalcDateDue($datedue,$itemtype,$issuedata->{'branchcode'},$borrower);
+ $datedue = CalcDateDue($datedue, $itemtype, $issuedata->{'branchcode'}, $borrower, 'is a renewal');
}
# Update the issues record to have the new due date, and a new count
);
$sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
- $sth->finish;
# Update the renewal count on the item, and tell zebra to reindex
$renews = $biblio->{'renewals'} + 1;
"Renewal of Rental Item $item->{'title'} $item->{'barcode'}",
'Rent', $charge, $itemnumber );
}
+
+ # Send a renewal slip according to checkout alert preferencei
+ if ( C4::Context->preference('RenewalSendNotice') eq '1') {
+ my $borrower = C4::Members::GetMemberDetails( $borrowernumber, 0 );
+ my $circulation_alert = 'C4::ItemCirculationAlertPreference';
+ my %conditions = (
+ branchcode => $branch,
+ categorycode => $borrower->{categorycode},
+ item_type => $item->{itype},
+ notification => 'CHECKOUT',
+ );
+ if ($circulation_alert->is_enabled_for(\%conditions)) {
+ SendCirculationAlert({
+ type => 'RENEWAL',
+ item => $item,
+ borrower => $borrower,
+ branch => $branch,
+ });
+ }
+ }
+
+ # Remove any OVERDUES related debarment if the borrower has no overdues
+ my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber );
+ if ( $borrowernumber
+ && $borrower->{'debarred'}
+ && !HasOverdues( $borrowernumber )
+ && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) }
+ ) {
+ DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
+ }
+
# Log the renewal
- UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber);
+ UpdateStats( $branch, 'renew', $charge, '', $itemnumber, $item->{itype}, $borrowernumber, undef, $item->{'ccode'});
return $datedue;
}
$sth->execute( $bornum, $itemno );
my $data = $sth->fetchrow_hashref;
$renewcount = $data->{'renewals'} if $data->{'renewals'};
- $sth->finish;
# $item and $borrower should be calculated
my $branchcode = _GetCircControlBranch($item, $borrower);
return ( $renewcount, $renewsallowed, $renewsleft );
}
+=head2 GetSoonestRenewDate
+
+ $NoRenewalBeforeThisDate = &GetSoonestRenewDate($borrowernumber, $itemnumber);
+
+Find out the soonest possible renew date of a borrowed item.
+
+C<$borrowernumber> is the borrower number of the patron who currently
+has the item on loan.
+
+C<$itemnumber> is the number of the item to renew.
+
+C<$GetSoonestRenewDate> returns the DateTime of the soonest possible
+renew date, based on the value "No renewal before" of the applicable
+issuing rule. Returns the current date if the item can already be
+renewed, and returns undefined if the borrower, loan, or item
+cannot be found.
+
+=cut
+
+sub GetSoonestRenewDate {
+ my ( $borrowernumber, $itemnumber ) = @_;
+
+ my $dbh = C4::Context->dbh;
+
+ my $item = GetItem($itemnumber) or return;
+ my $itemissue = GetItemIssue($itemnumber) or return;
+
+ $borrowernumber ||= $itemissue->{borrowernumber};
+ my $borrower = C4::Members::GetMemberDetails($borrowernumber)
+ or return;
+
+ my $branchcode = _GetCircControlBranch( $item, $borrower );
+ my $issuingrule =
+ GetIssuingRule( $borrower->{categorycode}, $item->{itype}, $branchcode );
+
+ my $now = DateTime->now( time_zone => C4::Context->tz() );
+
+ if ( $issuingrule->{norenewalbefore} ) {
+ my $soonestrenewal =
+ $itemissue->{date_due}->subtract(
+ $issuingrule->{lengthunit} => $issuingrule->{norenewalbefore} );
+
+ $soonestrenewal = $now > $soonestrenewal ? $now : $soonestrenewal;
+ return $soonestrenewal;
+ }
+ return $now;
+}
+
=head2 GetIssuingCharges
($charge, $item_type) = &GetIssuingCharges($itemnumber, $borrowernumber);
}
}
- $sth->finish; # we havent _explicitly_ fetched all rows
return ( $charge, $item_type );
}
";
my $sth = $dbh->prepare($query);
$sth->execute( $borrowernumber, $itemnumber, $nextaccntno, $charge, $charge, $manager_id );
- $sth->finish;
}
=head2 GetTransfers
my $sth = $dbh->prepare($query);
$sth->execute($itemnumber);
my @row = $sth->fetchrow_array();
- $sth->finish;
return @row;
}
while ( my $data = $sth->fetchrow_hashref ) {
push @gettransfers, $data;
}
- $sth->finish;
return (@gettransfers);
}
sub DeleteTransfer {
my ($itemnumber) = @_;
+ return unless $itemnumber;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare(
"DELETE FROM branchtransfers
WHERE itemnumber=?
AND datearrived IS NULL "
);
- $sth->execute($itemnumber);
- $sth->finish;
+ return $sth->execute($itemnumber);
}
=head2 AnonymiseIssueHistory
- $rows = AnonymiseIssueHistory($date,$borrowernumber)
+ ($rows,$err_history_not_deleted) = AnonymiseIssueHistory($date,$borrowernumber)
This function write NULL instead of C<$borrowernumber> given on input arg into the table issues.
if C<$borrowernumber> is not set, it will delete the issue history for all borrower older than C<$date>.
If c<$borrowernumber> is set, it will delete issue history for only that borrower, regardless of their opac privacy
setting (force delete).
-return the number of affected rows.
+return the number of affected rows and a value that evaluates to true if an error occurred deleting the history.
=cut
}
my $sth = $dbh->prepare($query);
$sth->execute(@bind_params);
+ my $anonymisation_err = $dbh->err;
my $rows_affected = $sth->rows; ### doublecheck row count return function
- return $rows_affected;
+ return ($rows_affected, $anonymisation_err);
}
=head2 SendCirculationAlert
my %message_name = (
CHECKIN => 'Item_Check_in',
CHECKOUT => 'Item_Checkout',
+ RENEWAL => 'Item_Checkout',
);
my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
borrowernumber => $borrower->{borrowernumber},
message_name => $message_name{$type},
});
- my $issues_table = ( $type eq 'CHECKOUT' ) ? 'issues' : 'old_issues';
+ my $issues_table = ( $type eq 'CHECKOUT' || $type eq 'RENEWAL' ) ? 'issues' : 'old_issues';
my $letter = C4::Letters::GetPreparedLetter (
module => 'circulation',
letter_code => $type,
"update branchtransfers set datearrived = now(),tobranch=?,comments='wrongtransfer' where itemnumber= ? AND datearrived IS NULL"
);
$sth->execute($FromLibrary,$itemNumber);
- $sth->finish;
# second step create a new line of branchtransfer to the right location .
ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary);
C<$itemtype> = itemtype code of item in question
C<$branch> = location whose calendar to use
C<$borrower> = Borrower object
+C<$isrenewal> = Boolean: is true if we want to calculate the date due for a renewal. Else is false.
=cut
sub CalcDateDue {
- my ( $startdate, $itemtype, $branch, $borrower ) = @_;
+ my ( $startdate, $itemtype, $branch, $borrower, $isrenewal ) = @_;
+
+ $isrenewal ||= 0;
# loanlength now a href
my $loanlength =
- GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
+ GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
- my $datedue;
+ my $length_key = ( $isrenewal and defined $loanlength->{renewalperiod} )
+ ? qq{renewalperiod}
+ : qq{issuelength};
- # if globalDueDate ON the datedue is set to that date
- if (C4::Context->preference('globalDueDate')
- && ( C4::Context->preference('globalDueDate') =~
- C4::Dates->regexp('syspref') )
- ) {
- $datedue = dt_from_string(
- C4::Context->preference('globalDueDate'),
- C4::Context->preference('dateformat')
- );
+ my $datedue;
+ if ( $startdate ) {
+ if (ref $startdate ne 'DateTime' ) {
+ $datedue = dt_from_string($datedue);
+ } else {
+ $datedue = $startdate->clone;
+ }
} else {
+ $datedue =
+ DateTime->now( time_zone => C4::Context->tz() )
+ ->truncate( to => 'minute' );
+ }
- # otherwise, calculate the datedue as normal
- if ( C4::Context->preference('useDaysMode') eq 'Days' )
- { # ignoring calendar
- my $dt =
- DateTime->now( time_zone => C4::Context->tz() )
- ->truncate( to => 'minute' );
- if ( $loanlength->{lengthunit} eq 'hours' ) {
- $dt->add( hours => $loanlength->{issuelength} );
- return $dt;
- } else { # days
- $dt->add( days => $loanlength->{issuelength} );
- $dt->set_hour(23);
- $dt->set_minute(59);
- return $dt;
- }
- } else {
- my $dur;
- if ($loanlength->{lengthunit} eq 'hours') {
- $dur = DateTime::Duration->new( hours => $loanlength->{issuelength});
- }
- else { # days
- $dur = DateTime::Duration->new( days => $loanlength->{issuelength});
- }
- if (ref $startdate ne 'DateTime' ) {
- $startdate = dt_from_string($startdate);
- }
- my $calendar = Koha::Calendar->new( branchcode => $branch );
- $datedue = $calendar->addDate( $startdate, $dur, $loanlength->{lengthunit} );
- if ($loanlength->{lengthunit} eq 'days') {
- $datedue->set_hour(23);
- $datedue->set_minute(59);
- }
+
+ # calculate the datedue as normal
+ if ( C4::Context->preference('useDaysMode') eq 'Days' )
+ { # ignoring calendar
+ if ( $loanlength->{lengthunit} eq 'hours' ) {
+ $datedue->add( hours => $loanlength->{$length_key} );
+ } else { # days
+ $datedue->add( days => $loanlength->{$length_key} );
+ $datedue->set_hour(23);
+ $datedue->set_minute(59);
+ }
+ } else {
+ my $dur;
+ if ($loanlength->{lengthunit} eq 'hours') {
+ $dur = DateTime::Duration->new( hours => $loanlength->{$length_key});
+ }
+ else { # days
+ $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
+ }
+ my $calendar = Koha::Calendar->new( branchcode => $branch );
+ $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
+ if ($loanlength->{lengthunit} eq 'days') {
+ $datedue->set_hour(23);
+ $datedue->set_minute(59);
}
}
}
# in all other cases, keep the date due as it is
+
}
# if ReturnBeforeExpiry ON the datedue can't be after borrower expirydate
if ( C4::Context->preference('ReturnBeforeExpiry') ) {
my $expiry_dt = dt_from_string( $borrower->{dateexpiry}, 'iso' );
+ $expiry_dt->set( hour => 23, minute => 59);
if ( DateTime->compare( $datedue, $expiry_dt ) == 1 ) {
$datedue = $expiry_dt->clone;
}
my $sth = $dbh->prepare($query);
$sth->execute($branchcode,$week_day);
my $result=$sth->fetchrow;
-$sth->finish;
return $result;
}
my $sth = $dbh->prepare($query);
$sth->execute($years,$month,$day,$branchcode);
my $countspecial=$sth->fetchrow ;
-$sth->finish;
return $countspecial;
}
my $sth = $dbh->prepare($query);
$sth->execute($month,$day,$branchcode);
my $countspecial=$sth->fetchrow ;
-$sth->finish;
return $countspecial;
}
my $sth = $dbh->prepare($query);
$sth->execute($barcode);
my $exist=$sth->fetchrow ;
-$sth->finish;
return $exist;
}
sub CreateBranchTransferLimit {
my ( $toBranch, $fromBranch, $code ) = @_;
-
+ return unless defined($toBranch) && defined($fromBranch);
my $limitType = C4::Context->preference("BranchTransferLimitsType");
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare("INSERT INTO branch_transfer_limits ( $limitType, toBranch, fromBranch ) VALUES ( ?, ?, ? )");
- $sth->execute( $code, $toBranch, $fromBranch );
+ return $sth->execute( $code, $toBranch, $fromBranch );
}
=head2 DeleteBranchTransferLimits
-DeleteBranchTransferLimits($frombranch);
+ my $result = DeleteBranchTransferLimits($frombranch);
-Deletes all the branch transfer limits for one branch
+Deletes all the library transfer limits for one library. Returns the
+number of limits deleted, 0e0 if no limits were deleted, or undef if
+no arguments are supplied.
=cut
sub DeleteBranchTransferLimits {
my $branch = shift;
+ return unless defined $branch;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare("DELETE FROM branch_transfer_limits WHERE fromBranch = ?");
- $sth->execute($branch);
+ return $sth->execute($branch);
}
sub ReturnLostItem{
MarkIssueReturned( $borrowernumber, $itemnum );
my $borrower = C4::Members::GetMember( 'borrowernumber'=>$borrowernumber );
+ my $item = C4::Items::GetItem( $itemnum );
+ my $old_note = ($item->{'paidfor'} && ($item->{'paidfor'} ne q{})) ? $item->{'paidfor'}.' / ' : q{};
my @datearr = localtime(time);
my $date = ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
my $bor = "$borrower->{'firstname'} $borrower->{'surname'} $borrower->{'cardnumber'}";
- ModItem({ paidfor => "Paid for by $bor $date" }, undef, $itemnum);
+ ModItem({ paidfor => $old_note."Paid for by $bor $date" }, undef, $itemnum);
}
sub LostItem{
- my ($itemnumber, $mark_returned, $charge_fee) = @_;
+ my ($itemnumber, $mark_returned) = @_;
my $dbh = C4::Context->dbh();
my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title
WHERE issues.itemnumber=?");
$sth->execute($itemnumber);
my $issues=$sth->fetchrow_hashref();
- $sth->finish;
- # if a borrower lost the item, add a replacement cost to the their record
+ # If a borrower lost the item, add a replacement cost to the their record
if ( my $borrowernumber = $issues->{borrowernumber} ){
my $borrower = C4::Members::GetMemberDetails( $borrowernumber );
- C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}")
- if $charge_fee;
- #FIXME : Should probably have a way to distinguish this from an item that really was returned.
- #warn " $issues->{'borrowernumber'} / $itemnumber ";
+ if (C4::Context->preference('WhenLostForgiveFine')){
+ my $fix = _FixOverduesOnReturn($borrowernumber, $itemnumber, 1, 0); # 1, 0 = exemptfine, no-dropbox
+ defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!"; # zero is OK, check defined
+ }
+ if (C4::Context->preference('WhenLostChargeReplacementFee')){
+ C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}");
+ #FIXME : Should probably have a way to distinguish this from an item that really was returned.
+ #warn " $issues->{'borrowernumber'} / $itemnumber ";
+ }
+
MarkIssueReturned($borrowernumber,$itemnumber,undef,undef,$borrower->{'privacy'}) if $mark_returned;
}
}
my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE branchcode=? ORDER BY timestamp");
$sth->execute(C4::Context->userenv->{'branch'});
my $results = $sth->fetchall_arrayref({});
- $sth->finish;
return $results;
}
sub GetOfflineOperation {
+ my $operationid = shift;
+ return unless $operationid;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare("SELECT * FROM pending_offline_operations WHERE operationid=?");
- $sth->execute( shift );
- my $result = $sth->fetchrow_hashref;
- $sth->finish;
- return $result;
+ $sth->execute( $operationid );
+ return $sth->fetchrow_hashref;
}
sub AddOfflineOperation {
+ my ( $userid, $branchcode, $timestamp, $action, $barcode, $cardnumber, $amount ) = @_;
my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber) VALUES(?,?,?,?,?,?)");
- $sth->execute( @_ );
+ my $sth = $dbh->prepare("INSERT INTO pending_offline_operations (userid, branchcode, timestamp, action, barcode, cardnumber, amount) VALUES(?,?,?,?,?,?,?)");
+ $sth->execute( $userid, $branchcode, $timestamp, $action, $barcode, $cardnumber, $amount );
return "Added.";
}
$report = ProcessOfflineReturn( $operation );
} elsif ( $operation->{action} eq 'issue' ) {
$report = ProcessOfflineIssue( $operation );
+ } elsif ( $operation->{action} eq 'payment' ) {
+ $report = ProcessOfflinePayment( $operation );
}
DeleteOfflineOperation( $operation->{operationid} ) if $operation->{operationid};
}
}
+sub ProcessOfflinePayment {
+ my $operation = shift;
+
+ my $borrower = C4::Members::GetMemberDetails( undef, $operation->{cardnumber} ); # Get borrower from operation cardnumber
+ my $amount = $operation->{amount};
+
+ recordpayment( $borrower->{borrowernumber}, $amount );
+
+ return "Success."
+}
=head2 TransferSlip
);
}
+=head2 CheckIfIssuedToPatron
+
+ CheckIfIssuedToPatron($borrowernumber, $biblionumber)
+
+ Return 1 if any record item is issued to patron, otherwise return 0
+
+=cut
+
+sub CheckIfIssuedToPatron {
+ my ($borrowernumber, $biblionumber) = @_;
+
+ my $items = GetItemsByBiblioitemnumber($biblionumber);
+
+ foreach my $item (@{$items}) {
+ return 1 if ($item->{borrowernumber} && $item->{borrowernumber} eq $borrowernumber);
+ }
+
+ return;
+}
+
+=head2 IsItemIssued
+
+ IsItemIssued( $itemnumber )
+
+ Return 1 if the item is on loan, otherwise return 0
+
+=cut
+
+sub IsItemIssued {
+ my $itemnumber = shift;
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare(q{
+ SELECT COUNT(*)
+ FROM issues
+ WHERE itemnumber = ?
+ });
+ $sth->execute($itemnumber);
+ return $sth->fetchrow;
+}
+
+sub GetAgeRestriction {
+ my ($record_restrictions) = @_;
+ my $markers = C4::Context->preference('AgeRestrictionMarker');
+
+ # Split $record_restrictions to something like FSK 16 or PEGI 6
+ my @values = split ' ', uc($record_restrictions);
+ return unless @values;
+
+ # Search first occurence of one of the markers
+ my @markers = split /\|/, uc($markers);
+ return unless @markers;
+
+ my $index = 0;
+ my $restriction_year = 0;
+ for my $value (@values) {
+ $index++;
+ for my $marker (@markers) {
+ $marker =~ s/^\s+//; #remove leading spaces
+ $marker =~ s/\s+$//; #remove trailing spaces
+ if ( $marker eq $value ) {
+ if ( $index <= $#values ) {
+ $restriction_year += $values[$index];
+ }
+ last;
+ }
+ elsif ( $value =~ /^\Q$marker\E(\d+)$/ ) {
+
+ # Perhaps it is something like "K16" (as in Finland)
+ $restriction_year += $1;
+ last;
+ }
+ }
+ last if ( $restriction_year > 0 );
+ }
+
+ return $restriction_year;
+}
1;