From: Bill Erickson Date: Tue, 14 Jun 2016 19:27:40 +0000 (-0400) Subject: hold targeter reify experiment X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=d36836fd42acb99dcbab57ddff728de7d4a4d310;p=working%2FEvergreen.git hold targeter reify experiment Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm index d17f44b7f1..b67dce0a88 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm @@ -9,9 +9,6 @@ use OpenSRF::Utils qw/:datetime/; 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; @@ -25,25 +22,125 @@ sub new { return bless($self, $class); } +# Target and retarget holds. +# By default, targets all holds that need targeting. +# +# Optional parameters: +# +# hold => +# -- ID of a specific hold to target. +# +# retarget_interval => +# -- 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. @@ -75,66 +172,13 @@ sub get_ou_setting { $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. # ----------------------------------------------------------------------- @@ -218,17 +262,16 @@ sub result { }; } -# 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; diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold_targeter.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold_targeter.sql index 914ad40b78..ba584f0f57 100644 --- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold_targeter.sql +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.hold_targeter.sql @@ -1,5 +1,3 @@ - - BEGIN; CREATE OR REPLACE FUNCTION @@ -9,5 +7,19 @@ CREATE OR REPLACE FUNCTION INSERT INTO action.hold_copy_map (hold, target_copy) SELECT $1, UNNEST($2); $$ LANGUAGE SQL; +-- DATA + +INSERT INTO config.global_flag (name, label, value, enabled) VALUES ( + 'circ.holds.retarget_interval', + oils_i18n_gettext( + 'circ.holds.retarget_interval', + 'Holds Retarget Interval', + 'cgf', + 'label' + ), + '24h', + TRUE +); + COMMIT; diff --git a/Open-ILS/src/support-scripts/test-scripts/hold_targeter.pl b/Open-ILS/src/support-scripts/test-scripts/hold_targeter.pl index 75a7fca75c..e4b8dab81b 100755 --- a/Open-ILS/src/support-scripts/test-scripts/hold_targeter.pl +++ b/Open-ILS/src/support-scripts/test-scripts/hold_targeter.pl @@ -13,13 +13,14 @@ use Data::Dumper; my $config = shift; # path to opensrf_core.xml osrf_connect($config); # connect to jabber -my $targeter = OpenILS::Utils::HoldTargeter->new( - retarget_interval => '12h', - newest_first => 1 +my $targeter = OpenILS::Utils::HoldTargeter->new; + +my $responses = $targeter->target( + #hold => 80, + #retarget_interval => '12h', + #newest_first => 1, + target_all => 1 ); -$targeter->init; -my $responses = $targeter->target_all; -#my $responses = $targeter->target_hold(80); print Dumper($responses); print "Processed " . scalar(@$responses) . " holds\n";