sub run_method {
my( $self, $conn, $auth, $args ) = @_;
translate_legacy_args($args);
+ $args->{override_args} = { all => 1 } unless defined $args->{override_args};
my $api = $self->api_name;
my $circulator =
retarget_mode
hold_as_transit
fake_hold_dest
+ limit_groups
+ override_args
+ checkout_is_for_hold
/;
}
sub check_captured_holds {
- my $self = shift;
- my $copy = $self->copy;
- my $patron = $self->patron;
+ my $self = shift;
+ my $copy = $self->copy;
+ my $patron = $self->patron;
return undef unless $copy;
$logger->info("circulator: copy is on holds shelf, searching for the correct hold");
# Item is on the holds shelf, make sure it's going to the right person
- my $holds = $self->editor->search_action_hold_request(
+ my $hold = $self->editor->search_action_hold_request(
[
{
current_copy => $copy->id ,
},
{ limit => 1 }
]
- );
+ )->[0];
- if( $holds and $$holds[0] ) {
- return undef if $$holds[0]->usr == $patron->id;
+ if ($hold and $hold->usr == $patron->id) {
+ $self->checkout_is_for_hold(1);
+ return undef;
}
$logger->info("circulator: this copy is needed by a different patron to fulfill a hold");
my $results = $self->run_indb_circ_test;
unless($self->circ_test_success) {
- # no_item result is OK during noncat checkout
- unless(@$results == 1 && $results->[0]->{fail_part} eq 'no_item' and $self->is_noncat) {
- push @allevents, $self->matrix_test_result_events;
+ my @trimmed_results;
+
+ if ($self->is_noncat) {
+ # no_item result is OK during noncat checkout
+ @trimmed_results = grep { ($_->{fail_part} || '') ne 'no_item' } @$results;
+
+ } else {
+
+ if ($self->checkout_is_for_hold) {
+ # if this checkout will fulfill a hold, ignore CIRC blocks
+ # and rely instead on the (later-checked) FULFILL block
+
+ my @pen_names = grep {$_} map {$_->{fail_part}} @$results;
+ my $fblock_pens = $self->editor->search_config_standing_penalty(
+ {name => [@pen_names], block_list => {like => '%CIRC%'}});
+
+ for my $res (@$results) {
+ my $name = $res->{fail_part} || '';
+ next if grep {$_->name eq $name} @$fblock_pens;
+ push(@trimmed_results, $res);
+ }
+
+ } else {
+ # not for hold or noncat
+ @trimmed_results = @$results;
+ }
}
+
+ # update the final set of test results
+ $self->matrix_test_result(\@trimmed_results);
+
+ push @allevents, $self->matrix_test_result_events;
}
} else {
$penalties = $penalties->{fatal_penalties};
for my $pen (@$penalties) {
+ # CIRC blocks are ignored if this is a FULFILL scenario
+ next if $mask eq 'CIRC' and $self->checkout_is_for_hold;
my $event = OpenILS::Event->new($pen->name);
$event->{desc} = $pen->label;
push(@allevents, $event);
}
$self->circ_matrix_matchpoint->max_fine_rule($self->editor->retrieve_config_rules_max_fine($results->[0]->{max_fine_rule}));
$self->circ_matrix_matchpoint->hard_due_date($self->editor->retrieve_config_hard_due_date($results->[0]->{hard_due_date}));
+ # Grab the *last* response for limit_groups, where it is more likely to be filled
+ $self->limit_groups($results->[-1]->{limit_groups});
}
return $self->matrix_test_result($results);
my $self = shift;
my @events = @{$self->events};
return unless @events;
+ my $oargs = $self->override_args;
if(!$self->override) {
return $self->bail_out(1)
$self->events([]);
- for my $e (@events) {
- my $tc = $e->{textcode};
- next if $tc eq 'SUCCESS';
- my $ov = "$tc.override";
- $logger->info("circulator: attempting to override event: $ov");
+ for my $e (@events) {
+ my $tc = $e->{textcode};
+ next if $tc eq 'SUCCESS';
+ if($oargs->{all} || grep { $_ eq $tc } @{$oargs->{events}}) {
+ my $ov = "$tc.override";
+ $logger->info("circulator: attempting to override event: $ov");
- return $self->bail_on_events($self->editor->event)
- unless( $self->editor->allowed($ov) );
+ return $self->bail_on_events($self->editor->event)
+ unless( $self->editor->allowed($ov) );
+ } else {
+ return $self->bail_out(1);
+ }
}
}
my $evt;
# - If the caller has set the override flag, we will check the item in
- if($self->override) {
+ if($self->override && ($self->override_args->{all} || grep { $_ eq 'CIRC_CLAIMS_RETURNED' } @{$self->override_args->{events}}) ) {
$CR->checkin_time('now');
$CR->checkin_scan_time('now');
# refresh the circ to force local time zone for now
$self->circ($self->editor->retrieve_action_circulation($self->circ->id));
+ if($self->limit_groups) {
+ $self->editor->json_query({ from => ['action.link_circ_limit_groups', $self->circ->id, $self->limit_groups] });
+ }
+
$self->copy->status(OILS_COPY_STATUS_CHECKED_OUT);
$self->update_copy;
return if $self->bail_out;
$self->bail_out(1);
}
+# ------------------------------------------------------------------------------
+# A hold FULFILL block is just like a CIRC block, except that FULFILL only
+# affects copies that will fulfill holds and CIRC affects all other copies.
+# If blocks exists, bail, push Events onto the event pile, and return true.
+# ------------------------------------------------------------------------------
+sub check_hold_fulfill_blocks {
+ my $self = shift;
+
+ # See if the user has any penalties applied that prevent hold fulfillment
+ my $pens = $self->editor->json_query({
+ select => {csp => ['name', 'label']},
+ from => {ausp => {csp => {}}},
+ where => {
+ '+ausp' => {
+ usr => $self->patron->id,
+ org_unit => $U->get_org_full_path($self->circ_lib),
+ '-or' => [
+ {stop_date => undef},
+ {stop_date => {'>' => 'now'}}
+ ]
+ },
+ '+csp' => {block_list => {'like' => '%FULFILL%'}}
+ }
+ });
+
+ return 0 unless @$pens;
+
+ for my $pen (@$pens) {
+ $logger->info("circulator: patron has hold FULFILL block " . $pen->{name});
+ my $event = OpenILS::Event->new($pen->{name});
+ $event->{desc} = $pen->{label};
+ $self->push_events($event);
+ }
+
+ $self->override_events;
+ return $self->bail_out;
+}
+
# ------------------------------------------------------------------------------
# When an item is checked out, see if we can fulfill a hold for this patron
$logger->info("circulator: found related hold to fulfill in checkout");
}
+ return if $self->check_hold_fulfill_blocks;
+
$logger->debug("circulator: checkout fulfilling hold " . $hold->id);
# if the hold was never officially captured, capture it.
# Check for parts on this copy
my $parts = $self->editor->search_asset_copy_part_map({ target_copy => $self->copy->id });
my %parts_hash = ();
- %parts_hash = map {$_->id, 1} @$parts if @$parts;
+ %parts_hash = map {$_->part, 1} @$parts if @$parts;
# Loop over holds in request-ish order
# Stage 1: Get them into request-ish order
$self->hold($hold);
- if( $hold and $hold->cancel_time ) { # this transited hold was cancelled mid-transit
+ if( $hold and ( $hold->cancel_time or $hold->fulfillment_time ) ) { # this transited hold was cancelled or filled mid-transit
- $logger->info("circulator: we received a transit on a cancelled hold " . $hold->id);
+ $logger->info("circulator: we received a transit on a cancelled or filled hold " . $hold->id);
$self->reshelve_copy(1);
$self->cancelled_hold_transit(1);
$self->notify_hold(0); # don't notify for cancelled holds
$self->fake_hold_dest(0);
return if $self->bail_out;
+ } elsif ($hold and $hold->hold_type eq 'R') {
+
+ $self->copy->status(OILS_COPY_STATUS_CATALOGING);
+ $self->notify_hold(0); # No need to notify
+ $self->fake_hold_dest(0);
+ $self->noop(1); # Don't try and capture for other holds/transits now
+ $self->update_copy();
+ $hold->fulfillment_time('now');
+ $self->bail_on_events($self->editor->event)
+ unless $self->editor->update_action_hold_request($hold);
+
} else {
# hold transited to correct location
$self->retarget($retarget);
+ my $suppress_transit = 0;
+ if( $hold->pickup_lib != $self->circ_lib and not $self->hold_as_transit ) {
+ my $suppress_transit_circ = $U->ou_ancestor_setting($self->circ_lib, 'circ.transit.suppress_hold');
+ if($suppress_transit_circ && $suppress_transit_circ->{value}) {
+ my $suppress_transit_pickup = $U->ou_ancestor_setting($hold->pickup_lib, 'circ.transit.suppress_hold');
+ if($suppress_transit_pickup && $suppress_transit_circ->{value} eq $suppress_transit_pickup->{value}) {
+ $suppress_transit = 1;
+ $self->hold->pickup_lib($self->circ_lib);
+ }
+ }
+ }
+
$logger->info("circulator: found permitted hold ".$hold->id." for copy, capturing...");
$hold->current_copy($copy->id);
$hold->capture_time('now');
$self->put_hold_on_shelf($hold)
- if $hold->pickup_lib == $self->circ_lib;
+ if ($suppress_transit || ($hold->pickup_lib == $self->circ_lib and not $self->hold_as_transit) );
# prevent DB errors caused by fetching
# holds from storage, and updating through cstore
return 0 if $self->bail_out;
- my $suppress_transit = 0;
- if( $hold->pickup_lib != $self->circ_lib and not $self->hold_as_transit ) {
- my $suppress_transit_circ = $U->ou_ancestor_setting($self->circ_lib, 'circ.transit.suppress_hold');
- if($suppress_transit_circ && $suppress_transit_circ->{value}) {
- my $suppress_transit_pickup = $U->ou_ancestor_setting($hold->pickup_lib, 'circ.transit.suppress_hold');
- if($suppress_transit_pickup && $suppress_transit_circ->{value} eq $suppress_transit_pickup->{value}) {
- $suppress_transit = 1;
- $self->hold->pickup_lib($self->circ_lib);
- }
- }
- }
-
if( $suppress_transit or ( $hold->pickup_lib == $self->circ_lib && not $self->hold_as_transit ) ) {
- # This hold was captured in the correct location
- $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
- $self->push_events(OpenILS::Event->new('SUCCESS'));
+ if ($hold->hold_type eq 'R') {
+ $copy->status(OILS_COPY_STATUS_CATALOGING);
+ $hold->fulfillment_time('now');
+ $self->noop(1); # Block other transit/hold checks
+ $self->bail_on_events($self->editor->event)
+ unless $self->editor->update_action_hold_request($hold);
+ } else {
+ # This hold was captured in the correct location
+ $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
+ $self->push_events(OpenILS::Event->new('SUCCESS'));
- #$self->do_hold_notify($hold->id);
- $self->notify_hold($hold->id);
+ #$self->do_hold_notify($hold->id);
+ $self->notify_hold($hold->id);
+ }
} else {
# make sure we save the copy status
$self->update_copy;
+ return 0 if $copy->status == OILS_COPY_STATUS_CATALOGING;
return 1;
}