Created two tables for recording information about when a hold has been user/lew/retargeter-hold-notes
authorLlewellyn Marshall <llewellyn.marshall@ncdcr.gov>
Wed, 13 Apr 2022 20:39:33 +0000 (16:39 -0400)
committerLlewellyn Marshall <llewellyn.marshall@ncdcr.gov>
Wed, 18 May 2022 15:15:31 +0000 (11:15 -0400)
reset. One table, the action.hold_request_reset_reason stores common
reasons for why a hold request has been reset. For example there's
manual reset, hold canceled, pickup lib changed etc. The IDS of these
are stored in the perl constants the same as your copy statuses.

The second table, action.hold_request_reset_reason_entry records every time a hold has
been reset for any reason, and who is responsible for the reset,
including their workstation.The entries also include a "note" field
which records extra information like which modified fields triggered the
reset or which patron recieved the target copy.

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/HoldTargeter.pm
Open-ILS/src/perlmods/lib/OpenILS/Const.pm

index 674ace4..9e46b71 100644 (file)
@@ -13513,6 +13513,34 @@ SELECT  usr,
                </permacrud>
        </class>
 
+       <class id="ahrrr" controller="open-ils.cstore" oils_obj:fieldmapper="action::hold_request_reset_reason" oils_persist:tablename="action.hold_request_reset_reason" reporter:label="Hold Request Reset Reason" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_reset_reason_id_seq">
+                       <field name="id" reporter:selector="name" reporter:datatype="id" reporter:label="ID"/>
+                       <field name="name"  reporter:datatype="text" oils_persist:i18n="true" reporter:label="Name"/>
+            <field name="manual" reporter:datatype="bool" reporter:label="Manual"/>
+               </fields>
+       </class>
+
+       <class id="ahrrre" controller="open-ils.cstore" oils_obj:fieldmapper="action::hold_request_reset_reason_entry" oils_persist:tablename="action.hold_request_reset_reason_entry" reporter:label="Hold Request Reset Reason Entry" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="action.hold_request_reset_reason_entry_id_seq">
+                       <field name="id" reporter:datatype="id" reporter:label="ID"/>
+            <field name="hold" reporter:label="ID" reporter:datatype="link"/>
+                       <field name="reset_reason" reporter:label="ID" reporter:datatype="link"/>
+            <field name="reset_time" reporter:datatype="timestamp" reporter:label="Reset Date/Time"/>
+                       <field name="note" reporter:datatype="text" oils_persist:i18n="true" reporter:label="Note"/>
+            <field name="requestor" reporter:label="Resetting User" reporter:datatype="link"/>
+            <field name="requestor_workstation" reporter:label="Resetting User Workstation" reporter:datatype="link"/>
+            <field name="previous_copy" reporter:label="Previous Copy" reporter:datatype="link"/>
+               </fields>
+               <links>
+            <link field="hold" reltype="has_a" key="id" map="" class="ahr"/>        
+            <link field="reset_reason" reltype="has_a" key="id" map="" class="ahrrr"/>        
+            <link field="requestor" reltype="might_have" key="id" map="" class="au"/>    
+            <link field="requestor_workstation" reltype="might_have" key="id" map="" class="aws"/>            
+            <link field="previous_copy" reltype="might_have" key="id" map="" class="acp"/>        
+        </links>
+       </class>
+
        <!-- ********************************************************************************************************************* -->
 </IDL>
 
