From: Bill Erickson Date: Mon, 13 Jun 2016 21:37:44 +0000 (-0400) Subject: hold targeter reify experiment X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=fd7d105aa17d0ace6cba2baba69b56792429057f;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 03e579ac66..d17f44b7f1 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/HoldTargeter.pm @@ -4,9 +4,10 @@ use warnings; use DateTime; use OpenSRF::AppSession; use OpenSRF::Utils::Logger qw(:logger); +use OpenSRF::Utils::JSON; +use OpenSRF::Utils qw/:datetime/; use OpenILS::Application::AppUtils; use OpenILS::Utils::CStoreEditor qw/:funcs/; -use OpenSRF::Utils qw/:datetime/; # WIP notes: # avoid 'duplicate key value violates unique constraint "copy_once_per_hold"' @@ -244,7 +245,6 @@ sub copy_prox_map { sub exit_targeter { my ($self, $msg, $is_error) = @_; $self->message($msg); - $self->{done} = 1; # Force a rollback when exiting. # This is a no-op if a commit or rollback have already occurred. @@ -552,9 +552,14 @@ sub filter_closed_date_copies { # Returns true if filtering completes without error, false otherwise. sub filter_copies_by_status { my $self = shift; + $self->copies([ grep {$_->{status} == 0 || $_->{status} == 7} @{$self->copies} ]); + + # Track checked out copies for later recall + $self->recall_copies([grep {$_->{status} == 1} @{$self->copies}]); + return 1; } @@ -602,14 +607,18 @@ sub inspect_previous_target { # 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; - my $force = shift; + my ($self, %args) = @_; - if (!$force) { + if (!$args{force}) { # Force just says don't bother checking the copies, # because other code already has. return 1 if @{$self->copies} || $self->{valid_previous_copy}; } + + if ($args{process_recalls}) { + # See if we have any copies to recall. + return unless $self->process_recalls; + } my $hold = $self->hold; $hold->clear_current_copy; @@ -772,8 +781,7 @@ sub attempt_prev_copy_retarget { my $self = shift; # attempt_remote_copy_target() can in some cases cancel the hold. - # Check our global 'done' flag to confirm we're still going. - return undef if $self->{done}; + return undef if $self->hold->cancel_time; my $prev_copy = $self->{valid_previous_copy}; return undef unless $prev_copy; @@ -901,6 +909,89 @@ sub log_unfulfilled_hold { sub process_recalls { my $self = shift; + my $e = $self->editor; + + my $pu_lib = $self->hold->pickup_lib; + + my $threshold = + $self->parent->get_ou_setting($pu_lib, 'circ.holds.recall_threshold') + or return 1; + + my $interval = + $self->parent->get_ou_setting($pu_lib, 'circ.holds.recall_return_interval') + or return 1; + + # Give me the ID of every checked out copy living at the hold + # pickup library. + my @copy_ids = map {$_->{id}} + grep {$_->{circ_lib} eq $pu_lib} @{$self->recall_copies}; + + return 1 unless @copy_ids; + + my $circ = $e->search_action_circulation([ + { target_copy => \@copy_ids, + checkin_time => undef, + duration => {'>' => $threshold} + }, { + order_by => 'due_date', + limit => 1 + } + ])->[0]; + + return unless $circ; + + $logger->info("targeter: recalling circ ".$circ->id); + + # Give the user a new due date of either a full recall threshold, + # or the return interval, whichever is further in the future. + my $threshold_date = DateTime::Format::ISO8601 + ->parse_datetime(cleanse_ISO8601($circ->xact_start)) + ->add(seconds => interval_to_seconds($threshold)) + ->iso8601(); + + my $return_date = DateTime->now(time_zone => 'local')->add( + seconds => interval_to_seconds($interval))->iso8601(); + + if (DateTime->compare( + DateTime::Format::ISO8601->parse_datetime($threshold_date), + DateTime::Format::ISO8601->parse_datetime($return_date)) == 1) { + $return_date = $threshold_date; + } + + my %update_fields = ( + due_date => $return_date, + renewal_remaining => 0, + ); + + my $fine_rules = + $self->parent->get_ou_setting($pu_lib, 'circ.holds.recall_fine_rules'); + + # If the OU hasn't defined new fine rules for recalls, keep them + # as they were + if ($fine_rules) { + $logger->info("targeter: applying recall fine rules: $fine_rules"); + my $rules = OpenSRF::Utils::JSON->JSON2perl($fine_rules); + $update_fields{recurring_fine} = $rules->[0]; + $update_fields{fine_interval} = $rules->[1]; + $update_fields{max_fine} = $rules->[2]; + } + + # Copy updated fields into circ object. + $circ->$_($update_fields{$_}) for keys %update_fields; + + if (!$e->update_action_circulation($circ)) { + my $evt = $e->die_event; + return $self->exit_targeter( + "Error updating circulation object in process_recalls: ". + $evt->{textcode}); + } + + # Create trigger event for notifying current user + my $ses = OpenSRF::AppSession->create('open-ils.trigger'); + $ses->request('open-ils.trigger.event.autocreate', + 'circ.recall.target', $circ, $circ->circ_lib); + + return 1; } # Target a single hold request @@ -929,30 +1020,34 @@ sub target { return unless $self->get_hold_copies; return unless $self->update_copy_maps; - # Confirm that we have something to work on. + # Confirm that we have something to work on. If we have no + # copies at this point, there's also nothing to recall. return unless $self->handle_no_copies; + # Trim the set of working copies down to those that are + # currently targetable. return unless $self->filter_copies_by_status; return unless $self->filter_copies_in_use; return unless $self->filter_closed_date_copies; + + # Set aside the previously targeted copy for later use as needed return unless $self->inspect_previous_target; - # Confirm again we have something to work on. - return unless $self->handle_no_copies; + # Confirm again we have something to work on. If we have no + # targetable copies now, there may be a copy that can be recalled. + return unless $self->handle_no_copies(process_recalls => 1); - # 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}]); + # At this point, the working list of copies has been trimmed to + # those that are currently targetable. my $copy = $self->attempt_force_recall_target || $self->attempt_local_copy_target || $self->attempt_remote_copy_target || $self->attempt_prev_copy_retarget; - # Be sure none of the above attempt_* calls set the exit/done flag. - # This can happen if the hold has to be forceably canceled. - return if $self->{done}; + # See if one of the above attempt* calls canceled the hold as a side + # effect of looking for a copy to target. + return if $hold->cancel_time; return unless $self->log_unfulfilled_hold; @@ -961,8 +1056,7 @@ sub target { # No targetable copy was found. Fire the no-copy # handler to update the hold accordingly. - return unless $self->process_recalls; - $self->handle_no_copies(1); + $self->handle_no_copies(force => 1, process_recalls => 1); }