use YAML::XS;
use Encode;
-use Koha::DateUtils qw( dt_from_string output_pref );
use C4::Context;
use C4::Stats qw( UpdateStats );
-use C4::Reserves qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority RevertWaitingStatus IsItemOnHoldAndFound IsAvailableForItemLevelRequest );
+use C4::Reserves qw( CheckReserves CanItemBeReserved MoveReserve ModReserve ModReserveMinusPriority RevertWaitingStatus IsItemOnHoldAndFound IsAvailableForItemLevelRequest ItemsAnyAvailableAndNotRestricted );
use C4::Biblio qw( UpdateTotalIssues );
use C4::Items qw( ModItemTransfer ModDateLastSeen CartToShelf );
use C4::Accounts;
use Data::Dumper qw( Dumper );
use Koha::Account;
use Koha::AuthorisedValues;
+use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
use Koha::Biblioitems;
-use Koha::DateUtils qw( dt_from_string output_pref );
+use Koha::DateUtils qw( dt_from_string );
use Koha::Calendar;
use Koha::Checkouts;
use Koha::Illrequests;
use Koha::SearchEngine::Indexer;
use Koha::Exceptions::Checkout;
use Koha::Plugins;
+use Koha::Recalls;
use Carp qw( carp );
use List::MoreUtils qw( any );
use Scalar::Util qw( looks_like_number );
GetBranchBorrowerCircRule
GetBranchItemRule
GetBiblioIssues
- GetOpenIssue
GetUpcomingDueIssues
CheckIfIssuedToPatron
IsItemIssued
transferbook
TooMany
- GetTransfers
GetTransfersFromTo
updateWrongTransfer
CalcDateDue
DeleteOfflineOperation
ProcessOfflineOperation
ProcessOfflinePayment
+ ProcessOfflineIssue
);
push @EXPORT_OK, '_GetCircControlBranch'; # This is wrong!
}
to circulation.pl that differs from the barcode stored for the item.
For proper functioning of this filter, calling the function on the
correct barcode string (items.barcode) should return an unaltered barcode.
+Barcode is going to be automatically trimmed of leading/trailing whitespaces.
The optional $filter argument is to allow for testing or explicit
behavior that ignores the System Pref. Valid values are the same as the
#
sub barcodedecode {
my ($barcode, $filter) = @_;
+
+ return unless defined $barcode;
+
my $branch = C4::Context::mybranch();
+ $barcode =~ s/^\s+|\s+$//g;
$filter = C4::Context->preference('itemBarcodeInputFilter') unless $filter;
Koha::Plugins->call('item_barcode_transform', \$barcode );
$filter or return $barcode; # ensure filter is defined, else return untouched barcode
}
# find recall
- my $recall = Koha::Recalls->find({ itemnumber => $itemnumber, status => 'in_transit' });
- if ( defined $recall and C4::Context->preference('UseRecalls') ) {
- # do a transfer if the recall branch is different to the item holding branch
- if ( $recall->branchcode eq $fbr ) {
- $dotransfer = 0;
- $messages->{'RecallPlacedAtHoldingBranch'} = 1;
- } else {
- $dotransfer = 1;
- $messages->{'RecallFound'} = $recall;
+ if ( C4::Context->preference('UseRecalls') ) {
+ my $recall = Koha::Recalls->find({ item_id => $itemnumber, status => 'in_transit' });
+ if ( defined $recall ) {
+ # do a transfer if the recall branch is different to the item holding branch
+ if ( $recall->pickup_library_id eq $fbr ) {
+ $dotransfer = 0;
+ $messages->{'RecallPlacedAtHoldingBranch'} = 1;
+ } else {
+ $dotransfer = 1;
+ $messages->{'RecallFound'} = $recall;
+ }
}
}
$checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron
} else {
$checkouts = $patron->checkouts->search(
- { 'item.homebranch' => $maxissueqty_rule->branchcode },
- { prefetch => 'item' } );
+ { 'item.homebranch' => $maxissueqty_rule->branchcode } );
}
} else {
$checkouts = $patron->checkouts; # if rule is not branch specific then count all loans by patron
}
+ $checkouts = $checkouts->search(undef, { prefetch => 'item' });
+
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');
- next if grep {$_ eq $itemtype} @types;
- } else {
- my @types;
- if ( $parent_maxissueqty_rule ) {
+ 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');
+ } else {
+ 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 ) {
+ 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
+ @types = $child_types->get_column('itemtype');
+ push @types, $type; # And don't forget to count our own types
+ } else {
+ # Otherwise only count the specific itemtype
+ push @types, $type;
+ }
+ }
+ while ( my $c = $checkouts->next ) {
+ my $itemtype = $c->item->effective_itemtype;
+
+ unless ( $rule_itemtype ) {
+ next if grep {$_ eq $itemtype} @types;
+ } else {
next unless grep {$_ eq $itemtype} @types;
}
+
$sum_checkouts->{total}++;
$sum_checkouts->{onsite_checkouts}++ if $c->onsite_checkout;
$sum_checkouts->{itemtype}->{$itemtype}++;
my $patron_unblessed = $patron->unblessed;
my $circ_library = Koha::Libraries->find( _GetCircControlBranch($item_unblessed, $patron_unblessed) );
- #
- # DUE DATE is OK ? -- should already have checked.
- #
- if ($duedate && ref $duedate ne 'DateTime') {
- $duedate = dt_from_string($duedate);
- }
- my $now = dt_from_string();
- unless ( $duedate ) {
- my $issuedate = $now->clone();
- $duedate = CalcDateDue( $issuedate, $effective_itemtype, $circ_library->branchcode, $patron_unblessed );
-
- # Offline circ calls AddIssue directly, doesn't run through here
- # So issuingimpossible should be ok.
+ my $now = dt_from_string();
+ $duedate ||= CalcDateDue( $now, $effective_itemtype, $circ_library->branchcode, $patron_unblessed );
+ if (DateTime->compare($duedate,$now) == -1 ) { # duedate cannot be before now
+ $needsconfirmation{INVALID_DATE} = $duedate;
}
my $fees = Koha::Charges::Fees->new(
}
);
- if ($duedate) {
- my $today = $now->clone();
- $today->truncate( to => 'minute');
- if (DateTime->compare($duedate,$today) == -1 ) { # duedate cannot be before now
- $needsconfirmation{INVALID_DATE} = output_pref($duedate);
- }
- } else {
- $issuingimpossible{INVALID_DATE} = output_pref($duedate);
- }
-
#
# BORROWER STATUS
#
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 });
+ 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;
# Only bother doing this if UseRecalls is enabled and the item is recallable
# Don't look at recalls that are in transit
if ( C4::Context->preference('UseRecalls') and $item_object->can_be_waiting_recall ) {
- my @recalls = $biblio->recalls->as_list;
+ my @recalls = $biblio->recalls({},{ order_by => { -asc => 'created_date' } })->filter_by_current->as_list;
foreach my $r ( @recalls ) {
- if ( $r->itemnumber and
- $r->itemnumber == $item_object->itemnumber and
- $r->borrowernumber == $patron->borrowernumber and
+ if ( $r->item_id and
+ $r->item_id == $item_object->itemnumber and
+ $r->patron_id == $patron->borrowernumber and
( $r->waiting or $r->requested ) ) {
- $messages{RECALLED} = $r->recall_id;
+ $messages{RECALLED} = $r->id;
$recall = $r;
# this item is recalled by or already waiting for this borrower and the recall can be fulfilled
last;
}
- elsif ( $r->itemnumber and
- $r->itemnumber == $item_object->itemnumber and
+ elsif ( $r->item_id and
+ $r->item_id == $item_object->itemnumber and
$r->in_transit ) {
# recalled item is in transit
- $issuingimpossible{RECALLED_INTRANSIT} = $r->branchcode;
+ $issuingimpossible{RECALLED_INTRANSIT} = $r->pickup_library_id;
}
- elsif ( $r->item_level_recall and
- $r->itemnumber == $item_object->itemnumber and
- $r->borrowernumber != $patron->borrowernumber and
+ elsif ( $r->item_level and
+ $r->item_id == $item_object->itemnumber and
+ $r->patron_id != $patron->borrowernumber and
!$r->in_transit ) {
# this specific item has been recalled by a different patron
$needsconfirmation{RECALLED} = $r;
$recall = $r;
last;
}
- elsif ( !$r->item_level_recall and
- $r->borrowernumber != $patron->borrowernumber and
+ elsif ( !$r->item_level and
+ $r->patron_id != $patron->borrowernumber and
!$r->in_transit ) {
# a different patron has placed a biblio-level recall and this item is eligible to fill it
$needsconfirmation{RECALLED} = $r;
my $check = checkHighHolds( $item_object, $patron );
if ( $check->{exceeded} ) {
+ my $highholds = {
+ num_holds => $check->{outstanding},
+ duration => $check->{duration},
+ returndate => $check->{due_date},
+ };
if ($override_high_holds) {
- $alerts{HIGHHOLDS} = {
- num_holds => $check->{outstanding},
- duration => $check->{duration},
- returndate => output_pref( { dt => dt_from_string($check->{due_date}), dateformat => 'iso', timeformat => '24hr' }),
- };
+ $alerts{HIGHHOLDS} = $highholds;
}
else {
- $needsconfirmation{HIGHHOLDS} = {
- num_holds => $check->{outstanding},
- duration => $check->{duration},
- returndate => output_pref( { dt => dt_from_string($check->{due_date}), dateformat => 'iso', timeformat => '24hr' }),
- };
+ $needsconfirmation{HIGHHOLDS} = $highholds;
}
}
}
my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron->unblessed );
- my $rule = Koha::CirculationRules->get_effective_rule(
+ my $rule = Koha::CirculationRules->get_effective_rule_value(
{
categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
);
my $duration;
- if ( defined($rule) && $rule->rule_value ne '' ){
+ if ( defined($rule) && $rule ne '' ){
# overrides decreaseLoanHighHoldsDuration syspref
- $duration = $rule->rule_value;
+ $duration = $rule;
} else {
$duration = C4::Context->preference('decreaseLoanHighHoldsDuration');
}
=item C<$cancelreserve> is 1 to override and cancel any pending reserves for the item (optional).
-=item C<$issuedate> is the date to issue the item in iso (YYYY-MM-DD) format (optional).
-Defaults to today. Unlike C<$datedue>, NOT a DateTime object, unfortunately.
+=item C<$issuedate> is a DateTime object for the date to issue the item (optional).
+Defaults to today.
AddIssue does the following things :
$datedue = CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
}
+
+ # Check if we need to use an exact due date set by the ILL module
+ if ( C4::Context->preference('ILLModule') ) {
+ # Check if there is an ILL connected with the biblio of the item we are issuing
+ my $ill_request = Koha::Illrequests->search({
+ biblio_id => $item_object->biblionumber,
+ borrowernumber => $borrower->{'borrowernumber'},
+ completed => undef,
+ due_date => { '!=', undef },
+ })->next;
+
+ if ( $ill_request and length( $ill_request->due_date ) > 0 ) {
+ my $ill_dt = dt_from_string( $ill_request->due_date );
+ $ill_dt->set_hour(23);
+ $ill_dt->set_minute(59);
+ $datedue = $ill_dt;
+ }
+ }
+
$datedue->truncate( to => 'minute' );
my $patron = Koha::Patrons->find( $borrower );
$item_object->discard_changes;
}
- Koha::Recalls->move_recall({ action => $cancel_recall, recall_id => $recall_id, item => $item_object, borrowernumber => $borrower->{borrowernumber} }) if C4::Context->preference('UseRecalls');
+ if ( C4::Context->preference('UseRecalls') ) {
+ Koha::Recalls->move_recall(
+ {
+ action => $cancel_recall,
+ recall_id => $recall_id,
+ item => $item_object,
+ borrowernumber => $borrower->{borrowernumber},
+ }
+ );
+ }
C4::Reserves::MoveReserve( $item_object->itemnumber, $borrower->{'borrowernumber'}, $cancelreserve );
# If automatic renewal wasn't selected while issuing, set the value according to the issuing rule.
unless ($auto_renew) {
- my $rule = Koha::CirculationRules->get_effective_rule(
+ my $rule = Koha::CirculationRules->get_effective_rule_value(
{
categorycode => $borrower->{categorycode},
itemtype => $item_object->effective_itemtype,
}
);
- $auto_renew = $rule->rule_value if $rule;
+ $auto_renew = $rule if defined $rule && $rule ne '';
}
my $issue_attributes = {
borrowernumber => $borrower->{'borrowernumber'},
- issuedate => $issuedate->strftime('%Y-%m-%d %H:%M:%S'),
- date_due => $datedue->strftime('%Y-%m-%d %H:%M:%S'),
+ issuedate => $issuedate,
+ date_due => $datedue,
branchcode => C4::Context->userenv->{'branch'},
onsite_checkout => $onsite_checkout,
auto_renew => $auto_renew ? 1 : 0,
}
if ( C4::Context->preference('UpdateTotalIssuesOnCirc') ) {
- UpdateTotalIssues( $item_object->biblionumber, 1 );
+ UpdateTotalIssues( $item_object->biblionumber, 1, undef, { skip_holds_queue => 1 } );
}
# Record if item was lost
$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});
+ $item_object->store( { log_action => 0, skip_holds_queue => 1 } );
# If the item was lost, it has now been found, charge the overdue if necessary
if ($was_lost) {
checkout => $issue->get_from_storage
}
});
+
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $item_object->biblionumber ]
+ }
+ ) if C4::Context->preference('RealTimeHoldsQueue');
}
}
return $issue;
. Dumper($issue->unblessed) . "\n";
} else {
$messages->{'NotIssued'} = $barcode;
- $item->onloan(undef)->store({skip_record_index=>1}) if defined $item->onloan;
+ $item->onloan(undef)->store( { skip_record_index => 1, skip_holds_queue => 1 } ) if defined $item->onloan;
# even though item is not on loan, it may still be transferred; therefore, get current branch info
$doreturn = 0;
(!defined $item->location && $update_loc_rules->{_ALL_} ne "")
) {
$messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{_ALL_} };
- $item->location($update_loc_rules->{_ALL_})->store({skip_record_index=>1});
+ $item->location($update_loc_rules->{_ALL_})->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 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({skip_record_index=>1});
+ $item->location($update_loc_rules->{$key})->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 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, skip_record_index => 1 });
+ $item->notforloan($rules->{$key})->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 1 });
last;
}
}
if ($patron) {
eval {
- MarkIssueReturned( $borrowernumber, $item->itemnumber, $return_date, $patron->privacy, { skip_record_index => 1} );
+ MarkIssueReturned( $borrowernumber, $item->itemnumber, $return_date, $patron->privacy, { skip_record_index => 1, skip_holds_queue => 1} );
};
unless ( $@ ) {
if (
$messages->{'WasReturned'} = 1;
} else {
- $item->onloan(undef)->store({ log_action => 0 , skip_record_index => 1 });
+ $item->onloan(undef)->store({ log_action => 0 , skip_record_index => 1, skip_holds_queue => 1 });
}
}
# 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
if ($item->holdingbranch ne $branch) {
- $item->holdingbranch($branch)->store({ skip_record_index => 1 });
+ $item->holdingbranch($branch)->store({ log_action => 0, skip_record_index => 1, skip_holds_queue => 1 });
}
my $item_was_lost = $item->itemlost;
my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0;
- my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost, { skip_record_index => 1 } ); # will unset itemlost if needed
+ my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost, { skip_record_index => 1, skip_holds_queue => 1 } ); # will unset itemlost if needed
# fix up the accounts.....
if ($item_was_lost) {
}
# find recalls...
- # check if this item is recallable first, which includes checking if UseRecalls syspref is enabled
- my $recall = undef;
- $recall = $item->check_recalls if $item->can_be_waiting_recall;
- if ( defined $recall ) {
- $messages->{RecallFound} = $recall;
- if ( $recall->branchcode ne $branch ) {
- $messages->{RecallNeedsTransfer} = $branch;
+ if ( C4::Context->preference('UseRecalls') ) {
+ # check if this item is recallable first, which includes checking if UseRecalls syspref is enabled
+ my $recall = undef;
+ $recall = $item->check_recalls if $item->can_be_waiting_recall;
+ if ( defined $recall ) {
+ $messages->{RecallFound} = $recall;
+ if ( $recall->pickup_library_id ne $branch ) {
+ $messages->{RecallNeedsTransfer} = $branch;
+ }
}
}
$request->status('RET') if $request;
}
- my $transfer_recall = Koha::Recalls->find({ itemnumber => $item->itemnumber, status => 'in_transit' }); # all recalls that have triggered a transfer will have an allocated itemnumber
- if ( $transfer_recall and
- $transfer_recall->branchcode eq $branch and
- C4::Context->preference('UseRecalls') ) {
- $messages->{TransferredRecall} = $transfer_recall;
+ if ( C4::Context->preference('UseRecalls') ) {
+ # all recalls that have triggered a transfer will have an allocated itemnumber
+ my $transfer_recall = Koha::Recalls->find({ item_id => $item->itemnumber, status => 'in_transit' });
+ if ( $transfer_recall and $transfer_recall->pickup_library_id eq $branch ) {
+ $messages->{TransferredRecall} = $transfer_recall;
+ }
}
# Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer
}
}
+ # Check for bundle status
+ if ( $item->in_bundle ) {
+ my $host = $item->bundle_host;
+ $messages->{InBundle} = $host;
+ }
+
my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
$indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" );
checkout=> $checkin
}
});
+
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $item->biblionumber ]
+ }
+ ) if C4::Context->preference('RealTimeHoldsQueue');
}
return ( $doreturn, $messages, $issue, ( $patron ? $patron->unblessed : {} ));
# And finally delete the issue
$issue->delete;
- $issue->item->onloan(undef)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
+ $issue->item->onloan(undef)->store(
+ { log_action => 0,
+ skip_record_index => $params->{skip_record_index},
+ skip_holds_queue => $params->{skip_holds_queue}
+ }
+ );
if ( C4::Context->preference('StoreLastBorrower') ) {
my $item = Koha::Items->find( $itemnumber );
return $branch;
}
-=head2 GetOpenIssue
-
- $issue = GetOpenIssue( $itemnumber );
-
-Returns the row from the issues table if the item is currently issued, undef if the item is not currently issued
-
-C<$itemnumber> is the item's itemnumber
-
-Returns a hashref
-
-=cut
-
-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 );
- return $sth->fetchrow_hashref();
-
-}
-
=head2 GetUpcomingDueIssues
my $upcoming_dues = GetUpcomingDueIssues( { days_in_advance => 4 } );
=head2 CanBookBeRenewed
- ($ok,$error) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
+ ($ok,$error,$info) = &CanBookBeRenewed($borrowernumber, $itemnumber[, $override_limit]);
Find out whether a borrowed item may be renewed.
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
+already renewed the loan.
+ $error will contain the reason the renewal can not proceed
+ $info will contain a hash of additional info
+ currently 'soonest_renew_date' if error is 'too soon'
=cut
my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_;
my $auto_renew = "no";
+ my $soonest;
my $item = Koha::Items->find($itemnumber) or return ( 0, 'no_item' );
my $issue = $item->checkout or return ( 0, 'no_checkout' );
);
return ( 0, "too_many" )
- if not $issuing_rule->{renewalsallowed} or $issuing_rule->{renewalsallowed} <= $issue->renewals;
+ if not $issuing_rule->{renewalsallowed} or $issuing_rule->{renewalsallowed} <= $issue->renewals_count;
return ( 0, "too_unseen" )
if C4::Context->preference('UnseenRenewals') &&
- $issuing_rule->{unseen_renewals_allowed} &&
+ looks_like_number($issuing_rule->{unseen_renewals_allowed}) &&
$issuing_rule->{unseen_renewals_allowed} <= $issue->unseen_renewals;
my $overduesblockrenewing = C4::Context->preference('OverduesBlockRenewing');
return ( 0, 'overdue');
}
- $auto_renew = _CanBookBeAutoRenewed({
+ ( $auto_renew, $soonest ) = _CanBookBeAutoRenewed({
patron => $patron,
item => $item,
branchcode => $branchcode,
issue => $issue
});
- return ( 0, $auto_renew ) if $auto_renew =~ 'auto_too_soon' && $cron;
+ return ( 0, $auto_renew, { soonest_renew_date => $soonest } ) if $auto_renew =~ 'auto_too_soon' && $cron;
# cron wants 'too_soon' over 'on_reserve' for performance and to avoid
# extra notices being sent. Cron also implies no override
return ( 0, $auto_renew ) if $auto_renew =~ 'auto_account_expired';
return ( 0, $auto_renew ) if $auto_renew =~ 'auto_too_much_oweing';
}
- my $recall = undef;
- $recall = $item->check_recalls if $item->can_be_waiting_recall;
- if ( defined $recall ) {
- if ( $recall->item_level_recall ) {
- # item-level recall. check if this item is the recalled item, otherwise renewal will be allowed
- return ( 0, 'recalled' ) if ( $recall->itemnumber == $item->itemnumber );
- } else {
- # biblio-level recall, so only disallow renewal if the biblio-level recall has been fulfilled by a different item
- return ( 0, 'recalled' ) unless ( $recall->waiting );
+ if ( C4::Context->preference('UseRecalls') ) {
+ my $recall = undef;
+ $recall = $item->check_recalls if $item->can_be_waiting_recall;
+ if ( defined $recall ) {
+ if ( $recall->item_level ) {
+ # item-level recall. check if this item is the recalled item, otherwise renewal will be allowed
+ return ( 0, 'recalled' ) if ( $recall->item_id == $item->itemnumber );
+ } else {
+ # biblio-level recall, so only disallow renewal if the biblio-level recall has been fulfilled by a different item
+ return ( 0, 'recalled' ) unless ( $recall->waiting );
+ }
}
}
- my ( $resfound, $resrec, $possible_reserves ) = 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
- && C4::Context->preference('AllowRenewalIfOtherItemsAvailable') )
- {
- my $item_holds = Koha::Holds->search( { itemnumber => $itemnumber, found => undef } )->count();
- if ($item_holds) {
- # There is an item level hold on this item, no other item can fill the hold
- $resfound = 1;
- }
- else {
+ my $fillable_holds = Koha::Holds->search(
+ {
+ biblionumber => $item->biblionumber,
+ non_priority => 0,
+ found => undef,
+ reservedate => { '<=' => \'NOW()' },
+ suspend => 0,
+ },
+ { prefetch => 'patron' }
+ );
+ if ( $fillable_holds->count ) {
+ if ( C4::Context->preference('AllowRenewalIfOtherItemsAvailable') ) {
+ my @possible_holds = $fillable_holds->as_list;
# Get all other items that could possibly fill reserves
- my $items = Koha::Items->search({
- biblionumber => $resrec->{biblionumber},
+ # FIXME We could join reserves (or more tables) here to eliminate some checks later
+ my @other_items = Koha::Items->search({
+ biblionumber => $item->biblionumber,
onloan => undef,
notforloan => 0,
- -not => { itemnumber => $itemnumber }
- });
-
- # Get all other reserves that could have been filled by this item
- my @borrowernumbers = map { $_->{borrowernumber} } @$possible_reserves;
- my $patrons = Koha::Patrons->search({
- borrowernumber => { -in => \@borrowernumbers }
- });
-
- # If the count of the union of the lists of reservable items for each borrower
- # is equal or greater than the number of borrowers, we know that all reserves
- # can be filled with available items. We can get the union of the sets simply
- # by pushing all the elements onto an array and removing the duplicates.
- my @reservable;
- ITEM: while ( my $item = $items->next ) {
- next if IsItemOnHoldAndFound( $item->itemnumber );
- while ( my $patron = $patrons->next ) {
- next unless IsAvailableForItemLevelRequest($item, $patron);
- next unless CanItemBeReserved($patron,$item,undef,{ignore_hold_counts=>1})->{status} eq 'OK';
- push @reservable, $item->itemnumber;
- if (@reservable >= @borrowernumbers) {
- $resfound = 0;
- last ITEM;
- }
- last;
+ -not => { itemnumber => $itemnumber } })->as_list;
+
+ return ( 0, "on_reserve" ) if @possible_holds && (scalar @other_items < scalar @possible_holds);
+
+ my %matched_items;
+ foreach my $possible_hold (@possible_holds) {
+ my $fillable = 0;
+ my $patron_with_reserve = Koha::Patrons->find($possible_hold->borrowernumber);
+ my $items_any_available = ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron_with_reserve });
+
+ # FIXME: We are not checking whether the item we are renewing can fill the hold
+
+ foreach my $other_item (@other_items) {
+ next if defined $matched_items{$other_item->itemnumber};
+ next if IsItemOnHoldAndFound( $other_item->itemnumber );
+ next unless IsAvailableForItemLevelRequest($other_item, $patron_with_reserve, undef, $items_any_available);
+ next unless CanItemBeReserved($patron_with_reserve,$other_item,undef,{ignore_hold_counts=>1})->{status} eq 'OK';
+ # NOTE: At checkin we call 'CheckReserves' which checks hold 'policy'
+ # CanItemBeReserved checks 'rules' and 'policies' which means
+ # items will fill holds at checkin that are rejected here
+ $fillable = 1;
+ $matched_items{$other_item->itemnumber} = 1;
+ last;
}
- $patrons->reset;
+ return ( 0, "on_reserve" ) unless $fillable;
}
+
+ } else {
+ my ($status, $matched_reserve, $possible_reserves) = CheckReserves($itemnumber);
+ return ( 0, "on_reserve" ) if $status;
}
}
- 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";
- if ( GetSoonestRenewDate($borrowernumber, $itemnumber) > dt_from_string() ){
- return (0, "too_soon") unless $override_limit;
+ return ( 0, $auto_renew, { soonest_renew_date => $soonest } ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok";
+ $soonest = GetSoonestRenewDate($borrowernumber, $itemnumber);
+ if ( $soonest > dt_from_string() ){
+ return (0, "too_soon", { soonest_renew_date => $soonest } ) unless $override_limit;
}
return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed
$borrowernumber ||= $issue->borrowernumber;
if ( defined $datedue && ref $datedue ne 'DateTime' ) {
- carp 'Invalid date passed to AddRenewal.';
- return;
+ $datedue = dt_from_string($datedue, 'sql');
}
my $patron = Koha::Patrons->find( $borrowernumber ) or return; # FIXME Should do more than just return
rule_name => 'unseen_renewals_allowed'
}
);
- if (!$seen && $rule && $rule->rule_value) {
+ if (!$seen && $rule && looks_like_number($rule->rule_value)) {
$unseen_renewals++;
} else {
# If the renewal is seen, unseen should revert to 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 = ?, unseen_renewals = ?, lastreneweddate = ? WHERE issue_id = ?");
+ my $renews = ( $issue->renewals_count || 0 ) + 1;
+ my $sth = $dbh->prepare("UPDATE issues SET date_due = ?, renewals_count = ?, unseen_renewals = ?, lastreneweddate = ? WHERE issue_id = ?");
eval{
$sth->execute( $datedue->strftime('%Y-%m-%d %H:%M'), $renews, $unseen_renewals, $lastreneweddate, $issue->issue_id );
DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' });
}
+ # Add renewal record
+ my $renewal = Koha::Checkouts::Renewal->new(
+ {
+ checkout_id => $issue->issue_id,
+ renewer_id => C4::Context->userenv ? C4::Context->userenv->{'number'} : undef,
+ seen => $seen,
+ interface => C4::Context->interface
+ }
+ )->store();
+
# Add the renewal to stats
C4::Stats::UpdateStats(
{
);
$sth->execute( $bornum, $itemno );
my $data = $sth->fetchrow_hashref;
- $renewcount = $data->{'renewals'} if $data->{'renewals'};
+ $renewcount = $data->{'renewals_count'} if $data->{'renewals_count'};
$unseencount = $data->{'unseen_renewals'} if $data->{'unseen_renewals'};
# $item and $borrower should be calculated
my $branchcode = _GetCircControlBranch($item->unblessed, $patron->unblessed);
);
}
-=head2 GetTransfers
-
- GetTransfers($itemnumber);
-
-=cut
-
-sub GetTransfers {
- my ($itemnumber) = @_;
-
- my $dbh = C4::Context->dbh;
-
- my $query = '
- SELECT datesent,
- frombranch,
- tobranch,
- branchtransfer_id,
- daterequested,
- reason
- FROM branchtransfers
- WHERE itemnumber = ?
- AND datearrived IS NULL
- AND datecancelled IS NULL
- ';
- my $sth = $dbh->prepare($query);
- $sth->execute($itemnumber);
- my @row = $sth->fetchrow_array();
- return @row;
-}
-
=head2 GetTransfersFromTo
@results = GetTransfersFromTo($frombranch,$tobranch);
my $loanlength =
GetLoanLength( $borrower->{'categorycode'}, $itemtype, $branch );
- my $length_key = ( $isrenewal and defined $loanlength->{renewalperiod} )
+ my $length_key = ( $isrenewal and defined $loanlength->{renewalperiod} and $loanlength->{renewalperiod} ne q{} )
? qq{renewalperiod}
: qq{issuelength};
if ( $item ) {
my $itemnumber = $item->itemnumber;
- my $issue = GetOpenIssue( $itemnumber );
+ my $issue = $item->checkout;
if ( $issue ) {
my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0;
ModDateLastSeen( $itemnumber, $leave_item_lost );
MarkIssueReturned(
- $issue->{borrowernumber},
+ $issue->borrowernumber,
$itemnumber,
$operation->{timestamp},
);
- $item->renewals(0);
+ $item->renewals_count(0);
$item->onloan(undef);
$item->store({ log_action => 0 });
return "Success.";
return "Barcode not found.";
}
my $itemnumber = $item->itemnumber;
- my $issue = GetOpenIssue( $itemnumber );
+ my $issue = $item->checkout;
- if ( $issue and ( $issue->{borrowernumber} ne $patron->borrowernumber ) ) { # Item already issued to another patron mark it returned
+ if ( $issue and ( $issue->borrowernumber ne $patron->borrowernumber ) ) { # Item already issued to another patron mark it returned
MarkIssueReturned(
- $issue->{borrowernumber},
+ $issue->borrowernumber,
$itemnumber,
$operation->{timestamp},
);
$patron->unblessed,
$operation->{'barcode'},
undef,
- 1,
+ undef,
$operation->{timestamp},
undef,
);
itemnumber => $issue->itemnumber,
borrowernumber => $issue->borrowernumber,
amount => $amount,
- due => output_pref($datedue),
+ due => $datedue,
});
}
elsif ($return_date) {
itemnumber => $issue->itemnumber,
borrowernumber => $issue->borrowernumber,
amount => 0,
- due => output_pref($datedue),
+ due => $datedue,
});
}
}
}
}
- if ( defined $issuing_rule->{norenewalbefore}
- and $issuing_rule->{norenewalbefore} ne "" ) {
- if ( GetSoonestRenewDate($patron->id, $item->id) > dt_from_string()) {
- return "auto_too_soon";
- } else {
- return "ok";
- }
+ my $soonest = GetSoonestRenewDate($patron->id, $item->id);
+ if ( $soonest > dt_from_string() )
+ {
+ return ( "auto_too_soon", $soonest );
}
- # Fallback for automatic renewals:
- # If norenewalbefore is undef, don't renew before due date.
- my $now = dt_from_string;
- if ( $now >= dt_from_string( $issue->date_due, 'sql' ) ){
- return "ok";
- } else {
- return "auto_too_soon";
- }
+ return "ok";
}
sub _item_denied_renewal {