index 7ac877b..3db5c15 100644 (file)
@@ -1887,7 +1887,8 @@ sub handle_checkout_holds {
         $hold->clear_shelf_time;
         $hold->clear_shelf_expire_time;
         $hold->clear_current_shelf_lib;
-
+        $U->simplereq('open-ils.circ', 
+            'open-ils.circ.hold_reset_reason_entry.create',$e->authtoken,$hold->id,OILS_HOLD_CHECK_OUT,"Checked out to patron #".$patron->id);
         return $self->bail_on_event($e->event)
             unless $e->update_action_hold_request($hold);
 
@@ -2662,6 +2663,8 @@ sub checkin_retarget {
                 next if ($_->{hold_type} eq 'P');
             }
             # So much for easy stuff, attempt a retarget!
+            $U->simplereq('open-ils.circ', 
+            'open-ils.circ.hold_reset_reason_entry.create',$self->editor->authtoken, $_->{id},OILS_HOLD_BETTER_HOLD);
             my $tresult = $U->simplereq(
                 'open-ils.hold-targeter',
                 'open-ils.hold-targeter.target', 
@@ -3308,7 +3311,9 @@ sub attempt_checkin_hold_capture {
     $hold->clear_expire_time; 
     $hold->clear_cancel_time;
     $hold->clear_prev_check_time unless $hold->prev_check_time;
-
+    
+    $U->simplereq('open-ils.circ', 
+    'open-ils.circ.hold_reset_reason_entry.create',$self->editor->authtoken, $hold->id, OILS_HOLD_CHECK_IN);
     $self->bail_on_events($self->editor->event)
         unless $self->editor->update_action_hold_request($hold);
     $self->hold($hold);
@@ -3452,6 +3457,8 @@ sub retarget_holds {
     my $self = shift;
     $logger->info("circulator: retargeting holds @{$self->retarget} after opportunistic capture");
     my $ses = OpenSRF::AppSession->create('open-ils.hold-targeter');
+    my $cses = OpenSRF::AppSession->create('open-ils.circ');            
+    $cses->request('open-ils.circ.hold_reset_reason_entry.create',$self->editor->authtoken, $self->retarget,OILS_HOLD_BETTER_HOLD);
     $ses->request('open-ils.hold-targeter.target', {hold => $self->retarget});
     # no reason to wait for the return value
     return;
index b9f3705..0cace5d 100644 (file)
@@ -1024,7 +1024,7 @@ sub uncancel_hold {
     $hold->clear_prev_check_time;
     $hold->clear_shelf_expire_time;
     $hold->clear_current_shelf_lib;
-    _create_reset_hold_note($e,$hold,"Hold Reset due to Uncancelation","Hold was reset due to Uncancelation.");
+    _create_reset_reason_entry($e,$hold,OILS_HOLD_UNCANCELED);
     $e->update_action_hold_request($hold) or return $e->die_event;
     $e->commit;
         
@@ -1107,10 +1107,21 @@ sub cancel_hold {
     $hold->cancel_time('now');
     $hold->cancel_cause($cause);
     $hold->cancel_note($note);
-    my $note_body = "Hold Cancelation.";
-    $note_body .= " Cancel Cause ID - $cause;";
-    $note_body .= " Cancel Note - $note" unless $note eq "";
-    _create_reset_hold_note($e,$hold,"Hold Reset due to Cancelation",$note_body);
+       my $note_body = "";
+       if($cause){
+               my $cancel_reason = "ID $cause";
+               my $cancel_cause = $e->retrieve_action_hold_request_cancel_cause($cause);
+               if($cancel_cause){
+                       $cancel_reason = $cancel_cause->label;
+               }               
+               $note_body .= "Cancel Cause: $cancel_reason";           
+       }
+       else{
+               $note_body .= "Cancel reason unknown";
+       }
+       $note_body .= "," unless $note_body eq "" || $note eq "";
+       $note_body .= " Cancel Note: \"$note\"" unless $note eq "";
+    _create_reset_reason_entry($e,$hold,OILS_HOLD_CANCELED,$note_body);
     $e->update_action_hold_request($hold)
         or return $e->die_event;
 
@@ -1211,6 +1222,7 @@ sub update_hold_impl {
     my($self, $e, $hold, $values) = @_;
     my $hold_status;
     my $need_retarget = 0;
+    my $reset_reason = OILS_HOLD_UPDATED;
     my $note_body = "";
 
     unless($hold) {
@@ -1223,10 +1235,12 @@ sub update_hold_impl {
                 if (defined $values->{$k} && defined $hold->$k() && $values->{$k} ne $hold->$k()) {
                     # Value changed? RETARGET!
                     $need_retarget = 1;
+                    $reset_reason = OILS_HOLD_UPDATED;
                     $note_body .= "$k value changed."
                 } elsif (defined $hold->$k() != defined $values->{$k}) {
                     # Value being set or cleared? RETARGET!
                     $need_retarget = 1;
+                    $reset_reason = OILS_HOLD_UPDATED;
                     $note_body .= "$k value cleared."
                 }
             }
@@ -1333,11 +1347,10 @@ sub update_hold_impl {
               $hold->clear_current_copy;
         }
     }
-
+   
     if($U->is_true($hold->frozen)) {
         $logger->info("clearing current_copy and check_time for frozen hold ".$hold->id);
-        $note_body .= "Hold was Frozen.";
-        $note_body .= " Previous target was copy ID ".$hold->current_copy."." if defined($hold->current_copy);
+               _create_reset_reason_entry($e,$hold,OILS_HOLD_FROZEN,$note_body) unless $U->is_true($orig_hold->frozen);
         $hold->clear_current_copy;
         $hold->clear_prev_check_time;
         # Clear expire_time to prevent frozen holds from expiring.
@@ -1356,15 +1369,16 @@ sub update_hold_impl {
     if(!$U->is_true($hold->frozen) && $U->is_true($orig_hold->frozen)) {
         $logger->info("Reset expire_time on activated hold ".$hold->id);
         $hold->expire_time(calculate_expire_time($hold->request_lib));
-        $note_body .= "Hold was Unfrozen.";
     }
 
     $e->update_action_hold_request($hold) or return $e->die_event;
-    _create_reset_hold_note($e,$hold,"Hold Reset due to Update",$note_body) unless $note_body eq "";
+    _create_reset_reason_entry($e,$hold,$reset_reason,$note_body) if $need_retarget;    
     $e->commit;
-
+    
     if(!$U->is_true($hold->frozen) && $U->is_true($orig_hold->frozen)) {
         $logger->info("Running targeter on activated hold ".$hold->id);
+        $U->simplereq('open-ils.circ', 
+            'open-ils.circ.hold_reset_reason_entry.create',$e->authtoken,$hold->id,OILS_HOLD_UNFROZEN);
         $U->simplereq('open-ils.hold-targeter', 
             'open-ils.hold-targeter.target', {hold => $hold->id});
     }
@@ -1372,11 +1386,13 @@ sub update_hold_impl {
     # a change to mint-condition changes the set of potential copies, so retarget the hold;
     if($U->is_true($hold->mint_condition) and !$U->is_true($orig_hold->mint_condition)) {
         _reset_hold($self, $e->requestor, $hold)
-    } elsif($need_retarget && !defined $hold->capture_time()) { # If needed, retarget the hold due to changes
+    } elsif($need_retarget && !defined $hold->capture_time()) { # If needed, retarget the hold due to changes        
         $U->simplereq('open-ils.hold-targeter', 
             'open-ils.hold-targeter.target', {hold => $hold->id});
     }
     
+   
+    
     return $hold->id;
 }
 
@@ -1462,7 +1478,7 @@ sub update_hold_if_frozen {
     } else {
         if($U->is_true($orig_hold->frozen)) {
             $logger->info("Running targeter on activated hold ".$hold->id);
-            _create_reset_hold_note($e,$hold,"Hold Reset by Unfreezing","Running targeter on activated hold");
+            _create_reset_reason_entry($e,$hold,OILS_HOLD_UNFROZEN,"Running targeter on activated hold");
             $U->simplereq('open-ils.hold-targeter', 
                 'open-ils.hold-targeter.target', {hold => $hold->id});
         }
@@ -2223,48 +2239,47 @@ sub reset_hold {
 }
 
 __PACKAGE__->register_method(
-    method   => 'create_reset_hold_note',
-    api_name => 'open-ils.circ.hold_reset_note.create'
+    method   => 'create_reset_reason_entry',
+    api_name => 'open-ils.circ.hold_reset_reason_entry.create'
 );
 
-sub create_reset_hold_note
+sub create_reset_reason_entry
 {
-    my($self, $conn, $holdid, $title, $note_body) = @_;
-    my $e = new_editor(xact => 1);
-    $logger->info("got editor");
-    #return $e->die_event unless $e->checkauth;
-    my ($hold, $evt) = $U->fetch_hold($holdid);
-    $logger->info("fetched hold");
-    return $evt if $evt;   
-    _create_reset_hold_note($e, $hold, $title, $note_body);
+    my($self, $conn, $auth, $hold, $reset_reason, $note) = @_;
+    my $e = new_editor(authtoken => $auth, xact => 1);
+       #checkauth to set the requestor (if available)
+       $e->checkauth;
+    my @holds;
+    if(ref $hold eq 'ARRAY'){
+        @holds = @{$hold};
+    }
+    else{
+        @holds = ($hold);
+    }
+    for my $holdid (@holds){
+        my ($hold, $evt) = $U->fetch_hold($holdid);
+        return $evt if $evt;   
+        _create_reset_reason_entry($e, $hold, $reset_reason, $note);
+    }
     $e->commit;
     return 1;
 }
 
-sub _create_reset_hold_note
+sub _create_reset_reason_entry
 {
-    my($e, $hold, $title, $note_body) = @_;
+    my($e, $hold, $reset_reason,$note) = @_;
     my $ts = DateTime->now;
-    my $note = Fieldmapper::action::hold_request_note->new; 
-    $logger->info("Creating hold note with title: " . $title . " and body: " . $note_body . " hold id: " . $hold->id);    
-    my $last_copy = $hold->current_copy || 0;
-    my $reqr = $e->requestor;
-    my $reqr_id = defined $reqr ? $reqr->id : 0;
-    my $reqr_usrname = $reqr_id ? $reqr->usrname : "";
-    $title.=" ".$ts->mdy." ".$ts->hms;
-    my $json = "{";
-    $json .= "\"Reset Reason\" : \"$note_body\",";
-    $json .= "\"Timestamp\" : \"".$ts->mdy." ".$ts->hms."\",";
-    $json .= "\"Previous Copy ID\" : \"$last_copy\"," unless $last_copy == 0;
-    $json .= "\"Requestor ID\" : \"".$reqr_id."\",";
-    $json .= "\"Requestor Usrname\" : \"".$reqr_usrname."\"";
-    $json .= "}";
-    $note->hold($hold);
-    $note->staff(1);
-    $note->pub(0);
-    $note->title($title);
-    $note->body($json);
-    $e->create_action_hold_request_note($note) or return $e->die_event;
+    my $entry = Fieldmapper::action::hold_request_reset_reason_entry->new; 
+    $logger->info("Creating reset reason entry for hold #" . $hold->id);    
+    my $last_copy = $hold->current_copy;
+    $entry->hold($hold->id);
+    $entry->reset_reason($reset_reason);
+    $entry->reset_time('now');
+    $entry->requestor($e->requestor->id) if defined $e->requestor;
+    $entry->requestor_workstation($e->requestor->wsid)  if defined $e->requestor;
+    $entry->previous_copy($last_copy);
+    $entry->note($note);
+    $e->create_action_hold_request_reset_reason_entry($entry) or return $e->die_event;
     return 1;
 }
 
@@ -2300,9 +2315,7 @@ sub _reset_hold {
     my $e = new_editor(xact =>1, requestor => $reqr);
     my $hid = $hold->id;
     $logger->info("reseting hold ".$hid." requestor was ".$reqr->usrname." (ID ".$reqr->id.")");
-    
-    my $note_body = "Manually retargetted.";
-    
+    my $note_body = "";
     if( $hold->capture_time and $hold->current_copy ) {
 
         my $copy = $e->retrieve_asset_copy($hold->current_copy)
@@ -2337,7 +2350,7 @@ sub _reset_hold {
         }
     }
 
-    _create_reset_hold_note($e,$hold,"Hold Reset by Staff",$note_body);
+    _create_reset_reason_entry($e,$hold,OILS_HOLD_MANUAL_RESET,$note_body);
     $hold->clear_capture_time;
     $hold->clear_current_copy;
     $hold->clear_shelf_time;
@@ -3558,7 +3571,7 @@ sub find_nearest_permitted_hold {
         next if $old_hold->id eq $best_hold->id; # don't re-target the hold we want
         $logger->info("circulator: clearing current_copy and prev_check_time on hold ".
             $old_hold->id." after a better hold [".$best_hold->id."] was found");
-        _create_reset_hold_note($editor,$old_hold,"Hold Reset due to Age","Old hold was reset. Last check time was ".$old_hold->prev_check_time.". a better hold [".$best_hold->id."] was found");
+        _create_reset_reason_entry($editor,$old_hold,OILS_HOLD_BETTER_HOLD,"Old hold was reset. Last check time was ".$old_hold->prev_check_time.". a better hold [".$best_hold->id."] was found");
         $old_hold->clear_current_copy;
         $old_hold->clear_prev_check_time;
         $editor->update_action_hold_request($old_hold)
index de1553d..7b54b39 100644 (file)
@@ -4,6 +4,7 @@ use warnings;
 use OpenILS::Application;
 use base qw/OpenILS::Application/;
 use OpenILS::Utils::HoldTargeter;
+use OpenILS::Const qw/:const/;
 use OpenSRF::Utils::Logger qw(:logger);
 
 __PACKAGE__->register_method(
@@ -88,11 +89,15 @@ sub hold_targeter {
         my $single = 
             OpenILS::Utils::HoldTargeter::Single->new(parent => $targeter);
 
-        $hold_ses->request(
-        "open-ils.circ.hold_reset_note.create",
+        # If targeter is issued without a hold
+        # it will retarget all of the holds that need it
+        # so we shoot off a RRE for all them.                  
+               $hold_ses->request(
+        "open-ils.circ.hold_reset_reason_entry.create",
+               $single->editor()->authtoken,
         $hold_id,
-        "Hold Automatically Retargeted",
-        "Automatic Reset.") unless defined $args->{hold};
+        OILS_HOLD_TIMED_OUT) 
+        unless defined $args->{hold};
 
         # Don't let an explosion on a single hold stop processing
         eval { $single->target($hold_id) };
index 85e8b71..d81d3e8 100644 (file)
@@ -130,6 +130,19 @@ econst OILS_PENALTY_INVALID_PATRON_ADDRESS => 29;
 
 econst OILS_BILLING_TYPE_NOTIFICATION_FEE => 9;
 
+# ---------------------------------------------------------------------
+# Hold reset reasons
+# ---------------------------------------------------------------------
+econst OILS_HOLD_TIMED_OUT => 1;
+econst OILS_HOLD_MANUAL_RESET => 2;
+econst OILS_HOLD_BETTER_HOLD => 3;
+econst OILS_HOLD_FROZEN => 4;
+econst OILS_HOLD_UNFROZEN => 5;
+econst OILS_HOLD_CANCELED => 6;
+econst OILS_HOLD_UNCANCELED => 7;
+econst OILS_HOLD_UPDATED => 8;
+econst OILS_HOLD_CHECK_OUT => 9;
+econst OILS_HOLD_CHECK_IN => 10;
 
 
 # ---------------------------------------------------------------------