use OpenILS::Application::AppUtils;
use OpenILS::Utils::CStoreEditor qw/:funcs/;
-# WIP notes:
-# avoid 'duplicate key value violates unique constraint "copy_once_per_hold"'
-
our $U = "OpenILS::Application::AppUtils";
our $dt_parser = DateTime::Format::ISO8601->new;
return bless($self, $class);
}
+# Target and retarget holds.
+# By default, targets all holds that need targeting.
+#
+# Optional parameters:
+#
+# hold => <id>
+# -- ID of a specific hold to target.
+#
+# retarget_interval => <interval string>
+# -- If set, this overrides the value found in the
+# 'circ.holds.retarget_interval' global flag.
+#
+# newest_first => 1
+# -- If set, holds will be targeted in reverse order of create_time.
+# This is useful for targeting / re-targeting newer holds first.
+#
+# target_all => 1
+# -- Forces targeting / re-targeting of all active holds.
+# This is primarily usefulf or testing. USE WITH CAUTION.
+#
+# Returns an array of hold targeter response objects, one response per hold.
+sub target {
+ my ($self, %args) = @_;
+
+ foreach (qw/hold retarget_interval newest_first target_all/) {
+ $self->{$_} = $args{$_} if exists $args{$_};
+ }
+
+ $self->init;
+
+ my @responses;
+ for my $hold_id ($self->find_holds_to_target) {
+ my $single = OpenILS::Utils::HoldTargeter::Single->new(parent => $self);
+ $single->target($hold_id);
+ push(@responses, $single->result);
+ }
+ return \@responses;
+}
+
+sub find_holds_to_target {
+ my $self = shift;
+
+ return ($self->{hold}) if $self->{hold};
+
+ my $query = {
+ select => {ahr => ['id']},
+ from => 'ahr',
+ where => {
+ capture_time => undef,
+ fulfillment_time => undef,
+ cancel_time => undef,
+ frozen => 'f'
+ },
+ order_by => [
+ {class => 'ahr', field => 'selection_depth', direction => 'DESC'},
+ {class => 'ahr', field => 'request_time'},
+ {class => 'ahr', field => 'prev_check_time'}
+ ]
+ };
+
+ if (!$self->{target_all}) {
+ # Unless we're retargeting all holds, limit to holds that have no
+ # prev_check_time or those whose prev_check_time occurred
+ # before the retarget interval.
+
+ my $date = DateTime->now->subtract(
+ seconds => $self->{retarget_interval});
+
+ $query->{where}->{'-or'} = [
+ {prev_check_time => undef},
+ {prev_check_time => {'<=' => $date->strftime('%F %T%z')}}
+ ];
+ }
+
+ # Newest-first sorting cares only about hold create_time.
+ $query->{order_by} =
+ [{class => 'ahr', field => 'request_time', direction => 'DESC'}]
+ if $self->{newest_first};
+
+ my $holds = $self->editor->json_query($query);
+
+ return map {$_->{id}} @$holds;
+}
+
sub editor {
my $self = shift;
return $self->{editor};
}
+# Load startup data required by all targeter actions.
sub init {
my $self = shift;
+ my $e = $self->editor;
my $closed_orgs_query = {
close_start => {'<=', 'now'},
close_end => {'>=', 'now'}
};
- if ($self->{retarget_interval}) {
+ if (!$self->{target_all}) {
- # Convert the interval to seconds for current and future use.
- $self->{retarget_interval} =
- interval_to_seconds($self->{retarget_interval});
+ # See if the caller provided an interval
+ my $interval = $self->{retarget_interval};
+ if (!$interval) {
+ # See if we have a global flag value for the interval
+
+ $interval = $e->search_config_global_flag({
+ name => 'circ.holds.retarget_interval',
+ enabled => 't'
+ })->[0];
+
+ # If no flag is present, default to a 24-hour retarget interval.
+ $interval = $interval ? $interval->value : '24h';
+ }
+
+ # Convert the interval to seconds for current and future use.
+ $self->{retarget_interval} = interval_to_seconds($interval);
+
# An org unit is considered closed for retargeting purposes
# if it's closed both now and at the next re-target date.
$c->{$org_id} = {} unless $c->{$org_id};
- if (not exists $c->{$org_id}->{$setting}) {
- my $r = $U->ou_ancestor_setting($org_id, $setting, $self->{editor});
- $c->{$org_id}->{$setting} = $r ? $r->{value} : undef;
- }
+ $c->{$org_id}->{$setting} =
+ $U->ou_ancestor_setting_value($org_id, $setting, $self->{editor})
+ unless exists $c->{$org_id}->{$setting};
return $c->{$org_id}->{$setting};
}
-sub target_hold {
- my ($self, $hold_id) = @_;
- my $targeter = OpenILS::Utils::HoldTargeter::Single->new(parent => $self);
- $targeter->target($hold_id);
- return $targeter->result;
-}
-
-# Targets all holds whose prev_check_time is older than the provide interval.
-# Also targets all holds that have never been targeted.
-sub target_all {
- my $self = shift;
- my @responses;
- for my $hold_id ($self->find_holds_to_target) {
- my $resp = $self->target_hold($hold_id);
- push(@responses, $resp);
- }
- return \@responses;
-}
-
-sub find_holds_to_target {
- my $self = shift;
-
- my $date = DateTime->now->subtract(seconds => $self->{retarget_interval});
-
- my $rtime_sort = $self->{newest_first} ? 'DESC' : 'ASC';
-
- my $query = {
- select => {ahr => ['id']},
- from => 'ahr',
- where => {
- capture_time => undef,
- fulfillment_time => undef,
- frozen => 'f',
- cancel_time => undef,
- '-or' => [
- {prev_check_time => undef},
- {prev_check_time => {'<=' => $date->strftime('%F %T%z')}}
- ]
- },
- order_by => [
- {class => 'ahr', field => 'selection_depth', direction => 'DESC'},
- {class => 'ahr', field => 'request_time', direction => $rtime_sort},
- {class => 'ahr', field => 'prev_check_time'}
- ]
- };
-
- my $holds = $self->editor->json_query($query);
-
- return map {$_->{id}} @$holds;
-}
-
-
# -----------------------------------------------------------------------
# Knows how to target a single hold.
# -----------------------------------------------------------------------
};
}
-# List of potential copies in the form of slim hashes.
-# This is a working list of copies that evolves as copies
-# are filtered for various reasons or deemed non-targetable.
+# List of potential copies in the form of slim hashes. This list
+# evolves as copies are filtered as they are 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.
+# Final set of potential copies, including those that may not be
+# currently targetable, that may be eligible for recall processing.
sub recall_copies {
my ($self, $recall_copies) = @_;
$self->{recall_copies} = $recall_copies if $recall_copies;