From: Bill Erickson Date: Wed, 8 Jun 2016 19:21:22 +0000 (-0400) Subject: hold targeter reify experiment X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=8e5df60dc125cdba0df23d9c3227dc28ae972cd6;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 7167e28f24..9375d67e60 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm @@ -71,6 +71,7 @@ use OpenSRF::AppSession; use OpenSRF::Utils::Logger qw(:logger); use OpenILS::Application::AppUtils; use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::PermitHold; sub new { my ($class, %args) = @_; @@ -172,10 +173,6 @@ sub exit_targeter { return 0; } -sub create_prox_list { - -} - # Cancel expired holds and kick off the A/T no-target event. Returns # true (i.e. keep going) if the hold is not expired. Returns false if # the hold is canceled or a non-recoverable error occcurred. @@ -448,7 +445,27 @@ sub build_copy_maps { return 1; } -sub build_copy_prox_list { +# Returns a map of proximity values to arrays of copy hashes. +# The copy hash arrays are weighted consistent with the org unit hold +# target weight, meaning that a given copy may appear more than once +# in its proximity list. +sub compile_weighted_proximity_map { + my $self = shift; + + my %prox_map; + for my $copy_hash (@{$self->copy_hashes}) { + my $prox = $self->copy_proximity_map->{$copy_hash->{id}}; + $prox_map{$prox} ||= []; + + my $weight = $self->parent->get_ou_setting( + $copy_hash->{circ_lib}, + 'circ.holds.org_unit_target_weight') || 1; + + # Each copy is added to the list once per target weight. + push(@{$prox_map{$prox}}, $copy_hash) foreach (1 .. $weight); + } + + return $self->{weighted_prox_map} = \%prox_map; } # Returns true if filtering completed without error, false otherwise. @@ -510,18 +527,6 @@ sub inspect_previous_target { $self->{had_previous_copy} = 1; - # TODO: is this step really necessary here?? Not if we always set - # or clear the value later. Confirm. - # Clear the previous copy regardless of - # whether we can use it again later. - $self->clear_current_copy; - - if (!$self->editor->update_action_hold_request($hold)) { - my $evt = $self->editor->die_event; - return $self->exit_targeter( - "Error updating hold request: ".$evt->{textcode}, 1); - } - # See if the previous copy is in our list of valid copies. my ($prev) = grep {$_->{id} eq $prev_id} @copies; @@ -530,19 +535,23 @@ sub inspect_previous_target { $self->{valid_previous_copy} = $prev; - # Remove the previous copy from the working set of potential copies - # if there are other copies we can focus on. If there are no other - # copies, treat the previous copy like any other. + # If there are other copies we can focus on first, remove the + # previous copy from the working set of potential copies. (The + # previous copy may be revisited later as needed). Otherwise, + # treat the previous copy as the only potential copy. $self->copy_hashes([grep {$_->{id} ne $prev_id} @copies]) if scalar(@copies) > 1; return 1; } -sub check_no_copies { +# Returns true if we have at least one potential copy, thus targeting +# should continue. Otherwise, the hold is updated to reflect that +# there is no target and returns false, to stop targeting. +sub handle_no_copies { my $self = shift; - return 1 if @{$self->copy_hashes}; + return 1 if @{$self->copy_hashes} || $self->{valid_previous_copy}; my $hold = $self->hold; $hold->clear_current_copy; @@ -558,6 +567,85 @@ sub check_no_copies { return $self->exit_targeter("Hold has no targetable copies"); } +# Force and recall holds bypass validity tests. Returns the first +# (and presumably only) copy in our list of valid copies when a +# F or R hold is encountered. Returns undef otherwise. +sub attempt_force_recall_target { + my $self = shift; + return $self->copy_hashes->[0] if + $self->hold->hold_type eq 'R' || $self->hold->hold_type = 'F'; + return undef; +} + +sub attempt_min_proxy_copy_target { + my $self = shift; +} + +sub attempt_remote_copy_target { + my $self = shift; +} + +sub attempt_prev_copy_retarget { + my $self = shift; + + $self->{valid_previous_copy} = undef; +} + +# Returns the closest copy by proximity that is a confirmed valid +# targetable copy. +sub find_nearest_copy { + my $self = shift; + my %prox_map = %{$self->{weighted_prox_map}}; + my $hold = $self->hold; + + for my $prox (sort {$a <=> $b} keys %prox_map) { + my @copies = @{$prox_map{$prox}}; + next unless @copies; + + my $rand = int(rand(scalar(@copies))); + my %seen = (); + + while (my ($c) = splice(@copies, $rand, 1)) { + next if $seen{$c->{id}}; + + return $c if OpenILS::Utils::PermitHold::permit_copy_hold({ + patron_id => $hold->usr, + copy_id => $c->{id}, + requestor => $hold->requestor, + request_lib => $hold->request_lib, + pickup_lib => $hold->pickup_lib, + retarget => 1 + }); + + $seen{$c->{id}} = 1; + + last unless(@copies); + $rand = int(rand(scalar(@copies))); + } + } + + return undef; +} + +sub apply_copy_target { + my ($self, $copy) = @_; + my $e = $self->editor; + my $hold = $self->hold; + + $hold->current_copy($copy->{id}); + $hold->prev_check_time('now'); + + if (!$e->update_action_hold_request($hold)) { + my $evt = $self->editor->die_event; + return $self->exit_targeter( + "Error updating hold request: ".$evt->{textcode}, 1); + } + + $e->commit; + $self->{success} = 1; + return $self->exit_targeter("Hold successfully targeted"); +} + # Target a single hold request sub target { my ($self, $hold_id) = @_; @@ -584,9 +672,20 @@ sub target { return unless $self->inspect_previous_target; return unless $self->filter_copies_by_status; return unless $self->filter_closed_date_copies; - return unless $self->check_no_copies; + return unless $self->handle_no_copies; + return unless $self->compile_weighted_proximity_map; - $e->commit; + my $copy = $self->attempt_force_recall_target || + $self->attempt_min_prox_copy_target || + $self->attempt_remote_copy_target || + $self->attempt_prev_copy_retarget; + + return $self->apply_copy_target($copy) if $copy; + + # No targetable copy was found. Remove he copy data and fire the + # no-copy handler to update the hold accordingly. + $self->copy_hashes([]); + $self->handle_no_copies; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm index f2153a8208..8fae5a7289 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/PermitHold.pm @@ -34,15 +34,19 @@ sub indb_hold_permit { ref($$params{patron}) ? $$params{patron}->id : $$params{patron_id}; my $request_lib = ref($$params{request_lib}) ? $$params{request_lib}->id : $$params{request_lib}; + my $copy_id = + ref($$params{copy}) ? $$params{copy}->id : $$params{copy_id}; + my $requestor_id = + ref($$params{requestor}) ? $$params{requestor}->id : $$params{requestor_id}; my $HOLD_TEST = { from => [ $function, $$params{pickup_lib}, $request_lib, - $$params{copy}->id, + $copy_id, $patron_id, - $$params{requestor}->id + $requestor_id ] };