LP1893968 SIP2 checkin-with-cancel / checkin API user/berick/lp1893968-sip-checkin-with-cancel
authorBill Erickson <berickxx@gmail.com>
Wed, 2 Sep 2020 16:56:34 +0000 (12:56 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 2 Sep 2020 19:19:45 +0000 (15:19 -0400)
Adds new option to open-ils.circ.checkin API called
"revert_hold_fulfillment".  It behaves much like a No-Op checkin, with
the addition that if a hold was fulfilled by the checked out item for
the patron which circulated the item, the hold fulfillment is rolled
back and the item is put back on the holds shelf.

Teaches the SIP Checkin code to pass the 'revert_hold_fulfillment' flag
to its checkin call when the SIP 'cancel' value is set, i.e. the SIP
'BI' field contains a 'Y' value.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/perlmods/lib/OpenILS/SIP.pm
Open-ILS/src/perlmods/lib/OpenILS/SIP/Transaction/Checkin.pm

index 66280f5..a460729 100644 (file)
@@ -255,6 +255,7 @@ sub run_method {
 
     $circulator->mk_env();
     $circulator->noop(1) if $circulator->claims_never_checked_out;
+    $circulator->noop(1) if $circulator->revert_hold_fulfillment;
 
     return circ_events($circulator) if $circulator->bail_out;
 
@@ -501,6 +502,7 @@ my @AUTOLOAD_FIELDS = qw/
     rental_billing
     capture
     noop
+    revert_hold_fulfillment
     void_overdues
     parent_circ
     return_patron
@@ -1916,6 +1918,51 @@ sub handle_checkout_holds {
 }
 
 
+sub undo_hold_fulfillment {
+    my $self = shift;
+    my $e = $self->editor;
+
+    my $hold = $e->search_action_hold_request([
+        {   usr => $self->patron->id,
+            cancel_time => undef,
+            fulfillment_time => {'!=' => undef},
+            current_copy => $self->copy->id
+        }, {
+            order_by => {ahr => 'fulfillment_time desc'},
+            limit => 1
+        }
+    ])->[0];
+
+    return unless $hold;
+
+    # The hold fulfillment time will match the xact_start time of its
+    # companion circulation, however in some cases the date stored in PG
+    # contains milliseconds and in other cases not.  To make an accurate
+    # comparison, truncate the milliseconds.
+
+    my $xact_time = DateTime::Format::ISO8601->new->parse_datetime(
+        clean_ISO8601($self->circ->xact_start))->strftime('%FT%T%z');
+
+    my $ff_time = DateTime::Format::ISO8601->new->parse_datetime(
+        clean_ISO8601($hold->fulfillment_time))->strftime('%FT%T%z');
+
+    return unless $xact_time eq $ff_time;
+
+    $logger->info("circulator: undoing fulfillment for hold ".$hold->id);
+
+    $hold->clear_fulfillment_time;
+    $hold->clear_fulfillment_staff;
+    $hold->clear_fulfillment_lib;
+
+    return $self->bail_on_events($e->event)
+        unless $e->update_action_hold_request($hold);
+
+    # Put the item back on the holds shelf.
+    $self->copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF);
+    $self->update_copy();
+}
+
+
 # ------------------------------------------------------------------------------
 # If the circ.checkout_fill_related_hold setting is turned on and no hold for
 # the patron directly targets the checked out item, see if there is another hold 
@@ -2683,7 +2730,7 @@ sub do_checkin {
 
     $self->fix_broken_transit_status; # if applicable
     $self->check_transit_checkin_interval;
-    $self->checkin_retarget;
+    $self->checkin_retarget unless $self->revert_hold_fulfillment;
 
     # the renew code and mk_env should have already found our circulation object
     unless( $self->circ ) {
@@ -2701,6 +2748,17 @@ sub do_checkin {
 
     my $stat = $U->copy_status($self->copy->status)->id;
 
+    if ($self->revert_hold_fulfillment) {
+        # Rule out any unexpected scenarios before continuing with
+        # reverting the hold fulfillment.
+        return $self->bail_on_events(OpenILS::Event->new('NO_CHANGE'))
+            unless (
+                $self->circ
+                && $stat == OILS_COPY_STATUS_CHECKED_OUT
+                && !$self->is_renewal
+            );
+    }
+
     # LOST (and to some extent, LONGOVERDUE) may optionally be handled
     # differently if they are already paid for.  We need to check for this
     # early since overdue generation is potentially affected.
@@ -2755,7 +2813,7 @@ sub do_checkin {
             unless $self->editor->allowed('COPY_CHECKIN');
     }
 
-    $self->push_events($self->check_copy_alert());
+    $self->push_events($self->check_copy_alert()) unless $self->revert_hold_fulfillment;
     $self->push_events($self->check_checkin_copy_status());
 
     # if the circ is marked as 'claims returned', add the event to the list
@@ -2763,7 +2821,7 @@ sub do_checkin {
         if ($self->circ and $self->circ->stop_fines 
                 and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED);
 
-    $self->check_circ_deposit();
+    $self->check_circ_deposit() unless $self->revert_hold_fulfillment;
 
     # handle the overridable events 
     $self->override_events unless $self->is_renewal;
@@ -2879,6 +2937,14 @@ sub do_checkin {
         return;
     }
 
+    if ($self->revert_hold_fulfillment) {
+        $self->undo_hold_fulfillment;
+        return if $self->bail_out;
+        $self->push_events(OpenILS::Event->new('SUCCESS'));
+        $self->checkin_flesh_events;
+        return;
+    }
+
    # ------------------------------------------------------------------------------
    # Circulations and transits are now closed where necessary.  Now go on to see if
    # this copy can fulfill a hold or needs to be routed to a different location
index 693d562..1911a7e 100644 (file)
@@ -469,7 +469,8 @@ sub checkin {
         return $xact;
     }
 
-    $xact->do_checkin( $self, $inst_id, $trans_date, $return_date, $current_loc, $item_props );
+    $xact->do_checkin($self, $inst_id, $trans_date, 
+        $return_date, $current_loc, $item_props, $cancel);
     
     if ($xact->ok) {
         $xact->patron($self->find_patron(usr => $xact->{circ_user_id}, slim_user => 1)) if $xact->{circ_user_id};
index c937892..186b9f1 100644 (file)
@@ -65,8 +65,8 @@ sub load_override_events {
 
 my %org_sn_cache;
 sub do_checkin {
-    my $self = shift;
-    my ($sip_handler, $inst_id, $trans_date, $return_date, $current_loc, $item_props) = @_; # most unused
+    my ($self, $sip_handler, $inst_id, $trans_date, 
+        $return_date, $current_loc, $item_props, $cancel) = @_;
 
     unless($self->{item}) {
         $self->ok(0);
@@ -81,6 +81,7 @@ sub do_checkin {
 
     my $args = {barcode => $self->{item}->id};
     $args->{hold_as_transit} = 1 if $hold_as_transit;
+    $args->{revert_hold_fulfillment} = 1 if $cancel;
 
     if($return_date) {
         # SIP date format is YYYYMMDD.  Translate to ISO8601