use Koha::Libraries;
use Koha::Account::Lines;
use Koha::Holds;
-use Koha::RefundLostItemFeeRules;
use Koha::Account::Lines;
use Koha::Account::Offsets;
use Koha::Config::SysPrefs;
use Koha::Charges::Fees;
use Koha::Util::SystemPreferences;
use Koha::Checkouts::ReturnClaims;
+use Koha::SearchEngine::Indexer;
use Carp;
use List::MoreUtils qw( uniq any );
use Scalar::Util qw( looks_like_number );
=head2 transferbook
- ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch,
- $barcode, $ignore_reserves, $trigger);
+ ($dotransfer, $messages, $iteminformation) = &transferbook({
+ from_branch => $frombranch
+ to_branch => $tobranch,
+ barcode => $barcode,
+ ignore_reserves => $ignore_reserves,
+ trigger => $trigger
+ });
Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer.
-C<$newbranch> is the code for the branch to which the item should be transferred.
+C<$fbr> is the code for the branch initiating the transfer.
+C<$tbr> is the code for the branch to which the item should be transferred.
C<$barcode> is the barcode of the item to be transferred.
=cut
sub transferbook {
- my ( $tbr, $barcode, $ignoreRs, $trigger ) = @_;
+ my $params = shift;
+ my $tbr = $params->{to_branch};
+ my $fbr = $params->{from_branch};
+ my $ignoreRs = $params->{ignore_reserves};
+ my $barcode = $params->{barcode};
+ my $trigger = $params->{trigger};
my $messages;
my $dotransfer = 1;
my $item = Koha::Items->find( { barcode => $barcode } );
+ Koha::Exceptions::MissingParameter->throw(
+ "Missing mandatory parameter: from_branch")
+ unless $fbr;
+
+ Koha::Exceptions::MissingParameter->throw(
+ "Missing mandatory parameter: to_branch")
+ unless $tbr;
+
# bad barcode..
unless ( $item ) {
$messages->{'BadBarcode'} = $barcode;
my $itemnumber = $item->itemnumber;
# get branches of book...
my $hbr = $item->homebranch;
- my $fbr = $item->holdingbranch;
# if using Branch Transfer Limits
if ( C4::Context->preference("UseBranchTransferLimits") == 1 ) {
my $switch_onsite_checkout = $params->{switch_onsite_checkout} || 0;
my $cat_borrower = $borrower->{'categorycode'};
my $dbh = C4::Context->dbh;
- my $branch;
- # Get which branchcode we need
- $branch = _GetCircControlBranch($item_object->unblessed,$borrower);
+ # Get which branchcode we need
+ my $branch = _GetCircControlBranch($item_object->unblessed,$borrower);
my $type = $item_object->effective_itemtype;
+ my ($type_object, $parent_type, $parent_maxissueqty_rule);
+ $type_object = Koha::ItemTypes->find( $type );
+ $parent_type = $type_object->parent_type if $type_object;
+ my $child_types = Koha::ItemTypes->search({ parent_type => $type });
+ # Find any children if we are a parent_type;
+
# given branch, patron category, and item type, determine
# applicable issuing rule
+
+ $parent_maxissueqty_rule = Koha::CirculationRules->get_effective_rule(
+ {
+ categorycode => $cat_borrower,
+ itemtype => $parent_type,
+ branchcode => $branch,
+ rule_name => 'maxissueqty',
+ }
+ ) if $parent_type;
+ # If the parent rule is for default type we discount it
+ $parent_maxissueqty_rule = undef if $parent_maxissueqty_rule && !defined $parent_maxissueqty_rule->itemtype;
+
my $maxissueqty_rule = Koha::CirculationRules->get_effective_rule(
{
categorycode => $cat_borrower,
rule_name => 'maxissueqty',
}
);
+
my $maxonsiteissueqty_rule = Koha::CirculationRules->get_effective_rule(
{
categorycode => $cat_borrower,
);
+ my $patron = Koha::Patrons->find($borrower->{borrowernumber});
# if a rule is found and has a loan limit set, count
# how many loans the patron already has that meet that
# rule
- if (defined($maxissueqty_rule) and $maxissueqty_rule->rule_value ne '') {
- my @bind_params;
- my $count_query = q|
- SELECT COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts
- FROM issues
- JOIN items USING (itemnumber)
- |;
+ if (defined($maxissueqty_rule) and $maxissueqty_rule->rule_value ne "") {
- my $rule_itemtype = $maxissueqty_rule->itemtype;
- unless ($rule_itemtype) {
- # matching rule has the default item type, so count only
- # those existing loans that don't fall under a more
- # specific rule
- if (C4::Context->preference('item-level_itypes')) {
- $count_query .= " WHERE items.itype NOT IN (
- SELECT itemtype FROM circulation_rules
- WHERE branchcode = ?
- AND (categorycode = ? OR categorycode = ?)
- AND itemtype IS NOT NULL
- AND rule_name = 'maxissueqty'
- ) ";
+ my $checkouts;
+ if ( $maxissueqty_rule->branchcode ) {
+ if ( C4::Context->preference('CircControl') eq 'PickupLibrary' ) {
+ $checkouts = $patron->checkouts->search(
+ { 'me.branchcode' => $maxissueqty_rule->branchcode } );
+ } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
+ $checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
- $count_query .= " JOIN biblioitems USING (biblionumber)
- WHERE biblioitems.itemtype NOT IN (
- SELECT itemtype FROM circulation_rules
- WHERE branchcode = ?
- AND (categorycode = ? OR categorycode = ?)
- AND itemtype IS NOT NULL
- AND rule_name = 'maxissueqty'
- ) ";
+ $checkouts = $patron->checkouts->search(
+ { 'item.homebranch' => $maxissueqty_rule->branchcode },
+ { prefetch => 'item' } );
}
- push @bind_params, $maxissueqty_rule->branchcode;
- push @bind_params, $maxissueqty_rule->categorycode;
- push @bind_params, $cat_borrower;
} else {
- # rule has specific item type, so count loans of that
- # specific item type
- if (C4::Context->preference('item-level_itypes')) {
- $count_query .= " WHERE items.itype = ? ";
- } else {
- $count_query .= " JOIN biblioitems USING (biblionumber)
- WHERE biblioitems.itemtype= ? ";
- }
- push @bind_params, $type;
+ $checkouts = $patron->checkouts; # if rule is not branch specific then count all loans by patron
}
+ my $sum_checkouts;
+ my $rule_itemtype = $maxissueqty_rule->itemtype;
+ while ( my $c = $checkouts->next ) {
+ my $itemtype = $c->item->effective_itemtype;
+ my @types;
+ unless ( $rule_itemtype ) {
+ # matching rule has the default item type, so count only
+ # those existing loans that don't fall under a more
+ # specific rule
+ @types = Koha::CirculationRules->search(
+ {
+ branchcode => $maxissueqty_rule->branchcode,
+ categorycode => [ $maxissueqty_rule->categorycode, $cat_borrower ],
+ itemtype => { '!=' => undef },
+ rule_name => 'maxissueqty'
+ }
+ )->get_column('itemtype');
- $count_query .= " AND borrowernumber = ? ";
- push @bind_params, $borrower->{'borrowernumber'};
- my $rule_branch = $maxissueqty_rule->branchcode;
- if ($rule_branch) {
- if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
- $count_query .= " AND issues.branchcode = ? ";
- push @bind_params, $rule_branch;
- } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
- ; # if branch is the patron's home branch, then count all loans by patron
+ next if grep {$_ eq $itemtype} @types;
} else {
- $count_query .= " AND items.homebranch = ? ";
- push @bind_params, $rule_branch;
+ my @types;
+ if ( $parent_maxissueqty_rule ) {
+ # if we have a parent item type then we count loans of the
+ # specific item type or its siblings or parent
+ my $children = Koha::ItemTypes->search({ parent_type => $parent_type });
+ @types = $children->get_column('itemtype');
+ push @types, $parent_type;
+ } elsif ( $child_types ) {
+ # If we are a parent type, we need to count all child types and our own type
+ @types = $child_types->get_column('itemtype');
+ push @types, $type; # And don't forget to count our own types
+ } else { push @types, $type; } # Otherwise only count the specific itemtype
+
+ next unless grep {$_ eq $itemtype} @types;
}
+ $sum_checkouts->{total}++;
+ $sum_checkouts->{onsite_checkouts}++ if $c->onsite_checkout;
+ $sum_checkouts->{itemtype}->{$itemtype}++;
}
- my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $count_query, {}, @bind_params );
-
- my $max_checkouts_allowed = $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef;
- my $max_onsite_checkouts_allowed = $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef;
-
- if ( $onsite_checkout and $max_onsite_checkouts_allowed ne '' ) {
- if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) {
- return {
- reason => 'TOO_MANY_ONSITE_CHECKOUTS',
- count => $onsite_checkout_count,
- max_allowed => $max_onsite_checkouts_allowed,
- }
- }
- }
- if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
- my $delta = $switch_onsite_checkout ? 1 : 0;
- if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
- return {
- reason => 'TOO_MANY_CHECKOUTS',
- count => $checkout_count,
- max_allowed => $max_checkouts_allowed,
- };
- }
- } elsif ( not $onsite_checkout ) {
- if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed ) {
- return {
- reason => 'TOO_MANY_CHECKOUTS',
- count => $checkout_count - $onsite_checkout_count,
- max_allowed => $max_checkouts_allowed,
- };
+ my $checkout_count_type = $sum_checkouts->{itemtype}->{$type} || 0;
+ my $checkout_count = $sum_checkouts->{total} || 0;
+ my $onsite_checkout_count = $sum_checkouts->{onsite_checkouts} || 0;
+
+ my $checkout_rules = {
+ checkout_count => $checkout_count,
+ onsite_checkout_count => $onsite_checkout_count,
+ onsite_checkout => $onsite_checkout,
+ max_checkouts_allowed => $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef,
+ max_onsite_checkouts_allowed => $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef,
+ switch_onsite_checkout => $switch_onsite_checkout,
+ };
+ # If parent rules exists
+ if ( defined($parent_maxissueqty_rule) and defined($parent_maxissueqty_rule->rule_value) ){
+ $checkout_rules->{max_checkouts_allowed} = $parent_maxissueqty_rule ? $parent_maxissueqty_rule->rule_value : undef;
+ my $qty_over = _check_max_qty($checkout_rules);
+ return $qty_over if defined $qty_over;
+
+ # If the parent rule is less than or equal to the child, we only need check the parent
+ if( $maxissueqty_rule->rule_value < $parent_maxissueqty_rule->rule_value && defined($maxissueqty_rule->itemtype) ) {
+ $checkout_rules->{checkout_count} = $checkout_count_type;
+ $checkout_rules->{max_checkouts_allowed} = $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef;
+ my $qty_over = _check_max_qty($checkout_rules);
+ return $qty_over if defined $qty_over;
}
+ } else {
+ my $qty_over = _check_max_qty($checkout_rules);
+ return $qty_over if defined $qty_over;
}
}
# Now count total loans against the limit for the branch
my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower);
if (defined($branch_borrower_circ_rule->{patron_maxissueqty}) and $branch_borrower_circ_rule->{patron_maxissueqty} ne '') {
- my @bind_params = ();
- my $branch_count_query = q|
- SELECT COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts
- FROM issues
- JOIN items USING (itemnumber)
- WHERE borrowernumber = ?
- |;
- push @bind_params, $borrower->{borrowernumber};
-
- if (C4::Context->preference('CircControl') eq 'PickupLibrary') {
- $branch_count_query .= " AND issues.branchcode = ? ";
- push @bind_params, $branch;
+ my $checkouts;
+ if ( C4::Context->preference('CircControl') eq 'PickupLibrary' ) {
+ $checkouts = $patron->checkouts->search(
+ { 'me.branchcode' => $branch} );
} elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') {
- ; # if branch is the patron's home branch, then count all loans by patron
+ $checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
- $branch_count_query .= " AND items.homebranch = ? ";
- push @bind_params, $branch;
+ $checkouts = $patron->checkouts->search(
+ { 'item.homebranch' => $branch},
+ { prefetch => 'item' } );
}
- my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $branch_count_query, {}, @bind_params );
+
+ my $checkout_count = $checkouts->count;
+ my $onsite_checkout_count = $checkouts->search({ onsite_checkout => 1 })->count;
my $max_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxissueqty};
- my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty};
-
- if ( $onsite_checkout and $max_onsite_checkouts_allowed ne '' ) {
- if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) {
- return {
- reason => 'TOO_MANY_ONSITE_CHECKOUTS',
- count => $onsite_checkout_count,
- max_allowed => $max_onsite_checkouts_allowed,
- }
- }
- }
- if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
- my $delta = $switch_onsite_checkout ? 1 : 0;
- if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
- return {
- reason => 'TOO_MANY_CHECKOUTS',
- count => $checkout_count,
- max_allowed => $max_checkouts_allowed,
- };
- }
- } elsif ( not $onsite_checkout ) {
- if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed ) {
- return {
- reason => 'TOO_MANY_CHECKOUTS',
- count => $checkout_count - $onsite_checkout_count,
- max_allowed => $max_checkouts_allowed,
- };
+ my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty} || undef;
+
+ my $qty_over = _check_max_qty(
+ {
+ checkout_count => $checkout_count,
+ onsite_checkout_count => $onsite_checkout_count,
+ onsite_checkout => $onsite_checkout,
+ max_checkouts_allowed => $max_checkouts_allowed,
+ max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed,
+ switch_onsite_checkout => $switch_onsite_checkout
}
- }
+ );
+ return $qty_over if defined $qty_over;
}
if ( not defined( $maxissueqty_rule ) and not defined($branch_borrower_circ_rule->{patron_maxissueqty}) ) {
return;
}
+sub _check_max_qty {
+ my $params = shift;
+ my $checkout_count = $params->{checkout_count};
+ my $onsite_checkout_count = $params->{onsite_checkout_count};
+ my $onsite_checkout = $params->{onsite_checkout};
+ my $max_checkouts_allowed = $params->{max_checkouts_allowed};
+ my $max_onsite_checkouts_allowed = $params->{max_onsite_checkouts_allowed};
+ my $switch_onsite_checkout = $params->{switch_onsite_checkout};
+
+ if ( $onsite_checkout and defined $max_onsite_checkouts_allowed ) {
+ if ( $max_onsite_checkouts_allowed eq '' ) { return; }
+ if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) {
+ return {
+ reason => 'TOO_MANY_ONSITE_CHECKOUTS',
+ count => $onsite_checkout_count,
+ max_allowed => $max_onsite_checkouts_allowed,
+ };
+ }
+ }
+ if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) {
+ if ( $max_checkouts_allowed eq '' ) { return; }
+ my $delta = $switch_onsite_checkout ? 1 : 0;
+ if ( $checkout_count >= $max_checkouts_allowed + $delta ) {
+ return {
+ reason => 'TOO_MANY_CHECKOUTS',
+ count => $checkout_count,
+ max_allowed => $max_checkouts_allowed,
+ };
+ }
+ }
+ elsif ( not $onsite_checkout ) {
+ if ( $max_checkouts_allowed eq '' ) { return; }
+ if (
+ $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed )
+ {
+ return {
+ reason => 'TOO_MANY_CHECKOUTS',
+ count => $checkout_count - $onsite_checkout_count,
+ max_allowed => $max_checkouts_allowed,
+ };
+ }
+ }
+
+ return;
+}
+
=head2 CanBookBeIssued
( $issuingimpossible, $needsconfirmation, [ $alerts ] ) = CanBookBeIssued( $patron,
}
}
+ # Check the debt of this patrons guarantors *and* the guarantees of those guarantors
+ my $no_issues_charge_guarantors = C4::Context->preference("NoIssuesChargeGuarantorsWithGuarantees");
+ $no_issues_charge_guarantors = undef unless looks_like_number( $no_issues_charge_guarantors );
+ if ( defined $no_issues_charge_guarantors ) {
+ my $guarantors_non_issues_charges += $patron->relationships_debt({ include_guarantors => 1, only_this_guarantor => 0, include_this_patron => 1 });
+
+ if ( $guarantors_non_issues_charges > $no_issues_charge_guarantors && !$inprocess && !$allowfineoverride) {
+ $issuingimpossible{DEBT_GUARANTORS} = $guarantors_non_issues_charges;
+ } elsif ( $guarantors_non_issues_charges > $no_issues_charge_guarantors && !$inprocess && $allowfineoverride) {
+ $needsconfirmation{DEBT_GUARANTORS} = $guarantors_non_issues_charges;
+ } elsif ( $allfinesneedoverride && $guarantors_non_issues_charges > 0 && $guarantors_non_issues_charges <= $no_issues_charge_guarantors && !$inprocess ) {
+ $needsconfirmation{DEBT_GUARANTORS} = $guarantors_non_issues_charges;
+ }
+ }
+
if ( C4::Context->preference("IssuingInProcess") ) {
if ( $non_issues_charges > $amountlimit && !$inprocess && !$allowfineoverride) {
$issuingimpossible{DEBT} = $non_issues_charges;
}
}
+ # Additional Materials Check
+ if ( C4::Context->preference("CircConfirmItemParts")
+ && $item_object->materials )
+ {
+ $needsconfirmation{ADDITIONAL_MATERIALS} = $item_object->materials;
+ }
+
#
# CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER
#
my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
- my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration');
+ my $rule = Koha::CirculationRules->get_effective_rule(
+ {
+ categorycode => $borrower->{categorycode},
+ itemtype => $item_object->effective_itemtype,
+ branchcode => $branchcode,
+ rule_name => 'decreaseloanholds',
+ }
+ );
- my $reduced_datedue = $calendar->addDate( $issuedate, $decreaseLoanHighHoldsDuration );
+ my $duration;
+ if ( defined($rule) && $rule->rule_value ne '' ){
+ # overrides decreaseLoanHighHoldsDuration syspref
+ $duration = $rule->rule_value;
+ } else {
+ $duration = C4::Context->preference('decreaseLoanHighHoldsDuration');
+ }
+ my $reduced_datedue = $calendar->addDate( $issuedate, $duration );
$reduced_datedue->set_hour($orig_due->hour);
$reduced_datedue->set_minute($orig_due->minute);
$reduced_datedue->truncate( to => 'minute' );
if ( DateTime->compare( $reduced_datedue, $orig_due ) == -1 ) {
$return_data->{exceeded} = 1;
- $return_data->{duration} = $decreaseLoanHighHoldsDuration;
+ $return_data->{duration} = $duration;
$return_data->{due_date} = $reduced_datedue;
}
}
C4::Reserves::MoveReserve( $item_object->itemnumber, $borrower->{'borrowernumber'}, $cancelreserve );
# Starting process for transfer job (checking transfert and validate it if we have one)
- my ($datesent) = GetTransfers( $item_object->itemnumber );
- if ($datesent) {
+ if ( my $transfer = $item_object->get_transfer ) {
# updating line of branchtranfert to finish it, and changing the to branch value, implement a comment for visibility of this case (maybe for stats ....)
- my $sth = $dbh->prepare(
- "UPDATE branchtransfers
- SET datearrived = now(),
- tobranch = ?,
- comments = 'Forced branchtransfer'
- WHERE itemnumber= ? AND datearrived IS NULL"
- );
- $sth->execute( C4::Context->userenv->{'branch'},
- $item_object->itemnumber );
+ $transfer->set(
+ {
+ datearrived => dt_from_string,
+ tobranch => C4::Context->userenv->{branch},
+ comments => 'Forced branchtransfer'
+ }
+ )->store;
+ if ( $transfer->reason && $transfer->reason eq 'Reserve' ) {
+ my $hold = $item_object->holds->search( { found => 'T' } )->next;
+ if ( $hold ) { # Is this really needed?
+ $hold->set( { found => undef } )->store;
+ C4::Reserves::ModReserveMinusPriority($item_object->itemnumber, $hold->reserve_id);
+ }
+ }
}
# If automatic renewal wasn't selected while issuing, set the value according to the issuing rule.
$auto_renew = $rule->rule_value if $rule;
}
- # Record in the database the fact that the book was issued.
- unless ($datedue) {
- my $itype = $item_object->effective_itemtype;
- $datedue = CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
-
- }
- $datedue->truncate( to => 'minute' );
-
my $issue_attributes = {
borrowernumber => $borrower->{'borrowernumber'},
issuedate => $issuedate->strftime('%Y-%m-%d %H:%M:%S'),
auto_renew => $auto_renew ? 1 : 0,
};
+ # Get ID of logged in user. if called from a batch job,
+ # no user session exists and C4::Context->userenv() returns
+ # the scalar '0'. Only do this if the syspref says so
+ if ( C4::Context->preference('RecordStaffUserOnCheckout') ) {
+ my $userenv = C4::Context->userenv();
+ my $usernumber = (ref($userenv) eq 'HASH') ? $userenv->{'number'} : undef;
+ if ($usernumber) {
+ $issue_attributes->{issuer_id} = $usernumber;
+ }
+ }
+
+ # In the case that the borrower has an on-site checkout
+ # and SwitchOnSiteCheckouts is enabled this converts it to a regular checkout
$issue = Koha::Checkouts->find( { itemnumber => $item_object->itemnumber } );
if ($issue) {
$issue->set($issue_attributes)->store;
}
)->store;
}
+ $issue->discard_changes;
if ( $item_object->location && $item_object->location eq 'CART'
&& ( !$item_object->permanent_location || $item_object->permanent_location ne 'CART' ) ) {
## Item was moved to cart via UpdateItemLocationOnCheckin, anything issued should be taken off the cart.
UpdateTotalIssues( $item_object->biblionumber, 1 );
}
- ## If item was lost, it has now been found, reverse any list item charges if necessary.
- if ( $item_object->itemlost ) {
- my $refund = 1;
- my $no_refund_after_days = C4::Context->preference('NoRefundOnLostReturnedItemsAge');
- if ($no_refund_after_days) {
- my $today = dt_from_string();
- my $lost_age_in_days =
- dt_from_string( $item_object->itemlost_on )
- ->delta_days($today)
- ->in_units('days');
-
- $refund = 0 unless ( $lost_age_in_days < $no_refund_after_days );
- }
-
- if (
- $refund
- && Koha::RefundLostItemFeeRules->should_refund(
- {
- current_branch => C4::Context->userenv->{branch},
- item_home_branch => $item_object->homebranch,
- item_holding_branch => $item_object->holdingbranch,
- }
- )
- )
- {
- _FixAccountForLostAndFound( $item_object->itemnumber, undef,
- $item_object->barcode );
- }
- }
+ # Record if item was lost
+ my $was_lost = $item_object->itemlost;
$item_object->issues( ( $item_object->issues || 0 ) + 1);
$item_object->holdingbranch(C4::Context->userenv->{'branch'});
$item_object->itemlost(0);
$item_object->onloan($datedue->ymd());
$item_object->datelastborrowed( dt_from_string()->ymd() );
+ $item_object->datelastseen( dt_from_string()->ymd() );
$item_object->store({log_action => 0});
- ModDateLastSeen( $item_object->itemnumber );
+
+ # If the item was lost, it has now been found, charge the overdue if necessary
+ if ($was_lost) {
+ if ( $item_object->{_charge} ) {
+ $actualissue //= Koha::Old::Checkouts->search(
+ { itemnumber => $item_unblessed->{itemnumber} },
+ {
+ order_by => { '-desc' => 'returndate' },
+ rows => 1
+ }
+ )->single;
+ unless ( exists( $borrower->{branchcode} ) ) {
+ my $patron = $actualissue->patron;
+ $borrower = $patron->unblessed;
+ }
+ _CalculateAndUpdateFine(
+ {
+ issue => $actualissue,
+ item => $item_unblessed,
+ borrower => $borrower,
+ return_date => $issuedate
+ }
+ );
+ _FixOverduesOnReturn( $borrower->{borrowernumber},
+ $item_object->itemnumber, undef, 'RENEWED' );
+ }
+ }
# If it costs to borrow this book, charge it to the patron's account.
my ( $charge, $itemtype ) = GetIssuingCharges( $item_object->itemnumber, $borrower->{'borrowernumber'} );
sub GetLoanLength {
my ( $categorycode, $itemtype, $branchcode ) = @_;
- # Set search precedences
- my @params = (
- {
- categorycode => $categorycode,
- itemtype => $itemtype,
- branchcode => $branchcode,
- },
- {
- categorycode => $categorycode,
- itemtype => undef,
- branchcode => $branchcode,
- },
- {
- categorycode => undef,
- itemtype => $itemtype,
- branchcode => $branchcode,
- },
- {
- categorycode => undef,
- itemtype => undef,
- branchcode => $branchcode,
- },
- {
- categorycode => $categorycode,
- itemtype => $itemtype,
- branchcode => undef,
- },
- {
- categorycode => $categorycode,
- itemtype => undef,
- branchcode => undef,
- },
- {
- categorycode => undef,
- itemtype => $itemtype,
- branchcode => undef,
- },
- {
- categorycode => undef,
- itemtype => undef,
- branchcode => undef,
- },
- );
-
# Initialize default values
my $rules = {
issuelength => 0,
lengthunit => 'days',
};
- # Search for rules!
- foreach my $rule_name (qw( issuelength renewalperiod lengthunit )) {
- foreach my $params (@params) {
- my $rule = Koha::CirculationRules->search(
- {
- rule_name => $rule_name,
- %$params,
- }
- )->next();
+ my $found = Koha::CirculationRules->get_effective_rules( {
+ branchcode => $branchcode,
+ categorycode => $categorycode,
+ itemtype => $itemtype,
+ rules => [
+ 'issuelength',
+ 'renewalperiod',
+ 'lengthunit'
+ ],
+ } );
- if ($rule) {
- $rules->{$rule_name} = $rule->rule_value;
- last;
- }
- }
+ # Search for rules!
+ foreach my $rule_name (keys %$found) {
+ $rules->{$rule_name} = $found->{$rule_name};
}
return $rules;
my $messages;
my $patron;
my $doreturn = 1;
- my $validTransfert = 0;
+ my $validTransfer = 1;
my $stat_type = 'return';
# get information on item
. Dumper($issue->unblessed) . "\n";
} else {
$messages->{'NotIssued'} = $barcode;
- $item->onloan(undef)->store if defined $item->onloan;
+ $item->onloan(undef)->store({skip_record_index=>1}) if defined $item->onloan;
# even though item is not on loan, it may still be transferred; therefore, get current branch info
$doreturn = 0;
if (defined $update_loc_rules->{_ALL_}) {
if ($update_loc_rules->{_ALL_} eq '_PERM_') { $update_loc_rules->{_ALL_} = $item->permanent_location; }
if ($update_loc_rules->{_ALL_} eq '_BLANK_') { $update_loc_rules->{_ALL_} = ''; }
- if ( $item->location ne $update_loc_rules->{_ALL_}) {
+ if ( defined $item->location && $item->location ne $update_loc_rules->{_ALL_}) {
$messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{_ALL_} };
- $item->location($update_loc_rules->{_ALL_})->store;
+ $item->location($update_loc_rules->{_ALL_})->store({skip_record_index=>1});
}
}
else {
if ( $update_loc_rules->{$key} eq '_BLANK_') { $update_loc_rules->{$key} = '' ;}
if ( ($item->location eq $key && $item->location ne $update_loc_rules->{$key}) || ($key eq '_BLANK_' && $item->location eq '' && $update_loc_rules->{$key} ne '') ) {
$messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{$key} };
- $item->location($update_loc_rules->{$key})->store;
+ $item->location($update_loc_rules->{$key})->store({skip_record_index=>1});
last;
}
}
foreach my $key ( keys %$rules ) {
if ( $item->notforloan eq $key ) {
$messages->{'NotForLoanStatusUpdated'} = { from => $item->notforloan, to => $rules->{$key} };
- $item->notforloan($rules->{$key})->store({ log_action => 0 });
+ $item->notforloan($rules->{$key})->store({ log_action => 0, skip_record_index => 1 });
last;
}
}
Rightbranch => $message
};
$doreturn = 0;
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" );
return ( $doreturn, $messages, $issue, $patron_unblessed);
}
if ($patron) {
eval {
- MarkIssueReturned( $borrowernumber, $item->itemnumber, $return_date, $patron->privacy );
+ MarkIssueReturned( $borrowernumber, $item->itemnumber, $return_date, $patron->privacy, { skip_record_index => 1} );
};
unless ( $@ ) {
if (
} else {
carp "The checkin for the following issue failed, Please go to the about page, section 'data corrupted' to know how to fix this problem ($@)" . Dumper( $issue->unblessed );
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" );
+
return ( 0, { WasReturned => 0, DataCorrupted => 1 }, $issue, $patron_unblessed );
}
# FIXME is the "= 1" right? This could be the borrower hash.
$messages->{'WasReturned'} = 1;
+ } else {
+ $item->onloan(undef)->store({ log_action => 0 , skip_record_index => 1 });
}
-
- $item->onloan(undef)->store({ log_action => 0 });
}
# the holdingbranch is updated if the document is returned to another location.
# this is always done regardless of whether the item was on loan or not
- my $item_holding_branch = $item->holdingbranch;
if ($item->holdingbranch ne $branch) {
- $item->holdingbranch($branch)->store;
+ $item->holdingbranch($branch)->store({ skip_record_index => 1 });
}
+ my $item_was_lost = $item->itemlost;
my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0;
- ModDateLastSeen( $item->itemnumber, $leave_item_lost );
+ my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost, { skip_record_index => 1 } ); # will unset itemlost if needed
+
+ # fix up the accounts.....
+ if ($item_was_lost) {
+ $messages->{'WasLost'} = 1;
+ unless ( C4::Context->preference("BlockReturnOfLostItems") ) {
+ $messages->{'LostItemFeeRefunded'} = $updated_item->{_refunded};
+ $messages->{'LostItemFeeRestored'} = $updated_item->{_restored};
+
+ if ( $updated_item->{_charge} ) {
+ $issue //= Koha::Old::Checkouts->search(
+ { itemnumber => $item->itemnumber },
+ { order_by => { '-desc' => 'returndate' }, rows => 1 } )
+ ->single;
+ unless ( exists( $patron_unblessed->{branchcode} ) ) {
+ my $patron = $issue->patron;
+ $patron_unblessed = $patron->unblessed;
+ }
+ _CalculateAndUpdateFine(
+ {
+ issue => $issue,
+ item => $item->unblessed,
+ borrower => $patron_unblessed,
+ return_date => $return_date
+ }
+ );
+ _FixOverduesOnReturn( $patron_unblessed->{borrowernumber},
+ $item->itemnumber, undef, 'RETURNED' );
+ $messages->{'LostItemFeeCharged'} = 1;
+ }
+ }
+ }
# check if we have a transfer for this document
my ($datesent,$frombranch,$tobranch) = GetTransfers( $item->itemnumber );
- # if we have a transfer to do, we update the line of transfers with the datearrived
+ # if we have a transfer to complete, we update the line of transfers with the datearrived
my $is_in_rotating_collection = C4::RotatingCollections::isItemInAnyCollection( $item->itemnumber );
if ($datesent) {
+ # At this point we will either fill the transfer or it is a wrong transfer
+ # either way we should not now generate a new transfer
+ $validTransfer = 0;
if ( $tobranch eq $branch ) {
my $sth = C4::Context->dbh->prepare(
"UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL"
);
$sth->execute( $item->itemnumber );
+ $messages->{'TransferArrived'} = $frombranch;
} else {
$messages->{'WrongTransfer'} = $tobranch;
$messages->{'WrongTransferItem'} = $item->itemnumber;
}
- $validTransfert = 1;
- }
-
- # fix up the accounts.....
- if ( $item->itemlost ) {
- $messages->{'WasLost'} = 1;
- unless ( C4::Context->preference("BlockReturnOfLostItems") ) {
- my $refund = 1;
- my $no_refund_after_days = C4::Context->preference('NoRefundOnLostReturnedItemsAge');
- if ($no_refund_after_days) {
- my $today = dt_from_string();
- my $lost_age_in_days =
- dt_from_string( $item->itemlost_on )
- ->delta_days($today)
- ->in_units('days');
-
- $refund = 0 unless ( $lost_age_in_days < $no_refund_after_days );
- }
-
- if (
- $refund &&
- Koha::RefundLostItemFeeRules->should_refund(
- {
- current_branch => C4::Context->userenv->{branch},
- item_home_branch => $item->homebranch,
- item_holding_branch => $item_holding_branch
- }
- )
- )
- {
- _FixAccountForLostAndFound( $item->itemnumber,
- $borrowernumber, $barcode );
- $messages->{'LostItemFeeRefunded'} = 1;
- }
- }
}
# fix up the overdues in accounts...
# Check if this item belongs to a biblio record that is attached to an
# ILL request, if it is we need to update the ILL request's status
- if (C4::Context->preference('CirculateILL')) {
+ if ( $doreturn and C4::Context->preference('CirculateILL')) {
my $request = Koha::Illrequests->find(
{ biblio_id => $item->biblio->biblionumber }
);
}
# Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer
- if (!$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) and not $messages->{'WrongTransfer'}){
+ if ($validTransfer && !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) ){
my $BranchTransferLimitsType = C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ? 'effective_itemtype' : 'ccode';
if (C4::Context->preference("AutomaticItemReturn" ) or
(C4::Context->preference("UseBranchTransferLimits") and
)) {
$debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s, %s)", $item->itemnumber,$branch, $returnbranch, $transfer_trigger;
$debug and warn "item: " . Dumper($item->unblessed);
- ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger);
+ ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger, { skip_record_index => 1 });
$messages->{'WasTransfered'} = 1;
} else {
$messages->{'NeedsTransfer'} = $returnbranch;
}
}
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" );
+
if ( $doreturn and $issue ) {
my $checkin = Koha::Old::Checkouts->find($issue->id);
=head2 MarkIssueReturned
- MarkIssueReturned($borrowernumber, $itemnumber, $returndate, $privacy);
+ MarkIssueReturned($borrowernumber, $itemnumber, $returndate, $privacy, [$params] );
Unconditionally marks an issue as being returned by
moving the C<issues> row to C<old_issues> and
not exported, but it is currently used in misc/cronjobs/longoverdue.pl
and offline_circ/process_koc.pl.
+The last optional parameter allos passing skip_record_index to the item store call.
+
=cut
sub MarkIssueReturned {
- my ( $borrowernumber, $itemnumber, $returndate, $privacy ) = @_;
+ my ( $borrowernumber, $itemnumber, $returndate, $privacy, $params ) = @_;
# Retrieve the issue
my $issue = Koha::Checkouts->find( { itemnumber => $itemnumber } ) or return;
# And finally delete the issue
$issue->delete;
- $issue->item->onloan(undef)->store({ log_action => 0 });
+ $issue->item->onloan(undef)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
if ( C4::Context->preference('StoreLastBorrower') ) {
my $item = Koha::Items->find( $itemnumber );
# grace period is measured in the same units as the loan
my $grace =
- DateTime::Duration->new( $unit => $issuing_rule->{firstremind} );
+ DateTime::Duration->new( $unit => $issuing_rule->{firstremind} // 0);
my $deltadays = DateTime::Duration->new(
days => $chargeable_units
my $amountoutstanding = $accountline->amountoutstanding;
if ( $accountline->amount == 0 && $payments->count == 0 ) {
$accountline->delete;
+ return 0; # no warning, we've just removed a zero value fine (backdated return)
} elsif ($exemptfine && ($amountoutstanding != 0)) {
my $account = Koha::Account->new({patron_id => $borrowernumber});
my $credit = $account->add_credit(
if (C4::Context->preference("FinesLog")) {
&logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item");
}
-
- $accountline->status('FORGIVEN');
- $accountline->store();
- } else {
- $accountline->status($status);
- $accountline->store();
-
}
- }
- );
- return $result;
-}
-
-=head2 _FixAccountForLostAndFound
-
- &_FixAccountForLostAndFound($itemnumber, [$borrowernumber, $barcode]);
-
-Finds the most recent lost item charge for this item and refunds the borrower
-appropriatly, taking into account any payments or writeoffs already applied
-against the charge.
-
-Internal function, not exported, called only by AddReturn.
-
-=cut
-
-sub _FixAccountForLostAndFound {
- my $itemnumber = shift or return;
- my $borrowernumber = @_ ? shift : undef;
- my $item_id = @_ ? shift : $itemnumber; # Send the barcode if you want that logged in the description
-
- my $credit;
-
- # check for charge made for lost book
- my $accountlines = Koha::Account::Lines->search(
- {
- itemnumber => $itemnumber,
- debit_type_code => 'LOST',
- status => [ undef, { '<>' => 'FOUND' } ]
- },
- {
- order_by => { -desc => [ 'date', 'accountlines_id' ] }
+ $accountline->status($status);
+ return $accountline->store();
}
);
- return unless $accountlines->count > 0;
- my $accountline = $accountlines->next;
- my $total_to_refund = 0;
-
- return unless $accountline->borrowernumber;
- my $patron = Koha::Patrons->find( $accountline->borrowernumber );
- return unless $patron; # Patron has been deleted, nobody to credit the return to
-
- my $account = $patron->account;
-
- # Use cases
- if ( $accountline->amount > $accountline->amountoutstanding ) {
- # some amount has been cancelled. collect the offsets that are not writeoffs
- # this works because the only way to subtract from this kind of a debt is
- # using the UI buttons 'Pay' and 'Write off'
- my $credits_offsets = Koha::Account::Offsets->search({
- debit_id => $accountline->id,
- credit_id => { '!=' => undef }, # it is not the debit itself
- type => { '!=' => 'Writeoff' },
- amount => { '<' => 0 } # credits are negative on the DB
- });
-
- $total_to_refund = ( $credits_offsets->count > 0 )
- ? $credits_offsets->total * -1 # credits are negative on the DB
- : 0;
- }
-
- my $credit_total = $accountline->amountoutstanding + $total_to_refund;
-
- if ( $credit_total > 0 ) {
- my $branchcode = C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
- $credit = $account->add_credit(
- {
- amount => $credit_total,
- description => 'Item found ' . $item_id,
- type => 'LOST_FOUND',
- interface => C4::Context->interface,
- library_id => $branchcode,
- item_id => $itemnumber
- }
- );
-
- $credit->apply( { debits => [ $accountline ] } );
- }
-
- # Update the account status
- $accountline->discard_changes->status('FOUND');
- $accountline->store;
-
- $accountline->item->paidfor('')->store({ log_action => 0 });
-
- if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) {
- $account->reconcile_balance;
- }
-
- return ($credit) ? $credit->id : undef;
+ return $result;
}
=head2 _GetCircControlBranch
=cut
sub CanBookBeRenewed {
- my ( $borrowernumber, $itemnumber, $override_limit ) = @_;
+ my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_;
my $dbh = C4::Context->dbh;
my $renews = 1;
- my $auto_renew = 0;
+ my $auto_renew = "no";
my $item = Koha::Items->find($itemnumber) or return ( 0, 'no_item' );
my $issue = $item->checkout or return ( 0, 'no_checkout' );
'no_auto_renewal_after_hard_limit',
'lengthunit',
'norenewalbefore',
+ 'unseen_renewals_allowed'
]
}
);
if ( $soonestrenewal > dt_from_string() )
{
- return ( 0, "auto_too_soon" ) if $issue->auto_renew && $patron->autorenew_checkouts;
- return ( 0, "too_soon" );
+ $auto_renew = ($issue->auto_renew && $patron->autorenew_checkouts) ? "auto_too_soon" : "too_soon";
}
elsif ( $issue->auto_renew && $patron->autorenew_checkouts ) {
- $auto_renew = 1;
+ $auto_renew = "ok";
}
}
# Fallback for automatic renewals:
# If norenewalbefore is undef, don't renew before due date.
- if ( $issue->auto_renew && !$auto_renew && $patron->autorenew_checkouts ) {
+ if ( $issue->auto_renew && $auto_renew eq "no" && $patron->autorenew_checkouts ) {
my $now = dt_from_string;
if ( $now >= dt_from_string( $issue->date_due, 'sql' ) ){
- $auto_renew = 1;
+ $auto_renew = "ok";
} else {
- return ( 0, "auto_too_soon" );
+ $auto_renew = "auto_too_soon";
}
}
}
my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber);
+ # If next hold is non priority, then check if any hold with priority (non_priority = 0) exists for the same biblionumber.
+ if ( $resfound && $resrec->{non_priority} ) {
+ $resfound = Koha::Holds->search(
+ { biblionumber => $resrec->{biblionumber}, non_priority => 0 } )
+ ->count > 0;
+ }
+
+
+
# This item can fill one or more unfilled reserve, can those unfilled reserves
# all be filled by other available items?
if ( $resfound
}
}
}
- return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found
- return ( 0, "auto_renew" ) if $auto_renew && !$override_limit; # 0 if auto-renewal should not succeed
+ if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve'
+ return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
+ return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found
+ } else { # For other purposes we want 'on_reserve' before 'too_soon'
+ return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found
+ return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
+ }
+
+ return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed
+
+ return ( 1, undef ) if $override_limit;
+
+ my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed );
+ my $issuing_rule = Koha::CirculationRules->get_effective_rules(
+ {
+ categorycode => $patron->categorycode,
+ itemtype => $item->effective_itemtype,
+ branchcode => $branchcode,
+ rules => [
+ 'renewalsallowed',
+ 'no_auto_renewal_after',
+ 'no_auto_renewal_after_hard_limit',
+ 'lengthunit',
+ 'norenewalbefore',
+ 'unseen_renewals_allowed'
+ ]
+ }
+ );
+
+ return ( 0, "too_many" )
+ if not $issuing_rule->{renewalsallowed} or $issuing_rule->{renewalsallowed} <= $issue->renewals;
+
+ return ( 0, "too_unseen" )
+ if C4::Context->preference('UnseenRenewals') &&
+ $issuing_rule->{unseen_renewals_allowed} &&
+ $issuing_rule->{unseen_renewals_allowed} <= $issue->unseen_renewals;
+
+ my $overduesblockrenewing = C4::Context->preference('OverduesBlockRenewing');
+ my $restrictionblockrenewing = C4::Context->preference('RestrictionBlockRenewing');
+ $patron = Koha::Patrons->find($borrowernumber); # FIXME Is this really useful?
+ my $restricted = $patron->is_debarred;
+ my $hasoverdues = $patron->has_overdues;
+
+ if ( $restricted and $restrictionblockrenewing ) {
+ return ( 0, 'restriction');
+ } elsif ( ($hasoverdues and $overduesblockrenewing eq 'block') || ($issue->is_overdue and $overduesblockrenewing eq 'blockitem') ) {
+ return ( 0, 'overdue');
+ }
+
+ if ( $issue->auto_renew ) {
+
+ if ( $patron->category->effective_BlockExpiredPatronOpacActions and $patron->is_expired ) {
+ return ( 0, 'auto_account_expired' );
+ }
+
+ if ( defined $issuing_rule->{no_auto_renewal_after}
+ and $issuing_rule->{no_auto_renewal_after} ne "" ) {
+ # Get issue_date and add no_auto_renewal_after
+ # If this is greater than today, it's too late for renewal.
+ my $maximum_renewal_date = dt_from_string($issue->issuedate, 'sql');
+ $maximum_renewal_date->add(
+ $issuing_rule->{lengthunit} => $issuing_rule->{no_auto_renewal_after}
+ );
+ my $now = dt_from_string;
+ if ( $now >= $maximum_renewal_date ) {
+ return ( 0, "auto_too_late" );
+ }
+ }
+ if ( defined $issuing_rule->{no_auto_renewal_after_hard_limit}
+ and $issuing_rule->{no_auto_renewal_after_hard_limit} ne "" ) {
+ # If no_auto_renewal_after_hard_limit is >= today, it's also too late for renewal
+ if ( dt_from_string >= dt_from_string( $issuing_rule->{no_auto_renewal_after_hard_limit} ) ) {
+ return ( 0, "auto_too_late" );
+ }
+ }
+
+ if ( C4::Context->preference('OPACFineNoRenewalsBlockAutoRenew') ) {
+ my $fine_no_renewals = C4::Context->preference("OPACFineNoRenewals");
+ my $amountoutstanding =
+ C4::Context->preference("OPACFineNoRenewalsIncludeCredit")
+ ? $patron->account->balance
+ : $patron->account->outstanding_debits->total_outstanding;
+ if ( $amountoutstanding and $amountoutstanding > $fine_no_renewals ) {
+ return ( 0, "auto_too_much_oweing" );
+ }
+ }
+ }
+
+ if ( defined $issuing_rule->{norenewalbefore}
+ and $issuing_rule->{norenewalbefore} ne "" )
+ {
+
+ # Calculate soonest renewal by subtracting 'No renewal before' from due date
+ my $soonestrenewal = dt_from_string( $issue->date_due, 'sql' )->subtract(
+ $issuing_rule->{lengthunit} => $issuing_rule->{norenewalbefore} );
+
+ # Depending on syspref reset the exact time, only check the date
+ if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date'
+ and $issuing_rule->{lengthunit} eq 'days' )
+ {
+ $soonestrenewal->truncate( to => 'day' );
+ }
+
+ if ( $soonestrenewal > DateTime->now( time_zone => C4::Context->tz() ) )
+ {
+ return ( 0, "auto_too_soon" ) if $issue->auto_renew;
+ return ( 0, "too_soon" );
+ }
+ elsif ( $issue->auto_renew ) {
+ return ( 0, "auto_renew" );
+ }
+ }
+
+ # Fallback for automatic renewals:
+ # If norenewalbefore is undef, don't renew before due date.
+ if ( $issue->auto_renew ) {
+ my $now = dt_from_string;
+ return ( 0, "auto_renew" )
+ if $now >= dt_from_string( $issue->date_due, 'sql' );
+ return ( 0, "auto_too_soon" );
+ }
return ( 1, undef );
}
=head2 AddRenewal
- &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate]);
+ &AddRenewal($borrowernumber, $itemnumber, $branch, [$datedue], [$lastreneweddate], [$seen]);
Renews a loan.
If C<$datedue> is the empty string, C<&AddRenewal> will calculate the due date automatically
from the book's item type.
+C<$seen> is a boolean flag indicating if the item was seen or not during the renewal. This
+informs the incrementing of the unseen_renewals column. If this flag is not supplied, we
+fallback to a true value
+
=cut
sub AddRenewal {
my $datedue = shift;
my $lastreneweddate = shift || dt_from_string();
my $skipfinecalc = shift;
+ my $seen = shift;
+
+ # Fallback on a 'seen' renewal
+ $seen = defined $seen && $seen == 0 ? 0 : 1;
my $item_object = Koha::Items->find($itemnumber) or return;
my $biblio = $item_object->biblio;
}
);
+ # Increment the unseen renewals, if appropriate
+ # We only do so if the syspref is enabled and
+ # a maximum value has been set in the circ rules
+ my $unseen_renewals = $issue->unseen_renewals;
+ if (C4::Context->preference('UnseenRenewals')) {
+ my $rule = Koha::CirculationRules->get_effective_rule(
+ { categorycode => $patron->categorycode,
+ itemtype => $item_object->effective_itemtype,
+ branchcode => $circ_library->branchcode,
+ rule_name => 'unseen_renewals_allowed'
+ }
+ );
+ if (!$seen && $rule && $rule->rule_value) {
+ $unseen_renewals++;
+ } else {
+ # If the renewal is seen, unseen should revert to 0
+ $unseen_renewals = 0;
+ }
+ }
+
# Update the issues record to have the new due date, and a new count
# of how many times it has been renewed.
my $renews = ( $issue->renewals || 0 ) + 1;
- my $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, lastreneweddate = ?
+ my $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals = ?, unseen_renewals = ?, lastreneweddate = ?
WHERE borrowernumber=?
AND itemnumber=?"
);
- $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $lastreneweddate, $borrowernumber, $itemnumber );
+ $sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $unseen_renewals, $lastreneweddate, $borrowernumber, $itemnumber );
# Update the renewal count on the item, and tell zebra to reindex
$renews = ( $item_object->renewals || 0 ) + 1;
my ( $bornum, $itemno ) = @_;
my $dbh = C4::Context->dbh;
my $renewcount = 0;
+ my $unseencount = 0;
my $renewsallowed = 0;
+ my $unseenallowed = 0;
my $renewsleft = 0;
+ my $unseenleft = 0;
my $patron = Koha::Patrons->find( $bornum );
my $item = Koha::Items->find($itemno);
$sth->execute( $bornum, $itemno );
my $data = $sth->fetchrow_hashref;
$renewcount = $data->{'renewals'} if $data->{'renewals'};
+ $unseencount = $data->{'unseen_renewals'} if $data->{'unseen_renewals'};
# $item and $borrower should be calculated
my $branchcode = _GetCircControlBranch($item->unblessed, $patron->unblessed);
- my $rule = Koha::CirculationRules->get_effective_rule(
+ my $rules = Koha::CirculationRules->get_effective_rules(
{
categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
branchcode => $branchcode,
- rule_name => 'renewalsallowed',
+ rules => [ 'renewalsallowed', 'unseen_renewals_allowed' ]
}
);
-
- $renewsallowed = $rule ? $rule->rule_value : 0;
+ $renewsallowed = $rules ? $rules->{renewalsallowed} : 0;
+ $unseenallowed = $rules->{unseen_renewals_allowed} ?
+ $rules->{unseen_renewals_allowed} :
+ 0;
$renewsleft = $renewsallowed - $renewcount;
+ $unseenleft = $unseenallowed - $unseencount;
if($renewsleft < 0){ $renewsleft = 0; }
- return ( $renewcount, $renewsallowed, $renewsleft );
+ if($unseenleft < 0){ $unseenleft = 0; }
+ return (
+ $renewcount,
+ $renewsallowed,
+ $renewsleft,
+ $unseencount,
+ $unseenallowed,
+ $unseenleft
+ );
}
=head2 GetSoonestRenewDate
MarkIssueReturned( $borrowernumber, $itemnum );
}
+=head2 LostItem
+
+ LostItem( $itemnumber, $mark_lost_from, $force_mark_returned, [$params] );
+
+The final optional parameter, C<$params>, expected to contain
+'skip_record_index' key, which relayed down to Koha::Item/store,
+there it prevents calling of ModZebra index_records,
+which takes most of the time in batch adds/deletes: index_records better
+to be called later in C<additem.pl> after the whole loop.
+
+$params:
+ skip_record_index => 1|0
+
+=cut
sub LostItem{
- my ($itemnumber, $mark_lost_from, $force_mark_returned) = @_;
+ my ($itemnumber, $mark_lost_from, $force_mark_returned, $params) = @_;
unless ( $mark_lost_from ) {
# Temporary check to avoid regressions
#When item is marked lost automatically cancel its outstanding transfers and set items holdingbranch to the transfer source branch (frombranch)
if (my ( $datesent,$frombranch,$tobranch ) = GetTransfers($itemnumber)) {
- Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store;
+ Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ skip_record_index => $params->{skip_record_index} });
}
my $transferdeleted = DeleteTransfer($itemnumber);
}