# if it's closed both now and at the next re-target date.
my $next_check_time =
- DateTime->now->add(seconds => $self->{retarget_interval});
+ DateTime->now->add(seconds => $self->{retarget_interval})
+ ->strftime('%F %T%z');
$closed_orgs_query = {
'-and' => [
sub target_all {
my $self = shift;
my @responses;
- push(@responses, $self->target_hold($_)) for $self->collect_hold_ids;
+ for my $hold_id ($self->find_holds_to_target) {
+ my $resp = $self->target_hold($hold_id);
+ push(@responses, $resp);
+ }
return \@responses;
}
-sub collect_hold_ids {
+sub find_holds_to_target {
my $self = shift;
my $date = DateTime->now->subtract(seconds => $self->{retarget_interval});
my $self = shift;
return {
- hold_id => $self->hold_id,
+ hold => $self->hold_id,
error => $self->error,
success => $self->success,
message => $self->message,
- targeted_copy => $self->{targeted_copy},
- found_copy => $self->{found_copy}
+ target => $self->hold ? $self->hold->current_copy : undef,
+ old_target => $self->{previous_copy_id},
+ found_copy => $self->{found_copy},
+ eligible_copies => $self->{eligible_copy_count}
};
}
# List of potential copies in the form of slim hashes.
-sub copy_hashes {
- my ($self, $copy_hashes) = @_;
- $self->{copy_hashes} = $copy_hashes if $copy_hashes;
- return $self->{copy_hashes};
+# This is a working list of copies that evolves as copies
+# are filtered for various reasons or deemed non-targetable.
+sub copies {
+ my ($self, $copies) = @_;
+ $self->{copies} = $copies if $copies;
+ return $self->{copies};
+}
+
+# Final set of targetable ("good") copies that may be eligible for
+# recall processing.
+sub recall_copies {
+ my ($self, $recall_copies) = @_;
+ $self->{recall_copies} = $recall_copies if $recall_copies;
+ return $self->{recall_copies};
}
# Maps copy ID's to their hold proximity
my $org_depth = $hold->selection_depth || 0;
my $query = {
- select => {acp => ['id', 'status', 'circ_lib']},
+ select => {
+ acp => ['id', 'status', 'circ_lib'],
+ ahr => ['current_copy']
+ },
from => {
acp => {
- # Exclude copies that are targeted by other active holds.
- # Include such copies that are targeted by the current hold.
+ # Tag copies that are in use by other holds so we don't
+ # try to target them for our hold.
ahr => {
type => 'left',
fkey => 'id', # acp.id
where => {id => $org_unit}
}
}
- },
- '+ahr' => {id => undef}
+ }
}
};
};
}
- $self->copy_hashes($e->json_query($query));
- return $self->inspect_potential_copies;
-}
+ my $copies = $e->json_query($query);
+ $self->{eligible_copy_count} = scalar(@$copies);
-# Returns true if we have copies to process, False if there are none.
-sub inspect_potential_copies {
- my $self = shift;
- my @copy_hashes = @{$self->copy_hashes};
+ $logger->info("targeter: Hold ".$self->hold_id." has ".
+ $self->{eligible_copy_count}." potential copies");
- my $e = $self->editor;
- my $hold = $self->hold;
- my $hold_id = $self->hold_id;
-
- $logger->info("targeter: Hold $hold_id has ".
- scalar(@copy_hashes)." potential copies");
-
- # Let the caller know we found the copy they were interested in.
+ # Let the caller know we encountered the copy they were interested in.
$self->{found_copy} = 1 if $self->{find_copy}
- && grep {$_->{id} eq $self->{find_copy}} @copy_hashes;
+ && grep {$_->{id} eq $self->{find_copy}} @$copies;
- # We have copies. Nothing left to do here.
- return 1 if @copy_hashes;
+ $self->copies($copies);
- $hold->prev_check_time('now');
- $hold->clear_current_copy;
-
- if (!$e->update_action_hold_request($hold)) {
- my $evt = $e->die_event;
- return $self->exit_targeter(
- "Error updating hold request: ".$evt->{textcode}, 1);
- }
-
- $e->commit;
- return $self->exit_targeter("No copies available for targeting");
+ return 1;
}
-
# Delete and rebuild copy maps
sub update_copy_maps {
my $self = shift;
$e->json_query({from => [
'action.hold_request_regen_copy_maps',
$self->hold_id,
- map {$_->{id}} @{$self->copy_hashes} # @array / variadic
+ '{' . join(',', map {$_->{id}} @{$self->copies}) . '}'
]});
return 1;
map {$_->{target_copy} => $_->{proximity}} @$hold_copy_maps;
my %prox_map;
- for my $copy_hash (@{$self->copy_hashes}) {
+ for my $copy_hash (@{$self->copies}) {
my $prox = $copy_prox_map{$copy_hash->{id}};
$prox_map{$prox} ||= [];
my $self = shift;
my @filtered_copies;
- for my $copy_hash (@{$self->copy_hashes}) {
+ for my $copy_hash (@{$self->copies}) {
my $clib = $copy_hash->{circ_lib};
if ($self->parent->{closed_orgs}->{$clib}) {
}
# Update our in-progress list of copies to reflect the filtered set.
- $self->copy_hashes(\@filtered_copies);
+ $self->copies(\@filtered_copies);
return 1;
}
# Returns true if filtering completes without error, false otherwise.
sub filter_copies_by_status {
my $self = shift;
- $self->copy_hashes([
- grep
- {$_->{status} == 0 || $_->{status} == 7}
- @{$self->copy_hashes}
+ $self->copies([
+ grep {$_->{status} == 0 || $_->{status} == 7} @{$self->copies}
]);
return 1;
}
+# Remove copies that are currently targeted by other holds.
+# Returns true if filtering completes without error, false otherwise.
+sub filter_copies_in_use {
+ my $self = shift;
+
+ # A copy with a 'current_copy' value means it's in use by another hold.
+ $self->copies([
+ grep {!$_->{current_copy}} @{$self->copies}
+ ]);
+
+ return 1;
+}
+
# Returns true if inspection completed without error, false otherwise.
sub inspect_previous_target {
my $self = shift;
my $hold = $self->hold;
- my @copies = @{$self->copy_hashes};
+ my @copies = @{$self->copies};
# no previous target
return 1 unless my $prev_id = $hold->current_copy;
# previous target is no longer valid.
return 1 unless $prev;
+ # Previous copy is targetable. Keep it around for later.
$self->{valid_previous_copy} = $prev;
# Remove the previous copy from the working set of potential copies.
# It will be revisited later if needed.
- $self->copy_hashes([grep {$_->{id} ne $prev_id} @copies]);
+ $self->copies([grep {$_->{id} ne $prev_id} @copies]);
return 1;
}
my $force = shift;
if (!$force) {
- # Force just says don't bother checking the copies, because
- # other code already has.
- return 1 if @{$self->copy_hashes} || $self->{valid_previous_copy};
+ # Force just says don't bother checking the copies,
+ # because other code already has.
+ return 1 if @{$self->copies} || $self->{valid_previous_copy};
}
my $hold = $self->hold;
# F or R hold is encountered. Returns undef otherwise.
sub attempt_force_recall_target {
my $self = shift;
- return $self->copy_hashes->[0] if
+ return $self->copies->[0] if
$self->hold->hold_type eq 'R' || $self->hold->hold_type eq 'F';
return undef;
}
my $self = shift;
my $pu_lib = $self->hold->pickup_lib;
- my @copies = @{$self->copy_hashes};
+ my @copies = @{$self->copies};
my @locals = grep {$_->{circ_lib} eq $pu_lib} @copies;
return undef unless @locals;
return $copy if $self->copy_is_permitted($copy);
}
+ $logger->info(
+ "targeter: no local targetable copies found for hold ".$self->hold_id);
+
return undef;
}
sub attempt_remote_copy_target {
my $self = shift;
- return undef unless @{$self->copy_hashes};
+ $logger->info("targeter: attempting to target a ".
+ "remote copy for hold ".$self->hold_id);
+
+ return undef unless @{$self->copies};
return undef unless $self->trim_copies_by_target_loop;
'circ.holds.max_org_unit_target_loops'
);
- return unless defined $max_loops;
+ return 1 unless defined $max_loops;
- my @copies = @{$self->copy_hashes};
+ my @copies = @{$self->copies};
my $e = $self->editor;
my %circ_lib_map = map {$_->{circ_lib} => 1} @copies;
if grep {$copy->{circ_lib} eq $_} @keep_libs;
}
- $self->copy_hashes(\@new_copies);
+ $self->copies(\@new_copies);
return 1;
}
my $prev_copy = $self->{valid_previous_copy};
return undef unless $prev_copy;
+ $logger->info("targeter: attempting to re-target ".
+ "previously targeted copy for hold ".$self->hold_id);
+
if ($self->copy_is_permitted($prev_copy)) {
$logger->debug("targeter: retargeting the ".
"previously targeted copy [".$prev_copy->{id}."]" );
# Copy is confirmed non-viable.
# Remove it from our potentials list.
- $self->copy_hashes([
- grep {$_->{id} ne $copy->{id}} @{$self->copy_hashes}
+ $self->copies([
+ grep {$_->{id} ne $copy->{id}} @{$self->copies}
]);
return 0;
$e->commit;
$self->{success} = 1;
- $self->{targeted_copy} = $hold->current_copy;
return $self->exit_targeter("Hold successfully targeted");
}
return 1;
}
+sub process_recalls {
+ my $self = shift;
+}
+
# Target a single hold request
sub target {
my ($self, $hold_id) = @_;
return unless $self->handle_expired_hold;
return unless $self->get_hold_copies;
return unless $self->update_copy_maps;
- return unless $self->inspect_previous_target;
+
+ # Confirm that we have something to work on.
+ return unless $self->handle_no_copies;
+
return unless $self->filter_copies_by_status;
+ return unless $self->filter_copies_in_use;
return unless $self->filter_closed_date_copies;
+ return unless $self->inspect_previous_target;
+
+ # Confirm again we have something to work on.
return unless $self->handle_no_copies;
+ # At this point, we have trimmed the working set of copies
+ # down to those that are currently targetable. Clone this
+ # list for later use by recalls processing if needed.
+ $self->recall_copies([@{$self->copies}]);
+
my $copy = $self->attempt_force_recall_target ||
$self->attempt_local_copy_target ||
$self->attempt_remote_copy_target ||
# No targetable copy was found. Fire the no-copy
# handler to update the hold accordingly.
- # TODO: process_recall()
+ return unless $self->process_recalls;
$self->handle_no_copies(1);
}