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"'
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.
# 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;
}
# 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;
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;
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
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;
# 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);
}