diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb
index 02a881a48af..23628e730c7 100644
--- a/app/models/organizations/business_line.rb
+++ b/app/models/organizations/business_line.rb
@@ -126,6 +126,15 @@ class QueryBuilder
}
}.freeze
+ USER_TABLE_ALIASES = [
+ :intake_users,
+ :update_users,
+ :decision_users,
+ :decision_users_completed_by,
+ :requestor,
+ :decider
+ ].freeze
+
def initialize(query_type: :in_progress, parent: business_line, query_params: {})
@query_type = query_type
@parent = parent
@@ -210,9 +219,9 @@ def issue_type_count
nonrating_issue_count = ActiveRecord::Base.connection.execute <<-SQL
WITH task_review_issues AS (
#{hlr_query.to_sql}
- UNION ALL
+ UNION
#{sc_query.to_sql}
- UNION ALL
+ UNION
#{appeals_query.to_sql}
)
SELECT issue_category, COUNT(1) AS nonrating_issue_count
@@ -246,7 +255,7 @@ def change_history_rows
SELECT
versions.item_id,
versions.item_type,
- ARRAY_AGG(versions.object_changes ORDER BY versions.id) AS object_changes_array,
+ STRING_AGG(versions.object_changes, '|||' ORDER BY versions.id) AS object_changes_array,
MAX(CASE
WHEN versions.object_changes LIKE '%closed_at:%' THEN versions.whodunnit
ELSE NULL
@@ -259,8 +268,48 @@ def change_history_rows
AND tasks.assigned_to_id = '#{parent.id.to_i}'
GROUP BY
versions.item_id, versions.item_type
+ ), imr_version_agg AS (SELECT
+ versions.item_id,
+ versions.item_type,
+ STRING_AGG(versions.object, '|||' ORDER BY versions.id) AS object_array,
+ STRING_AGG(versions.object_changes, '|||' ORDER BY versions.id) AS object_changes_array
+ FROM
+ versions
+ INNER JOIN issue_modification_requests ON issue_modification_requests.id = versions.item_id
+ WHERE versions.item_type = 'IssueModificationRequest'
+ GROUP BY
+ versions.item_id, versions.item_type
+ ), imr_distinct AS (
+ SELECT DISTINCT ON (imr_cte.id)
+ imr_cte.id,
+ imr_cte.decided_at,
+ imr_cte.created_at,
+ imr_cte.decision_review_type,
+ imr_cte.decision_review_id,
+ imr_cte.status,
+ imr_cte.updated_at
+ FROM issue_modification_requests imr_cte
+ ), imr_lead_decided AS (
+ SELECT id,
+ decision_review_id,
+ LEAD(
+ CASE
+ WHEN status = 'cancelled' THEN updated_at
+ ELSE decided_at
+ END,
+ 1,
+ '9999-12-31 23:59:59' -- Fake value to indicate out of bounds
+ ) OVER (PARTITION BY decision_review_id, decision_review_type ORDER BY decided_at, created_at DESC) AS next_decided_or_cancelled_at
+ FROM imr_distinct
+ ), imr_lead_created AS (
+ SELECT id,
+ LEAD(created_at, 1, '9999-12-31 23:59:59') OVER (PARTITION BY decision_review_id, decision_review_type ORDER BY created_at ASC) AS next_created_at
+ FROM imr_distinct
)
- SELECT tasks.id AS task_id, tasks.status AS task_status, request_issues.id AS request_issue_id,
+ SELECT tasks.id AS task_id,
+ check_imr_current_status.is_assigned_present,
+ tasks.status AS task_status,
+ request_issues.id AS request_issue_id,
request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description,
request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id,
request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at,
@@ -283,7 +332,38 @@ def change_history_rows
NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '),
NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '),
bgs_attorneys.name
- ) AS claimant_name, 'HigherLevelReview' AS type_classifier
+ ) AS claimant_name,
+ 'HigherLevelReview' AS type_classifier,
+ imr.id AS issue_modification_request_id,
+ imr.nonrating_issue_category AS requested_issue_type,
+ imr.nonrating_issue_description As requested_issue_description,
+ imr.remove_original_issue,
+ imr.request_reason AS modification_request_reason,
+ imr.decision_date AS requested_decision_date,
+ imr.request_type AS request_type,
+ imr.status AS issue_modification_request_status,
+ imr.decision_reason AS decision_reason,
+ imr.decider_id decider_id,
+ imr.requestor_id as requestor_id,
+ CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END AS decided_at,
+ imr.created_at AS issue_modification_request_created_at,
+ imr.updated_at AS issue_modification_request_updated_at,
+ imr.edited_at AS issue_modification_request_edited_at,
+ imr.withdrawal_date AS issue_modification_request_withdrawal_date,
+ imr.decision_review_id AS decision_review_id,
+ imr.decision_review_type AS decision_review_type,
+ requestor.full_name AS requestor,
+ requestor.station_id AS requestor_station_id,
+ requestor.css_id AS requestor_css_id,
+ decider.full_name AS decider,
+ decider.station_id AS decider_station_id,
+ decider.css_id AS decider_css_id,
+ itv.object_changes_array AS imr_versions,
+ LAG(imr.created_at, 1) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) AS previous_imr_created_at,
+ LAG(CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) AS previous_imr_decided_at,
+ itv.object_array as previous_state_array,
+ imr_lead_decided.next_decided_or_cancelled_at,
+ imr_lead_created.next_created_at
FROM tasks
INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type
AND request_issues.decision_review_id = tasks.appeal_id
@@ -293,6 +373,18 @@ def change_history_rows
AND intakes.detail_id = tasks.appeal_id
LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type
AND request_issues_updates.review_id = tasks.appeal_id
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM issue_modification_requests imr
+ WHERE imr.decision_review_id = tasks.appeal_id
+ AND imr.decision_review_type = 'HigherLevelReview'
+ AND (
+ imr.request_issue_id = request_issues.id
+ OR imr.request_type = 'addition'
+ )
+ ) imr ON true
+ LEFT JOIN imr_lead_decided ON imr_lead_decided.id = imr.id
+ LEFT JOIN imr_lead_created ON imr_lead_created.id = imr.id
LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id
LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id
AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id
@@ -307,63 +399,136 @@ def change_history_rows
LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id
LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int
LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id
+ LEFT JOIN users requestor ON imr.requestor_id = requestor.id
+ LEFT JOIN users decider ON imr.decider_id = decider.id
+ LEFT JOIN imr_version_agg itv ON itv.item_type = 'IssueModificationRequest' AND itv.item_id = imr.id
+ LEFT JOIN LATERAL (
+ SELECT CASE
+ WHEN EXISTS (
+ SELECT 1
+ FROM issue_modification_requests imr
+ WHERE imr.decision_review_id = request_issues.decision_review_id
+ AND imr.decision_review_type = 'HigherLevelReview'
+ AND imr.status = 'assigned'
+ ) THEN true
+ ELSE false
+ END AS is_assigned_present
+ ) check_imr_current_status on true
WHERE tasks.type = 'DecisionReviewTask'
AND tasks.assigned_to_type = 'Organization'
AND tasks.assigned_to_id = '#{parent.id.to_i}'
#{sanitized_filters}
- UNION ALL
- SELECT tasks.id AS task_id, tasks.status AS task_status, request_issues.id AS request_issue_id,
- request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description,
- request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id,
- request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at,
- intakes.completed_at AS intake_completed_at, update_users.full_name AS update_user_name, tasks.created_at AS task_created_at,
- intake_users.full_name AS intake_user_name, update_users.station_id AS update_user_station_id, tasks.closed_at AS task_closed_at,
- intake_users.station_id AS intake_user_station_id, decision_issues.created_at AS decision_created_at,
- COALESCE(decision_users.station_id, decision_users_completed_by.station_id) AS decision_user_station_id,
- COALESCE(decision_users.full_name, decision_users_completed_by.full_name) AS decision_user_name,
- COALESCE(decision_users.css_id, decision_users_completed_by.css_id) AS decision_user_css_id,
- intake_users.css_id AS intake_user_css_id, update_users.css_id AS update_user_css_id,
- request_issues_updates.before_request_issue_ids, request_issues_updates.after_request_issue_ids,
- request_issues_updates.withdrawn_request_issue_ids, request_issues_updates.edited_request_issue_ids,
- decision_issues.caseflow_decision_date, request_issues.decision_date_added_at,
- tasks.appeal_type, tasks.appeal_id, request_issues.nonrating_issue_category, request_issues.nonrating_issue_description,
- request_issues.decision_date, decision_issues.disposition, tasks.assigned_at, request_issues.unidentified_issue_text,
- request_decision_issues.decision_issue_id, request_issues.closed_at AS request_issue_closed_at,
- tv.object_changes_array AS task_versions, (CURRENT_TIMESTAMP::date - tasks.assigned_at::date) AS days_waiting,
- COALESCE(intakes.veteran_file_number, supplemental_claims.veteran_file_number) AS veteran_file_number,
- COALESCE(
- NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '),
- NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '),
- bgs_attorneys.name
- ) AS claimant_name, supplemental_claims.type AS type_classifier
- FROM tasks
- INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type
- AND request_issues.decision_review_id = tasks.appeal_id
- INNER JOIN supplemental_claims ON tasks.appeal_type = 'SupplementalClaim'
- AND tasks.appeal_id = supplemental_claims.id
- LEFT JOIN intakes ON tasks.appeal_type = intakes.detail_type
- AND intakes.detail_id = tasks.appeal_id
- LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type
- AND request_issues_updates.review_id = tasks.appeal_id
- LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id
- LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id
- AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id
- LEFT JOIN claimants ON claimants.decision_review_id = tasks.appeal_id
- AND claimants.decision_review_type = tasks.appeal_type
- LEFT JOIN versions_agg tv ON tv.item_type = 'Task' AND tv.item_id = tasks.id
- LEFT JOIN people ON claimants.participant_id = people.participant_id
- LEFT JOIN bgs_attorneys ON claimants.participant_id = bgs_attorneys.participant_id
- LEFT JOIN unrecognized_appellants ON claimants.id = unrecognized_appellants.claimant_id
- LEFT JOIN unrecognized_party_details ON unrecognized_appellants.unrecognized_party_detail_id = unrecognized_party_details.id
- LEFT JOIN users intake_users ON intakes.user_id = intake_users.id
- LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id
- LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int
- LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id
- WHERE tasks.type = 'DecisionReviewTask'
- AND tasks.assigned_to_type = 'Organization'
- AND tasks.assigned_to_id = '#{parent.id.to_i}'
- #{sanitized_filters}
- #{sc_type_clauses}
+ UNION ALL
+ SELECT tasks.id AS task_id, check_imr_current_status.is_assigned_present, tasks.status AS task_status, request_issues.id AS request_issue_id,
+ request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description,
+ request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id,
+ request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at,
+ intakes.completed_at AS intake_completed_at, update_users.full_name AS update_user_name, tasks.created_at AS task_created_at,
+ intake_users.full_name AS intake_user_name, update_users.station_id AS update_user_station_id, tasks.closed_at AS task_closed_at,
+ intake_users.station_id AS intake_user_station_id, decision_issues.created_at AS decision_created_at,
+ COALESCE(decision_users.station_id, decision_users_completed_by.station_id) AS decision_user_station_id,
+ COALESCE(decision_users.full_name, decision_users_completed_by.full_name) AS decision_user_name,
+ COALESCE(decision_users.css_id, decision_users_completed_by.css_id) AS decision_user_css_id,
+ intake_users.css_id AS intake_user_css_id, update_users.css_id AS update_user_css_id,
+ request_issues_updates.before_request_issue_ids, request_issues_updates.after_request_issue_ids,
+ request_issues_updates.withdrawn_request_issue_ids, request_issues_updates.edited_request_issue_ids,
+ decision_issues.caseflow_decision_date, request_issues.decision_date_added_at,
+ tasks.appeal_type, tasks.appeal_id, request_issues.nonrating_issue_category, request_issues.nonrating_issue_description,
+ request_issues.decision_date, decision_issues.disposition, tasks.assigned_at, request_issues.unidentified_issue_text,
+ request_decision_issues.decision_issue_id, request_issues.closed_at AS request_issue_closed_at,
+ tv.object_changes_array AS task_versions, (CURRENT_TIMESTAMP::date - tasks.assigned_at::date) AS days_waiting,
+ COALESCE(intakes.veteran_file_number, supplemental_claims.veteran_file_number) AS veteran_file_number,
+ COALESCE(
+ NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '),
+ NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '),
+ bgs_attorneys.name
+ ) AS claimant_name,
+ supplemental_claims.type AS type_classifier,
+ imr.id AS issue_modification_request_id,
+ imr.nonrating_issue_category AS requested_issue_type,
+ imr.nonrating_issue_description As requested_issue_description,
+ imr.remove_original_issue,
+ imr.request_reason AS modification_request_reason,
+ imr.decision_date AS requested_decision_date,
+ imr.request_type AS request_type,
+ imr.status AS issue_modification_request_status,
+ imr.decision_reason AS decision_reason,
+ imr.decider_id AS decider_id,
+ imr.requestor_id AS requestor_id,
+ CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END AS decided_at,
+ imr.created_at AS issue_modification_request_created_at,
+ imr.updated_at AS issue_modification_request_updated_at,
+ imr.edited_at AS issue_modification_request_edited_at,
+ imr.withdrawal_date AS issue_modification_request_withdrawal_date,
+ imr.decision_review_id AS decision_review_id,
+ imr.decision_review_type AS decision_review_type,
+ requestor.full_name AS requestor,
+ requestor.station_id AS requestor_station_id,
+ requestor.css_id AS requestor_css_id,
+ decider.full_name AS decider,
+ decider.station_id AS decider_station_id,
+ decider.css_id AS decider_css_id,
+ itv.object_changes_array AS imr_versions,
+ LAG(imr.created_at, 1) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) AS previous_imr_created_at,
+ LAG(CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) AS previous_imr_decided_at,
+ itv.object_array as previous_state_array,
+ imr_lead_decided.next_decided_or_cancelled_at,
+ imr_lead_created.next_created_at
+ FROM tasks
+ INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type
+ AND request_issues.decision_review_id = tasks.appeal_id
+ INNER JOIN supplemental_claims ON tasks.appeal_type = 'SupplementalClaim'
+ AND tasks.appeal_id = supplemental_claims.id
+ LEFT JOIN intakes ON tasks.appeal_type = intakes.detail_type
+ AND intakes.detail_id = tasks.appeal_id
+ LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type
+ AND request_issues_updates.review_id = tasks.appeal_id
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM issue_modification_requests imr
+ WHERE imr.decision_review_id = tasks.appeal_id
+ AND imr.decision_review_type = 'SupplementalClaim'
+ AND (
+ imr.request_issue_id = request_issues.id
+ OR imr.request_type = 'addition'
+ )
+ ) imr ON true
+ LEFT JOIN imr_lead_decided ON imr_lead_decided.id = imr.id
+ LEFT JOIN imr_lead_created ON imr_lead_created.id = imr.id
+ LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id
+ LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id
+ AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id
+ LEFT JOIN claimants ON claimants.decision_review_id = tasks.appeal_id
+ AND claimants.decision_review_type = tasks.appeal_type
+ LEFT JOIN versions_agg tv ON tv.item_type = 'Task' AND tv.item_id = tasks.id
+ LEFT JOIN people ON claimants.participant_id = people.participant_id
+ LEFT JOIN bgs_attorneys ON claimants.participant_id = bgs_attorneys.participant_id
+ LEFT JOIN unrecognized_appellants ON claimants.id = unrecognized_appellants.claimant_id
+ LEFT JOIN unrecognized_party_details ON unrecognized_appellants.unrecognized_party_detail_id = unrecognized_party_details.id
+ LEFT JOIN users intake_users ON intakes.user_id = intake_users.id
+ LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id
+ LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int
+ LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id
+ LEFT JOIN users requestor ON imr.requestor_id = requestor.id
+ LEFT JOIN users decider ON imr.decider_id = decider.id
+ LEFT JOIN imr_version_agg itv ON itv.item_type = 'IssueModificationRequest' AND itv.item_id = imr.id
+ LEFT JOIN LATERAL (
+ SELECT CASE
+ WHEN EXISTS (
+ SELECT 1
+ FROM issue_modification_requests imr
+ WHERE imr.decision_review_id = request_issues.decision_review_id
+ AND imr.decision_review_type = 'SupplementalClaim'
+ AND imr.status = 'assigned'
+ ) THEN true
+ ELSE false
+ END AS is_assigned_present
+ ) check_imr_current_status on true
+ WHERE tasks.type = 'DecisionReviewTask'
+ AND tasks.assigned_to_type = 'Organization'
+ AND tasks.assigned_to_id = '#{parent.id.to_i}'
+ #{sanitized_filters}
+ #{sc_type_clauses}
SQL
ActiveRecord::Base.transaction do
@@ -395,12 +560,40 @@ def change_history_sql_filter_array
def task_status_filter
if query_params[:task_status].present?
- " AND #{where_clause_from_array(Task, :status, query_params[:task_status]).to_sql}"
+ task_specific_status_filter
else
" AND tasks.status IN ('assigned', 'in_progress', 'on_hold', 'completed', 'cancelled') "
end
end
+ def task_specific_status_filter
+ if query_params[:task_status].include?("pending")
+ task_status_pending_filter
+ else
+ task_status_without_pending_filter
+ end
+ end
+
+ def task_status_pending_filter
+ <<-SQL
+ AND (
+ (imr.id IS NOT NULL AND imr.status = 'assigned')
+ OR #{where_clause_from_array(Task, :status, query_params[:task_status].uniq).to_sql}
+ )
+ SQL
+ end
+
+ def task_status_without_pending_filter
+ <<-SQL
+ AND NOT EXISTS(
+ SELECT decision_review_id FROM issue_modification_requests WHERE
+ issue_modification_requests.status = 'assigned'
+ AND issue_modification_requests.decision_review_id = tasks.appeal_id
+ AND tasks.appeal_type = issue_modification_requests.decision_review_type)
+ AND #{where_clause_from_array(Task, :status, query_params[:task_status].uniq).to_sql}
+ SQL
+ end
+
def claim_type_filter
if query_params[:claim_type].present?
temp_claim_types = query_params[:claim_type].dup
@@ -476,19 +669,16 @@ def days_waiting_filter
end
end
- # rubocop:disable Metrics/AbcSize
def station_id_filter
if query_params[:facilities].present?
+ conditions = USER_TABLE_ALIASES.map do |alias_name|
+ User.arel_table.alias(alias_name)[:station_id].in(query_params[:facilities]).to_sql
+ end
+
<<-SQL
AND
(
- #{User.arel_table.alias(:intake_users)[:station_id].in(query_params[:facilities]).to_sql}
- OR
- #{User.arel_table.alias(:update_users)[:station_id].in(query_params[:facilities]).to_sql}
- OR
- #{User.arel_table.alias(:decision_users)[:station_id].in(query_params[:facilities]).to_sql}
- OR
- #{User.arel_table.alias(:decision_users_completed_by)[:station_id].in(query_params[:facilities]).to_sql}
+ #{conditions.join(' OR ')}
)
SQL
end
@@ -496,21 +686,18 @@ def station_id_filter
def user_css_id_filter
if query_params[:personnel].present?
+ conditions = USER_TABLE_ALIASES.map do |alias_name|
+ User.arel_table.alias(alias_name)[:css_id].in(query_params[:personnel]).to_sql
+ end
+
<<-SQL
AND
(
- #{User.arel_table.alias(:intake_users)[:css_id].in(query_params[:personnel]).to_sql}
- OR
- #{User.arel_table.alias(:update_users)[:css_id].in(query_params[:personnel]).to_sql}
- OR
- #{User.arel_table.alias(:decision_users)[:css_id].in(query_params[:personnel]).to_sql}
- OR
- #{User.arel_table.alias(:decision_users_completed_by)[:css_id].in(query_params[:personnel]).to_sql}
+ #{conditions.join(' OR ')}
)
SQL
end
end
- # rubocop:enable Metrics/AbcSize
#################### End of Change history filter helpers ########################
diff --git a/app/services/claim_change_history/change_history_event_serializer.rb b/app/services/claim_change_history/change_history_event_serializer.rb
index 5b8bca631ce..5bd16ea05e9 100644
--- a/app/services/claim_change_history/change_history_event_serializer.rb
+++ b/app/services/claim_change_history/change_history_event_serializer.rb
@@ -24,4 +24,27 @@ class ChangeHistoryEventSerializer
withdrawalRequestDate: object.withdrawal_request_date
}
end
+
+ attribute :modificationRequestDetails do |object|
+ {
+ requestType: object.request_type,
+ benefitType: object.benefit_type,
+ newIssueType: object.new_issue_type,
+ newIssueDescription: object.new_issue_description,
+ newDecisionDate: object.new_decision_date,
+ modificationRequestReason: object.modification_request_reason,
+ issueModificationRequestWithdrawalDate: object.issue_modification_request_withdrawal_date,
+ removeOriginalIssue: object.remove_original_issue,
+ issueModificationRequestStatus: object.issue_modification_request_status,
+ requestor: object.requestor,
+ decider: object.decider,
+ decidedAtDate: object.decided_at_date,
+ decisionReason: object.decision_reason,
+ previousIssueType: object.previous_issue_type,
+ previousIssueDescription: object.previous_issue_description,
+ previousDecisionDate: object.previous_decision_date,
+ previousModificationRequestReason: object.previous_modification_request_reason,
+ previousWithdrawalDate: object.previous_withdrawal_date
+ }
+ end
end
diff --git a/app/services/claim_change_history/change_history_filter_parser.rb b/app/services/claim_change_history/change_history_filter_parser.rb
index 2e423e43e13..4fbcd6b2826 100644
--- a/app/services/claim_change_history/change_history_filter_parser.rb
+++ b/app/services/claim_change_history/change_history_filter_parser.rb
@@ -25,6 +25,7 @@ def parse_filters
private
+ # rubocop:disable Metrics/MethodLength
def events_filter_helper
event_mapping = {
"added_decision_date" => :added_decision_date,
@@ -34,21 +35,32 @@ def events_filter_helper
"claim_closed" => [:completed, :cancelled],
"claim_status_incomplete" => :incomplete,
"claim_status_inprogress" => :in_progress,
+ "claim_status_pending" => :pending,
"completed_disposition" => :completed_disposition,
"removed_issue" => :removed_issue,
"withdrew_issue" => :withdrew_issue,
- "claim_cancelled" => :cancelled
+ "claim_cancelled" => :cancelled,
+ "requested_issue_modification" => :modification,
+ "requested_issue_addition" => :addition,
+ "requested_issue_removal" => :removal,
+ "requested_issue_withdrawal" => :withdrawal,
+ "approval_of_request" => :request_approved,
+ "rejection_of_request" => :request_denied,
+ "cancellation_of_request" => :request_cancelled,
+ "edit_of_request" => :request_edited
}
filter_params[:events]&.values&.map { |event_type| event_mapping[event_type] }&.flatten
end
+ # rubocop:enable Metrics/MethodLength
def task_status_filter_helper
status_mapping = {
"incomplete" => "on_hold",
"in_progress" => %w[assigned in_progress],
"completed" => "completed",
- "cancelled" => "cancelled"
+ "cancelled" => "cancelled",
+ "pending" => "pending"
}
filter_params[:statuses]&.values&.map { |task_status| status_mapping[task_status] }&.flatten
diff --git a/app/services/claim_change_history/change_history_reporter.rb b/app/services/claim_change_history/change_history_reporter.rb
index 68cf043c19c..8025b63d60e 100644
--- a/app/services/claim_change_history/change_history_reporter.rb
+++ b/app/services/claim_change_history/change_history_reporter.rb
@@ -18,7 +18,12 @@ class ChangeHistoryReporter
Edit\ Action
Issue\ Type
Issue\ Description
- Prior\ Decision\ Date
+ Decision\ Date
+ New\ Issue\ Type
+ New\ Issue\ Description
+ New\ Decision\ Date
+ Request\ Reason
+ Reason\ for\ Rejection
Disposition
Disposition\ Description
Disposition\ Date
diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb
index 010273a561b..e19ac3ed289 100644
--- a/app/services/claim_change_history/claim_history_event.rb
+++ b/app/services/claim_change_history/claim_history_event.rb
@@ -15,7 +15,12 @@ class ClaimHistoryEvent
:benefit_type, :issue_type, :issue_description, :decision_date,
:disposition, :decision_description, :withdrawal_request_date,
:task_status, :disposition_date, :intake_completed_date, :event_user_name,
- :event_user_css_id
+ :event_user_css_id, :new_issue_type, :new_issue_description, :new_decision_date,
+ :modification_request_reason, :request_type, :decision_reason, :decided_at_date,
+ :issue_modification_request_withdrawal_date, :requestor,
+ :decider, :remove_original_issue, :issue_modification_request_status,
+ :previous_issue_type, :previous_issue_description, :previous_decision_date,
+ :previous_modification_request_reason, :previous_withdrawal_date
EVENT_TYPES = [
:completed_disposition,
@@ -28,7 +33,16 @@ class ClaimHistoryEvent
:in_progress,
:completed,
:incomplete,
- :cancelled
+ :cancelled,
+ :pending,
+ :modification,
+ :addition,
+ :withdrawal,
+ :removal,
+ :request_approved,
+ :request_denied,
+ :request_cancelled,
+ :request_edited
].freeze
ISSUE_EVENTS = [
@@ -47,10 +61,31 @@ class ClaimHistoryEvent
:added_decision_date
].freeze
- STATUS_EVENTS = [:in_progress, :incomplete, :completed, :claim_creation, :cancelled].freeze
+ STATUS_EVENTS = [
+ :completed,
+ :claim_creation,
+ :cancelled,
+ :in_progress,
+ :incomplete,
+ :pending
+ ].freeze
+
+ REQUEST_ISSUE_MODIFICATION_EVENTS = [
+ :modification,
+ :addition,
+ :withdrawal,
+ :removal,
+ :request_approved,
+ :request_denied,
+ :request_cancelled,
+ :request_edited
+ ].freeze
REQUEST_ISSUE_TIME_WINDOW = 15
STATUS_EVENT_TIME_WINDOW = 2
+ ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW = 60
+ # Used to signal when the database lead function is out of bounds
+ OUT_OF_BOUNDS_LEAD_TIME = Time.utc(9999, 12, 31, 23, 59, 59)
class << self
def from_change_data(event_type, change_data)
@@ -73,11 +108,242 @@ def create_claim_creation_event(change_data)
from_change_data(:claim_creation, change_data.merge(intake_event_hash(change_data)))
end
+ def create_issue_modification_request_event(change_data)
+ issue_modification_events = []
+ request_type = change_data["request_type"]
+ event_hash = request_issue_modification_event_hash(change_data)
+
+ if change_data["previous_state_array"].present?
+ first_version = parse_versions(change_data["previous_state_array"])[0]
+ event_hash.merge!(update_event_hash_data_from_version_object(first_version))
+ end
+
+ if request_type == "addition"
+ change_data = issue_attributes_for_request_type_addition(change_data)
+ end
+
+ issue_modification_events.push from_change_data(request_type.to_sym, change_data.merge(event_hash))
+ end
+
+ def create_edited_request_issue_events(change_data)
+ edited_events = []
+ imr_versions = parse_versions(change_data["imr_versions"])
+ previous_version = parse_versions(change_data["previous_state_array"])
+
+ if imr_versions.present?
+ *rest_of_versions, last_version = imr_versions
+
+ if last_version["status"].present?
+ edited_events.push(*create_last_version_events(change_data, last_version))
+ else
+ rest_of_versions.push(last_version)
+ end
+ edited_events.push(*create_event_from_rest_of_versions(change_data, rest_of_versions, previous_version))
+ else
+ create_pending_status_event(change_data, change_data["issue_modification_request_updated_at"])
+ end
+ edited_events
+ end
+
+ def create_event_from_rest_of_versions(change_data, edited_versions, previous_version)
+ edit_of_request_events = []
+ event_type = :request_edited
+ event_date_hash = {}
+ edited_versions.map.with_index do |version, index|
+ event_date_hash = request_issue_modification_event_hash(change_data)
+ .merge("event_date" => version["updated_at"][1])
+ # this create_event_from_version_object updated the previous version fields in change data
+ # that is being used in the front end to show the original records.
+ if !previous_version[index].nil?
+ event_date_hash.merge!(create_event_from_version_object(previous_version[index]))
+ # this update_event_hash_data_from_version_object updates the change_data values with previous or
+ # unedited data. since change_data has the final version of the data that was updated.
+ # this is necessary to preserve the history that is displayed in the frontend.
+ event_date_hash.merge!(update_event_hash_data_from_version_object(previous_version[index]))
+ end
+
+ event_date_hash.merge!(update_event_hash_data_from_version(version, 1))
+ edit_of_request_events.push(*from_change_data(event_type, change_data.merge(event_date_hash)))
+ end
+ edit_of_request_events
+ end
+
+ def create_last_version_events(change_data, last_version)
+ edited_events = []
+
+ last_version["status"].map.with_index do |status, index|
+ if status == "assigned"
+ edited_events.push(*create_pending_status_event(change_data, last_version["updated_at"][index]))
+ else
+ edited_events.push(*create_request_issue_decision_events(
+ change_data, last_version["updated_at"][index], status
+ ))
+ end
+ end
+ edited_events
+ end
+
+ def create_request_issue_decision_events(change_data, event_date, event)
+ events = []
+ event_user = change_data["decider"] || change_data["requestor"]
+
+ decision_event_hash = pending_system_hash
+ .merge("event_date" => event_date,
+ "event_user_name" => event_user,
+ "user_facility" => change_data["decider_station_id"] || change_data["requestor_station_id"],
+ "event_user_css_id" => change_data["decider_css_id"] || change_data["requestor_css_id"])
+
+ change_data = issue_attributes_for_request_type_addition(change_data) if change_data["request_type"] == "addition"
+
+ request_event_type = "request_#{event}"
+ events.push from_change_data(request_event_type.to_sym, change_data.merge(decision_event_hash))
+
+ events.push create_imr_in_progress_status_event(change_data)
+ events
+ end
+
+ def create_imr_in_progress_status_event(change_data)
+ in_progress_system_hash_events = pending_system_hash
+ .merge("event_date" => (change_data["decided_at"] ||
+ change_data["issue_modification_request_updated_at"]))
+
+ # If the imr is not decided, then always skip in progress creation
+ if imr_decided_or_cancelled?(change_data) && create_imr_in_progress_status_event?(change_data)
+ from_change_data(:in_progress, change_data.merge(in_progress_system_hash_events))
+ end
+ end
+
+ def create_imr_in_progress_status_event?(change_data)
+ # If the next imr is already decided in the same transaction, it's not in reverse order, and it's
+ # not the last imr then defer creation
+ return false if early_deferral?(change_data)
+
+ if do_not_defer_in_progress_creation?(change_data)
+ # If it's in reverse order and the creation of the next imr is after the current decision time then generate
+ # an event since the next imr will start a new pending/in progress loop
+ # Or
+ # If the next created by was after the decided_at then, this was an in progress transition so create one
+ # Or
+ # If it's the last IMR and the next imr was decided or cancelled in the same transaction then go ahead
+ # and generate an in progress event since the ordering is odd due to the decided at in the same transaction
+ true
+ elsif next_imr_decided_is_out_of_bounds?(change_data)
+ # If it's the end of the lead rows, then this is the last decided row
+ # If the next created at is in the same transaction, then defer event creation, otherwise create an in progress
+ # Or
+ # If the next imr was created at the same time that the current imr is decided, then defer
+ create_in_progress_event_for_last_decided_by_imr?(change_data)
+ elsif defer_in_progress_creation?(change_data)
+ # If the next imr was in the same transaction and it's also decided, then defer event creation to it.
+ # Or
+ # If the next imr was created in the same transaction as the next decided, then defer to the next imr
+ # Or
+ # If the next imr was created at the same time that the current imr is decided, then defer
+ # since it should never leave the current pending loop in that case
+ false
+ else
+ # If nothing else matches and the next one is also decided then go ahead and generate an in progress event
+ # This may occasionally result in a false positive but it should be right most of the time
+ change_data["next_decided_or_cancelled_at"].present?
+ end
+ end
+
+ def do_not_defer_in_progress_creation?(change_data)
+ (imr_reverse_order?(change_data) && next_imr_created_by_after_current_decided_at?(change_data)) ||
+ (change_data["next_decided_or_cancelled_at"].nil? &&
+ next_imr_created_by_after_current_decided_at?(change_data)) ||
+ (last_imr?(change_data) && next_imr_decided_or_cancelled_in_same_transaction?(change_data))
+ end
+
+ def defer_in_progress_creation?(change_data)
+ (next_imr_created_in_same_transaction?(change_data) && change_data["next_decided_or_cancelled_at"]) ||
+ next_imr_created_at_and_decided_at_in_same_transaction?(change_data) ||
+ next_imr_created_in_same_transaction_as_decided_at?(change_data)
+ end
+
+ def imr_decided_or_cancelled?(change_data)
+ %w[cancelled denied approved].include?(change_data["issue_modification_request_status"])
+ end
+
+ def next_imr_decided_or_cancelled_in_same_transaction?(change_data)
+ timestamp_within_seconds?(change_data["decided_at"], change_data["next_decided_or_cancelled_at"], 2)
+ end
+
+ def next_imr_created_in_same_transaction?(change_data)
+ timestamp_within_seconds?(change_data["issue_modification_request_created_at"],
+ change_data["next_created_at"],
+ 2)
+ end
+
+ def next_imr_created_in_same_transaction_as_decided_at?(change_data)
+ timestamp_within_seconds?(change_data["next_created_at"],
+ change_data["decided_at"],
+ 2)
+ end
+
+ def next_imr_created_by_after_current_decided_at?(change_data)
+ change_data["next_created_at"] &&
+ change_data["decided_at"] &&
+ !last_imr?(change_data) &&
+ (change_data["next_created_at"].change(usec: 0) > change_data["decided_at"].change(usec: 0))
+ end
+
+ def next_imr_created_at_and_decided_at_in_same_transaction?(change_data)
+ timestamp_within_seconds?(change_data["next_decided_or_cancelled_at"],
+ change_data["next_created_at"],
+ 2)
+ end
+
+ def imr_reverse_order?(change_data)
+ change_data["previous_imr_decided_at"].nil? || change_data["decided_at"].nil? ||
+ (change_data["previous_imr_decided_at"] > change_data["decided_at"])
+ end
+
+ def next_imr_decided_is_out_of_bounds?(change_data)
+ change_data["next_decided_or_cancelled_at"] == OUT_OF_BOUNDS_LEAD_TIME
+ end
+
+ def last_imr?(change_data)
+ change_data["next_created_at"] == OUT_OF_BOUNDS_LEAD_TIME
+ end
+
+ def create_in_progress_event_for_last_decided_by_imr?(change_data)
+ if next_imr_created_in_same_transaction?(change_data) ||
+ next_imr_created_in_same_transaction_as_decided_at?(change_data)
+ false
+ else
+ true
+ end
+ end
+
+ def early_deferral?(change_data)
+ next_imr_decided_or_cancelled_in_same_transaction?(change_data) &&
+ !imr_reverse_order?(change_data) && !last_imr?(change_data)
+ end
+
+ def create_pending_status_event(change_data, event_date)
+ pending_system_hash_events = pending_system_hash
+ .merge("event_date" => event_date)
+
+ if change_data["previous_imr_created_at"].nil?
+ # If this is the first IMR then it will always generate a pending event.
+ from_change_data(:pending, change_data.merge(pending_system_hash_events))
+ elsif timestamp_within_seconds?(change_data["previous_imr_decided_at"],
+ change_data["issue_modification_request_created_at"],
+ STATUS_EVENT_TIME_WINDOW)
+ # If this IMR was created at the same time as the previous decided at then skip pending event creation.
+ nil
+ elsif !previous_imr_created_in_same_transaction?(change_data)
+ # if two imr's are of different transaction and if decision has already been made then we
+ # want to put pending status since it went back to pending status before it was approved/cancelled or denied.
+ from_change_data(:pending, change_data.merge(pending_system_hash_events))
+ end
+ end
+
# rubocop:disable Metrics/MethodLength
def create_status_events(change_data)
status_events = []
- versions = parse_versions(change_data)
-
+ versions = parse_versions(change_data["task_versions"])
hookless_cancelled_events = handle_hookless_cancelled_status_events(versions, change_data)
status_events.push(*hookless_cancelled_events)
@@ -99,29 +365,34 @@ def create_status_events(change_data)
rest_of_versions.map do |version|
status_events.push event_from_version(version, 1, change_data)
end
+
+ # If there are no events, then it had versions but none that altered status so create one from current status
+ status_events.compact!
+ if status_events.empty?
+ status_events.push create_status_event_from_current_status(change_data)
+ end
elsif hookless_cancelled_events.empty?
# No versions so make an event with the current status
- # There is a chance that a task has no intake either through data setup or through a remanded SC
- event_date = change_data["intake_completed_at"] || change_data["task_created_at"]
- status_events.push from_change_data(task_status_to_event_type(change_data["task_status"]),
- change_data.merge("event_date" => event_date,
- "event_user_name" => "System"))
+ status_events.push create_status_event_from_current_status(change_data)
end
status_events
end
# rubocop:enable Metrics/MethodLength
- def parse_versions(change_data)
- versions = change_data["task_versions"]
- if versions
- # Quite a bit faster but less safe. Should probably be fine since it's coming from the database
- # rubocop:disable Security/YAMLLoad
- versions[1..-2].split(",").map { |yaml| YAML.load(yaml.gsub(/^"|"$/, "")) }
- # versions[1..-2].split(",").map { |yaml| YAML.safe_load(yaml.gsub(/^"|"$/, ""), [Time]) }
- # rubocop:enable Security/YAMLLoad
+ def create_status_event_from_current_status(change_data)
+ # There is a chance that a task has no intake either through data setup or through a remanded SC
+ from_change_data(task_status_to_event_type(change_data["task_status"]),
+ change_data.merge("event_date" => change_data["intake_completed_at"] ||
+ change_data["task_created_at"],
+ "event_user_name" => "System"))
+ end
- end
+ def parse_versions(versions)
+ # Quite a bit faster but less safe. Should probably be fine since it's coming from the database
+ # rubocop:disable Security/YAMLLoad
+ versions&.split("|||")&.map { |yaml| YAML.load(yaml.gsub(/^"|"$/, "")) }
+ # rubocop:enable Security/YAMLLoad
end
def create_issue_events(change_data)
@@ -144,10 +415,32 @@ def create_issue_events(change_data)
issue_events
end
+ def issue_attributes_for_request_type_addition(change_data)
+ # addition should not have issue_type that is pre-existing
+ issue_data = {
+ "nonrating_issue_category" => nil,
+ "nonrating_issue_description" => nil,
+ "decision_date" => nil
+ }
+
+ change_data.merge(issue_data)
+ end
+
+ def previous_imr_created_in_same_transaction?(change_data)
+ timestamp_within_seconds?(change_data["issue_modification_request_created_at"],
+ change_data["previous_imr_created_at"] ||
+ change_data["issue_modification_request_created_at"],
+ ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW)
+ end
+
def extract_issue_ids_from_change_data(change_data, key)
(change_data[key] || "").scan(/\d+/).map(&:to_i)
end
+ def decider_user_facility(change_data)
+ change_data["decider_station_id"] || change_data["requestor_station_id"]
+ end
+
def process_issue_ids(request_issue_ids, event_type, change_data)
created_events = []
@@ -221,10 +514,29 @@ def task_status_to_event_type(task_status)
"assigned" => :in_progress,
"on_hold" => :incomplete,
"completed" => :completed,
- "cancelled" => :cancelled
+ "cancelled" => :cancelled,
+ "pending" => :pending
}[task_status]
end
+ def update_event_hash_data_from_version(version, index)
+ version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data|
+ data[db_key] = version[version_key][index] unless version[version_key].nil?
+ end
+ end
+
+ def update_event_hash_data_from_version_object(version)
+ version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data|
+ data[db_key] = version[version_key]
+ end
+ end
+
+ def create_event_from_version_object(version)
+ previous_version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data|
+ data[db_key] = version[version_key]
+ end
+ end
+
def event_from_version(changes, index, change_data)
# If there is no task status change in the set of papertrail changes, ignore the object
if changes["status"]
@@ -269,6 +581,28 @@ def update_event_hash(change_data)
}
end
+ def version_database_field_mapping
+ {
+ "nonrating_issue_category" => "requested_issue_type",
+ "nonrating_issue_description" => "requested_issue_description",
+ "remove_original_issue" => "remove_original_issue",
+ "request_reason" => "modification_request_reason",
+ "decision_date" => "requested_decision_date",
+ "decision_reason" => "decision_reason",
+ "withdrawal_date" => "issue_modification_request_withdrawal_date"
+ }
+ end
+
+ def previous_version_database_field_mapping
+ {
+ "nonrating_issue_category" => "previous_issue_type",
+ "nonrating_issue_description" => "previous_issue_description",
+ "decision_date" => "previous_decision_date",
+ "request_reason" => "previous_modification_request_reason",
+ "withdrawal_date" => "previous_withdrawal_date"
+ }
+ end
+
def add_issue_update_event_hash(change_data)
# Check the current request issue updates time to see if the issue update is in the correct row
# If it is, then do the normal update_event_hash information
@@ -301,6 +635,22 @@ def retrieve_issue_update_data(change_data)
end
end
+ def request_issue_modification_event_hash(change_data)
+ {
+ "event_date" => change_data["issue_modification_request_created_at"],
+ "event_user_name" => change_data["requestor"],
+ "user_facility" => change_data["requestor_station_id"],
+ "event_user_css_id" => change_data["requestor_css_id"]
+ }
+ end
+
+ def pending_system_hash
+ {
+ "event_user_name" => "System",
+ "event_type" => "in_progress"
+ }
+ end
+
def timestamp_within_seconds?(first_date, second_date, time_in_seconds)
return false unless first_date && second_date
@@ -356,7 +706,7 @@ def to_csv_array
[
veteran_file_number, claimant_name, task_url, readable_task_status,
days_waiting, readable_claim_type, readable_facility_name, readable_user_name, readable_event_date,
- readable_event_type, issue_or_status_information, disposition_information
+ readable_event_type, issue_or_status_information, issue_modification_request_information, disposition_information
]
end
@@ -371,7 +721,8 @@ def readable_task_status
"in_progress" => "in progress",
"on_hold" => "incomplete",
"completed" => "completed",
- "cancelled" => "cancelled"
+ "cancelled" => "cancelled",
+ "pending" => "pending"
}[task_status]
end
@@ -399,6 +750,10 @@ def readable_decision_date
format_date_string(decision_date)
end
+ def readable_new_decision_date
+ format_date_string(new_decision_date)
+ end
+
def readable_disposition_date
format_date_string(disposition_date)
end
@@ -409,10 +764,12 @@ def readable_facility_name
[Constants::BGS_FACILITY_CODES[user_facility], " (", user_facility, ")"].join
end
+ # rubocop:disable Metrics/MethodLength
def readable_event_type
{
in_progress: "Claim status - In progress",
incomplete: "Claim status - Incomplete",
+ pending: "Claim status - Pending",
completed: "Claim closed",
claim_creation: "Claim created",
completed_disposition: "Completed disposition",
@@ -421,9 +778,18 @@ def readable_event_type
withdrew_issue: "Withdrew issue",
removed_issue: "Removed issue",
added_decision_date: "Added decision date",
- cancelled: "Claim closed"
+ cancelled: "Claim closed",
+ addition: "Requested issue addition",
+ removal: "Requested issue removal",
+ modification: "Requested issue modification",
+ withdrawal: "Requested issue withdrawal",
+ request_approved: "Approval of request - issue #{request_type}",
+ request_denied: "Rejection of request - issue #{request_type}",
+ request_cancelled: "Cancellation of request",
+ request_edited: "Edit of request - issue #{request_type}"
}[event_type]
end
+ # rubocop:enable Metrics/MethodLength
def issue_event?
ISSUE_EVENTS.include?(event_type)
@@ -441,6 +807,10 @@ def status_event?
STATUS_EVENTS.include?(event_type)
end
+ def event_has_modification_request?
+ REQUEST_ISSUE_MODIFICATION_EVENTS.include?(event_type)
+ end
+
private
def set_attributes_from_change_history_data(new_event_type, change_data)
@@ -452,23 +822,28 @@ def set_attributes_from_change_history_data(new_event_type, change_data)
parse_task_attributes(change_data)
parse_issue_attributes(change_data)
parse_disposition_attributes(change_data)
+ parse_request_issue_modification_attributes(change_data)
end
def parse_task_attributes(change_data)
@task_id = change_data["task_id"]
- @task_status = change_data["task_status"]
+ @task_status = derive_task_status(change_data)
@claim_type = change_data["type_classifier"]
@assigned_at = change_data["assigned_at"]
@days_waiting = change_data["days_waiting"]
end
+ def derive_task_status(change_data)
+ change_data["is_assigned_present"] ? "pending" : change_data["task_status"]
+ end
+
def parse_intake_attributes(change_data)
@intake_completed_date = change_data["intake_completed_at"]
@veteran_file_number = change_data["veteran_file_number"]
end
def parse_issue_attributes(change_data)
- if issue_event?
+ if issue_event? || event_has_modification_request?
@issue_type = change_data["nonrating_issue_category"]
@issue_description = change_data["nonrating_issue_description"] || change_data["unidentified_issue_text"]
@decision_date = change_data["decision_date"]
@@ -492,6 +867,52 @@ def parse_event_attributes(change_data)
@event_user_css_id = change_data["event_user_css_id"]
end
+ def parse_request_issue_modification_attributes(change_data)
+ if event_has_modification_request?
+ @request_type = change_data["request_type"]
+ @new_issue_type = change_data["requested_issue_type"]
+ @new_issue_description = change_data["requested_issue_description"]
+ @new_decision_date = change_data["requested_decision_date"]
+ @modification_request_reason = change_data["modification_request_reason"]
+ @decision_reason = change_data["decision_reason"]
+ @decided_at_date = change_data["decided_at"]
+ @issue_modification_request_withdrawal_date = change_data["issue_modification_request_withdrawal_date"]
+ @remove_original_issue = change_data["remove_original_issue"]
+ @issue_modification_request_status = change_data["issue_modification_request_status"]
+ @requestor = change_data["requestor"]
+ @decider = change_data["decider"]
+ parse_previous_issue_modification_attributes(change_data)
+ end
+ end
+
+ def parse_previous_issue_modification_attributes(change_data)
+ @previous_issue_type = derive_previous_issue_type(change_data)
+ @previous_decision_date = derive_previous_decision_date(change_data)
+ @previous_modification_request_reason = derive_previous_modification_request_reason(change_data)
+ @previous_issue_description = derive_previous_issue_description(change_data)
+ @previous_withdrawal_date = derive_previous_withdrawal_date(change_data)
+ end
+
+ def derive_previous_issue_type(change_data)
+ change_data["previous_issue_type"] || change_data["requested_issue_type"]
+ end
+
+ def derive_previous_decision_date(change_data)
+ change_data["previous_decision_date"] || change_data["requested_decision_date"]
+ end
+
+ def derive_previous_issue_description(change_data)
+ change_data["previous_issue_description"] || change_data["requested_issue_description"]
+ end
+
+ def derive_previous_modification_request_reason(change_data)
+ change_data["previous_modification_request_reason"] || change_data["modification_request_reason"]
+ end
+
+ def derive_previous_withdrawal_date(change_data)
+ change_data["previous_withdrawal_date"] || change_data["issue_modification_request_withdrawal_date"]
+ end
+
############ CSV and Serializer Helpers ############
def abbreviated_user_name(name_string)
@@ -500,11 +921,19 @@ def abbreviated_user_name(name_string)
end
def issue_information
- if issue_event?
+ if issue_event? || event_has_modification_request?
[issue_type, issue_description, readable_decision_date]
end
end
+ def issue_modification_request_information
+ if event_has_modification_request?
+ [new_issue_type, new_issue_description, readable_new_decision_date, modification_request_reason, decision_reason]
+ else
+ [nil, nil, nil, nil, nil]
+ end
+ end
+
def disposition_information
if disposition_event?
[disposition, decision_description, readable_disposition_date]
@@ -525,7 +954,8 @@ def status_description
incomplete: "Claim cannot be processed until decision date is entered.",
completed: "Claim closed.",
claim_creation: "Claim created.",
- cancelled: "Claim closed."
+ cancelled: "Claim cancelled.",
+ pending: "Claim cannot be processed until VHA admin reviews pending requests."
}[event_type]
end
diff --git a/app/services/claim_change_history/claim_history_service.rb b/app/services/claim_change_history/claim_history_service.rb
index c778ae4f01b..5ed8065f002 100644
--- a/app/services/claim_change_history/claim_history_service.rb
+++ b/app/services/claim_change_history/claim_history_service.rb
@@ -4,7 +4,9 @@
class ClaimHistoryService
attr_reader :business_line, :processed_task_ids,
:processed_request_issue_ids, :processed_request_issue_update_ids,
- :processed_decision_issue_ids, :events, :filters
+ :processed_decision_issue_ids, :processed_issue_modification_request_ids,
+ :processed_issue_modification_task_ids,
+ :events, :filters
attr_writer :filters
TIMING_RANGES = %w[
@@ -23,23 +25,23 @@ def initialize(business_line = VhaBusinessLine.singleton, filters = {})
@processed_request_issue_update_ids = Set.new
@processed_decision_issue_ids = Set.new
@processed_request_issue_ids = Set.new
+ @processed_issue_modification_request_ids = Set.new
+ @processed_issue_modification_task_ids = Set.new
@events = []
end
def build_events
# Reset the instance attributes from the last time build_events was ran
reset_processing_attributes
-
all_data = business_line.change_history_rows(@filters)
-
all_data.entries.each do |change_data|
process_request_issue_update_events(change_data)
process_request_issue_events(change_data)
process_decision_issue_and_task_events(change_data)
+ process_request_issue_modification_events(change_data)
# Don't process task events outside of decision issues unless there are no decision issues
process_task_events(change_data) unless change_data["task_status"] == "completed"
end
-
# Compact and sort in place to reduce garbage collection
@events.compact!
@events.sort_by! do |event|
@@ -64,6 +66,8 @@ def reset_processing_attributes
@processed_request_issue_update_ids.clear
@processed_decision_issue_ids.clear
@processed_request_issue_ids.clear
+ @processed_issue_modification_request_ids.clear
+ @processed_issue_modification_task_ids.clear
@events.clear
end
@@ -98,7 +102,9 @@ def matches_filter(new_events)
def process_event_filter(new_events)
return new_events if @filters[:events].blank?
- new_events.select { |event| event && ensure_array(@filters[:events]).include?(event.event_type) }
+ new_events.select do |event|
+ event && ensure_array(@filters[:events]).include?(event.event_type)
+ end
end
def process_issue_type_filter(new_events)
@@ -205,7 +211,6 @@ def process_request_issue_update_events(change_data)
def process_task_events(change_data)
task_id = change_data["task_id"]
-
if task_id && !@processed_task_ids.include?(task_id)
@processed_task_ids.add(task_id)
save_events(ClaimHistoryEvent.create_claim_creation_event(change_data))
@@ -235,6 +240,39 @@ def process_decision_issue_and_task_events(change_data)
end
end
+ def process_request_issue_modification_events(change_data)
+ issue_modification_request_id = change_data["issue_modification_request_id"]
+
+ return unless issue_modification_request_id
+
+ is_processed = @processed_issue_modification_request_ids.include?(issue_modification_request_id)
+
+ if issue_modification_request_id && !is_processed
+ @processed_issue_modification_request_ids.add(issue_modification_request_id)
+ save_events(ClaimHistoryEvent.create_issue_modification_request_event(change_data))
+ save_events(ClaimHistoryEvent.create_edited_request_issue_events(change_data))
+ end
+ # if imr version doesn't have status it means, imr has been made but no action yet taken from admin
+ process_pending_status_event(change_data) if !is_processed
+ end
+
+ def process_pending_status_event(change_data)
+ task_id = change_data["task_id"]
+
+ # processed_issue_modification_task_id stores all the task id that has already
+ # been processed so that it prevents the duplicate entry of pending event based on change_data.
+ if task_id &&
+ !@processed_issue_modification_task_ids.include?(task_id) &&
+ change_data["issue_modification_request_status"] == "assigned"
+ @processed_issue_modification_task_ids.add(task_id)
+
+ save_events(
+ ClaimHistoryEvent.create_pending_status_event(change_data,
+ change_data["issue_modification_request_created_at"])
+ )
+ end
+ end
+
def ensure_array(variable)
variable.is_a?(Array) ? variable : [variable]
end
diff --git a/client/app/nonComp/components/IndividualClaimHistoryTable.jsx b/client/app/nonComp/components/IndividualClaimHistoryTable.jsx
index a42bb20078b..78b766a8e4d 100644
--- a/client/app/nonComp/components/IndividualClaimHistoryTable.jsx
+++ b/client/app/nonComp/components/IndividualClaimHistoryTable.jsx
@@ -1,8 +1,12 @@
-import React from 'react';
+/* eslint-disable max-lines */
+import React, { useState } from 'react';
import QueueTable from '../../queue/QueueTable';
import BENEFIT_TYPES from 'constants/BENEFIT_TYPES';
import { formatDateStr } from 'app/util/DateUtil';
import PropTypes from 'prop-types';
+import StringUtil from 'app/util/StringUtil';
+
+const { capitalizeFirst } = StringUtil;
const IndividualClaimHistoryTable = (props) => {
@@ -34,25 +38,153 @@ const IndividualClaimHistoryTable = (props) => {
return Claim can be processed.;
};
+ const ClaimPendingFragment = () => {
+ return Claim cannot be processed until VHA admin reviews pending requests.;
+ };
+
const ClaimIncompleteFragment = () => {
return Claim cannot be processed until decision date is entered.;
};
+ const benefitType = (details) => {
+ return <>
+ Benefit type: {BENEFIT_TYPES[details.benefitType]}
+ >;
+ };
+
const ClaimClosedFragment = (details) => {
- return
- Claim closed.
+ const fragment = details.eventType === 'cancelled' ? <>
+ Claim cancelled.
+ > : <>
+ Claim closed. Claim decision date: {formatDecisionDate(details.dispositionDate)}
- ;
+ >;
+
+ return (
+