creates "reset reason entries" whenever a hold has been reset. Records
authorLlewellyn Marshall <llewellyn.marshall@ncdcr.gov>
Wed, 18 Aug 2021 17:13:47 +0000 (13:13 -0400)
committerLlewellyn Marshall <llewellyn.marshall@ncdcr.gov>
Wed, 22 Mar 2023 20:45:09 +0000 (16:45 -0400)
previous copy, requesting user/ws, reset time and reset reason. reset
reasons are stored in their own table and referenced in the perl
constants file. Hold reset reason entries can be inspected in the staff client by viewing
the "hold details" within a patron's profile

signed-off-by: Llewellyn Marshall <llewellyn.marshall@ncdcr.gov>

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
Open-ILS/src/sql/Pg/upgrade/xxxx.hold_reset_reasons.sql [new file with mode: 0644]
Open-ILS/src/templates/staff/circ/share/t_hold_details.tt2
Open-ILS/web/js/ui/default/staff/circ/services/holds.js

index 609a596..bd3b786 100644 (file)
@@ -6811,6 +6811,7 @@ SELECT  usr,
                        <field reporter:label="Requesting Library" name="request_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Request Date/Time" name="request_time" reporter:datatype="timestamp"/>
                        <field reporter:label="Requesting User" name="requestor" reporter:datatype="link"/>
+            <field reporter:label="Reset Entries" name="reset_entries" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Item Selection Depth" name="selection_depth" />
                        <field reporter:label="Selection Locus" name="selection_ou" reporter:datatype="org_unit"/>
                        <field reporter:label="Target Object ID" name="target" reporter:datatype="link"/>
@@ -6846,6 +6847,7 @@ SELECT  usr,
                        <link field="request_lib" reltype="has_a" key="id" map="" class="aou"/>
                        <link field="transit" reltype="might_have" key="hold" map="" class="ahtc"/>
                        <link field="notifications" reltype="has_many" key="hold" map="" class="ahn"/>
+                       <link field="reset_entries" reltype="has_many" key="hold" map="" class="ahrrre"/>
                        <link field="eligible_copies" reltype="has_many" key="hold" map="target_copy" class="ahcm"/>
                        <link field="bib_rec" reltype="might_have" key="id" map="" class="rhrr"/>
                        <link field="cancel_cause" reltype="might_have" key="id" map="" class="ahrcc"/>
@@ -15428,6 +15430,47 @@ SELECT  usr,
              <field reporter:label="Transaction Closed?" name="xact_closed" sr:suggest_filter="true" reporter:datatype="bool"/>
         </fields>
     </class>
+    
+       <class id="ahrrr" controller="open-ils.cstore open-ils.pcrud" 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>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <retrieve/>
+                       </actions>
+               </permacrud>
+       </class>
+
+       <class id="ahrrre" controller="open-ils.cstore open-ils.pcrud" 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="has_a" key="id" map="" class="au"/>    
+            <link field="requestor_workstation" reltype="has_a" key="id" map="" class="aws"/>            
+            <link field="previous_copy" reltype="has_a" key="id" map="" class="acp"/>        
+        </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                               <retrieve permission="VIEW_HOLD">
+                    <context link="pickup_lib" field="hold"/>
+                </retrieve>
+            </actions>
+        </permacrud>
+       </class>
+
 
        <!-- ********************************************************************************************************************* -->
 </IDL>
index 0e417c5..1a8e2a9 100644 (file)
@@ -1868,14 +1868,22 @@ sub handle_checkout_holds {
     
         $logger->info("circulator: un-targeting hold ".$hold->id.
             " because copy ".$copy->id." is getting checked out");
-
+        try {
+            $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);
+        } catch Error with {
+            $logger->error("circulate: create reset reason failed with ".shift());
+        };
         $hold->clear_prev_check_time; 
         $hold->clear_current_copy;
         $hold->clear_capture_time;
         $hold->clear_shelf_time;
         $hold->clear_shelf_expire_time;
         $hold->clear_current_shelf_lib;
-
         return $self->bail_on_event($e->event)
             unless $e->update_action_hold_request($hold);
 
@@ -2650,6 +2658,13 @@ sub checkin_retarget {
                 next if ($_->{hold_type} eq 'P');
             }
             # So much for easy stuff, attempt a retarget!
+            try{
+                $U->simplereq('open-ils.circ', 
+                'open-ils.circ.hold_reset_reason_entry.create',$self->editor->authtoken, $_->{id},OILS_HOLD_BETTER_HOLD);
+            }
+            catch Error with{
+                $logger->error("circulate: create reset reason failed with ".shift());
+            };
             my $tresult = $U->simplereq(
                 'open-ils.hold-targeter',
                 'open-ils.hold-targeter.target', 
@@ -3302,7 +3317,14 @@ sub attempt_checkin_hold_capture {
     $hold->clear_expire_time; 
     $hold->clear_cancel_time;
     $hold->clear_prev_check_time unless $hold->prev_check_time;
-
+    
+    try{
+        $U->simplereq('open-ils.circ', 
+        'open-ils.circ.hold_reset_reason_entry.create',$self->editor->authtoken, $hold->id, OILS_HOLD_CHECK_IN);
+    }
+    catch Error with{
+        $logger->error("circulate: create reset reason failed with ".shift());
+    };
     $self->bail_on_events($self->editor->event)
         unless $self->editor->update_action_hold_request($hold);
     $self->hold($hold);
@@ -3446,7 +3468,14 @@ 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');
-    $ses->request('open-ils.hold-targeter.target', {hold => $self->retarget});
+    $ses->request('open-ils.hold-targeter.target', {hold => $self->retarget});   
+    try{
+        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);
+    }
+    catch Error with{
+        $logger->error("circulate: create reset reason failed with ".shift());
+    };
     # no reason to wait for the return value
     return;
 }
index a11d0ab..9ac558a 100644 (file)
@@ -1024,13 +1024,12 @@ sub uncancel_hold {
     $hold->clear_prev_check_time;
     $hold->clear_shelf_expire_time;
     $hold->clear_current_shelf_lib;
-
+    _create_reset_reason_entry($e,$hold,OILS_HOLD_UNCANCELED);
     $e->update_action_hold_request($hold) or return $e->die_event;
     $e->commit;
-
+        
     $U->simplereq('open-ils.hold-targeter',
         'open-ils.hold-targeter.target', {hold => $hold_id});
-
     return 1;
 }
 
@@ -1058,11 +1057,13 @@ sub cancel_hold {
 
     my $e = new_editor(authtoken=>$auth, xact=>1);
     return $e->die_event unless $e->checkauth;
-
+    
+    my $req = $e->requestor->id;
+    
     my $hold = $e->retrieve_action_hold_request($holdid)
         or return $e->die_event;
 
-    if( $e->requestor->id ne $hold->usr ) {
+    if( $req ne $hold->usr ) {
         return $e->die_event unless $e->allowed('CANCEL_HOLDS');
     }
 
@@ -1106,6 +1107,21 @@ sub cancel_hold {
     $hold->cancel_time('now');
     $hold->cancel_cause($cause);
     $hold->cancel_note($note);
+    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;
 
@@ -1206,6 +1222,8 @@ 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) {
         $hold = $e->retrieve_action_hold_request($values->{id})
@@ -1217,9 +1235,13 @@ 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."
                 }
             }
             if (defined $values->{$k}) {
@@ -1325,9 +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);
+        _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.
@@ -1349,10 +1372,13 @@ sub update_hold_impl {
     }
 
     $e->update_action_hold_request($hold) or return $e->die_event;
+    _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});
     }
@@ -1360,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;
 }
 
@@ -1450,6 +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_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});
         }
@@ -2209,6 +2238,67 @@ sub reset_hold {
     return 1;
 }
 
+__PACKAGE__->register_method(
+    method   => 'create_reset_reason_entry',
+    api_name => 'open-ils.circ.hold_reset_reason_entry.create'
+);
+
+sub create_reset_reason_entry
+{
+    my($self, $conn, $auth, $hold, $reset_reason, $note, $previous_copy) = @_;
+    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){
+        try{
+            my ($hold, $evt) = $U->fetch_hold($holdid);  
+            _create_reset_reason_entry(
+                $e, 
+                $hold, 
+                $reset_reason, 
+                $note, 
+                $previous_copy) 
+            unless $evt;
+        }
+        catch Error with{
+            $logger->error("holds: create reset reason failed with ".shift());
+        };
+    }
+    $e->commit;
+    return 1;
+}
+
+sub _create_reset_reason_entry
+{
+    my($e, $hold, $reset_reason,$note,$previous_copy) = @_;
+    return 1 unless _reset_reason_entry_flag();
+    my $ts = DateTime->now;
+    my $entry = Fieldmapper::action::hold_request_reset_reason_entry->new; 
+    $logger->info("Creating reset reason entry for hold #" . $hold->id);    
+    my $last_copy = defined $previous_copy ? $previous_copy : $hold->current_copy;
+    $entry->hold($hold->id);
+    $entry->reset_reason($reset_reason);
+    $entry->reset_time('now');
+    $entry->previous_copy($last_copy);
+    $entry->note($note) if defined $note;
+    $entry->requestor($e->requestor->id) if defined $e->requestor;
+    $entry->requestor_workstation($e->requestor->wsid)  if defined $e->requestor;
+    $e->create_action_hold_request_reset_reason_entry($entry) or return $e->die_event;
+    return 1;
+}
+
+sub _reset_reason_entry_flag
+{
+    my $do_ahrrre = $U->get_global_flag('circ.holds.create_reset_reason_entries');
+    return ($do_ahrrre and $U->is_true($do_ahrrre->enabled));
+}
 
 __PACKAGE__->register_method(
     method   => 'reset_hold_batch',
@@ -2239,11 +2329,9 @@ sub _reset_hold {
     my ($self, $reqr, $hold) = @_;
 
     my $e = new_editor(xact =>1, requestor => $reqr);
-
-    $logger->info("reseting hold ".$hold->id);
-
     my $hid = $hold->id;
-
+    $logger->info("reseting hold ".$hid." requestor was ".$reqr->usrname." (ID ".$reqr->id.")");
+    my $note_body = "";
     if( $hold->capture_time and $hold->current_copy ) {
 
         my $copy = $e->retrieve_asset_copy($hold->current_copy)
@@ -2251,6 +2339,7 @@ sub _reset_hold {
 
         if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) {
             $logger->info("setting copy to status 'reshelving' on hold retarget");
+            $note_body.=" set copy to status 'reshelving'.";
             $copy->status(OILS_COPY_STATUS_RESHELVING);
             $copy->editor($e->requestor->id);
             $copy->edit_date('now');
@@ -2267,6 +2356,7 @@ sub _reset_hold {
                     $logger->info("Aborting transit [$transid] on hold [$hid] reset...");
                     my $evt = OpenILS::Application::Circ::Transit::__abort_transit($e, $trans, $copy, 1, 1);
                     $logger->info("Transit abort completed with result $evt");
+                    $note_body.=" Transit abort completed with result $evt.";
                     unless ("$evt" eq 1) {
                         $e->rollback;
                         return $evt;
@@ -2276,6 +2366,7 @@ sub _reset_hold {
         }
     }
 
+    _create_reset_reason_entry($e,$hold,OILS_HOLD_MANUAL_RESET,$note_body);
     $hold->clear_capture_time;
     $hold->clear_current_copy;
     $hold->clear_shelf_time;
@@ -2283,8 +2374,9 @@ sub _reset_hold {
     $hold->clear_current_shelf_lib;
 
     $e->update_action_hold_request($hold) or return $e->die_event;
+   
     $e->commit;
-
+    
     $U->simplereq('open-ils.hold-targeter', 
         'open-ils.hold-targeter.target', {hold => $hold->id});
 
@@ -3495,6 +3587,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_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 afca2fc..ad7ed5b 100644 (file)
@@ -4,7 +4,9 @@ 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);
+use OpenSRF::EX qw(:try);
 
 __PACKAGE__->register_method(
     method    => 'hold_targeter',
@@ -80,7 +82,8 @@ sub hold_targeter {
     my $total = scalar(@hold_ids);
 
     $logger->info("targeter processing $total holds");
-
+    my $hold_ses = create OpenSRF::AppSession("open-ils.circ");
+    
     for my $hold_id (@hold_ids) {
         $count++;
 
@@ -96,6 +99,27 @@ sub hold_targeter {
             $logger->error($msg);
             $single->message($msg) unless $single->message;
         }
+        else{
+            try{
+                # create a TIMED_OUT reset reason 
+                # other types of resets are handled
+                # at their sources. 
+                $hold_ses->request(
+                    "open-ils.circ.hold_reset_reason_entry.create",
+                    $single->editor()->authtoken,
+                    $hold_id,
+                    OILS_HOLD_TIMED_OUT,
+                    undef,
+                    $single->{previous_copy_id}
+                ) unless defined 
+                            $args->{hold} || 
+                            $single->{previous_copy_id} == $single->hold->current_copy;
+            } catch Error with {
+                $logger->error(
+                    "hold-targeter: create reset reason failed with ".shift()
+                );          
+            }
+        }
 
         if (($count % $throttle) == 0) { 
             # Time to reply to the caller.  Return either the number
@@ -105,9 +129,10 @@ sub hold_targeter {
             $client->respond($res);
 
             $logger->info("targeted $count of $total holds");
-        }
+        }        
     }
-
+    $hold_ses->disconnect;
+    
     return undef;
 }
 
index 0ff4880..093f1a6 100644 (file)
@@ -133,6 +133,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;
 
 # ---------------------------------------------------------------------
 # Non Evergreen-specific constants
diff --git a/Open-ILS/src/sql/Pg/upgrade/xxxx.hold_reset_reasons.sql b/Open-ILS/src/sql/Pg/upgrade/xxxx.hold_reset_reasons.sql
new file mode 100644 (file)
index 0000000..39ab13a
--- /dev/null
@@ -0,0 +1,68 @@
+CREATE TABLE action.hold_request_reset_reason
+(
+ id serial NOT NULL,
+ manual  boolean,
+ name text,
+CONSTRAINT hold_request_reset_reason_pkey PRIMARY KEY (id),
+CONSTRAINT hold_request_reset_reason_name_key UNIQUE (name)
+) WITH (
+  OIDS=FALSE
+);
+
+INSERT INTO action.hold_request_reset_reason (id, name, manual) VALUES
+(1,'HOLD_TIMED_OUT',false),
+(2,'HOLD_MANUAL_RESET',true),
+(3,'HOLD_BETTER_HOLD',false),
+(4,'HOLD_FROZEN',true),
+(5,'HOLD_UNFROZEN',true),
+(6,'HOLD_CANCELED',true),
+(7,'HOLD_UNCANCELED',true),
+(8,'HOLD_UPDATED',true),
+(9,'HOLD_CHECKED_OUT',true),
+(10,'HOLD_CHECKED_IN',true);
+
+CREATE TABLE action.hold_request_reset_reason_entry
+(
+  id serial NOT NULL,
+  hold int,
+  reset_reason int,
+  note text,
+  reset_time timestamp with time zone,
+  previous_copy bigint,
+  requestor int,
+  requestor_workstation int,
+  CONSTRAINT hold_request_reset_reason_entry_pkey PRIMARY KEY (id),
+  CONSTRAINT action_hold_request_reset_reason_entry_reason_fkey FOREIGN KEY (reset_reason)
+      REFERENCES action.hold_request_reset_reason (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,  
+CONSTRAINT action_hold_request_reset_reason_entry_previous_copy_fkey FOREIGN KEY (previous_copy)
+      REFERENCES asset.copy (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
+  CONSTRAINT action_hold_request_reset_reason_entry_requestor_fkey FOREIGN KEY (requestor)
+      REFERENCES actor.usr (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
+  CONSTRAINT action_hold_request_reset_reason_entry_requestor_workstation_fkey FOREIGN KEY (requestor_workstation)
+      REFERENCES actor.workstation (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
+  CONSTRAINT action_hold_request_reset_reason_entry_hold_fkey FOREIGN KEY (hold)
+      REFERENCES action.hold_request (id) MATCH SIMPLE
+      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED
+)
+
+WITH (
+  OIDS=FALSE
+);
+
+CREATE INDEX ahrrre_hold ON action.hold_request_reset_reason_entry (hold);
+
+INSERT INTO config.global_flag (name, label, enabled)
+    VALUES (
+        'circ.holds.create_reset_reason_entries',
+        oils_i18n_gettext(
+            'circ.holds.create_reset_reason_entries',
+            'Create reset reasons whenever a hold has been reset.',
+            'cgf',
+            'label'
+        ),
+        TRUE
+    );
index ece4719..7da498a 100644 (file)
       [% l('Staff Notifications') %]
     </a>
   </li>
+  <li ng-class="{active : detail_tab == 'resets'}">
+    <a href ng-click="show_resets_tab()">
+      [% l('Reset Entries') %]
+    </a>
+  </li>
 </ul>
 <div class="tab-content">
   <div class="tab-pane active">
         </div>
       </div>
     </div><!-- notes tab content -->
+    
+<div ng-if="detail_tab == 'resets'">
+        <div class="btn-group column-picker">
+            <!-- change order -->           
+            <button type="button" class="btn btn-default" 
+              ng-click="reverse_reset_order()"
+              title="[% l('Reverse Order') %]">
+                <span class="glyphicon {{reverseResetOrder ? 'glyphicon-chevron-up' : 'glyphicon-chevron-down' }}"></span>
+            </button>
+            
+            <!-- first page -->
+            <button type="button" class="btn btn-default" 
+              
+              ng-class="{disabled : on_first_rs_page()}" 
+              ng-disabled="on_first_rs_page()"
+              ng-click="first_rs_page()"
+              title="[% l('Start') %]">
+                <span class="glyphicon glyphicon-fast-backward"></span>
+            </button>
+
+            <!-- previous page -->
+            <button type="button" class="btn btn-default" 
+      
+              ng-class="{disabled : on_first_rs_page()}"
+              ng-disabled="on_first_rs_page()"
+              ng-click="decrement_rs_page()"
+              title="[% l('Previous Page') %]">
+                <span class="glyphicon glyphicon-backward"></span>
+            </button>
+
+            <!-- next page -->
+            <!-- todo: paging needs a total count value to be fully functional -->
+            <button type="button" class="btn btn-default" 
+
+              ng-class="{disabled : !has_next_rs_page()}"
+              ng-disabled="!has_next_rs_page()"
+              ng-click="increment_rs_page()"
+              title="[% l('Next Page') %]">
+                <span class="glyphicon glyphicon-forward"></span>
+            </button>
+        </div>
+      <div ng-show="!resetsLoaded" style="text-align:center;">      
+        <img src='[% ctx.media_prefix %]/opac/images/progressbar_green.gif[% ctx.cache_key %]' style='height:32px;width:32px;' alt='[% l("Search In Progress") %]'/>
+      </div>      
+      <div class="flex-container-striped flex-container-bordered" ng-show="resetsLoaded && filteredResets.length">
+        <div class="flex-row">
+            <div class="flex-cell strong-text">Time</div>
+            <div class="flex-cell strong-text">Reason</div>
+            <div class="flex-cell strong-text">Requestor</div>
+            <div class="flex-cell strong-text">Note</div>
+            <div class="flex-cell strong-text">Previous Copy</div>
+        </div>
+        <div class="flex-row" ng-repeat="reset in filteredResets">
+            <div class="flex-cell">{{reset.reset_time() | date:$root.egDateAndTimeFormat}}</div>
+            <div class="flex-cell">{{reset.reset_reason().name()}}</div>
+            <div class="flex-cell">{{reset.requestor().usrname()}}</div>
+            <div class="flex-cell">{{reset.note()}}</div>
+            <div class="flex-cell">
+                <a href="./cat/item/{{reset.previous_copy().id()}}" target="_self">
+                        {{reset.previous_copy().barcode()}}
+                </a>
+            </div>
+        </div>
+      </div>
+      <div class="flex-container-striped flex-container-bordered" ng-show="resetsLoaded && !filteredResets.length">
+        <div class="flex-row">
+            [%- l('No reset entries found for this hold.') -%]
+        </div>
+      </div>
+    </div><!-- resets tab content -->
 
   </div><!-- tab pane -->
 </div><!-- tab-content -->
index 251ceb2..6779376 100644 (file)
@@ -858,7 +858,85 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) {
 
                     });
                 }
+                
+                $scope.resetPage = 1;
+                $scope.resetsPerPage = 10;
+                $scope.maximumPages = 25;
+                $scope.resetsLoaded = false;
+                $scope.reverseResetOrder = false;
+                
+                $scope.show_resets_tab = function() {
+                    $scope.detail_tab = 'resets';
+                    egCore.pcrud.search('ahrrre',
+                        {hold : $scope.hold.id()}, 
+                        {
+                            flesh : 1, 
+                            flesh_fields : {ahrrre : ['reset_reason','requestor','previous_copy']},                          
+                            limit : $scope.resetsPerPage * $scope.maximumPages
+                        },
+                        {atomic : true}
+                    ).then(function(ents) {
+                        // sort the reset notes by date
+                        ents.sort(
+                            function(a,b){
+                                return Date.parse(a.reset_time()) - Date.parse(b.reset_time());
+                            }
+                        );
+                        $scope.hold.reset_entries(ents);
+                        $scope.filter_resets();
+                        $scope.resetsLoaded = true;
+                    });
+                }
+                
+                $scope.filter_resets = function(){
+                    if(
+                        typeof($scope.hold) === 'undefined' || 
+                        typeof($scope.hold.reset_entries) === 'undefined' || 
+                        $scope.hold.reset_entries() === null
+                    )
+                        return;
+                    var begin = (($scope.resetPage - 1) * $scope.resetsPerPage),
+                        end = begin + $scope.resetsPerPage;
+                    $scope.filteredResets = $scope.hold
+                                                .reset_entries()
+                                                .slice(begin,end);
+                }
+                
+                $scope.reverse_reset_order = function(){
+                    $scope.hold.reset_entries().reverse()
+                    $scope.reverseResetOrder = !$scope.reverseResetOrder;
+                    $scope.first_rs_page();
+                }
+                
+                $scope.on_first_rs_page = function(){
+                    return $scope.resetPage == 1;
+                } 
+                
+                $scope.has_next_rs_page = function(){
+                    return $scope.resetPage < $scope.max_rs_pages();                
+                }
+                
+                $scope.max_rs_pages = function(){
+                    if(typeof($scope.hold.reset_entries) === 'undefined' || $scope.hold.reset_entries() === null)
+                        return 0;
+                    return $scope.hold.reset_entries().length/$scope.resetsPerPage;
+                }
+                
+                $scope.first_rs_page = function() {
+                    $scope.resetPage = 1;
+                }  
+                
+                $scope.increment_rs_page = function() {
+                    $scope.resetPage++;
+                }
 
+                $scope.decrement_rs_page = function() {
+                    $scope.resetPage--;
+                }  
+                
+                $scope.$watch('resetPage',$scope.filter_resets);
+                $scope.$watch('reverseResetOrder',$scope.filter_resets);
+                
                 $scope.show_notify_tab = function() {
                     $scope.detail_tab = 'notify';
                     egCore.pcrud.search('ahn',