diff --git a/Gemfile b/Gemfile
index f3ffe697c16..f75dbc4f033 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,7 +12,7 @@ gem "acts_as_tree"
gem "amoeba"
# BGS
-gem "bgs", git: "https://github.com/department-of-veterans-affairs/ruby-bgs.git", ref: "7d7c67f7bad5e5aa03e257f0d8e57a4aa1a6cbbf"
+gem "bgs", git: "https://github.com/department-of-veterans-affairs/ruby-bgs.git", ref: "5f47e7b2656ef347d314ef43c93d38a9f20816ec"
# Bootsnap speeds up app boot (and started to be a default gem in 5.2).
gem "bootsnap", require: false
gem "browser"
diff --git a/Gemfile.lock b/Gemfile.lock
index 3e82890113a..012a1b5e716 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -57,10 +57,10 @@ GIT
GIT
remote: https://github.com/department-of-veterans-affairs/ruby-bgs.git
- revision: 7d7c67f7bad5e5aa03e257f0d8e57a4aa1a6cbbf
- ref: 7d7c67f7bad5e5aa03e257f0d8e57a4aa1a6cbbf
+ revision: 5f47e7b2656ef347d314ef43c93d38a9f20816ec
+ ref: 5f47e7b2656ef347d314ef43c93d38a9f20816ec
specs:
- bgs (0.2)
+ bgs (0.3)
httpclient
nokogiri (>= 1.11.0.rc4)
savon (~> 2.12)
diff --git a/app/controllers/appeals_controller.rb b/app/controllers/appeals_controller.rb
index 609ad735de4..73898b4f39c 100644
--- a/app/controllers/appeals_controller.rb
+++ b/app/controllers/appeals_controller.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# rubocop:disable Metrics/ClassLength
class AppealsController < ApplicationController
include UpdatePOAConcern
before_action :react_routed
@@ -49,7 +50,7 @@ def show_case_list
end
end
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def fetch_notification_list
appeals_id = params[:appeals_id]
respond_to do |format|
@@ -84,7 +85,7 @@ def fetch_notification_list
end
end
end
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
def document_count
doc_count = EFolderService.document_count(appeal.veteran_file_number, current_user)
@@ -154,7 +155,9 @@ def show
def edit
# only AMA appeals may call /edit
- return not_found if appeal.is_a?(LegacyAppeal)
+ # this was removed for MST/PACT initiative to edit MST/PACT for legacy issues
+ return not_found if appeal.is_a?(LegacyAppeal) &&
+ !FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
end
helper_method :appeal, :url_appeal_uuid
@@ -168,18 +171,12 @@ def url_appeal_uuid
end
def update
- if request_issues_update.perform!
- # if cc appeal, create SendInitialNotificationLetterTask
- if appeal.contested_claim? && FeatureToggle.enabled?(:cc_appeal_workflow)
- # check if an existing letter task is open
- existing_letter_task_open = appeal.tasks.any? do |task|
- task.class == SendInitialNotificationLetterTask && task.status == "assigned"
- end
- # create SendInitialNotificationLetterTask unless one is open
- send_initial_notification_letter unless existing_letter_task_open
- end
-
+ if appeal.is_a?(LegacyAppeal) &&
+ FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+ legacy_mst_pact_updates
+ elsif request_issues_update.perform!
set_flash_success_message
+ create_subtasks!
render json: {
beforeIssues: request_issues_update.before_issues.map(&:serialize),
@@ -193,6 +190,18 @@ def update
private
+ def create_subtasks!
+ # if cc appeal, create SendInitialNotificationLetterTask
+ if appeal.contested_claim? && FeatureToggle.enabled?(:cc_appeal_workflow)
+ # check if an existing letter task is open
+ existing_letter_task_open = appeal.tasks.any? do |task|
+ task.class == SendInitialNotificationLetterTask && task.status == "assigned"
+ end
+ # create SendInitialNotificationLetterTask unless one is open
+ send_initial_notification_letter unless existing_letter_task_open
+ end
+ end
+
# :reek:DuplicateMethodCall { allow_calls: ['result.extra'] }
# :reek:FeatureEnvy
def render_search_results_as_json(result)
@@ -260,7 +269,238 @@ def review_edited_message
"You have successfully " + [added_issues, removed_issues, withdrawn_issues].compact.to_sentence + "."
end
+ # check if changes in params
+ def mst_pact_changes?
+ request_issues_update.mst_edited_issues.any? || request_issues_update.pact_edited_issues.any?
+ end
+
+ # format MST/PACT edit success banner message
+ # rubocop:disable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
+ def mst_and_pact_edited_issues
+ # list of edit counts
+ mst_added = 0
+ mst_removed = 0
+ pact_added = 0
+ pact_removed = 0
+ # get edited issues from params and reject new issues without id
+ if !appeal.is_a?(LegacyAppeal)
+ existing_issues = params[:request_issues].reject { |iss| iss[:request_issue_id].nil? }
+
+ # get added issues
+ new_issues = request_issues_update.after_issues - request_issues_update.before_issues
+ # get removed issues
+ removed_issues = request_issues_update.before_issues - request_issues_update.after_issues
+
+ # calculate edits
+ existing_issues.each do |issue_edit|
+ # find the original issue and compare MST/PACT changes
+ before_issue = request_issues_update.before_issues.find { |b_issue| b_issue.id == issue_edit[:request_issue_id].to_i }
+
+ # increment edit counts if they meet the criteria for added/removed
+ mst_added += 1 if issue_edit[:mst_status] != before_issue.mst_status && issue_edit[:mst_status]
+ mst_removed += 1 if issue_edit[:mst_status] != before_issue.mst_status && !issue_edit[:mst_status]
+ pact_added += 1 if issue_edit[:pact_status] != before_issue.pact_status && issue_edit[:pact_status]
+ pact_removed += 1 if issue_edit[:pact_status] != before_issue.pact_status && !issue_edit[:pact_status]
+ end
+ else
+ existing_issues = legacy_issue_params[:request_issues]
+ existing_issues.each do |issue_edit|
+ mst_added += 1 if legacy_issues_with_updated_mst_pact_status[:mst_edited].include?(issue_edit) && issue_edit[:mst_status]
+ mst_removed += 1 if legacy_issues_with_updated_mst_pact_status[:mst_edited].include?(issue_edit) && !issue_edit[:mst_status]
+ pact_added += 1 if legacy_issues_with_updated_mst_pact_status[:pact_edited].include?(issue_edit) && issue_edit[:pact_status]
+ pact_removed += 1 if legacy_issues_with_updated_mst_pact_status[:pact_edited].include?(issue_edit) && !issue_edit[:pact_status]
+ new_issues = []
+ removed_issues = []
+ end
+ end
+
+ # return if no edits, removals, or additions
+ return if (mst_added + mst_removed + pact_added + pact_removed == 0) && removed_issues.empty? && new_issues.empty?
+
+ message = []
+
+ message << "#{pact_removed} #{'issue'.pluralize(pact_removed)} unmarked as PACT" unless pact_removed == 0
+ message << "#{mst_removed} #{'issue'.pluralize(mst_removed)} unmarked as MST" unless mst_removed == 0
+ message << "#{mst_added} #{'issue'.pluralize(mst_added)} marked as MST" unless mst_added == 0
+ message << "#{pact_added} #{'issue'.pluralize(pact_added)} marked as PACT" unless pact_added == 0
+
+ # add in removed message and added message, if any
+ message << create_mst_pact_message_for_new_and_removed_issues(new_issues, "added") unless new_issues.empty?
+ message << create_mst_pact_message_for_new_and_removed_issues(removed_issues, "removed") unless removed_issues.empty?
+
+ message.flatten
+ end
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
+
+ # create MST/PACT message for added/removed issues
+ # rubocop:disable Layout/LineLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ def create_mst_pact_message_for_new_and_removed_issues(issues, type)
+ special_issue_message = []
+ # check if any added/removed issues have MST/PACT and get the count
+ mst_count = issues.count { |issue| issue.mst_status && !issue.pact_status }
+ pact_count = issues.count { |issue| issue.pact_status && !issue.mst_status }
+ both_count = issues.count { |issue| issue.pact_status && issue.mst_status }
+ none_count = issues.count { |issue| !issue.pact_status && !issue.mst_status }
+
+ special_issue_message << "#{mst_count} #{'issue'.pluralize(mst_count)} with MST #{type}" unless mst_count == 0
+ special_issue_message << "#{pact_count} #{'issue'.pluralize(pact_count)} with PACT #{type}" unless pact_count == 0
+ special_issue_message << "#{both_count} #{'issue'.pluralize(both_count)} with MST and PACT #{type}" unless both_count == 0
+ special_issue_message << "#{none_count} #{'issue'.pluralize(none_count)} #{type}" unless none_count == 0
+
+ special_issue_message
+ end
+ # rubocop:enable Layout/LineLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+
+ # check if there is a change in mst/pact on legacy issue
+ # if there is a change, creat an issue update task
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
+ def legacy_mst_pact_updates
+ legacy_issue_params[:request_issues].each do |current_issue|
+ issue = appeal.issues.find { |iss| iss.vacols_sequence_id == current_issue[:vacols_sequence_id].to_i }
+
+ # Check for changes in mst/pact status
+ next unless issue.mst_status != current_issue[:mst_status] || issue.pact_status != current_issue[:pact_status]
+
+ # If there is a change :
+ # Create issue_update_task to populate casetimeline if there is a change
+ create_legacy_issue_update_task(issue, current_issue)
+
+ # Grab record from Vacols database to issue.
+ # When updating an Issue, method in IssueMapper and IssueRepo requires the attrs show below in issue_attrs:{}
+ record = VACOLS::CaseIssue.find_by(isskey: appeal.vacols_id, issseq: current_issue[:vacols_sequence_id])
+ Issue.update_in_vacols!(
+ vacols_id: appeal.vacols_id,
+ vacols_sequence_id: current_issue[:vacols_sequence_id],
+ issue_attrs: {
+ mst_status: current_issue[:mst_status] ? "Y" : "N",
+ pact_status: current_issue[:pact_status] ? "Y" : "N",
+ program: record[:issprog],
+ issue: record[:isscode],
+ level_1: record[:isslev1],
+ level_2: record[:isslev2],
+ level_3: record[:isslev3]
+ }
+ )
+ end
+ set_flash_mst_edit_message
+ render json: { issues: json_issues }, status: :ok
+ end
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
+
+ def json_issues
+ appeal.issues.map do |issue|
+ ::WorkQueue::LegacyIssueSerializer.new(issue).serializable_hash[:data][:attributes]
+ end
+ end
+
+ def legacy_issues_with_updated_mst_pact_status
+ mst_edited = legacy_issue_params[:request_issues].find_all do |current_issue|
+ issue = appeal.issues.find { |iss| iss.vacols_sequence_id == current_issue[:vacols_sequence_id].to_i }
+ issue.mst_status != current_issue[:mst_status]
+ end
+ pact_edited = legacy_issue_params[:request_issues].find_all do |current_issue|
+ issue = appeal.issues.find { |iss| iss.vacols_sequence_id == current_issue[:vacols_sequence_id].to_i }
+ issue.pact_status != current_issue[:pact_status]
+ end
+ { mst_edited: mst_edited, pact_edited: pact_edited }
+ end
+
+ def legacy_issue_params
+ # Checks the keys for each object in request_issues array
+ request_issue_params = params.require("request_issues").each do |current_param|
+ current_param.permit(:request_issue_id,
+ :withdrawal_date,
+ :vacols_sequence_id,
+ :mst_status,
+ :pact_status,
+ :mst_status_update_reason_notes,
+ :pact_status_update_reason_notes).to_h
+ end
+
+ # After check, recreate safe_params object and include vacols_uniq_id
+ safe_params = {
+ request_issues: request_issue_params,
+ vacols_user_id: current_user.vacols_uniq_id
+ }
+ safe_params
+ end
+
+ def create_params
+ legacy_issue_params.merge(vacols_id: appeal.vacols_id)
+ end
+
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
+ # :reek:FeatureEnvy
+ def create_legacy_issue_update_task(before_issue, current_issue)
+ user = RequestStore[:current_user]
+
+ # close out any tasks that might be open
+ open_issue_task = Task.where(
+ assigned_to: SpecialIssueEditTeam.singleton
+ ).where(status: "assigned").where(appeal: appeal)
+ open_issue_task[0].delete unless open_issue_task.empty?
+
+ task = IssuesUpdateTask.create!(
+ appeal: appeal,
+ parent: appeal.root_task,
+ assigned_to: SpecialIssueEditTeam.singleton,
+ assigned_by: user,
+ completed_by: user
+ )
+ # format the task instructions and close out
+ set = CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: [
+ "Benefit Type: #{before_issue.labels[0]}\n",
+ "Issue: #{before_issue.labels[1..-2].join("\n")}\n",
+ "Code: #{[before_issue.codes[-1], before_issue.labels[-1]].join(' - ')}\n",
+ "Note: #{before_issue.note}\n",
+ "Disposition: #{before_issue.readable_disposition}\n"
+ ].compact.join("\r\n"),
+ benefit_type: "",
+ original_mst: before_issue.mst_status,
+ original_pact: before_issue.pact_status,
+ edit_mst: current_issue[:mst_status],
+ edit_pact: current_issue[:pact_status]
+ )
+ task.format_instructions(set)
+ task.completed!
+
+ # create SpecialIssueChange record to log the changes
+ SpecialIssueChange.create!(
+ issue_id: before_issue.id,
+ appeal_id: appeal.id,
+ appeal_type: "LegacyAppeal",
+ task_id: task.id,
+ created_at: Time.zone.now.utc,
+ created_by_id: RequestStore[:current_user].id,
+ created_by_css_id: RequestStore[:current_user].css_id,
+ original_mst_status: before_issue.mst_status,
+ original_pact_status: before_issue.pact_status,
+ updated_mst_status: current_issue[:mst_status],
+ updated_pact_status: current_issue[:pact_status],
+ change_category: "Edited Issue"
+ )
+ end
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
+
+ # updated flash message to show mst/pact message if mst/pact changes (not to legacy)
+ # rubocop:disable Layout/LineLength
def set_flash_success_message
+ return set_flash_mst_edit_message if mst_pact_changes? &&
+ (FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user]) ||
+ FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user]))
+
+ set_flash_edit_message
+ end
+ # rubocop:enable Layout/LineLength
+
+ # create success message with added and removed issues
+ def set_flash_mst_edit_message
+ flash[:mst_pact_edited] = mst_and_pact_edited_issues
+ end
+
+ def set_flash_edit_message
flash[:edited] = if request_issues_update.after_issues.empty?
review_removed_message
elsif (request_issues_update.after_issues - request_issues_update.withdrawn_issues).empty?
@@ -355,3 +595,4 @@ def get_appeal_object(appeals_id)
end
end
end
+# rubocop:enable Metrics/ClassLength
diff --git a/app/controllers/case_reviews_controller.rb b/app/controllers/case_reviews_controller.rb
index 45b15d0df80..290a60811ba 100644
--- a/app/controllers/case_reviews_controller.rb
+++ b/app/controllers/case_reviews_controller.rb
@@ -10,8 +10,8 @@ def set_application
end
def complete
- result = CompleteCaseReview.new(case_review_class: case_review_class, params: complete_params).call
-
+ new_complete_case_review = CompleteCaseReview.new(case_review_class: case_review_class, params: complete_params)
+ result = new_complete_case_review.call
if result.success?
case_review = result.extra[:case_review]
render json: {
@@ -74,6 +74,7 @@ def judge_case_review_params
def issues_params
# This is a combined list of params for ama and legacy appeals
+ # Reprsents the information the front end is sending to create a decision issue object
[
:id,
:disposition,
@@ -81,6 +82,8 @@ def issues_params
:readjudication,
:benefit_type,
:diagnostic_code,
+ :mst_status,
+ :pact_status,
request_issue_ids: [],
remand_reasons: [
:code,
diff --git a/app/controllers/intakes_controller.rb b/app/controllers/intakes_controller.rb
index 2a2f7ded773..37e21cb3ba0 100644
--- a/app/controllers/intakes_controller.rb
+++ b/app/controllers/intakes_controller.rb
@@ -10,7 +10,6 @@ class IntakesController < ApplicationController
def index
no_cache
-
respond_to do |format|
format.html { render(:index) }
end
@@ -43,6 +42,7 @@ def destroy
def review
if intake.review!(params)
render json: intake.ui_hash
+
else
render json: { error_codes: intake.review_errors }, status: :unprocessable_entity
end
@@ -98,6 +98,7 @@ def index_props
{
userDisplayName: current_user.display_name,
userCanIntakeAppeals: current_user.can_intake_appeals?,
+ userCanEditIntakeIssues: current_user.can_edit_intake_issues?,
serverIntake: intake_ui_hash,
dropdownUrls: dropdown_urls,
applicationUrls: application_urls,
@@ -151,6 +152,10 @@ def feature_toggle_ui_hash
filedByVaGovHlr: FeatureToggle.enabled?(:filed_by_va_gov_hlr, user: current_user),
updatedIntakeForms: FeatureToggle.enabled?(:updated_intake_forms, user: current_user),
eduPreDocketAppeals: FeatureToggle.enabled?(:edu_predocket_appeals, user: current_user),
+ mstIdentification: FeatureToggle.enabled?(:mst_identification, user: current_user),
+ pactIdentification: FeatureToggle.enabled?(:pact_identification, user: current_user),
+ legacyMstPactIdentification: FeatureToggle.enabled?(:legacy_mst_pact_identification, user: current_user),
+ justificationReason: FeatureToggle.enabled?(:justification_reason, user: current_user),
updatedAppealForm: FeatureToggle.enabled?(:updated_appeal_form, user: current_user),
hlrScUnrecognizedClaimants: FeatureToggle.enabled?(:hlr_sc_unrecognized_claimants, user: current_user),
vhaClaimReviewEstablishment: FeatureToggle.enabled?(:vha_claim_review_establishment, user: current_user),
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index 980f828e09f..fa6d8052b65 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# IssuesController for LegacyAppeals
class IssuesController < ApplicationController
before_action :validate_access_to_task
@@ -16,24 +17,45 @@ class IssuesController < ApplicationController
handle_non_critical_error("issues", e)
end
+ # rubocop:disable Layout/LineLength
def create
return record_not_found unless appeal
- Issue.create_in_vacols!(issue_attrs: create_params)
+ issue = Issue.create_in_vacols!(issue_attrs: create_params)
+
+ # create MST/PACT task if issue was created
+ if convert_to_bool(create_params[:mst_status]) ||
+ convert_to_bool(create_params[:pact_status])
+ issue_in_caseflow = appeal.issues.find { |iss| iss.vacols_sequence_id == issue.issseq.to_i }
+ create_legacy_issue_update_task(issue_in_caseflow) if FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+ end
render json: { issues: json_issues }, status: :created
end
+ # rubocop:enable Layout/LineLength
+ # rubocop:disable Layout/LineLength, Metrics/AbcSize
def update
return record_not_found unless appeal
+ issue = appeal.issues.find { |iss| iss.vacols_sequence_id == params[:vacols_sequence_id].to_i }
+ if issue.mst_status != convert_to_bool(params[:issues][:mst_status]) ||
+ issue.pact_status != convert_to_bool(params[:issues][:pact_status])
+ create_legacy_issue_update_task(issue) if FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+ end
+
Issue.update_in_vacols!(
vacols_id: appeal.vacols_id,
vacols_sequence_id: params[:vacols_sequence_id],
issue_attrs: issue_params
)
+
+ # Set LegacyAppeal issues to nil in order to refresh and retrieve new update
+ appeal.issues = nil if appeal.is_legacy?
+
render json: { issues: json_issues }, status: :ok
end
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize
def destroy
return record_not_found unless appeal
@@ -47,6 +69,82 @@ def destroy
private
+ # rubocop:disable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
+ def create_legacy_issue_update_task(issue)
+ user = current_user
+
+ # close out any tasks that might be open
+ open_issue_task = Task.where(
+ assigned_to: SpecialIssueEditTeam.singleton
+ ).where(status: "assigned").where(appeal: appeal)
+ open_issue_task[0].delete unless open_issue_task.empty?
+
+ task = IssuesUpdateTask.create!(
+ appeal: appeal,
+ parent: appeal.root_task,
+ assigned_to: SpecialIssueEditTeam.singleton,
+ assigned_by: user,
+ completed_by: user
+ )
+
+ # set up data for added or edited issue depending on the params action
+ disposition = issue.readable_disposition.nil? ? "N/A" : issue.readable_disposition
+ change_category = (params[:action] == "create") ? "Added Issue" : "Edited Issue"
+ updated_mst_status = convert_to_bool(params[:issues][:mst_status]) unless params[:action] == "create"
+ updated_pact_status = convert_to_bool(params[:issues][:pact_status]) unless params[:action] == "create"
+
+ note = params[:issues][:note].nil? ? "N/A" : params[:issues][:note]
+ # use codes from params to get descriptions
+ # opting to use params vs issue model to capture in-flight issue changes
+ program_code = params[:issues][:program]
+ issue_code = params[:issues][:issue]
+ level_1_code = params[:issues][:level_1]
+
+ # line up param codes to their descriptions
+ param_issue = Constants::ISSUE_INFO[program_code]
+ iss = param_issue["levels"][issue_code]["description"] unless issue_code.nil?
+ level_1_description = level_1_code.nil? ? "N/A" : param_issue["levels"][issue_code]["levels"][level_1_code]["description"]
+
+ # format the task instructions and close out
+ set = CaseTimelineInstructionSet.new(
+ change_type: change_category,
+ issue_category: [
+ "Benefit Type: #{param_issue['description']}\n",
+ "Issue: #{iss}\n",
+ "Code: #{[level_1_code, level_1_description].join(' - ')}\n",
+ "Note: #{note}\n",
+ "Disposition: #{disposition}\n"
+ ].compact.join("\r\n"),
+ benefit_type: "",
+ original_mst: issue.mst_status,
+ original_pact: issue.pact_status,
+ edit_mst: updated_mst_status,
+ edit_pact: updated_pact_status
+ )
+ task.format_instructions(set)
+ task.completed!
+ # create SpecialIssueChange record to log the changes
+ SpecialIssueChange.create!(
+ issue_id: issue.id,
+ appeal_id: appeal.id,
+ appeal_type: "LegacyAppeal",
+ task_id: task.id,
+ created_at: Time.zone.now.utc,
+ created_by_id: user.id,
+ created_by_css_id: user.css_id,
+ original_mst_status: issue.mst_status,
+ original_pact_status: issue.pact_status,
+ updated_mst_status: updated_mst_status,
+ updated_pact_status: updated_pact_status,
+ change_category: change_category
+ )
+ end
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
+
+ def convert_to_bool(status)
+ status == "Y"
+ end
+
def json_issues
appeal.issues.map do |issue|
::WorkQueue::LegacyIssueSerializer.new(issue).serializable_hash[:data][:attributes]
@@ -68,7 +166,9 @@ def issue_params
:issue,
:level_1,
:level_2,
- :level_3).to_h
+ :level_3,
+ :mst_status,
+ :pact_status).to_h
safe_params[:vacols_user_id] = current_user.vacols_uniq_id
safe_params
end
diff --git a/app/mappers/issue_mapper.rb b/app/mappers/issue_mapper.rb
index f4492bbeadb..92e79a28384 100644
--- a/app/mappers/issue_mapper.rb
+++ b/app/mappers/issue_mapper.rb
@@ -10,7 +10,9 @@ module IssueMapper
note: :issdesc,
disposition: :issdc,
disposition_date: :issdcls,
- vacols_id: :isskey
+ vacols_id: :isskey,
+ mst_status: :issmst,
+ pact_status: :isspact
}.freeze
# For disposition descriptions, please see the VACOLS_DISPOSITIONS_BY_ID file
@@ -39,10 +41,10 @@ def rename_and_validate_vacols_attrs(action:, issue_attrs:)
private
def validate!(issue_attrs)
- return if (issue_attrs.keys & [:issprog, :isscode, :isslev1, :isslev2, :isslev3]).empty?
+ return if (issue_attrs.keys & [:issprog, :isscode, :isslev1, :isslev2, :isslev3, :issmst, :isspact]).empty?
- if issue_attrs.slice(:issprog, :isscode, :isslev1, :isslev2, :isslev3).size != 5
- msg = "All keys must be present: program, issue, level_1, level_2, level_3"
+ if issue_attrs.slice(:issprog, :isscode, :isslev1, :isslev2, :isslev3, :issmst, :isspact).size != 7
+ msg = "All keys must be present: program, issue, level_1, level_2, level_3, mst_status, pact_status"
fail Caseflow::Error::IssueRepositoryError, message: msg
end
diff --git a/app/models/appeal.rb b/app/models/appeal.rb
index ca8fff8310e..13e6ecc6a48 100644
--- a/app/models/appeal.rb
+++ b/app/models/appeal.rb
@@ -254,6 +254,28 @@ def contested_claim?
end
end
+ # :reek:RepeatedConditionals
+ # decision issue status overrules request issues/special issue list for both mst and pact
+ def mst?
+ return false unless FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])
+
+ return decision_issues.any?(&:mst_status) unless decision_issues.empty?
+
+ request_issues.active.any?(&:mst_status) ||
+ (special_issue_list &&
+ special_issue_list.created_at < "2023-06-01".to_date &&
+ special_issue_list.military_sexual_trauma)
+ end
+
+ # :reek:RepeatedConditionals
+ def pact?
+ return false unless FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
+
+ return decision_issues.any?(&:pact_status) unless decision_issues.empty?
+
+ request_issues.active.any?(&:pact_status)
+ end
+
# Returns the most directly responsible party for an appeal when it is at the Board,
# mirroring Legacy Appeals' location code in VACOLS
def assigned_to_location
@@ -280,6 +302,7 @@ def decorated_with_status
AppealStatusApiDecorator.new(self)
end
+ # :reek:RepeatedConditionals
def active_request_issues_or_decision_issues
decision_issues.empty? ? active_request_issues : fetch_all_decision_issues
end
@@ -351,6 +374,7 @@ def clone_cavc_remand(parent_appeal, user_css_id)
dup_remand&.save
end
+ # :reek:RepeatedConditionals
# clone issues clones request_issues the user selected
# and anydecision_issues/decision_request_issues tied to the request issue
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
@@ -470,7 +494,7 @@ def clone_hearings(parent_appeal)
end
end
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def clone_task_tree(parent_appeal, user_css_id)
# get the task tree from the parent
parent_ordered_tasks = parent_appeal.tasks.order(:created_at)
@@ -505,9 +529,9 @@ def clone_task_tree(parent_appeal, user_css_id)
break if parent_appeal.tasks.count == tasks.count
end
end
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
- # clone_task is used for splitting an appeal, tie to css_id for split
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ # clone_task is used for splitting an appeal, tie to css_id for split
def clone_task(original_task, user_css_id)
# clone the task
dup_task = original_task.amoeba_dup
@@ -919,6 +943,10 @@ def can_redistribute_appeal?
return true if relevant_tasks.all?(&:closed?)
end
+ def is_legacy?
+ false
+ end
+
private
def business_lines_needing_assignment
diff --git a/app/models/case_timeline_instruction_set.rb b/app/models/case_timeline_instruction_set.rb
new file mode 100644
index 00000000000..de4af8ff15b
--- /dev/null
+++ b/app/models/case_timeline_instruction_set.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+# :reek:TooManyInstanceVariables
+class CaseTimelineInstructionSet
+ attr_reader :change_type,
+ :issue_category,
+ :benefit_type,
+ :original_mst,
+ :original_pact,
+ :edit_mst,
+ :edit_pact,
+ :mst_edit_reason,
+ :pact_edit_reason
+
+ # rubocop:disable Metrics/ParameterLists
+ # :reek:LongParameterList and :reek:TooManyInstanceVariables
+ def initialize(
+ change_type:,
+ issue_category:,
+ benefit_type:,
+ original_mst:,
+ original_pact:,
+ edit_mst: nil,
+ edit_pact: nil,
+ mst_edit_reason: nil,
+ pact_edit_reason: nil
+ )
+ @change_type = change_type
+ @issue_category = issue_category
+ @benefit_type = benefit_type
+ @original_mst = original_mst
+ @original_pact = original_pact
+ @edit_mst = edit_mst
+ @edit_pact = edit_pact
+ @mst_edit_reason = mst_edit_reason
+ @pact_edit_reason = pact_edit_reason
+ end
+ # rubocop:enable Metrics/ParameterLists
+end
diff --git a/app/models/concerns/issue_updater.rb b/app/models/concerns/issue_updater.rb
index aba47fca7a9..3b5347083be 100644
--- a/app/models/concerns/issue_updater.rb
+++ b/app/models/concerns/issue_updater.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# rubocop:disable Metrics/ModuleLength
module IssueUpdater
extend ActiveSupport::Concern
@@ -35,8 +36,11 @@ def update_issue_dispositions_in_vacols!
private
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
+ # :reek:FeatureEnvy
def create_decision_issues!
- issues.each do |issue_attrs|
+ ordered_issues = issues.sort_by { |issue| issue[:request_issue_ids]&.first }
+ ordered_issues.each do |issue_attrs|
request_issues = appeal.request_issues.active_or_withdrawn.where(id: issue_attrs[:request_issue_ids])
next if request_issues.empty?
@@ -47,15 +51,27 @@ def create_decision_issues!
diagnostic_code: issue_attrs[:diagnostic_code].presence,
participant_id: appeal.veteran.participant_id,
decision_review: appeal,
- caseflow_decision_date: appeal.decision_document&.decision_date
+ caseflow_decision_date: appeal.decision_document&.decision_date,
+ mst_status: issue_attrs[:mst_status],
+ pact_status: issue_attrs[:pact_status]
)
request_issues.each do |request_issue|
RequestDecisionIssue.create!(decision_issue: decision_issue, request_issue: request_issue)
+
+ # compare the MST/PACT status of the orignial issue and decision to create task and record
+ next unless (request_issue.mst_status != decision_issue.mst_status &&
+ FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])) ||
+ (request_issue.pact_status != decision_issue.pact_status &&
+ FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user]))
+
+ create_issue_update_task(request_issue, decision_issue)
end
+
create_remand_reasons(decision_issue, issue_attrs[:remand_reasons] || [])
end
end
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
def fail_if_not_all_request_issues_have_decision!
unless appeal.every_request_issue_has_decision?
@@ -108,4 +124,77 @@ def create_remand_reasons(decision_issue, remand_reasons_attrs)
end
end
end
+
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
+ # :reek:FeatureEnvy
+ def create_issue_update_task(original_issue, decision_issue)
+ root_task = RootTask.find_or_create_by!(appeal: appeal)
+
+ # close out any tasks that might be open
+ open_issue_task = Task.where(
+ assigned_to: SpecialIssueEditTeam.singleton
+ ).where(status: "assigned").where(appeal: appeal)
+ open_issue_task[0].delete unless open_issue_task.empty?
+
+ task = IssuesUpdateTask.create!(
+ appeal: appeal,
+ parent: root_task,
+ assigned_to: SpecialIssueEditTeam.singleton,
+ assigned_by: RequestStore[:current_user],
+ completed_by: RequestStore[:current_user]
+ )
+
+ set = CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: task_text_helper(
+ [
+ original_issue.contested_issue_description,
+ original_issue.nonrating_issue_category,
+ original_issue.nonrating_issue_description
+ ]
+ ),
+ benefit_type: task_text_benefit_type(original_issue),
+ original_mst: original_issue.mst_status,
+ original_pact: original_issue.pact_status,
+ edit_mst: decision_issue.mst_status,
+ edit_pact: decision_issue.pact_status
+ )
+ task.format_instructions(set)
+
+ task.completed!
+
+ SpecialIssueChange.create!(
+ issue_id: original_issue.id,
+ appeal_id: appeal.id,
+ appeal_type: "Appeal",
+ task_id: task.id,
+ created_at: Time.zone.now.utc,
+ created_by_id: RequestStore[:current_user].id,
+ created_by_css_id: RequestStore[:current_user].css_id,
+ original_mst_status: original_issue.mst_status,
+ original_pact_status: original_issue.pact_status,
+ updated_mst_status: decision_issue.mst_status,
+ updated_pact_status: decision_issue.pact_status,
+ mst_from_vbms: original_issue&.vbms_mst_status,
+ pact_from_vbms: original_issue&.vbms_pact_status,
+ change_category: "Edited Decision Issue",
+ decision_issue_id: decision_issue.id
+ )
+ end
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
+
+ def task_text_benefit_type(issue)
+ issue.benefit_type ? issue.benefit_type.capitalize : ""
+ end
+
+ def task_text_helper(text_array)
+ if text_array.compact.length > 1
+ text_array.compact.join(" - ")
+ elsif text_array.compact.length == 1
+ text_array.join
+ else
+ "Description unavailable"
+ end
+ end
end
+# rubocop:enable Metrics/ModuleLength
diff --git a/app/models/contestable_issue.rb b/app/models/contestable_issue.rb
index 32a44afec5f..cf456ad3703 100644
--- a/app/models/contestable_issue.rb
+++ b/app/models/contestable_issue.rb
@@ -11,7 +11,7 @@ class ContestableIssue
:decision_issue, :rating_issue_profile_date, :source_request_issues,
:rating_issue_diagnostic_code, :source_decision_review,
:rating_decision_reference_id, :rating_issue_subject_text,
- :rating_issue_percent_number
+ :rating_issue_percent_number, :special_issues
class << self
def from_rating_issue(rating_issue, contesting_decision_review)
@@ -30,7 +30,8 @@ def from_rating_issue(rating_issue, contesting_decision_review)
# TODO: These should never be set unless there is a decision issue. We should refactor this to
# account for that.
source_request_issues: rating_issue.source_request_issues,
- source_decision_review: rating_issue.source_request_issues.first&.decision_review
+ source_decision_review: rating_issue.source_request_issues.first&.decision_review,
+ special_issues: SpecialIssuesComparator.new(rating_issue).special_issues
)
end
@@ -61,6 +62,7 @@ def from_rating_decision(rating_decision, contesting_decision_review)
description: rating_decision.decision_text,
contesting_decision_review: contesting_decision_review,
rating_issue_diagnostic_code: rating_decision.diagnostic_code,
+ special_issues: SpecialIssuesComparator.new(rating_decision).special_issues,
is_rating: true # true even if rating_reference_id is nil
)
end
@@ -80,7 +82,9 @@ def serialize
sourceReviewType: source_review_type,
timely: timely?,
latestIssuesInChain: serialize_latest_decision_issues,
- isRating: is_rating
+ isRating: is_rating,
+ mstAvailable: mst_available?,
+ pactAvailable: pact_available?
}
end
@@ -112,6 +116,33 @@ def timely?
approx_decision_date && contesting_decision_review.timely_issue?(approx_decision_date)
end
+ # cycle the issues to see if the past decision had any mst codes on contentions
+ def mst_available?
+ return false unless FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])
+
+ source_request_issues.try(:each) do |issue|
+ return true if issue.mst_contention_status? || issue.mst_status?
+ end
+ special_issues&.each do |special_issue|
+ return true if special_issue[:mst_available]
+ end
+ false
+ end
+
+ # cycle the issues to see if the past decision had any pact codes on contentions
+ def pact_available?
+ return false unless FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
+
+ source_request_issues.try(:each) do |issue|
+ return true if issue.pact_contention_status? || issue.pact_status?
+ end
+ special_issues&.each do |special_issue|
+ return true if special_issue[:pact_available]
+ end
+
+ false
+ end
+
private
def contested_by_request_issue
diff --git a/app/models/decision_issue.rb b/app/models/decision_issue.rb
index b2a8d76dc19..7ce7f6f3d9d 100644
--- a/app/models/decision_issue.rb
+++ b/app/models/decision_issue.rb
@@ -187,7 +187,9 @@ def create_contesting_request_issue!(appeal)
nonrating_issue_category: nonrating_issue_category,
benefit_type: benefit_type,
decision_date: caseflow_decision_date,
- veteran_participant_id: decision_review.veteran.participant_id
+ veteran_participant_id: decision_review.veteran.participant_id,
+ mst_status: mst_status,
+ pact_status: pact_status
)
end
diff --git a/app/models/etl/decision_issue.rb b/app/models/etl/decision_issue.rb
index 4d05446e259..d0a1d455b30 100644
--- a/app/models/etl/decision_issue.rb
+++ b/app/models/etl/decision_issue.rb
@@ -3,6 +3,8 @@
# copy of decision_issues
class ETL::DecisionIssue < ETL::Record
+ attr_accessor :mst_status, :pact_status
+
class << self
private
diff --git a/app/models/hearing_issue_note.rb b/app/models/hearing_issue_note.rb
index 406f69515e7..e9b31029a2d 100644
--- a/app/models/hearing_issue_note.rb
+++ b/app/models/hearing_issue_note.rb
@@ -9,12 +9,14 @@ class HearingIssueNote < CaseflowRecord
delegate :description, to: :request_issue
delegate :notes, to: :request_issue
delegate :benefit_type, to: :request_issue
+ delegate :mst_status, to: :request_issue
+ delegate :pact_status, to: :request_issue
alias program benefit_type
def to_hash
serializable_hash(
- methods: [:docket_name, :diagnostic_code, :description, :notes, :program],
+ methods: [:docket_name, :diagnostic_code, :description, :notes, :program, :mst_status, :pact_status],
include: [hearing: { methods: [:external_id] }]
)
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 79de40bd2c7..385fe61436b 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -8,7 +8,8 @@ class Issue
include ActiveModel::Serialization
attr_accessor :id, :vacols_sequence_id, :codes, :disposition, :disposition_date,
- :disposition_id, :readable_disposition, :close_date, :note
+ :disposition_id, :readable_disposition, :close_date, :note,
+ :mst_status, :pact_status
# Labels are only loaded if we run the joins to ISSREF and VFTYPES (see VACOLS::CaseIssue)
attr_writer :labels
@@ -334,7 +335,9 @@ def load_from_vacols(hash)
disposition_date: hash["issdcls"],
# readable disposition is a string, i.e. "Remanded"
readable_disposition: Constants::VACOLS_DISPOSITIONS_BY_ID[hash["issdc"]],
- close_date: AppealRepository.normalize_vacols_date(hash["issdcls"])
+ close_date: AppealRepository.normalize_vacols_date(hash["issdcls"]),
+ mst_status: hash["issmst"]&.casecmp("y")&.zero? || false,
+ pact_status: hash["isspact"]&.casecmp("y")&.zero? || false
)
end
diff --git a/app/models/legacy_appeal.rb b/app/models/legacy_appeal.rb
index 3ed9539fb55..823eaa563dd 100644
--- a/app/models/legacy_appeal.rb
+++ b/app/models/legacy_appeal.rb
@@ -632,6 +632,23 @@ def special_issues?
end
end
+ def mst?
+ return false unless FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user]) &&
+ FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+
+ issues.any?(&:mst_status) ||
+ (special_issue_list &&
+ special_issue_list.created_at < "2023-06-01".to_date &&
+ special_issue_list.military_sexual_trauma)
+ end
+
+ def pact?
+ return false unless FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user]) &&
+ FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+
+ issues.any?(&:pact_status)
+ end
+
def documents_with_type(*types)
@documents_by_type ||= {}
types.reduce([]) do |accumulator, type|
@@ -918,6 +935,28 @@ def claimant_participant_id
veteran_is_not_claimant ? person_for_appellant&.participant_id : veteran&.participant_id
end
+ # :reek:FeatureEnvy
+ def hearing_day_if_schedueled
+ hearing_date = Hearing.find_by(appeal_id: id)
+
+ if hearing_date.nil?
+ nil
+
+ else
+ hearing_date.hearing_day.scheduled_for
+ end
+ end
+
+ def ui_hash
+ Intake::LegacyAppealSerializer.new(self).serializable_hash[:data][:attributes]
+ end
+
+ # rubocop:disable Naming/PredicateName
+ def is_legacy?
+ true
+ end
+ # rubocop:enable Naming/PredicateName
+
private
def soc_eligible_for_opt_in?(receipt_date:, covid_flag: false)
diff --git a/app/models/legacy_hearing.rb b/app/models/legacy_hearing.rb
index 07bbbfb9acc..370ce6aa900 100644
--- a/app/models/legacy_hearing.rb
+++ b/app/models/legacy_hearing.rb
@@ -268,6 +268,18 @@ def original_request_type
end
end
+ # :reek:FeatureEnvy
+ def prepare_worksheet_issues
+ worksheet_issues = []
+ appeal.worksheet_issues.each_with_index do |wi, idx|
+ worksheet_issues.push(wi.attributes)
+ issue = appeal.issues.find { |iss| iss.vacols_sequence_id.to_i == wi[:vacols_sequence_id].to_i }
+ worksheet_issues[idx][:mst_status] = issue&.mst_status
+ worksheet_issues[idx][:pact_status] = issue&.pact_status
+ end
+ worksheet_issues
+ end
+
def quick_to_hash(current_user_id)
::LegacyHearingSerializer.quick(
self,
@@ -324,7 +336,9 @@ def current_issue_count
# we want to fetch it from BGS, save it to the DB, then return it
def military_service
super || begin
- update(military_service: veteran.periods_of_service.join("\n")) if persisted? && veteran
+ if !HearingDay.find_by(id: hearing_day_vacols_id).nil? || !HearingDay.find_by(id: hearing_day_id).nil?
+ update(military_service: veteran.periods_of_service.join("\n")) if persisted? && veteran
+ end
super
end
end
diff --git a/app/models/organizations/special_issue_edit_team.rb b/app/models/organizations/special_issue_edit_team.rb
new file mode 100644
index 00000000000..8b8643ef3dd
--- /dev/null
+++ b/app/models/organizations/special_issue_edit_team.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class SpecialIssueEditTeam < Organization
+ alias_attribute :full_name, :name
+
+ def self.singleton
+ SpecialIssueEditTeam.first || SpecialIssueEditTeam.create(
+ name: "Special Issue Edit Team",
+ url: "special-issue-edit-team"
+ )
+ end
+end
diff --git a/app/models/rating.rb b/app/models/rating.rb
index 2b90da330dd..f743e2f99ad 100644
--- a/app/models/rating.rb
+++ b/app/models/rating.rb
@@ -65,12 +65,13 @@ def issues
issues.map do |issue|
most_recent_disability_hash_for_issue = map_of_dis_sn_to_most_recent_disability_hash[issue[:dis_sn]]
most_recent_evaluation_for_issue = most_recent_disability_hash_for_issue&.most_recent_evaluation
+ special_issues = most_recent_disability_hash_for_issue&.special_issues
if most_recent_evaluation_for_issue
issue[:dgnstc_tc] = most_recent_evaluation_for_issue[:dgnstc_tc]
issue[:prcnt_no] = most_recent_evaluation_for_issue[:prcnt_no]
end
-
+ issue[:special_issues] = special_issues if special_issues
RatingIssue.from_bgs_hash(self, issue)
end
end
@@ -81,6 +82,9 @@ def decisions
disability_data = Array.wrap(rating_profile[:disabilities] || rating_profile.dig(:disability_list, :disability))
disability_data.map do |disability|
+ most_recent_disability_hash_for_issue = map_of_dis_sn_to_most_recent_disability_hash[disability[:dis_sn]]
+ special_issues = most_recent_disability_hash_for_issue&.special_issues
+ disability[:special_issues] = special_issues if special_issues
RatingDecision.from_bgs_disability(self, disability)
end
end
diff --git a/app/models/rating_decision.rb b/app/models/rating_decision.rb
index 915a48369b4..2cce62f309c 100644
--- a/app/models/rating_decision.rb
+++ b/app/models/rating_decision.rb
@@ -27,9 +27,12 @@ class RatingDecision
:promulgation_date,
:rating_sequence_number,
:rating_issue_reference_id,
- :type_name
+ :type_name,
+ :special_issues,
+ :rba_contentions_data
class << self
+ # rubocop:disable Metrics/MethodLength
def from_bgs_disability(rating, disability)
latest_evaluation = RatingProfileDisability.new(disability).most_recent_evaluation || {}
new(
@@ -49,9 +52,12 @@ def from_bgs_disability(rating, disability)
profile_date: rating.profile_date,
promulgation_date: rating.promulgation_date,
participant_id: rating.participant_id,
- benefit_type: rating.pension? ? :pension : :compensation
+ benefit_type: rating.pension? ? :pension : :compensation,
+ special_issues: disability[:special_issues],
+ rba_contentions_data: disability[:rba_contentions_data]
)
end
+ # rubocop:enable Metrics/MethodLength
def deserialize(hash)
new(hash)
diff --git a/app/models/rating_issue.rb b/app/models/rating_issue.rb
index 0569fd13b98..382c4c37a1f 100644
--- a/app/models/rating_issue.rb
+++ b/app/models/rating_issue.rb
@@ -17,7 +17,8 @@ class RatingIssue
:promulgation_date,
:rba_contentions_data,
:reference_id,
- :subject_text
+ :subject_text,
+ :special_issues
# adding another field? *
)
@@ -48,11 +49,17 @@ def from_bgs_hash(rating, bgs_data)
promulgation_date: rating.promulgation_date,
rba_contentions_data: ensure_array_of_hashes(bgs_data.dig(:rba_issue_contentions)),
reference_id: bgs_data[:rba_issue_id],
- subject_text: bgs_data[:subjct_txt]
+ subject_text: bgs_data[:subjct_txt],
+ special_issues: bgs_data[:special_issues]
)
end
def deserialize(serialized_hash)
+ DataDogService.increment_counter(
+ metric_group: "mst_pact_group",
+ metric_name: "bgs_service.previous_service_call.rating_issue",
+ app_name: RequestStore[:application]
+ )
new(
serialized_hash.slice(
:benefit_type,
@@ -64,7 +71,8 @@ def deserialize(serialized_hash)
:promulgation_date,
:rba_contentions_data,
:reference_id,
- :subject_text
+ :subject_text,
+ :special_issues
).merge(associated_end_products: deserialize_end_products(serialized_hash))
)
end
diff --git a/app/models/request_issue.rb b/app/models/request_issue.rb
index 99fdbc5ad4b..9cf778c8119 100644
--- a/app/models/request_issue.rb
+++ b/app/models/request_issue.rb
@@ -190,7 +190,7 @@ def from_intake_data(data, decision_review: nil)
private
- # rubocop:disable Metrics/MethodLength
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def attributes_from_intake_data(data)
contested_issue_present = attributes_look_like_contested_issue?(data)
issue_text = (data[:is_unidentified] || data[:verified_unidentified_issue]) ? data[:decision_text] : nil
@@ -219,10 +219,16 @@ def attributes_from_intake_data(data)
edited_description: data[:edited_description],
correction_type: data[:correction_type],
verified_unidentified_issue: data[:verified_unidentified_issue],
- is_predocket_needed: data[:is_predocket_needed]
+ is_predocket_needed: data[:is_predocket_needed],
+ mst_status: data[:mst_status],
+ vbms_mst_status: data[:vbms_mst_status],
+ mst_status_update_reason_notes: data[:mst_status_update_reason_notes],
+ pact_status: data[:pact_status],
+ vbms_pact_status: data[:vbms_pact_status],
+ pact_status_update_reason_notes: data[:pact_status_update_reason_notes]
}
end
- # rubocop:enable Metrics/MethodLength
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
def attributes_look_like_contested_issue?(data)
data[:rating_issue_reference_id] ||
@@ -259,6 +265,34 @@ def status_active?
end_product_establishment.status_active?
end
+ def mst_contention_status?
+ return false if bgs_contention.nil?
+
+ if bgs_contention.special_issues.is_a?(Hash)
+ return bgs_contention.special_issues[:spis_tc] == "MST" if bgs_contention&.special_issues
+ elsif bgs_contention.special_issues.is_a?(Array)
+ bgs_contention.special_issues.each do |issue|
+ return true if issue[:spis_tc] == "MST"
+ end
+ end
+ false
+ end
+
+ def pact_contention_status?
+ return false if bgs_contention.nil?
+
+ if bgs_contention.special_issues.is_a?(Hash)
+ if bgs_contention&.special_issues
+ return %w[PACT PACTDICRE PEES1].include?(bgs_contention.special_issues[:spis_tc])
+ end
+ elsif bgs_contention.special_issues.is_a?(Array)
+ bgs_contention.special_issues.each do |issue|
+ return true if %w[PACT PACTDICRE PEES1].include?(issue[:spis_tc])
+ end
+ end
+ false
+ end
+
def rating?
!!associated_rating_issue? ||
!!previous_rating_issue? ||
@@ -644,7 +678,7 @@ def contention_missing?
end
def contention
- end_product_establishment.contention_for_object(self)
+ end_product_establishment&.contention_for_object(self)
end
def bgs_contention
diff --git a/app/models/request_issues_update.rb b/app/models/request_issues_update.rb
index 8b8c29a4001..2e9cedeed31 100644
--- a/app/models/request_issues_update.rb
+++ b/app/models/request_issues_update.rb
@@ -16,6 +16,7 @@ class RequestIssuesUpdate < CaseflowRecord
delegate :withdrawn_issues, to: :withdrawal
delegate :corrected_issues, :correction_issues, to: :correction
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def perform!
return false unless validate_before_perform
return false if processed?
@@ -28,8 +29,14 @@ def perform!
after_request_issue_ids: after_issues.map(&:id),
withdrawn_request_issue_ids: withdrawn_issues.map(&:id),
edited_request_issue_ids: edited_issues.map(&:id),
+ mst_edited_request_issue_ids: mst_edited_issues.map(&:id),
+ pact_edited_request_issue_ids: pact_edited_issues.map(&:id),
corrected_request_issue_ids: corrected_issues.map(&:id)
)
+ if FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user]) ||
+ FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
+ create_mst_pact_issue_update_tasks
+ end
create_business_line_tasks! if added_issues.present?
cancel_active_tasks
submit_for_processing!
@@ -39,6 +46,7 @@ def perform!
true
end
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
def process_job
if run_async?
@@ -90,8 +98,17 @@ def edited_issues
@edited_issues ||= edited_request_issue_ids ? fetch_edited_issues : calculate_edited_issues
end
+ def mst_edited_issues
+ @mst_edited_issues ||= mst_edited_request_issue_ids ? fetch_mst_edited_issues : calculate_mst_edited_issues
+ end
+
+ def pact_edited_issues
+ @pact_edited_issues ||= pact_edited_request_issue_ids ? fetch_pact_edited_issues : calculate_pact_edited_issues
+ end
+
def all_updated_issues
- added_issues + removed_issues + withdrawn_issues + edited_issues + correction_issues
+ added_issues + removed_issues + withdrawn_issues + edited_issues +
+ correction_issues + mst_edited_issues + pact_edited_issues
end
private
@@ -115,6 +132,18 @@ def calculate_edited_issues
end
end
+ def calculate_mst_edited_issues
+ mst_edited_issue_data.map do |mst_issue_data|
+ review.find_or_build_request_issue_from_intake_data(mst_issue_data)
+ end
+ end
+
+ def calculate_pact_edited_issues
+ pact_edited_issue_data.map do |pact_issue_data|
+ review.find_or_build_request_issue_from_intake_data(pact_issue_data)
+ end
+ end
+
def edited_issue_data
return [] unless @request_issues_data
@@ -128,6 +157,33 @@ def edited_issue?(request_issue)
request_issue[:request_issue_id]
end
+ def mst_edited_issue_data
+ return [] unless @request_issues_data
+
+ # cycle through the request issue change data for changes in before/after MST/PACT
+ @request_issues_data.select do |issue|
+ # skip if the issue is a new issue
+ next if issue[:request_issue_id].nil?
+
+ # find the before issue
+ original_issue = before_issues.find { |bi| bi&.id == issue[:request_issue_id].to_i }
+ original_issue&.mst_status != !!issue[:mst_status]
+ end
+ end
+
+ def pact_edited_issue_data
+ return [] unless @request_issues_data
+
+ @request_issues_data.select do |issue|
+ # skip if the issue is a new issue
+ next if issue[:request_issue_id].nil?
+
+ # find the before issue
+ original_issue = before_issues.find { |bi| bi.id == issue[:request_issue_id].to_i }
+ original_issue&.pact_status != !!issue[:pact_status]
+ end
+ end
+
def calculate_before_issues
review.request_issues.active_or_ineligible.select(&:persisted?)
end
@@ -136,6 +192,9 @@ def validate_before_perform
if !changes?
@error_code = :no_changes
elsif RequestIssuesUpdate.where(review: review).where.not(id: id).processable.exists?
+ if @error_code == :no_changes
+ RequestIssuesUpdate.where(review: review).where.not(id: id).processable.last.destroy
+ end
@error_code = :previous_update_not_done_processing
end
@@ -154,6 +213,14 @@ def fetch_edited_issues
RequestIssue.where(id: edited_request_issue_ids)
end
+ def fetch_mst_edited_issues
+ RequestIssue.where(id: mst_edited_issue_data.map(&:id))
+ end
+
+ def fetch_pact_edited_issues
+ RequestIssue.where(id: pact_edited_issue_data.map(&:id))
+ end
+
def process_issues!
review.create_issues!(added_issues, self)
process_removed_issues!
@@ -161,6 +228,8 @@ def process_issues!
process_withdrawn_issues!
process_edited_issues!
process_corrected_issues!
+ process_mst_edited_issues! if FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])
+ process_pact_edited_issues! if FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
end
def process_legacy_issues!
@@ -201,6 +270,39 @@ def edit_decision_date(edited_issue_params, request_issue)
end
end
+ # :reek:FeatureEnvy
+ def process_mst_edited_issues!
+ return if mst_edited_issues.empty?
+
+ mst_edited_issue_data.each do |mst_edited_issue|
+ RequestIssue.find(mst_edited_issue[:request_issue_id].to_s)
+ .update!(
+ mst_status: mst_edited_issue[:mst_status],
+ mst_status_update_reason_notes: mst_edited_issue[:mst_status_update_reason_notes]
+ )
+ end
+ end
+
+ # :reek:FeatureEnvy
+ def process_pact_edited_issues!
+ return if pact_edited_issues.empty?
+
+ pact_edited_issue_data.each do |pact_edited_issue|
+ RequestIssue.find(
+ pact_edited_issue[:request_issue_id].to_s
+ ).update!(
+ pact_status: pact_edited_issue[:pact_status],
+ pact_status_update_reason_notes: pact_edited_issue[:pact_status_update_reason_notes]
+ )
+ end
+ end
+
+ def create_mst_pact_issue_update_tasks
+ handle_mst_pact_edits_task
+ handle_mst_pact_removal_task
+ handle_added_mst_pact_edits_task
+ end
+
def process_removed_issues!
removed_issues.each(&:remove!)
end
@@ -216,4 +318,125 @@ def correction
def process_corrected_issues!
correction.call
end
+
+ def handle_mst_pact_edits_task
+ # filter out added or removed issues
+ after_issues = fetch_after_issues
+ edited_issues = before_issues & after_issues
+ # cycle each edited issue (before) and compare MST/PACT with (fetch_after_issues)
+ # reverse_each to make the issues on the case timeline appear in UI in a similar sequence to the edit issues page
+ edited_issues.reverse_each do |before_issue|
+ after_issue = after_issues.find { |issue| issue.id == before_issue.id }
+ # if before/after has a change in MST/PACT, create issue update task
+ if (before_issue.mst_status != after_issue.mst_status) || (before_issue.pact_status != after_issue.pact_status)
+ create_issue_update_task("Edited Issue", before_issue, after_issue)
+ end
+ end
+ end
+
+ def handle_added_mst_pact_edits_task
+ after_issues = fetch_after_issues
+ added_issues = after_issues - before_issues
+ added_issues.reverse_each do |issue|
+ if issue.mst_status || issue.pact_status
+ create_issue_update_task("Added Issue", issue)
+ end
+ end
+ end
+
+ def handle_mst_pact_removal_task
+ # filter out added or removed issues
+ after_issues = fetch_after_issues
+ edited_issues = before_issues - after_issues
+ # cycle each edited issue (before) and compare MST/PACT with (fetch_after_issues)
+ edited_issues.reverse_each do |before_issue|
+ # lazily create a new RequestIssue since the mst/pact status would be removed if deleted?
+ if before_issue.mst_status || before_issue.pact_status
+ create_issue_update_task("Removed Issue", before_issue)
+ end
+ end
+ end
+
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
+ # :reek:FeatureEnvy
+ def create_issue_update_task(change_type, before_issue, after_issue = nil)
+ transaction do
+ # close out any tasks that might be open
+ open_issue_task = Task.where(
+ assigned_to: SpecialIssueEditTeam.singleton
+ ).where(status: "assigned").where(appeal: before_issue.decision_review)
+ open_issue_task[0].delete unless open_issue_task.empty?
+
+ task = IssuesUpdateTask.create!(
+ appeal: before_issue.decision_review,
+ parent: RootTask.find_by(appeal: before_issue.decision_review),
+ assigned_to: SpecialIssueEditTeam.singleton,
+ assigned_by: RequestStore[:current_user],
+ completed_by: RequestStore[:current_user]
+ )
+
+ # check if change from vbms mst/pact status
+ vbms_mst_edit = if before_issue.vbms_mst_status.nil?
+ false
+ else
+ !before_issue.vbms_mst_status && before_issue.mst_status
+ end
+
+ vbms_pact_edit = if before_issue.vbms_pact_status.nil?
+ false
+ else
+ !before_issue.vbms_pact_status && before_issue.pact_status
+ end
+
+ # if a new issue is added and VBMS was edited, reference the original status
+ if change_type == "Added Issue" && (vbms_mst_edit || vbms_pact_edit)
+ set = CaseTimelineInstructionSet.new(
+ change_type: change_type,
+ issue_category: before_issue.contested_issue_description,
+ benefit_type: before_issue.benefit_type&.capitalize,
+ original_mst: before_issue.vbms_mst_status,
+ original_pact: before_issue.vbms_pact_status,
+ edit_mst: before_issue.mst_status,
+ edit_pact: before_issue.pact_status
+ )
+ else
+ # format the task instructions and close out
+ # use contested issue description if nonrating issue category is nil
+ # rubocop:disable Layout/LineLength
+ issue_description = "#{before_issue.nonrating_issue_category} - #{before_issue.nonrating_issue_description}" unless before_issue.nonrating_issue_category.nil?
+ issue_description = before_issue.contested_issue_description if issue_description.nil?
+ set = CaseTimelineInstructionSet.new(
+ change_type: change_type,
+ issue_category: issue_description,
+ benefit_type: before_issue.benefit_type&.capitalize,
+ original_mst: before_issue.mst_status,
+ original_pact: before_issue.pact_status,
+ edit_mst: after_issue&.mst_status,
+ edit_pact: after_issue&.pact_status
+ )
+ end
+ task.format_instructions(set)
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize
+ task.completed!
+
+ # create SpecialIssueChange record to log the changes
+ SpecialIssueChange.create!(
+ issue_id: before_issue.id,
+ appeal_id: before_issue.decision_review.id,
+ appeal_type: "Appeal",
+ task_id: task.id,
+ created_at: Time.zone.now.utc,
+ created_by_id: RequestStore[:current_user].id,
+ created_by_css_id: RequestStore[:current_user].css_id,
+ original_mst_status: before_issue.mst_status,
+ original_pact_status: before_issue.pact_status,
+ updated_mst_status: after_issue&.mst_status,
+ updated_pact_status: after_issue&.pact_status,
+ mst_from_vbms: before_issue&.vbms_mst_status,
+ pact_from_vbms: before_issue&.vbms_pact_status,
+ change_category: change_type
+ )
+ end
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
end
diff --git a/app/models/serializers/idt/v1/appeal_details_serializer.rb b/app/models/serializers/idt/v1/appeal_details_serializer.rb
index 0f675ddfbbd..8119ef32fe3 100644
--- a/app/models/serializers/idt/v1/appeal_details_serializer.rb
+++ b/app/models/serializers/idt/v1/appeal_details_serializer.rb
@@ -67,13 +67,18 @@ class Idt::V1::AppealDetailsSerializer
attribute :badges do |object|
if object.is_a?(LegacyAppeal)
- nil
+ {
+ mst: !!object.mst?,
+ pact: !!object.pact?
+ }
else
{
contested_claim: object.contested_claim?,
fnod: object.veteran_appellant_deceased?,
hearing: object.hearings.any?(&:held?),
- overtime: object.overtime?
+ overtime: object.overtime?,
+ mst: object.mst?,
+ pact: object.pact?
}
end
end
diff --git a/app/models/serializers/work_queue/appeal_serializer.rb b/app/models/serializers/work_queue/appeal_serializer.rb
index 6a2f2cc69ca..99926d79078 100644
--- a/app/models/serializers/work_queue/appeal_serializer.rb
+++ b/app/models/serializers/work_queue/appeal_serializer.rb
@@ -30,6 +30,10 @@ class WorkQueue::AppealSerializer
attribute :contested_claim, &:contested_claim?
+ attribute :mst, &:mst?
+
+ attribute :pact, &:pact?
+
attribute :issues do |object|
object.request_issues.active_or_decided_or_withdrawn.includes(:remand_reasons).map do |issue|
{
@@ -40,7 +44,11 @@ class WorkQueue::AppealSerializer
diagnostic_code: issue.contested_rating_issue_diagnostic_code,
remand_reasons: issue.remand_reasons,
closed_status: issue.closed_status,
- decision_date: issue.decision_date
+ decision_date: issue.decision_date,
+ mst_status: FeatureToggle.enabled?(:mst_identification) ? issue.mst_status : false,
+ pact_status: FeatureToggle.enabled?(:pact_identification) ? issue.pact_status : false,
+ mst_justification: issue&.mst_status_update_reason_notes,
+ pact_justification: issue&.pact_status_update_reason_notes
}
end
end
@@ -61,7 +69,9 @@ class WorkQueue::AppealSerializer
benefit_type: issue.benefit_type,
remand_reasons: issue.remand_reasons,
diagnostic_code: issue.diagnostic_code,
- request_issue_ids: issue.request_decision_issues.pluck(:request_issue_id)
+ request_issue_ids: issue.request_decision_issues.pluck(:request_issue_id),
+ mst_status: FeatureToggle.enabled?(:mst_identification) ? issue.mst_status : false,
+ pact_status: FeatureToggle.enabled?(:pact_identification) ? issue.pact_status : false
}
end
end
diff --git a/app/models/serializers/work_queue/legacy_appeal_serializer.rb b/app/models/serializers/work_queue/legacy_appeal_serializer.rb
index 0f7fca12051..b5200ac1605 100644
--- a/app/models/serializers/work_queue/legacy_appeal_serializer.rb
+++ b/app/models/serializers/work_queue/legacy_appeal_serializer.rb
@@ -67,6 +67,10 @@ class WorkQueue::LegacyAppealSerializer
attribute :closest_regional_office_label
+ attribute :mst, &:mst?
+
+ attribute :pact, &:pact?
+
attribute(:available_hearing_locations) { |object| available_hearing_locations(object) }
attribute :docket_name do
@@ -92,6 +96,10 @@ class WorkQueue::LegacyAppealSerializer
).editable?
end
+ attribute :can_edit_request_issues do |object, params|
+ AppealRequestIssuesPolicy.new(user: params[:user], appeal: object).legacy_issues_editable?
+ end
+
attribute :attorney_case_review_id do |object|
latest_vacols_attorney_case_review(object)&.vacols_id
end
diff --git a/app/models/serializers/work_queue/legacy_issue_serializer.rb b/app/models/serializers/work_queue/legacy_issue_serializer.rb
index 850389cb35c..00d43d12b8e 100644
--- a/app/models/serializers/work_queue/legacy_issue_serializer.rb
+++ b/app/models/serializers/work_queue/legacy_issue_serializer.rb
@@ -16,4 +16,6 @@ class WorkQueue::LegacyIssueSerializer
attribute :labels
attribute(:readjudication) { false }
attribute :remand_reasons
+ attribute(:mst_status) { |object| FeatureToggle.enabled?(:mst_identification) ? object.mst_status : false }
+ attribute(:pact_status) { |object| FeatureToggle.enabled?(:pact_identification) ? object.pact_status : false }
end
diff --git a/app/models/serializers/work_queue/legacy_task_serializer.rb b/app/models/serializers/work_queue/legacy_task_serializer.rb
index 5427ed4502a..39dc35d7338 100644
--- a/app/models/serializers/work_queue/legacy_task_serializer.rb
+++ b/app/models/serializers/work_queue/legacy_task_serializer.rb
@@ -64,6 +64,14 @@ class WorkQueue::LegacyTaskSerializer
object.appeal.overtime?
end
+ attribute :mst do |object|
+ object.appeal.mst?
+ end
+
+ attribute :pact do |object|
+ object.appeal.pact?
+ end
+
attribute :veteran_appellant_deceased do |object|
object.appeal.veteran_appellant_deceased?
end
diff --git a/app/models/serializers/work_queue/task_column_serializer.rb b/app/models/serializers/work_queue/task_column_serializer.rb
index 9d20508bb3f..f2ed1f5655a 100644
--- a/app/models/serializers/work_queue/task_column_serializer.rb
+++ b/app/models/serializers/work_queue/task_column_serializer.rb
@@ -265,6 +265,22 @@ def self.serialize_attribute?(params, columns)
end
end
+ attribute :mst do |object, params|
+ columns = [Constants.QUEUE_CONFIG.COLUMNS.BADGES.name]
+
+ if serialize_attribute?(params, columns)
+ object.appeal.try(:mst?)
+ end
+ end
+
+ attribute :pact do |object, params|
+ columns = [Constants.QUEUE_CONFIG.COLUMNS.BADGES.name]
+
+ if serialize_attribute?(params, columns)
+ object.appeal.try(:pact?)
+ end
+ end
+
attribute :veteran_appellant_deceased do |object, params|
columns = [Constants.QUEUE_CONFIG.COLUMNS.BADGES.name]
diff --git a/app/models/serializers/work_queue/task_serializer.rb b/app/models/serializers/work_queue/task_serializer.rb
index dbdbbcfb0a1..8490d064bdf 100644
--- a/app/models/serializers/work_queue/task_serializer.rb
+++ b/app/models/serializers/work_queue/task_serializer.rb
@@ -35,7 +35,7 @@ class WorkQueue::TaskSerializer
end
attribute :completed_by do |object|
- object.try(:completed_by).try(:css_id) unless object.appeal.is_a?(LegacyAppeal)
+ object.try(:completed_by).try(:css_id)
end
attribute :assigned_to do |object|
@@ -129,6 +129,14 @@ class WorkQueue::TaskSerializer
object.appeal.try(:contested_claim?)
end
+ attribute :mst do |object|
+ object.appeal.try(:mst?)
+ end
+
+ attribute :pact do |object|
+ object.appeal.try(:pact?)
+ end
+
attribute :veteran_appellant_deceased do |object|
object.appeal.try(:veteran_appellant_deceased?)
end
diff --git a/app/models/special_issue_change.rb b/app/models/special_issue_change.rb
new file mode 100644
index 00000000000..b82228e800b
--- /dev/null
+++ b/app/models/special_issue_change.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+# class for keeping track of MST/PACT special issue changes on legacy and AMA issues
+class SpecialIssueChange < CaseflowRecord
+end
diff --git a/app/models/special_issues_comparator.rb b/app/models/special_issues_comparator.rb
new file mode 100644
index 00000000000..da96c274060
--- /dev/null
+++ b/app/models/special_issues_comparator.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+# class to get special issues from ratings
+# built for MST/PACT release
+
+class SpecialIssuesComparator
+ attr_accessor :issue, :rating_special_issues, :bgs_client, :veteran_contentions, :linked_contentions
+
+ def initialize(issue)
+ @issue = issue
+ @rating_special_issues = issue&.special_issues
+ @bgs_client = BGSService.new
+ end
+
+ MST_SPECIAL_ISSUES = ["sexual assault trauma", "sexual trauma/assault", "sexual harassment"].freeze
+ PACT_SPECIAL_ISSUES = [
+ "agent orange - outside vietnam or unknown",
+ "agent orange - vietnam",
+ "amyotrophic lateral sclerosis (als)",
+ "burn pit exposure",
+ "environmental hazard in gulf war",
+ "gulf war presumptive",
+ "radiation"
+ ].freeze
+ CONTENTION_PACT_ISSUES = %w[
+ pact
+ pactdicre
+ pees1
+ ].freeze
+ CONTENTION_MST_ISSUES = [
+ "mst"
+ ].freeze
+
+ # returns a hash with mst_available and pact_available values
+ # values generated from ratings special issues and contentions
+ def special_issues
+ [{
+ mst_available: mst_from_rating_or_contention,
+ pact_available: pact_from_rating_or_contention
+ }]
+ end
+
+ # check rating for existing mst status; if none, search contentions
+ def mst_from_rating_or_contention
+ return false unless FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])
+ return true if mst_from_rating?
+ return true if mst_from_contention?
+
+ false
+ end
+
+ # check rating for existing pact status; if none, search contentions
+ def pact_from_rating_or_contention
+ return false unless FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
+ return true if pact_from_rating?
+ return true if pact_from_contention?
+
+ false
+ end
+
+ # cycles rating special issues and returns if a special issue is MST
+ def mst_from_rating?
+ return false if rating_special_issues.blank?
+
+ rating_special_issues.each do |special_issue|
+ return true if special_issue_has_mst?(special_issue)
+ end
+
+ false
+ end
+
+ # cycles rating special issues and returns if a special issue is PACT
+ def pact_from_rating?
+ return false if rating_special_issues.blank?
+
+ rating_special_issues.each do |special_issue|
+ return true if special_issue_has_pact?(special_issue)
+ end
+
+ false
+ end
+
+ # :reek:UtilityFunction
+ # checks if rating special issue meets MST criteria
+ def special_issue_has_mst?(special_issue)
+ special_issue.transform_keys!(&:to_s)
+ if special_issue["spis_tn"]&.casecmp("ptsd - personal trauma")&.zero?
+ return MST_SPECIAL_ISSUES.include?(special_issue["spis_basis_tn"]&.downcase)
+ end
+
+ if special_issue["spis_tn"]&.casecmp("non-ptsd personal trauma")&.zero?
+ MST_SPECIAL_ISSUES.include?(special_issue["spis_basis_tn"]&.downcase)
+ end
+ end
+
+ # :reek:UtilityFunction
+ # checks if rating special issue meets PACT criteria
+ def special_issue_has_pact?(special_issue)
+ special_issue.transform_keys!(&:to_s)
+ if special_issue["spis_tn"]&.casecmp("gulf war presumptive 3.320")&.zero?
+ return special_issue.keys(&:to_s)["spis_basis_tn"]&.casecmp("particulate matter")&.zero?
+ end
+
+ PACT_SPECIAL_ISSUES.include?(special_issue["spis_tn"]&.downcase)
+ end
+
+ # cycle contentions tied to the rating issue/decision and return true if there is a match for mst
+ def mst_from_contention?
+ self.linked_contentions ||= contentions_tied_to_issue
+ return false if linked_contentions.blank?
+
+ linked_contentions.each do |contention|
+ return true if mst_contention_status?(contention)
+ end
+
+ false
+ end
+
+ # cycle contentions tied to the rating issue/decision and return true if there is a match for pact
+ def pact_from_contention?
+ self.linked_contentions ||= contentions_tied_to_issue
+ return false if linked_contentions.blank?
+
+ linked_contentions.each do |contention|
+ return true if pact_contention_status?(contention)
+ end
+
+ false
+ end
+
+ # checks single contention special issue status for MST
+ # :reek:UtilityFunction
+ def mst_contention_status?(bgs_contention)
+ bgs_contention.transform_keys!(&:to_s)
+ return false if bgs_contention.nil? || bgs_contention["special_issues"].blank?
+
+ if bgs_contention["special_issues"].is_a?(Hash)
+ CONTENTION_MST_ISSUES.include?(bgs_contention["special_issues"][:spis_tc]&.downcase)
+ elsif bgs_contention["special_issues"].is_a?(Array)
+ bgs_contention["special_issues"].any? { |issue| CONTENTION_MST_ISSUES.include?(issue[:spis_tc]&.downcase) }
+ end
+ end
+
+ # checks single contention special issue status for PACT
+ # :reek:UtilityFunction
+ def pact_contention_status?(bgs_contention)
+ bgs_contention.transform_keys!(&:to_s)
+ return false if bgs_contention.nil? || bgs_contention["special_issues"].blank?
+
+ if bgs_contention["special_issues"].is_a?(Hash)
+ CONTENTION_PACT_ISSUES.include?(bgs_contention["special_issues"][:spis_tc]&.downcase)
+ elsif bgs_contention["special_issues"].is_a?(Array)
+ bgs_contention["special_issues"].any? { |issue| CONTENTION_PACT_ISSUES.include?(issue[:spis_tc]&.downcase) }
+ end
+ end
+
+ # get the contentions for the veteran, find the contentions that are tied to the rating issue
+ def contentions_tied_to_issue
+ # establish veteran contentions
+ self.veteran_contentions ||= fetch_contentions_by_participant_id(issue.participant_id)
+
+ return nil if veteran_contentions.blank?
+
+ match_ratings_with_contentions
+ end
+
+ def fetch_contentions_by_participant_id(participant_id)
+ bgs_client.find_contentions_by_participant_id(participant_id)
+ end
+
+ # cycles list of rba_contentions on the rating issue and matches them with
+ # contentions tied to the veteran
+ def match_ratings_with_contentions
+ contention_matches = []
+
+ return [] if issue.rba_contentions_data.blank?
+
+ # cycle contentions tied to rating issue
+ issue.rba_contentions_data.each do |rba|
+ # grab contention on the rating
+ rba_contention = rba.with_indifferent_access
+ # cycle through the list of contentions from the BGS call (all contentions tied to veteran)
+ veteran_contentions.each do |contention|
+ next unless contention.is_a?(Hash)
+
+ # store any matches that are found
+ link_contention_to_rating(contention, rba_contention, contention_matches)
+ end
+ end
+ contention_matches&.compact
+ end
+
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ # :reek:UtilityFunction
+
+ # takes the contention given and tries to match it to the current rating issue (issue)
+ def link_contention_to_rating(contention, rba_contention, contention_matches)
+ # if only one contention, check the contention info
+ if contention.dig(:contentions).is_a?(Hash)
+ # get the single contention from the response
+ single_contention_info = contention.dig(:contentions)
+
+ return if single_contention_info.blank?
+
+ # see if the contention ties to the rating. if it does, add it to the matches list
+ if single_contention_info.dig(:cntntn_id) == rba_contention.dig(:cntntn_id)
+ contention_matches << single_contention_info
+ end
+
+ # if the response contains an array of contentions, unpack each one and compare
+ elsif contention.dig(:contentions).is_a?(Array)
+
+ # cycle the contentions within the array to make the comparison to the rba_contention
+ contention.dig(:contentions).each do |contention_info|
+ next if contention_info.dig(:cntntn_id).blank?
+
+ # see if the contention ties to the rating. if it does, add it to the matches list
+ contention_matches << contention_info if contention_info.dig(:cntntn_id) == rba_contention.dig(:cntntn_id)
+ end
+ end
+ contention_matches
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+end
diff --git a/app/models/tasks/establishment_task.rb b/app/models/tasks/establishment_task.rb
new file mode 100644
index 00000000000..8f1f61798e4
--- /dev/null
+++ b/app/models/tasks/establishment_task.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+class EstablishmentTask < Task
+ validates :parent, presence: true
+
+ def label
+ "Establishment Task"
+ end
+
+ # :reek:FeatureEnvy
+ # :reek:DuplicateMethodCall { max_calls: 2 }
+ def format_instructions(request_issues)
+ # format the instructions by loading an array and adding it to the instructions
+ added_issue_format = []
+ request_issues.each do |issue|
+ original_special_issue_status = ""
+ # ignore issues that don't have mst or pact status
+ next if !issue.mst_status && !issue.pact_status
+
+ # Logic for checking if a prior decision from vbms with mst/pact designation was updated in intake process
+ if issue.contested_issue_description
+ if issue.vbms_mst_status != issue.mst_status || issue.vbms_pact_status != issue.pact_status
+ original_special_issue_status = format_special_issues_text(issue.vbms_mst_status, issue.vbms_pact_status).to_s
+ end
+ end
+
+ special_issue_status = format_special_issues_text(issue.mst_status, issue.pact_status).to_s
+ added_issue_format << [
+ format_description_text(issue),
+ issue.benefit_type.capitalize,
+ original_special_issue_status,
+ special_issue_status
+ ]
+
+ # create record to log the special issues changes
+ create_special_issue_changes_record(issue)
+ end
+ # add edit_issue_format into the instructions array for the task
+ instructions << added_issue_format
+
+ save!
+ end
+
+ private
+
+ def format_description_text(issue)
+ if issue.contested_issue_description || issue.nonrating_issue_category && issue.nonrating_issue_description
+ issue.contested_issue_description || issue.nonrating_issue_category + " - " + issue.nonrating_issue_description
+ else
+ # we should probably remove this before pushing to prod
+ "Description unavailable"
+ end
+ end
+
+ # rubocop:disable Metrics/CyclomaticComplexity
+ def format_special_issues_text(mst_status, pact_status)
+ # same method as issues_update_task
+ # format the special issues comment to display the change in the special issues status(es)
+ special_issue_phrase = "Special Issues:"
+
+ return special_issue_phrase + " None" if !mst_status && !pact_status
+ return special_issue_phrase + " MST, PACT" if mst_status && pact_status
+ return special_issue_phrase + " MST" if mst_status
+ return special_issue_phrase + " PACT" if pact_status
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity
+
+ # :reek:FeatureEnvy
+ def create_special_issue_changes_record(issue)
+ # create SpecialIssueChange record to log the changes
+ SpecialIssueChange.create!(
+ issue_id: issue.id,
+ appeal_id: appeal.id,
+ appeal_type: "Appeal",
+ task_id: id,
+ created_at: Time.zone.now.utc,
+ created_by_id: RequestStore[:current_user].id,
+ created_by_css_id: RequestStore[:current_user].css_id,
+ original_mst_status: issue.mst_status,
+ original_pact_status: issue.pact_status,
+ mst_from_vbms: issue&.vbms_mst_status,
+ pact_from_vbms: issue&.vbms_pact_status,
+ change_category: "Established Issue"
+ )
+ end
+end
diff --git a/app/models/tasks/issues_update_task.rb b/app/models/tasks/issues_update_task.rb
new file mode 100644
index 00000000000..5239d9294e1
--- /dev/null
+++ b/app/models/tasks/issues_update_task.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class IssuesUpdateTask < Task
+ validates :parent, presence: true
+
+ def label
+ "Issues Update Task"
+ end
+
+ # :reek:FeatureEnvy
+ def format_instructions(set)
+ # format the instructions by loading an array and adding it to the instructions
+ edit_issue_format = []
+ # add the change type
+ edit_issue_format << set.change_type
+ edit_issue_format << set.benefit_type
+ edit_issue_format << set.issue_category
+ original_comment = format_special_issues_text(set.original_mst, set.original_pact).to_s
+ edit_issue_format << original_comment
+
+ # format edit if edit values are given
+ unless set.edit_mst.nil? || set.edit_pact.nil?
+ updated_comment = format_special_issues_text(set.edit_mst, set.edit_pact).to_s
+ edit_issue_format << updated_comment
+ end
+
+ # add the MST and PACT edit reasons. Removed on release but kept incase we need it for the future
+ # edit_issue_format << mst_edit_reason
+ # edit_issue_format << pact_edit_reason
+
+ # add edit_issue_format into the instructions array for the task
+ instructions << edit_issue_format
+
+ save!
+ end
+
+ private
+
+ # rubocop:disable Metrics/CyclomaticComplexity
+ def format_special_issues_text(mst_status, pact_status)
+ # format the special issues comment to display the change in the special issues status(es)
+ special_issue_status = "Special Issues:"
+
+ return special_issue_status + " None" if !mst_status && !pact_status
+ return special_issue_status + " MST, PACT" if mst_status && pact_status
+ return special_issue_status + " MST" if mst_status
+ return special_issue_status + " PACT" if pact_status
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity
+end
diff --git a/app/models/tasks/judge_decision_review_task.rb b/app/models/tasks/judge_decision_review_task.rb
index 2c59e5b54d6..e0ae4a8b86f 100644
--- a/app/models/tasks/judge_decision_review_task.rb
+++ b/app/models/tasks/judge_decision_review_task.rb
@@ -35,6 +35,10 @@ def self.label
private
def ama_judge_actions
- Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SP_ISSUES.to_h
+ # bypass special issues page if mst/pact enabled
+ return Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.to_h if
+ FeatureToggle.enabled?(:mst_identification) || FeatureToggle.enabled?(:pact_identification)
+
+ Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SPECIAL_ISSUES.to_h
end
end
diff --git a/app/models/tasks/judge_dispatch_return_task.rb b/app/models/tasks/judge_dispatch_return_task.rb
index c8dee67b1f9..fe0a6209656 100644
--- a/app/models/tasks/judge_dispatch_return_task.rb
+++ b/app/models/tasks/judge_dispatch_return_task.rb
@@ -6,7 +6,7 @@
class JudgeDispatchReturnTask < JudgeTask
def additional_available_actions(_user)
[
- Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.to_h,
+ ama_issue_checkout,
Constants.TASK_ACTIONS.JUDGE_DISPATCH_RETURN_TO_ATTORNEY.to_h,
Constants.TASK_ACTIONS.CANCEL_TASK.to_h
]
@@ -15,4 +15,13 @@ def additional_available_actions(_user)
def self.label
COPY::JUDGE_DISPATCH_RETURN_TASK_LABEL
end
+
+ # :reek:UtilityFunction
+ def ama_issue_checkout
+ # bypass special issues page if mst/pact enabled
+ return Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.to_h if
+ FeatureToggle.enabled?(:mst_identification) || FeatureToggle.enabled?(:pact_identification)
+
+ Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SPECIAL_ISSUES.to_h
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 000aec8502f..e663375c066 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -151,6 +151,15 @@ def administer_org_users?
admin? || granted?("Admin Intake") || roles.include?("Admin Intake") || member_of_organization?(Bva.singleton)
end
+ # editing logic for MST and PACT
+ def can_edit_intake_issues?
+ return false unless FeatureToggle.enabled?(:mst_identification) ||
+ FeatureToggle.enabled?(:pact_identification) ||
+ FeatureToggle.enabled?(:legacy_mst_pact_identification)
+
+ BvaIntake.singleton.admins.include?(self) || member_of_organization?(ClerkOfTheBoard.singleton)
+ end
+
def can_view_overtime_status?
(attorney_in_vacols? || judge_in_vacols?) && FeatureToggle.enabled?(:overtime_revamp, user: self)
end
diff --git a/app/models/vacols/case_issue.rb b/app/models/vacols/case_issue.rb
index f4a54243233..2f3848f8b71 100644
--- a/app/models/vacols/case_issue.rb
+++ b/app/models/vacols/case_issue.rb
@@ -27,7 +27,9 @@ def attributes_for_readjudication
level_2: isslev2,
level_3: isslev3,
vacols_id: isskey,
- note: issdesc
+ note: issdesc,
+ mst_status: issmst,
+ pact_status: isspact
}
end
@@ -55,6 +57,8 @@ def self.descriptions(vacols_ids)
ISSUES.ISSLEV1,
ISSUES.ISSLEV2,
ISSUES.ISSLEV3,
+ ISSUES.ISSMST,
+ ISSUES.ISSPACT,
ISSREF.PROG_DESC ISSPROG_LABEL,
ISSREF.ISS_DESC ISSCODE_LABEL,
case when ISSUES.ISSLEV1 is not null then
diff --git a/app/policies/appeal_request_issues_policy.rb b/app/policies/appeal_request_issues_policy.rb
index 8e5a7ade34b..9035112c95b 100644
--- a/app/policies/appeal_request_issues_policy.rb
+++ b/app/policies/appeal_request_issues_policy.rb
@@ -9,7 +9,11 @@ def initialize(user:, appeal:)
def editable?
editable_by_case_review_team_member? || case_is_in_active_review_by_current_user? ||
hearing_is_assigned_to_judge_user? || editable_by_cavc_team_member? ||
- editable_by_ssc_team_member?
+ editable_by_ssc_team_member? || editable_by_cob_team_member?
+ end
+
+ def legacy_issues_editable?
+ FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user]) && editable?
end
private
@@ -30,6 +34,19 @@ def editable_by_ssc_team_member?
FeatureToggle.enabled?(:split_appeal_workflow)
end
+ # editable option added for MST and PACT editing
+ def editable_by_cob_team_member?
+ ClerkOfTheBoard.singleton.users.include?(user) &&
+ mst_pact_feature_toggles_enabled?
+ end
+
+ # returns true if one feature toggle is enabled
+ def mst_pact_feature_toggles_enabled?
+ FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user]) ||
+ FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user]) ||
+ FeatureToggle.enabled?(:legacy_mst_pact_identification, user: RequestStore[:current_user])
+ end
+
def current_user_can_edit_issues?
user&.can_edit_issues?
end
diff --git a/app/repositories/task_action_repository.rb b/app/repositories/task_action_repository.rb
index 0de87e8e529..cc6374acd72 100644
--- a/app/repositories/task_action_repository.rb
+++ b/app/repositories/task_action_repository.rb
@@ -890,6 +890,10 @@ def mark_task_complete_data(*)
def select_ama_review_decision_action(task)
return Constants.TASK_ACTIONS.REVIEW_VACATE_DECISION.to_h if task.appeal.vacate?
+ # route to decision if mst/pact toggles are enabled.
+ return Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.to_h if
+ FeatureToggle.enabled?(:mst_identification) || FeatureToggle.enabled?(:pact_identification)
+
Constants.TASK_ACTIONS.REVIEW_AMA_DECISION_SP_ISSUES.to_h
end
diff --git a/app/serializers/hearings/hearing_serializer.rb b/app/serializers/hearings/hearing_serializer.rb
index 11c6bdedac2..5cea1784a1a 100644
--- a/app/serializers/hearings/hearing_serializer.rb
+++ b/app/serializers/hearings/hearing_serializer.rb
@@ -46,6 +46,12 @@ class HearingSerializer
attribute :contested_claim do |hearing|
hearing.appeal.contested_claim?
end
+ attribute :mst do |hearing|
+ hearing.appeal.mst?
+ end
+ attribute :pact do |hearing|
+ hearing.appeal.pact?
+ end
attribute :current_issue_count
attribute :disposition
attribute :disposition_editable
diff --git a/app/serializers/hearings/legacy_hearing_serializer.rb b/app/serializers/hearings/legacy_hearing_serializer.rb
index 339bca6f189..b1b92417f82 100644
--- a/app/serializers/hearings/legacy_hearing_serializer.rb
+++ b/app/serializers/hearings/legacy_hearing_serializer.rb
@@ -122,4 +122,13 @@ class LegacyHearingSerializer
attribute :current_user_timezone do |_, params|
params[:user]&.timezone
end
+
+ attribute :worksheet_issues, &:prepare_worksheet_issues
+ attribute :mst do |object|
+ object.appeal.mst?
+ end
+
+ attribute :pact do |object|
+ object.appeal.pact?
+ end
end
diff --git a/app/serializers/intake/decision_issue_serializer.rb b/app/serializers/intake/decision_issue_serializer.rb
index 6876b94366c..c644a925adb 100644
--- a/app/serializers/intake/decision_issue_serializer.rb
+++ b/app/serializers/intake/decision_issue_serializer.rb
@@ -9,6 +9,8 @@ class Intake::DecisionIssueSerializer
attribute :description
attribute :disposition
attribute :approx_decision_date
+ attribute :mst_status
+ attribute :pact_status
attribute :request_issue_id do |object|
object.request_issues&.first&.id
end
diff --git a/app/serializers/intake/legacy_appeal_serializer.rb b/app/serializers/intake/legacy_appeal_serializer.rb
new file mode 100644
index 00000000000..d5dda33ca95
--- /dev/null
+++ b/app/serializers/intake/legacy_appeal_serializer.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+class Intake::LegacyAppealSerializer
+ include FastJsonapi::ObjectSerializer
+ set_key_transform :camel_lower
+
+ attribute :claimant, &:claimant_participant_id
+ attribute :claimant_type do |object|
+ object.claimant[:representative][:type]
+ end
+ attribute :claimant_name, &:veteran_full_name
+ attribute :veteran_is_not_claimant
+ attribute :request_issues, &:issues
+
+ attribute :intake_user
+
+ attribute :processed_in_caseflow do |_object|
+ true
+ end
+
+ attribute :legacy_opt_in_approved do |_object|
+ true
+ end
+
+ attribute :legacy_appeals do |_object|
+ []
+ end
+
+ attribute :ratings do |_object|
+ []
+ end
+
+ attribute :edit_issues_url do |object|
+ "/appeals/#{object.id}/edit"
+ end
+
+ attribute :processed_at do |_object|
+ nil
+ end
+
+ attribute :veteran_invalid_fields do |_object|
+ nil
+ end
+
+ attribute :active_nonrating_request_issues do |_object|
+ []
+ end
+
+ attribute :contestable_issues_by_date do |_object|
+ []
+ end
+
+ attribute :intake_user do |_object|
+ nil
+ end
+
+ attribute :receipt_date do |_object|
+ nil
+ end
+
+ attribute :decision_issues do |object|
+ object.veteran.decision_issues.map(&:serialize)
+ end
+
+ attribute :relationships do |object|
+ object.veteran&.relationships&.map(&:serialize)
+ end
+
+ attribute :veteran_valid do |object|
+ object.veteran&.valid?(:bgs)
+ end
+
+ attribute :veteran do |object|
+ {
+ name: object.veteran&.name&.formatted(:readable_short),
+ fileNumber: object.veteran_file_number,
+ formName: object.veteran&.name&.formatted(:form),
+ ssn: object.veteran&.ssn
+ }
+ end
+
+ attribute :power_of_attorney_name do |_object|
+ nil
+ end
+
+ attribute :claimant_relationship do |_object|
+ nil
+ end
+
+ attribute :docket_type do
+ "Legacy"
+ end
+
+ attribute :is_outcoded do
+ nil
+ end
+
+ attribute :form_type do
+ "appeal"
+ end
+end
diff --git a/app/serializers/intake/rating_issue_serializer.rb b/app/serializers/intake/rating_issue_serializer.rb
index 5cbabac34dd..41d0416293c 100644
--- a/app/serializers/intake/rating_issue_serializer.rb
+++ b/app/serializers/intake/rating_issue_serializer.rb
@@ -18,4 +18,5 @@ class Intake::RatingIssueSerializer
attribute :rba_contentions_data
attribute :reference_id
attribute :subject_text
+ attribute :special_issues
end
diff --git a/app/serializers/intake/request_issue_serializer.rb b/app/serializers/intake/request_issue_serializer.rb
index dc234fa65ac..263bea4454e 100644
--- a/app/serializers/intake/request_issue_serializer.rb
+++ b/app/serializers/intake/request_issue_serializer.rb
@@ -39,4 +39,10 @@ class Intake::RequestIssueSerializer
end
attribute :benefit_type
attribute :is_predocket_needed
+ attribute :mst_status
+ attribute :vbms_mst_status
+ attribute :pact_status
+ attribute :vbms_pact_status
+ attribute :mst_status_update_reason_notes
+ attribute :pact_status_update_reason_notes
end
diff --git a/app/services/external_api/bgs_service.rb b/app/services/external_api/bgs_service.rb
index 05db6b81545..0b155f89293 100644
--- a/app/services/external_api/bgs_service.rb
+++ b/app/services/external_api/bgs_service.rb
@@ -3,6 +3,7 @@
require "bgs"
# Thin interface to all things BGS
+# rubocop:disable Metrics/ClassLength
class ExternalApi::BGSService
include PowerOfAttorneyMapper
include AddressMapper
@@ -324,6 +325,11 @@ def bust_fetch_veteran_info_cache(vbms_id)
def fetch_ratings_in_range(participant_id:, start_date:, end_date:)
DBService.release_db_connections
+ DataDogService.increment_counter(
+ metric_group: "mst_pact_group",
+ metric_name: "bgs_service.fetch_ratings_in_range_called",
+ app_name: RequestStore[:application]
+ )
start_date, end_date = formatted_start_and_end_dates(start_date, end_date)
MetricsService.record("BGS: fetch ratings in range: \
@@ -343,6 +349,12 @@ def fetch_ratings_in_range(participant_id:, start_date:, end_date:)
def fetch_rating_profile(participant_id:, profile_date:)
DBService.release_db_connections
+ DataDogService.increment_counter(
+ metric_group: "mst_pact_group",
+ metric_name: "bgs_service.fetch_rating_profile_called",
+ app_name: RequestStore[:application]
+ )
+
MetricsService.record("BGS: fetch rating profile: \
participant_id = #{participant_id}, \
profile_date = #{profile_date}",
@@ -451,6 +463,23 @@ def find_contentions_by_claim_id(claim_id)
end
end
+ def find_contentions_by_participant_id(participant_id)
+ return [] unless FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user]) ||
+ FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user])
+
+ # find contention info in cache; if not there, call to BGS and cache it
+ Rails.cache.fetch("find_contentions_by_participant_id_#{participant_id}", expires_in: 24.hours) do
+ DBService.release_db_connections
+ MetricsService.record("BGS: find contentions for veteran by participant_id #{participant_id}",
+ service: :bgs,
+ name: "contention.find_contention_by_participant_id") do
+ client.contention.find_contention_by_participant_id(participant_id)
+ rescue BGS::ShareError
+ []
+ end
+ end
+ end
+
def find_current_rating_profile_by_ptcpnt_id(participant_id)
DBService.release_db_connections
MetricsService.record("BGS: find current rating profile for veteran by participant_id #{participant_id}",
@@ -515,3 +544,4 @@ def formatted_start_and_end_dates(start_date, end_date)
end
# :nocov:
end
+# rubocop:enable Metrics/ClassLength
diff --git a/app/views/appeals/edit.html.erb b/app/views/appeals/edit.html.erb
index f543d778340..052d84c36c8 100644
--- a/app/views/appeals/edit.html.erb
+++ b/app/views/appeals/edit.html.erb
@@ -3,6 +3,7 @@
userDisplayName: current_user.display_name,
userCanWithdrawIssues: current_user.can_withdraw_issues?,
userCanSplitAppeal: current_user.can_split_appeal?(appeal),
+ userCanEditIntakeIssues: current_user.can_edit_intake_issues?,
appeal: appeal,
hearings: appeal.hearings,
hearingDayDate: appeal.hearing_day_if_schedueled,
@@ -13,12 +14,17 @@
buildDate: build_date,
serverIntake: appeal.ui_hash,
claimId: url_appeal_uuid,
+ isLegacy: appeal.is_legacy?,
featureToggles: {
useAmaActivationDate: FeatureToggle.enabled?(:use_ama_activation_date, user: current_user),
correctClaimReviews: FeatureToggle.enabled?(:correct_claim_reviews, user: current_user),
covidTimelinessExemption: FeatureToggle.enabled?(:covid_timeliness_exemption, user: current_user),
split_appeal_workflow: FeatureToggle.enabled?(:split_appeal_workflow, user: current_user),
cc_appeal_workflow: FeatureToggle.enabled?(:cc_appeal_workflow, user: current_user),
+ mstIdentification: FeatureToggle.enabled?(:mst_identification, user: current_user),
+ pactIdentification: FeatureToggle.enabled?(:pact_identification, user: current_user),
+ legacyMstPactIdentification: FeatureToggle.enabled?(:legacy_mst_pact_identification, user: current_user),
+ justificationReason: FeatureToggle.enabled?(:justification_reason, user: current_user),
vhaPreDocketAppeals: false
}
}) %>
diff --git a/app/views/hearings/index.html.erb b/app/views/hearings/index.html.erb
index 5d605dd89d5..30f37fe1828 100644
--- a/app/views/hearings/index.html.erb
+++ b/app/views/hearings/index.html.erb
@@ -14,6 +14,9 @@
applicationUrls: application_urls,
feedbackUrl: feedback_url,
buildDate: build_date,
+ mstIdentification: FeatureToggle.enabled?(:mst_identification, user: current_user),
+ pactIdentification: FeatureToggle.enabled?(:pact_identification, user: current_user),
+ legacyMstPactIdentification: FeatureToggle.enabled?(:legacy_mst_pact_identification, user: current_user),
userCanAddVirtualHearingDays: FeatureToggle.enabled?(:national_vh_queue, user: current_user),
userCanAssignHearingSchedule: current_user.can_assign_hearing_schedule?,
userCanBuildHearingSchedule: current_user.can?('Build HearSched'),
diff --git a/app/views/higher_level_reviews/edit.html.erb b/app/views/higher_level_reviews/edit.html.erb
index 6468416219d..c8e45f5e183 100644
--- a/app/views/higher_level_reviews/edit.html.erb
+++ b/app/views/higher_level_reviews/edit.html.erb
@@ -2,6 +2,7 @@
<%= react_component("IntakeEdit", props: {
userDisplayName: current_user.display_name,
userCanWithdrawIssues: current_user.can_withdraw_issues?,
+ userCanEditIntakeIssues: current_user.can_edit_intake_issues?,
dropdownUrls: dropdown_urls,
applicationUrls: application_urls,
feedbackUrl: feedback_url,
diff --git a/app/views/queue/index.html.erb b/app/views/queue/index.html.erb
index b12500347a8..c63f943d613 100644
--- a/app/views/queue/index.html.erb
+++ b/app/views/queue/index.html.erb
@@ -52,6 +52,10 @@
split_appeal_workflow: FeatureToggle.enabled?(:split_appeal_workflow, user: current_user),
cavc_remand_granted_substitute_appellant: FeatureToggle.enabled?(:cavc_remand_granted_substitute_appellant, user: current_user),
cavc_dashboard_workflow: FeatureToggle.enabled?(:cavc_dashboard_workflow, user: current_user),
+ mstIdentification: FeatureToggle.enabled?(:mst_identification, user: current_user),
+ pactIdentification: FeatureToggle.enabled?(:pact_identification, user: current_user),
+ legacyMstPactIdentification: FeatureToggle.enabled?(:legacy_mst_pact_identification, user: current_user),
+ justificationReason: FeatureToggle.enabled?(:justification_reason, user: current_user),
cc_appeal_workflow: FeatureToggle.enabled?(:cc_appeal_workflow, user: current_user),
metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user),
cc_vacatur_visibility: FeatureToggle.enabled?(:cc_vacatur_visibility, user: current_user),
diff --git a/app/views/supplemental_claims/edit.html.erb b/app/views/supplemental_claims/edit.html.erb
index b3c8d18054e..8293ea763a2 100644
--- a/app/views/supplemental_claims/edit.html.erb
+++ b/app/views/supplemental_claims/edit.html.erb
@@ -2,6 +2,7 @@
<%= react_component("IntakeEdit", props: {
userDisplayName: current_user.display_name,
userCanWithdrawIssues: current_user.can_withdraw_issues?,
+ userCanEditIntakeIssues: current_user.can_edit_intake_issues?,
dropdownUrls: dropdown_urls,
applicationUrls: application_urls,
feedbackUrl: feedback_url,
diff --git a/app/workflows/initial_tasks_factory.rb b/app/workflows/initial_tasks_factory.rb
index 9c9cfe798bd..6c642bee735 100644
--- a/app/workflows/initial_tasks_factory.rb
+++ b/app/workflows/initial_tasks_factory.rb
@@ -21,7 +21,13 @@ def initialize(appeal)
STATE_CODES_REQUIRING_TRANSLATION_TASK = %w[VI VQ PR PH RP PI].freeze
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def create_root_and_sub_tasks!
+ # if changes to mst or pact, create IssueUpdateTask
+ if (@appeal.mst? && FeatureToggle.enabled?(:mst_identification, user: RequestStore[:current_user])) ||
+ (@appeal.pact? && FeatureToggle.enabled?(:pact_identification, user: RequestStore[:current_user]))
+ create_establishment_task
+ end
create_vso_tracking_tasks
ActiveRecord::Base.transaction do
create_subtasks! if @appeal.original? || @appeal.cavc? || @appeal.appellant_substitution?
@@ -31,6 +37,7 @@ def create_root_and_sub_tasks!
end
maybe_create_translation_task
end
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
private
@@ -45,7 +52,6 @@ def create_vso_tracking_tasks
# rubocop:disable Metrics/CyclomaticComplexity
def create_subtasks!
distribution_task # ensure distribution_task exists
-
if @appeal.appellant_substitution?
create_selected_tasks
elsif @appeal.cavc?
@@ -201,4 +207,16 @@ def maybe_create_translation_task
ensure
TranslationTask.create_from_parent(distribution_task) if STATE_CODES_REQUIRING_TRANSLATION_TASK.include?(state_code)
end
+
+ def create_establishment_task
+ task = EstablishmentTask.create!(
+ appeal: @appeal,
+ parent: @root_task,
+ assigned_by: RequestStore[:current_user],
+ assigned_to: SpecialIssueEditTeam.singleton,
+ completed_by: RequestStore[:current_user]
+ )
+ task.format_instructions(@appeal.request_issues)
+ task.completed!
+ end
end
diff --git a/app/workflows/rating_profile_disability.rb b/app/workflows/rating_profile_disability.rb
index 45dbf24e088..d3ba0d8d4df 100644
--- a/app/workflows/rating_profile_disability.rb
+++ b/app/workflows/rating_profile_disability.rb
@@ -38,4 +38,8 @@ def most_recent_evaluation
evaluation[:conv_begin_dt] || evaluation[:begin_dt] || evaluation[:dis_dt] || Time.zone.local(0)
end
end
+
+ def special_issues
+ @special_issues ||= Array.wrap(self[:disability_special_issues])
+ end
end
diff --git a/client/COPY.json b/client/COPY.json
index b92e0df6a5c..9664b5b65df 100644
--- a/client/COPY.json
+++ b/client/COPY.json
@@ -634,6 +634,8 @@
"SPECIAL_ISSUES_BENEFIT_TYPE_SECTION": "Benefit Types: ",
"SPECIAL_ISSUES_ISSUES_ON_APPEAL_SECTION": "Issues on Appeal: ",
"SPECIAL_ISSUES_DIC_OR_PENSION_SECTION": "Dependency and Indemnity Compensation (DIC) or Pension: ",
+ "SPECIAL_ISSUES_BANNER_TEXT": "Military Sexual Trauma and PACT Act will be identified at the issue level. If you need to add these issues, please add them on the next page by editing the applicable issue(s)",
+
"SPECIAL_ISSUES_NONE_CHOSEN_TITLE": "Choose at least one.",
"SPECIAL_ISSUES_NONE_CHOSEN_DETAIL": "If no special issues apply to this case, please confirm by selecting \"No special issues\"",
"ADVANCE_ON_DOCKET_MOTION_PAGE_TITLE": "Update Advanced on Docket (AOD) Status",
@@ -943,6 +945,13 @@
"INTAKE_REQUEST_ISSUE_UNTIMELY": "Please note: The issue requested isn't usually eligible because its decision date is older than what's allowed.",
"INTAKE_LEGACY_ISSUE_UNTIMELY": "Please note: The legacy issue isn't eligible for SOC/SSOC opt-in unless an exemption has been requested for reasons related to good cause.",
"INTAKE_REQUEST_ISSUE_AND_LEGACY_ISSUE_UNTIMELY": "Please note: The issue isn't usually eligible because its decision date is older than what is allowed, and the legacy issue issue isn't eligible for SOC/SSOC opt-in unless an exemption has been requested for reasons related to good cause.",
+ "INTAKE_ADD_EDIT_SPECIAL_ISSUES_LABEL": "Special Issues: ",
+ "INTAKE_EDIT_ISSUE_TITLE": "Edit issue",
+ "INTAKE_EDIT_ISSUE_SELECT_SPECIAL_ISSUES": "Select any special issues that apply",
+ "INTAKE_EDIT_ISSUE_CHANGE_MESSAGE": "Why was this change made?",
+ "INTAKE_EDIT_ISSUE_LABEL": "Issue: ",
+ "INTAKE_EDIT_ISSUE_BENEFIT_TYPE": "Benefit type: ",
+ "INTAKE_EDIT_ISSUE_DECISION_DATE": "Decision date: ",
"INTAKE_VHA_CLAIM_REVIEW_REQUIREMENT": "Only VHA team members can establish Higher Level Reviews and Supplemental Claims with VHA issues. If you have a VHA claim, please return the packet to the main “VHA” queue in the Centralized Mail Portal or send downloaded documents to %s.",
"VHA_BENEFIT_EMAIL_ADDRESS": "VHABENEFITAPPEALS@va.gov",
"VHA_CAREGIVER_SUPPORT_EMAIL_ADDRESS": "VHA.CSPAppeals@va.gov",
@@ -957,6 +966,12 @@
"UNTIMELY_EXEMPTION_COPY_VHA": " OR is this issue related to a VHA Caregiver appeal",
"VHA_PRE_DOCKET_ADD_ISSUES_NOTICE": "You have added VHA issues. Once you click \"Submit Appeal\", this appeal will be marked for pre-docketing.",
"VHA_PRE_DOCKET_ISSUE_BANNER": "Based on the issue selected, this will go to pre-docket queue.",
+
+ "MST_SHORT_LABEL": "MST",
+ "PACT_SHORT_LABEL": "PACT",
+ "MST_LABEL": "Military Sexual Trauma (MST)",
+ "PACT_LABEL": "PACT Act",
+
"VHA_CAMO_PRE_DOCKET_INTAKE_SUCCESS_TITLE": "Appeal recorded and sent to VHA CAMO for document assessment",
"VHA_CAREGIVER_SUPPORT_PRE_DOCKET_INTAKE_SUCCESS_TITLE": "Appeal recorded and sent to VHA Caregiver for document assessment",
"VHA_NO_DECISION_DATE_BANNER": "This claim will be saved, but cannot be worked on until a decision date is added to this issue.",
diff --git a/client/app/admin/reducers/featureToggle.js b/client/app/admin/reducers/featureToggle.js
index 331e3653b7a..8f85fb53bf9 100644
--- a/client/app/admin/reducers/featureToggle.js
+++ b/client/app/admin/reducers/featureToggle.js
@@ -22,7 +22,10 @@ const updateFromServerFeatures = (state, featureToggles) => {
},
updatedAppealForm: {
$set: Boolean(featureToggles.updatedAppealForm)
- }
+ },
+ justificationReason: {
+ $set: Boolean(featureToggles.justificationReason)
+ },
});
};
@@ -34,7 +37,8 @@ export const mapDataToFeatureToggle = (data = { featureToggles: {} }) =>
filedByVaGovHlr: false,
updatedIntakeForms: false,
eduPreDocketAppeals: false,
- updatedAppealForm: false
+ updatedAppealForm: false,
+ justificationReason: false
},
data.featureToggles
);
diff --git a/client/app/components/AmaIssueList.jsx b/client/app/components/AmaIssueList.jsx
index e4bd42001ec..7a509e63f52 100644
--- a/client/app/components/AmaIssueList.jsx
+++ b/client/app/components/AmaIssueList.jsx
@@ -33,12 +33,30 @@ const issueErrorStyling = css({
borderLeft: '4px solid #cd2026'
});
+// format special issues to display 'None', 'PACT', 'MST', or 'MST and PACT'
+const specialIssuesFormatting = (mstStatus, pactStatus) => {
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST and PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
export const AmaIssue = (props) => {
return
- Benefit type: {BENEFIT_TYPES[props.issue.program]}
- Issue: {props.issue.description}
+ Benefit type: {BENEFIT_TYPES[props.issue.program]}
+ Issue: {props.issue.description}
{ props.issue.diagnostic_code &&
- Diagnostic code: {props.issue.diagnostic_code}
}
+ Diagnostic code: {props.issue.diagnostic_code}
}
+ { (props.mstFeatureToggle || props.pactFeatureToggle) &&
+ Special Issues: {
+ specialIssuesFormatting(props.issue.mst_status, props.issue.pact_status)
+ }
+
}
{ props.issue.notes &&
Note from NOD: {props.issue.notes}
}
{ props.issue.closed_status && props.issue.closed_status === 'withdrawn' &&
@@ -55,7 +73,9 @@ export default class AmaIssueList extends React.PureComponent {
const {
requestIssues,
children,
- errorMessages
+ errorMessages,
+ mstFeatureToggle,
+ pactFeatureToggle,
} = this.props;
return
@@ -71,6 +91,10 @@ export default class AmaIssueList extends React.PureComponent {
{children}
@@ -88,13 +112,19 @@ AmaIssue.propTypes = {
description: PropTypes.string,
diagnostic_code: PropTypes.string,
notes: PropTypes.string,
- closed_status: PropTypes.string
+ closed_status: PropTypes.string,
+ mst_status: PropTypes.bool,
+ pact_status: PropTypes.bool
}),
- children: PropTypes.node
+ children: PropTypes.node,
+ pactFeatureToggle: PropTypes.bool,
+ mstFeatureToggle: PropTypes.bool
};
AmaIssueList.propTypes = {
children: PropTypes.node,
requestIssues: PropTypes.array,
- errorMessages: PropTypes.object
+ errorMessages: PropTypes.object,
+ pactFeatureToggle: PropTypes.bool,
+ mstFeatureToggle: PropTypes.bool
};
diff --git a/client/app/components/Checkbox.jsx b/client/app/components/Checkbox.jsx
index e6c67ea3905..79199ea778a 100644
--- a/client/app/components/Checkbox.jsx
+++ b/client/app/components/Checkbox.jsx
@@ -22,7 +22,6 @@ export const Checkbox = (props) => {
} = props;
const handleChange = (event) => onChange?.(event.target.checked, event);
-
const wrapperClasses = classnames(`checkbox-wrapper-${name}`, {
'cf-form-checkboxes': !unpadded,
'usa-input-error': Boolean(errorMessage),
diff --git a/client/app/components/CheckboxGroup.jsx b/client/app/components/CheckboxGroup.jsx
index 22b1d527eeb..f69e59d826c 100644
--- a/client/app/components/CheckboxGroup.jsx
+++ b/client/app/components/CheckboxGroup.jsx
@@ -83,7 +83,8 @@ CheckboxGroup.propTypes = {
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
- ])
+ ]),
+ requiresJustification: PropTypes.bool
})
).isRequired,
onChange: PropTypes.func.isRequired,
@@ -95,5 +96,5 @@ CheckboxGroup.propTypes = {
getCheckbox: PropTypes.func,
styling: PropTypes.object,
strongLabel: PropTypes.bool,
- disableAll: PropTypes.bool
+ disableAll: PropTypes.bool,
};
diff --git a/client/app/components/RadioField.jsx b/client/app/components/RadioField.jsx
index 5786e43f906..1880ba7c00e 100644
--- a/client/app/components/RadioField.jsx
+++ b/client/app/components/RadioField.jsx
@@ -68,11 +68,11 @@ export const RadioField = (props) => {
const maybeAddTooltip = (option, radioField) => {
if (option.tooltipText) {
- const idKey = `tooltip-${option.value}`;
+ const keyId = `tooltip-${option.value}`;
return {
{options.map((option, i) => {
const optionDisabled = isDisabled(option);
-
const radioField = (
+
+
;
} else {
badges =
@@ -46,6 +50,8 @@ class BadgeArea extends React.PureComponent {
+
+
;
}
diff --git a/client/app/components/badges/MstBadge/MstBadge.jsx b/client/app/components/badges/MstBadge/MstBadge.jsx
new file mode 100644
index 00000000000..8726dd68f61
--- /dev/null
+++ b/client/app/components/badges/MstBadge/MstBadge.jsx
@@ -0,0 +1,44 @@
+import PropTypes from 'prop-types';
+import * as React from 'react';
+
+import Badge from '../Badge';
+import { COLORS } from 'app/constants/AppConstants';
+
+/**
+ * Component to display if the appeal is a mst.
+ */
+
+const MstBadge = (props) => {
+ const { appeal } = props;
+
+ // During decision review workflow, saved/staged changes made are updated to appeal.decisionIssues
+ // if legacy check issues for changes, if ama check decision for changes
+ const issues = (appeal.isLegacyAppeal || appeal.type === 'LegacyAppeal') ? appeal.issues : appeal.decisionIssues;
+
+ // check the issues/decisions for mst/pact changes in flight
+ if (issues && issues?.length > 0) {
+ if (!issues.some((issue) => issue.mst_status === true)) {
+ return null;
+ }
+ } else if (!appeal?.mst) {
+ // if issues are empty/undefined, use appeal model mst check
+ return null;
+ }
+
+ const tooltipText = 'Appeal has issue(s) related to Military Sexual Trauma';
+
+ return
;
+};
+
+MstBadge.propTypes = {
+ appeal: PropTypes.object,
+};
+
+export default MstBadge;
diff --git a/client/app/components/badges/MstBadge/MstBadge.stories.js b/client/app/components/badges/MstBadge/MstBadge.stories.js
new file mode 100644
index 00000000000..f35c330be32
--- /dev/null
+++ b/client/app/components/badges/MstBadge/MstBadge.stories.js
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import MstBadgeComponent from './MstBadge';
+
+export default {
+ title: 'Commons/Components/Badges/MST Badge',
+ component: MstBadgeComponent,
+ parameters: {
+ layout: 'centered',
+ },
+ args: {
+ appeal: {
+ mst: true,
+ }
+ }
+};
+
+const Template = (args) =>
;
+
+export const MSTBadge = Template.bind({});
diff --git a/client/app/components/badges/MstBadge/MstBadge.test.js b/client/app/components/badges/MstBadge/MstBadge.test.js
new file mode 100644
index 00000000000..0b4e1c377b0
--- /dev/null
+++ b/client/app/components/badges/MstBadge/MstBadge.test.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware } from 'redux';
+
+import rootReducer from 'app/queue/reducers';
+import MstBadge from './MstBadge';
+
+describe('MstBadge', () => {
+ const defaultAppeal = {
+ mst: true,
+ };
+
+ const getStore = () => createStore(rootReducer, applyMiddleware(thunk));
+
+ const setupMstBadge = (store) => {
+ return mount(
+
+
+
+ );
+ };
+
+ it('renders correctly', () => {
+ const store = getStore();
+ const component = setupMstBadge(store);
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/client/app/components/badges/MstBadge/__snapshots__/MstBadge.test.js.snap b/client/app/components/badges/MstBadge/__snapshots__/MstBadge.test.js.snap
new file mode 100644
index 00000000000..99038478f4f
--- /dev/null
+++ b/client/app/components/badges/MstBadge/__snapshots__/MstBadge.test.js.snap
@@ -0,0 +1,163 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MstBadge renders correctly 1`] = `
+
+
+
+
+
+
+ MST
+
+
+
+
+
+ Appeal has issue(s) related to Military Sexual Trauma
+
+
+
+
+
+
+
+
+`;
diff --git a/client/app/components/badges/PactBadge/PactBadge.jsx b/client/app/components/badges/PactBadge/PactBadge.jsx
new file mode 100644
index 00000000000..e8e58362fe0
--- /dev/null
+++ b/client/app/components/badges/PactBadge/PactBadge.jsx
@@ -0,0 +1,44 @@
+import PropTypes from 'prop-types';
+import * as React from 'react';
+
+import Badge from '../Badge';
+import { COLORS } from 'app/constants/AppConstants';
+
+/**
+ * Component to display if the appeal is a pact.
+ */
+
+const PactBadge = (props) => {
+ const { appeal } = props;
+
+ // During decision review workflow, saved/staged changes made are updated to appeal.decisionIssues
+ // if legacy check issues for changes, if ama check decision for changes
+ const issues = (appeal.isLegacyAppeal || appeal.type === 'LegacyAppeal') ? appeal.issues : appeal.decisionIssues;
+
+ // check the issues/decisions for mst/pact changes in flight
+ if (issues && issues?.length > 0) {
+ if (!issues.some((issue) => issue.pact_status === true)) {
+ return null;
+ }
+ } else if (!appeal?.pact) {
+ // if issues are empty/undefined, use appeal model mst check
+ return null;
+ }
+
+ const tooltipText = 'Appeal has issue(s) related to Promise to Address Comprehensive Toxics (PACT) Act.';
+
+ return
;
+};
+
+PactBadge.propTypes = {
+ appeal: PropTypes.object
+};
+
+export default PactBadge;
diff --git a/client/app/components/badges/PactBadge/PactBadge.stories.js b/client/app/components/badges/PactBadge/PactBadge.stories.js
new file mode 100644
index 00000000000..119c71d5aa2
--- /dev/null
+++ b/client/app/components/badges/PactBadge/PactBadge.stories.js
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import PactBadgeComponent from './PactBadge';
+
+export default {
+ title: 'Commons/Components/Badges/PACT Badge',
+ component: PactBadgeComponent,
+ parameters: {
+ layout: 'centered',
+ },
+ args: {
+ appeal: {
+ pact: true,
+ }
+ }
+};
+
+const Template = (args) =>
;
+
+export const PactBadge = Template.bind({});
diff --git a/client/app/components/badges/PactBadge/PactBadge.test.js b/client/app/components/badges/PactBadge/PactBadge.test.js
new file mode 100644
index 00000000000..64e79980a3b
--- /dev/null
+++ b/client/app/components/badges/PactBadge/PactBadge.test.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware } from 'redux';
+
+import rootReducer from 'app/queue/reducers';
+import PactBadge from './PactBadge';
+
+describe('PactBadge', () => {
+ const defaultAppeal = {
+ pact: true,
+ };
+
+ const getStore = () => createStore(rootReducer, applyMiddleware(thunk));
+
+ const setupPactBadge = (store) => {
+ return mount(
+
+
+
+ );
+ };
+
+ it('renders correctly', () => {
+ const store = getStore();
+ const component = setupPactBadge(store);
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/client/app/components/badges/PactBadge/__snapshots__/PactBadge.test.js.snap b/client/app/components/badges/PactBadge/__snapshots__/PactBadge.test.js.snap
new file mode 100644
index 00000000000..356f96605f1
--- /dev/null
+++ b/client/app/components/badges/PactBadge/__snapshots__/PactBadge.test.js.snap
@@ -0,0 +1,163 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PactBadge renders correctly 1`] = `
+
+
+
+
+
+
+ PACT
+
+
+
+
+
+ Appeal has issue(s) related to Promise to Address Comprehensive Toxics (PACT) Act.
+
+
+
+
+
+
+
+
+`;
diff --git a/client/app/constants/AppConstants.js b/client/app/constants/AppConstants.js
index 5406d0fe57a..bc5945afa58 100644
--- a/client/app/constants/AppConstants.js
+++ b/client/app/constants/AppConstants.js
@@ -29,10 +29,12 @@ export const COLORS = {
GREEN_LIGHTER: '#94BFA2',
GREY: '#5b616b',
GREY_BACKGROUND: '#f9f9f9',
+ GRAY: '#21827F',
PRIMARY: '#0071bc',
PURPLE: '#844E9F',
RED: '#E31C3D',
RED_DARK: '#cd2026',
+ ORANGE: '#BD5727'
};
export const LOGO_COLORS = {
diff --git a/client/app/constants/SpecialIssueEnabler.js b/client/app/constants/SpecialIssueEnabler.js
index ce63c79c524..ea4876c6d49 100644
--- a/client/app/constants/SpecialIssueEnabler.js
+++ b/client/app/constants/SpecialIssueEnabler.js
@@ -1,3 +1,4 @@
import { SPECIAL_ISSUES, AMA_SPECIAL_ISSUES } from './SpecialIssues';
-export const enabledSpecialIssues = wantsAmaIssues => wantsAmaIssues ? SPECIAL_ISSUES.concat(AMA_SPECIAL_ISSUES) : SPECIAL_ISSUES
+export const enabledSpecialIssues = (wantsAmaIssues) =>
+ wantsAmaIssues ? SPECIAL_ISSUES.concat(AMA_SPECIAL_ISSUES) : SPECIAL_ISSUES;
diff --git a/client/app/constants/SpecialIssueFilters.js b/client/app/constants/SpecialIssueFilters.js
index 87f43d14fed..5fdbc84061e 100644
--- a/client/app/constants/SpecialIssueFilters.js
+++ b/client/app/constants/SpecialIssueFilters.js
@@ -1,6 +1,6 @@
import { enabledSpecialIssues } from './SpecialIssueEnabler.js';
-const specialIssueFilters = (isFeatureToggled) => ({
+const specialIssueFilters = (isFeatureToggled, mstIdentification) => ({
unhandledSpecialIssues() {
return enabledSpecialIssues(isFeatureToggled).filter((issue) => {
@@ -43,8 +43,14 @@ const specialIssueFilters = (isFeatureToggled) => ({
return enabledSpecialIssues(isFeatureToggled).filter((issue) => issue.queueSection === 'benefitType');
},
+ // hide mst section on the special issues list if mst_identification feature toggle is enabled
issuesOnAppealSection () {
- return enabledSpecialIssues(isFeatureToggled).filter((issue) => issue.queueSection === 'issuesOnAppeal');
+
+ return mstIdentification ?
+ enabledSpecialIssues(isFeatureToggled).filter((issue) =>
+ issue.queueSection === 'issuesOnAppeal' && issue.specialIssue !== 'militarySexualTrauma') :
+ enabledSpecialIssues(isFeatureToggled).filter((issue) =>
+ issue.queueSection === 'issuesOnAppeal');
},
dicOrPensionSection () {
@@ -52,7 +58,8 @@ const specialIssueFilters = (isFeatureToggled) => ({
},
amaIssuesOnAppealSection () {
- return enabledSpecialIssues(isFeatureToggled).filter((issue) => issue.isAmaRelevant && issue.queueSection === 'issuesOnAppeal');
+ return enabledSpecialIssues(isFeatureToggled).filter((issue) =>
+ issue.isAmaRelevant && issue.queueSection === 'issuesOnAppeal');
}
});
diff --git a/client/app/hearings/HearingsApp.jsx b/client/app/hearings/HearingsApp.jsx
index 98e027d603f..299029f8c48 100644
--- a/client/app/hearings/HearingsApp.jsx
+++ b/client/app/hearings/HearingsApp.jsx
@@ -70,12 +70,18 @@ export default class HearingsApp extends React.PureComponent {
propsForAssignHearingsContainer = () => {
const {
userId,
- userCssId
+ userCssId,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification
} = this.props;
return Object.freeze({
userId,
- userCssId
+ userCssId,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification
});
};
@@ -241,5 +247,8 @@ HearingsApp.propTypes = {
userIsHearingManagement: PropTypes.bool,
userIsBoardAttorney: PropTypes.bool,
userIsHearingAdmin: PropTypes.bool,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool,
userIsNonBoardEmployee: PropTypes.bool
};
diff --git a/client/app/hearings/components/Details.jsx b/client/app/hearings/components/Details.jsx
index 123eeba4d78..74c368c1f95 100644
--- a/client/app/hearings/components/Details.jsx
+++ b/client/app/hearings/components/Details.jsx
@@ -373,6 +373,7 @@ const HearingDetails = (props) => {
veteranFileNumber={hearing?.veteranFileNumber}
veteranFirstName={hearing?.veteranFirstName}
veteranLastName={hearing?.veteranLastName}
+ hearing={hearing}
/>
isEmpty(upcomingHearingDays) ? (
) : (
@@ -28,6 +31,9 @@ export const AssignHearings = ({
selectedRegionalOffice={selectedRegionalOffice}
selectedHearingDay={selectedHearingDay}
room={selectedHearingDay?.room}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
/>
);
@@ -42,5 +48,8 @@ AssignHearings.propTypes = {
PropTypes.string,
PropTypes.object
]),
- userId: PropTypes.number
+ userId: PropTypes.number,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
diff --git a/client/app/hearings/components/assignHearings/AssignHearingsList.jsx b/client/app/hearings/components/assignHearings/AssignHearingsList.jsx
index 8f9ec8a8c1e..7b69cdde2de 100644
--- a/client/app/hearings/components/assignHearings/AssignHearingsList.jsx
+++ b/client/app/hearings/components/assignHearings/AssignHearingsList.jsx
@@ -10,13 +10,21 @@ import { TimeSlotCard } from './TimeSlotCard';
* Assign Hearings List Component
* @param {Object} props -- Contains the hearings list, selected hearing day and regional office
*/
-export const AssignHearingsList = ({ hearings, hearingDay, regionalOffice }) => {
+export const AssignHearingsList = ({ hearings,
+ hearingDay,
+ regionalOffice,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification }) => {
return hearings.length ? sortBy(hearings, 'scheduledTimeString').map((hearing) => (
)) : (
No Upcoming hearings to display
@@ -27,4 +35,7 @@ AssignHearingsList.propTypes = {
hearings: PropTypes.array,
hearingDay: PropTypes.object,
regionalOffice: PropTypes.string,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
diff --git a/client/app/hearings/components/assignHearings/AssignHearingsTable.jsx b/client/app/hearings/components/assignHearings/AssignHearingsTable.jsx
index 1017d16579d..74aec3da034 100644
--- a/client/app/hearings/components/assignHearings/AssignHearingsTable.jsx
+++ b/client/app/hearings/components/assignHearings/AssignHearingsTable.jsx
@@ -22,6 +22,8 @@ import ApiUtil from '../../../util/ApiUtil';
import LinkToAppeal from './LinkToAppeal';
import QUEUE_CONFIG from '../../../../constants/QUEUE_CONFIG';
import QueueTable from '../../../queue/QueueTable';
+import MstBadge from '../../../components/badges/MstBadge/MstBadge';
+import PactBadge from '../../../components/badges/PactBadge/PactBadge';
const TASKS_ENDPOINT = '/hearings/schedule_hearing_tasks';
const COLUMNS_ENDPOINT = '/hearings/schedule_hearing_tasks_columns';
@@ -35,7 +37,10 @@ export default class AssignHearingsTable extends React.PureComponent {
showNoVeteransToAssignError: false,
colsFromApi: null,
amaDocketLineIndex: null,
- rowOffset: 0
+ rowOffset: 0,
+ mstIdentification: this.mstIdentification,
+ pactIdentification: this.pactIdentification,
+ legacyMstPactIdentification: this.legacyMstPactIdentification
};
}
@@ -89,8 +94,13 @@ export default class AssignHearingsTable extends React.PureComponent {
/*
* Gets the list of columns to populate the QueueTable with.
*/
+
getColumns = () => {
- const { selectedRegionalOffice, selectedHearingDay } = this.props;
+ const { selectedRegionalOffice,
+ selectedHearingDay,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification } = this.props;
const { colsFromApi } = this.state;
@@ -98,6 +108,24 @@ export default class AssignHearingsTable extends React.PureComponent {
return [];
}
+ let mstPactBadgeColumn =
+ {
+ header: '',
+ align: 'left',
+ cellClass: 'badge-designation',
+ valueFunction: (row) => (
+
+
+
+ )
+ };
+
const columns = [
{
header: '',
@@ -194,6 +222,10 @@ export default class AssignHearingsTable extends React.PureComponent {
}
];
+ if (mstIdentification || pactIdentification || legacyMstPactIdentification) {
+ columns.splice(1, 0, mstPactBadgeColumn);
+ }
+
return columns;
}
@@ -283,5 +315,8 @@ AssignHearingsTable.propTypes = {
// Selected Regional Office Key
selectedRegionalOffice: PropTypes.string,
- tabName: PropTypes.string
+ tabName: PropTypes.string,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
diff --git a/client/app/hearings/components/assignHearings/AssignHearingsTabs.jsx b/client/app/hearings/components/assignHearings/AssignHearingsTabs.jsx
index c34e0cdce57..f5d258e2f10 100644
--- a/client/app/hearings/components/assignHearings/AssignHearingsTabs.jsx
+++ b/client/app/hearings/components/assignHearings/AssignHearingsTabs.jsx
@@ -34,7 +34,10 @@ export default class AssignHearingsTabs extends React.PureComponent {
selectedHearingDay,
selectedRegionalOffice,
room,
- defaultTabIndex
+ defaultTabIndex,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification
} = this.props;
const availableSlots = Math.max(
@@ -45,7 +48,7 @@ export default class AssignHearingsTabs extends React.PureComponent {
{!_.isNil(selectedHearingDay) &&
- {`${moment(selectedHearingDay.scheduledFor).format('ddd M/DD/YYYY')}
+ {`${moment(selectedHearingDay.scheduledFor).format('ddd M/DD/YYYY')}
${room ?? ''} (${availableSlots} slots remaining)`}
}
@@ -59,6 +62,9 @@ export default class AssignHearingsTabs extends React.PureComponent {
page:
},
{
@@ -69,6 +75,9 @@ export default class AssignHearingsTabs extends React.PureComponent {
tabName={QUEUE_CONFIG.LEGACY_ASSIGN_HEARINGS_TAB_NAME}
key={QUEUE_CONFIG.LEGACY_ASSIGN_HEARINGS_TAB_NAME}
clicked={this.state && this.state.clickedTab === 1}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
/>
},
{
@@ -79,6 +88,9 @@ export default class AssignHearingsTabs extends React.PureComponent {
tabName={QUEUE_CONFIG.AMA_ASSIGN_HEARINGS_TAB_NAME}
key={QUEUE_CONFIG.AMA_ASSIGN_HEARINGS_TAB_NAME}
clicked={this.state && this.state.clickedTab === 2}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
/>
}
]}
@@ -104,7 +116,10 @@ AssignHearingsTabs.propTypes = {
// Selected Regional Office Key
selectedRegionalOffice: PropTypes.string,
- room: PropTypes.string
+ room: PropTypes.string,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
AssignHearingsTabs.defaultProps = {
diff --git a/client/app/hearings/components/assignHearings/TimeSlotCard.jsx b/client/app/hearings/components/assignHearings/TimeSlotCard.jsx
index 3c5b1c8fa79..7a33dacb479 100644
--- a/client/app/hearings/components/assignHearings/TimeSlotCard.jsx
+++ b/client/app/hearings/components/assignHearings/TimeSlotCard.jsx
@@ -6,7 +6,13 @@ import { HearingAppellantName } from './AssignHearingsFields';
import { HearingTime } from '../HearingTime';
import { Dot } from '../../../components/Dot';
-export const TimeSlotCard = ({ hearing, hearingDay, regionalOffice }) => {
+export const TimeSlotCard = ({
+ hearing,
+ hearingDay,
+ regionalOffice,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification }) => {
return (
@@ -27,6 +33,10 @@ export const TimeSlotCard = ({ hearing, hearingDay, regionalOffice }) => {
showType
showDetails
itemSpacing={5}
+ hearing={hearing}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
label={
{
+export const UpcomingHearingsTable = ({ selectedHearingDay,
+ selectedRegionalOffice,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification }) => {
const [loading, setLoading] = useState(false);
const [hearings, setHearings] = useState({});
@@ -47,6 +51,9 @@ export const UpcomingHearingsTable = ({ selectedHearingDay, selectedRegionalOffi
hearings={Object.values(hearings)}
hearingDay={selectedHearingDay}
regionalOffice={selectedRegionalOffice}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
/>
);
@@ -60,6 +67,9 @@ UpcomingHearingsTable.propTypes = {
// Selected Regional Office Key
selectedRegionalOffice: PropTypes.string,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
export default UpcomingHearingsTable;
diff --git a/client/app/hearings/components/dailyDocket/DailyDocketPrinted.jsx b/client/app/hearings/components/dailyDocket/DailyDocketPrinted.jsx
index a9d7902c6f9..1d794ddd4e1 100644
--- a/client/app/hearings/components/dailyDocket/DailyDocketPrinted.jsx
+++ b/client/app/hearings/components/dailyDocket/DailyDocketPrinted.jsx
@@ -41,6 +41,10 @@ export class DailyDocketPrinted extends React.Component {
Number: {hearing.docketNumber}
Disposition: {disposition}
+ Special Issues:
+ {hearing.mst && MST}
+ {hearing.pact && PACT}
+
{this.isUserJudge() &&
AOD: {hearing.aod ? AOD_CODE_TO_LABEL_MAP[hearing.aod] : 'None'}
diff --git a/client/app/hearings/components/dailyDocket/DailyDocketRowDisplayText.jsx b/client/app/hearings/components/dailyDocket/DailyDocketRowDisplayText.jsx
index 19b2f59dc6f..d644fcec437 100644
--- a/client/app/hearings/components/dailyDocket/DailyDocketRowDisplayText.jsx
+++ b/client/app/hearings/components/dailyDocket/DailyDocketRowDisplayText.jsx
@@ -10,8 +10,11 @@ import { PowerOfAttorneyName } from 'app/queue/PowerOfAttorneyDetail';
import FnodBadge from 'app/components/badges/FnodBadge/FnodBadge';
import ContestedClaimBadge from 'app/components/badges/ContestedBadge/ContestedClaimBadge';
+import MstBadge from 'app/components/badges/MstBadge/MstBadge';
+import PactBadge from 'app/components/badges/PactBadge/PactBadge';
import { tooltipListStyling } from 'app/components/badges/style';
import { DateString } from 'app/util/DateUtil';
+import { badgeStyle } from './style';
const hearingPropTypes = PropTypes.shape({
appealExternalId: PropTypes.string,
@@ -80,6 +83,10 @@ const AppellantInformation = ({ hearing, userCanViewFnodBadgeInHearings }) => {
}
/>
+
{hearing.appellantAddressLine1}
{hearing.appellantCity ?
diff --git a/client/app/hearings/components/dailyDocket/style.js b/client/app/hearings/components/dailyDocket/style.js
index 48f96f2e631..80af05d1dac 100644
--- a/client/app/hearings/components/dailyDocket/style.js
+++ b/client/app/hearings/components/dailyDocket/style.js
@@ -35,3 +35,8 @@ export const inputSpacing = css({
marginTop: '25px'
}
});
+
+export const badgeStyle = css({
+ display: 'flex',
+ justifyContent: 'left'
+});
diff --git a/client/app/hearings/components/details/DetailsHeader.jsx b/client/app/hearings/components/details/DetailsHeader.jsx
index 3109531027c..66116f9bd84 100644
--- a/client/app/hearings/components/details/DetailsHeader.jsx
+++ b/client/app/hearings/components/details/DetailsHeader.jsx
@@ -10,6 +10,8 @@ import CopyTextButton from '../../../components/CopyTextButton';
import * as DateUtil from '../../../util/DateUtil';
import { dispositionLabel } from '../../utils';
import DocketTypeBadge from '../../../components/DocketTypeBadge';
+import MstBadge from '../../../components/badges/MstBadge/MstBadge';
+import PactBadge from '../../../components/badges/PactBadge/PactBadge';
const headerContainerStyling = css({
margin: '-2rem 0 0 0',
@@ -41,7 +43,8 @@ export const DetailsHeader = (
scheduledFor,
veteranFirstName,
veteranLastName,
- veteranFileNumber
+ veteranFileNumber,
+ hearing
}
) => {
const columns = [
@@ -92,6 +95,10 @@ export const DetailsHeader = (
Veteran ID:
+
@@ -112,5 +119,6 @@ DetailsHeader.propTypes = {
scheduledFor: PropTypes.string,
veteranFirstName: PropTypes.string,
veteranLastName: PropTypes.string,
- veteranFileNumber: PropTypes.string
+ veteranFileNumber: PropTypes.string,
+ hearing: PropTypes.object
};
diff --git a/client/app/hearings/components/hearingWorksheet/HearingWorksheetPrinted.jsx b/client/app/hearings/components/hearingWorksheet/HearingWorksheetPrinted.jsx
index 1937fa7d925..fc8b7545c65 100644
--- a/client/app/hearings/components/hearingWorksheet/HearingWorksheetPrinted.jsx
+++ b/client/app/hearings/components/hearingWorksheet/HearingWorksheetPrinted.jsx
@@ -51,6 +51,18 @@ const getLegacyHearingWorksheetDocsSection = (appeal) => {
);
};
+const specialIssuesFormatting = (mstStatus, pactStatus) => {
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST, PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
export class HearingWorksheetPrinted extends React.Component {
componentDidMount() {
@@ -124,6 +136,10 @@ export class HearingWorksheetPrinted extends React.Component {
{issue.disposition}
}
+
+
Special Issues
+
{specialIssuesFormatting(issue.mst_status, issue.pact_status)}
+
{
issue.notes &&
diff --git a/client/app/hearings/components/scheduleHearing/AppealInformation.jsx b/client/app/hearings/components/scheduleHearing/AppealInformation.jsx
index 81387603822..4dfc2955c4e 100644
--- a/client/app/hearings/components/scheduleHearing/AppealInformation.jsx
+++ b/client/app/hearings/components/scheduleHearing/AppealInformation.jsx
@@ -7,6 +7,8 @@ import { ReadOnly } from '../details/ReadOnly';
import { AddressLine } from '../details/Address';
import { renderAppealType } from '../../../queue/utils';
import { formatDateStr } from '../../../util/DateUtil';
+import MstBadge from '../../../components/badges/MstBadge/MstBadge';
+import PactBadge from '../../../components/badges/PactBadge/PactBadge';
export const AppealStreamDetails = ({
remandSourceAppealId,
@@ -95,6 +97,10 @@ export const AppealInformation = ({ appeal, hearing }) => {
}
/>
+
{
const issueLabel = issueCount === 1 ? `${issueCount} issue` : `${issueCount} issues`;
@@ -37,6 +45,19 @@ export const TimeSlotDetail = ({
{' '}
{showType && docketNumber}{' '}
{' '}
+ {(mstIdentification || legacyMstPactIdentification) &&
+
+
+
}
+ {(pactIdentification || legacyMstPactIdentification) &&
+ }
+ {' '}
{poaName}
@@ -84,4 +105,8 @@ TimeSlotDetail.propTypes = {
aod: PropTypes.bool,
itemSpacing: PropTypes.number,
poaName: PropTypes.string,
+ hearing: PropTypes.object,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
diff --git a/client/app/hearings/containers/AssignHearingsContainer.jsx b/client/app/hearings/containers/AssignHearingsContainer.jsx
index 6e078a46177..86275bb790c 100644
--- a/client/app/hearings/containers/AssignHearingsContainer.jsx
+++ b/client/app/hearings/containers/AssignHearingsContainer.jsx
@@ -73,7 +73,10 @@ class AssignHearingsContainer extends React.PureComponent {
};
render = () => {
- const { selectedRegionalOffice } = this.props;
+ const { selectedRegionalOffice,
+ mstIdentification,
+ pactIdentification,
+ legacyMstPactIdentification } = this.props;
return (
@@ -106,6 +109,9 @@ class AssignHearingsContainer extends React.PureComponent {
onSelectedHearingDayChange={this.props.onSelectedHearingDayChange}
selectedHearingDay={this.props.selectedHearingDay}
userId={this.props.userId}
+ mstIdentification={mstIdentification}
+ pactIdentification={pactIdentification}
+ legacyMstPactIdentification={legacyMstPactIdentification}
/>
}
@@ -129,7 +135,10 @@ AssignHearingsContainer.propTypes = {
setUserCssId: PropTypes.func,
upcomingHearingDays: PropTypes.object,
userCssId: PropTypes.string,
- userId: PropTypes.number
+ userId: PropTypes.number,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ legacyMstPactIdentification: PropTypes.bool
};
const mapStateToProps = (state) => ({
diff --git a/client/app/hearings/utils.js b/client/app/hearings/utils.js
index 5910ee2fb6e..ac97feab52b 100644
--- a/client/app/hearings/utils.js
+++ b/client/app/hearings/utils.js
@@ -56,6 +56,8 @@ export const getWorksheetAppealsAndIssues = (worksheet) => {
const worksheetAppeals = keyBy(worksheet.appeals_ready_for_hearing, 'id');
let worksheetIssues = keyBy(flatMap(worksheetAppeals, 'worksheet_issues'), 'id');
+ worksheetIssues = { ...worksheetIssues, ...keyBy(worksheet.worksheet_issues, 'id') };
+
if (isEmpty(worksheetIssues)) {
worksheetIssues = keyBy(worksheet.worksheet_issues, 'id');
}
diff --git a/client/app/intake/IntakeFrame.jsx b/client/app/intake/IntakeFrame.jsx
index 526a61f6cbc..56b57338bfb 100644
--- a/client/app/intake/IntakeFrame.jsx
+++ b/client/app/intake/IntakeFrame.jsx
@@ -117,7 +117,10 @@ export const IntakeFrame = (props) => {
title="Add / Remove Issues | Caseflow Intake"
>
}>
-
+
diff --git a/client/app/intake/actions/addIssues.js b/client/app/intake/actions/addIssues.js
index d3a366deefe..481ad930a5e 100644
--- a/client/app/intake/actions/addIssues.js
+++ b/client/app/intake/actions/addIssues.js
@@ -47,6 +47,21 @@ export const toggleLegacyOptInModal = (currentIssueAndNotes = {}) => ({
payload: { currentIssueAndNotes }
});
+export const toggleEditIntakeIssueModal = (index) => ({
+ type: ACTIONS.TOGGLE_EDIT_INTAKE_ISSUES_MODAL,
+ payload: { index }
+});
+
+export const setMstPactDetails = (editIssuesDetails) => ({
+ type: ACTIONS.SET_MST_PACT_DETAILS,
+ payload: { editIssuesDetails }
+});
+
+export const setMstPactIssue = (mstPactValues) => ({
+ type: ACTIONS.SET_MST_PACT_ISSUE,
+ payload: { mstPactValues }
+});
+
export const addDecisionDate = ({ decisionDate, index }) => ({
type: ACTIONS.ADD_DECISION_DATE,
payload: { decisionDate, index }
diff --git a/client/app/intake/components/AddIssueManager.jsx b/client/app/intake/components/AddIssueManager.jsx
index b36340937fb..c6e75e159f1 100644
--- a/client/app/intake/components/AddIssueManager.jsx
+++ b/client/app/intake/components/AddIssueManager.jsx
@@ -44,13 +44,15 @@ class AddIssueManager extends React.Component {
}
setupAddIssuesModal = () => {
- const { intakeData, formType } = this.props;
+ const { intakeData, formType, featureToggles, userCanEditIntakeIssues } = this.props;
return {
component: AddIssuesModal,
props: {
intakeData,
formType,
+ featureToggles,
+ userCanEditIntakeIssues,
onCancel: () => this.cancel(),
onSubmit: ({ selectedContestableIssueIndex, currentIssue, notes }) => {
this.setState(
@@ -120,7 +122,7 @@ class AddIssueManager extends React.Component {
};
setupNonratingRequestIssueModal = () => {
- const { intakeData, formType, featureToggles } = this.props;
+ const { intakeData, formType, featureToggles, userCanEditIntakeIssues } = this.props;
return {
component: NonratingRequestIssueModal,
@@ -128,6 +130,7 @@ class AddIssueManager extends React.Component {
intakeData,
formType,
featureToggles,
+ userCanEditIntakeIssues,
submitText: this.hasLegacyAppeals() ? 'Next' : 'Add this issue',
onCancel: () => this.cancel(),
onSkip: () => this.setState({ currentModal: 'UnidentifiedIssuesModal' }),
@@ -336,6 +339,7 @@ AddIssueManager.propTypes = {
currentModal: PropTypes.string,
onComplete: PropTypes.func,
featureToggles: PropTypes.object,
+ userCanEditIntakeIssues: PropTypes.bool,
intakeData: PropTypes.object,
formType: PropTypes.string,
addIssue: PropTypes.func,
diff --git a/client/app/intake/components/AddIssuesModal.jsx b/client/app/intake/components/AddIssuesModal.jsx
index 7bc3f0af343..649b1ee4c17 100644
--- a/client/app/intake/components/AddIssuesModal.jsx
+++ b/client/app/intake/components/AddIssuesModal.jsx
@@ -5,7 +5,7 @@ import { map, findIndex, uniq } from 'lodash';
import { formatDateStr } from '../../util/DateUtil';
import Modal from '../../components/Modal';
-import RadioField from '../../components/RadioField';
+import IntakeRadioField from './IntakeRadioField';
import TextField from '../../components/TextField';
import { issueByIndex } from '../util/issues';
import { generateSkipButton } from '../util/buttonUtils';
@@ -17,16 +17,57 @@ class AddIssuesModal extends React.Component {
this.state = {
approxDecisionDate: '',
selectedContestableIssueIndex: '',
- notes: ''
+ notes: '',
+ mstJustification: '',
+ pactJustification: '',
+ mstChecked: false,
+ pactChecked: false,
+ vbmsMstChecked: false,
+ vbmsPactChecked: false,
+ elementCount: 0,
+ renderedFirstRadiofield: false
};
}
- radioOnChange = (selectedContestableIssueIndex) => this.setState({ selectedContestableIssueIndex });
+ addToElementCount = (increaseAmount) => {
+ this.setState({ elementCount: this.elementCount + increaseAmount });
+ }
+
+ getElementCount = () => {
+ if (this.state.renderedFirstRadiofield) {
+ return this.state.elementCount;
+ }
+ this.setState({ renderedFirstRadiofield: true });
+
+ return 0;
+ }
+ mstCheckboxChange = (checked) => this.setState({ mstChecked: checked });
+ pactCheckboxChange = (checked) => this.setState({ pactChecked: checked });
+
+ vbmsMstCheckedChange = (checked) => this.setState({ vbmsMstChecked: checked });
+ vbmsPactCheckedChange = (checked) => this.setState({ vbmsPactChecked: checked });
+
+ radioOnChange = (selectedContestableIssueIndex) => {
+ this.setState({ selectedContestableIssueIndex });
+ }
notesOnChange = (notes) => this.setState({ notes });
+ mstJustificationOnChange = (mstJustification) => this.setState({ mstJustification });
+ pactJustificationOnChange = (pactJustification) => this.setState({ pactJustification });
+
onAddIssue = () => {
- const { selectedContestableIssueIndex, notes } = this.state;
+ const {
+ selectedContestableIssueIndex,
+ notes,
+ mstChecked,
+ pactChecked,
+ vbmsMstChecked,
+ vbmsPactChecked,
+ mstJustification,
+ pactJustification
+ } = this.state;
+
const currentIssue = issueByIndex(this.props.intakeData.contestableIssues, selectedContestableIssueIndex);
if (selectedContestableIssueIndex && !currentIssue.index) {
@@ -36,18 +77,87 @@ class AddIssuesModal extends React.Component {
// Ensure we have a value for decisionDate
currentIssue.decisionDate = currentIssue.decisionDate || currentIssue.approxDecisionDate;
+ if (this.props.featureToggles.justificationReason) {
+ if (mstChecked && mstJustification === '') {
+ if (!currentIssue.mstAvailable) {
+ return;
+ }
+ }
+ if (pactChecked && pactJustification === '') {
+ if (!currentIssue.pactAvailable) {
+ return;
+ }
+ }
+ }
+
this.props.onSubmit({
currentIssue: {
...currentIssue,
- notes
+ notes,
+ mstChecked,
+ pactChecked,
+ vbmsMstChecked,
+ vbmsPactChecked,
+ mstJustification,
+ pactJustification,
}
});
};
getContestableIssuesSections() {
- const { intakeData } = this.props;
+ const { intakeData, formType, featureToggles } = this.props;
+ let iterations = -1;
const addedIssues = intakeData.addedIssues ? intakeData.addedIssues : [];
+ let counter = 0;
+ const issueKeys = Object.keys(intakeData.contestableIssues);
+
+ let nestedIssues = [issueKeys.length];
+
+ for (let i = 0; i < issueKeys.length; i++) {
+ nestedIssues.push(intakeData.contestableIssues[issueKeys[i]]);
+ }
+
+ const sizes = nestedIssues.map((foundIssue) => {
+ return Object.keys(foundIssue).length;
+ });
+
+ let accumulation = 0;
+ let accumulationArr = [sizes.length];
+
+ // Each contestable issue section on the child RadioField component consist of a seperate
+ // array. This is a problem because this.state.selectedContestableIssueIndex does not
+ // account for that, and the selected index is used as a prop to identify which Radio option is selected.
+ // The fix is applying offsets so that the index is back to 0 for each new group.
+ for (let i = 0; i < sizes.length; i++) {
+ if (i === 0) {
+ accumulationArr.push(0);
+ } else {
+ accumulation += sizes[i];
+ }
+ accumulationArr[i] = accumulation;
+ }
+
+ const renderTrueOrFalse = (whatToRender) => {
+ let featureFlagToUse;
+
+ switch (whatToRender) {
+ case 'mst':
+ featureFlagToUse = featureToggles.mstIdentification;
+ break;
+ case 'pact':
+ featureFlagToUse = featureToggles.pactIdentification;
+ break;
+ case 'justification':
+ featureFlagToUse = featureToggles.justificationReason;
+ break;
+ default:
+ // Do nothing if variable not specified in list
+ break;
+ }
+
+ return featureFlagToUse && formType === 'appeal';
+ };
return map(intakeData.contestableIssues, (contestableIssuesByIndex, approxDecisionDate) => {
const radioOptions = map(contestableIssuesByIndex, (issue) => {
@@ -74,21 +184,42 @@ class AddIssuesModal extends React.Component {
}
return {
+ counterVal: counter,
displayText: text,
value: issue.index,
- disabled: foundIndex !== -1 || hasLaterIssueInChain
+ disabled: foundIndex !== -1 || hasLaterIssueInChain,
+ mst: contestableIssuesByIndex[issue.index].mstAvailable,
+ pact: contestableIssuesByIndex[issue.index].pactAvailable
};
});
+ counter += 1;
+ iterations += 1;
+
return (
- Past decisions from {formatDateStr(approxDecisionDate)}}
+ totalElements={accumulationArr[iterations]}
name="rating-radio"
options={radioOptions}
key={approxDecisionDate}
value={this.state.selectedContestableIssueIndex}
onChange={this.radioOnChange}
+ mstJustification={this.state.mstJustification}
+ mstJustificationOnChange={this.mstJustificationOnChange}
+ pactJustification={this.state.pactJustification}
+ pactJustificationOnChange={this.pactJustificationOnChange}
+ renderMst={renderTrueOrFalse('mst')}
+ renderPact={renderTrueOrFalse('pact')}
+ renderJustification={renderTrueOrFalse('justification')}
+ userCanEditIntakeIssues={this.props.userCanEditIntakeIssues}
+ mstChecked={this.state.mstChecked}
+ setMstCheckboxFunction={this.mstCheckboxChange}
+ pactChecked={this.state.pactChecked}
+ setPactCheckboxFunction={this.pactCheckboxChange}
+ setVbmsMstCheckedFunction={this.vbmsMstCheckedChange}
+ setVbmsPactCheckedFunction={this.vbmsPactCheckedChange}
/>
);
});
@@ -118,7 +249,6 @@ class AddIssuesModal extends React.Component {
render() {
const { intakeData, onCancel } = this.props;
-
const issueNumber = (intakeData.addedIssues || []).length + 1;
return (
@@ -147,7 +277,10 @@ AddIssuesModal.propTypes = {
cancelText: PropTypes.string,
onSkip: PropTypes.func,
skipText: PropTypes.string,
- intakeData: PropTypes.object
+ intakeData: PropTypes.object,
+ featureToggles: PropTypes.object,
+ userCanEditIntakeIssues: PropTypes.bool,
+ formType: PropTypes.string
};
AddIssuesModal.defaultProps = {
diff --git a/client/app/intake/components/AddedIssue.jsx b/client/app/intake/components/AddedIssue.jsx
index 76e152be0a8..0c315f72bb2 100644
--- a/client/app/intake/components/AddedIssue.jsx
+++ b/client/app/intake/components/AddedIssue.jsx
@@ -82,13 +82,18 @@ class AddedIssue extends React.PureComponent {
};
render() {
- const { issue, issueIdx, legacyAppeals } = this.props;
+ const { issue, issueIdx, legacyAppeals, featureToggles } = this.props;
let eligibleState = {
errorMsg: '',
cssKlasses: ['issue-desc']
};
+ const mstFeatureToggle = featureToggles.mstIdentification;
+ const pactFeatureToggle = featureToggles.pactIdentification;
+ // eslint-disable-next-line max-len
+ const legacyMstPactFeatureToggle = featureToggles.legacyMstPactIdentification;
+
const vacolsIssue = legacyIssue(issue, legacyAppeals);
if (this.needsEligibilityCheck()) {
@@ -107,6 +112,18 @@ class AddedIssue extends React.PureComponent {
eligibleState.cssKlasses.push('withdrawn-issue');
}
+ let specialIssuesMessage = 'None';
+ const showMstStatus = issue.mstChecked && mstFeatureToggle;
+ const showPactStatus = issue.pactChecked && pactFeatureToggle;
+
+ if (showMstStatus && showPactStatus) {
+ specialIssuesMessage = `${COPY.MST_SHORT_LABEL}, ${COPY.PACT_SHORT_LABEL}`;
+ } else if (showMstStatus) {
+ specialIssuesMessage = COPY.MST_SHORT_LABEL;
+ } else if (showPactStatus) {
+ specialIssuesMessage = COPY.PACT_SHORT_LABEL;
+ }
+
return (
{issueIdx + 1}.
@@ -121,6 +138,9 @@ class AddedIssue extends React.PureComponent {
Decision date: {issue.date ? formatDateStr(issue.date) : COPY.NO_DATE_ENTERED }
{issue.notes &&
Notes: {issue.notes}}
+ { (mstFeatureToggle || pactFeatureToggle || legacyMstPactFeatureToggle) &&
+ // eslint-disable-next-line max-len
+
{COPY.INTAKE_ADD_EDIT_SPECIAL_ISSUES_LABEL}{specialIssuesMessage}}
{issue.untimelyExemptionNotes && (
Untimely Exemption Notes: {issue.untimelyExemptionNotes}
)}
@@ -170,8 +190,11 @@ AddedIssue.propTypes = {
untimelyExemptionNotes: PropTypes.string,
vacolsId: PropTypes.string,
withdrawalPending: PropTypes.string,
- withdrawalDate: PropTypes.string
+ withdrawalDate: PropTypes.string,
+ mstChecked: PropTypes.bool,
+ pactChecked: PropTypes.bool,
}).isRequired,
+ featureToggles: PropTypes.object,
issueIdx: PropTypes.number.isRequired,
legacyAppeals: PropTypes.array,
legacyOptInApproved: PropTypes.bool.isRequired,
diff --git a/client/app/intake/components/IntakeRadioField.jsx b/client/app/intake/components/IntakeRadioField.jsx
new file mode 100644
index 00000000000..af4c81b34e6
--- /dev/null
+++ b/client/app/intake/components/IntakeRadioField.jsx
@@ -0,0 +1,325 @@
+import React, { useMemo } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import Checkbox from '../../components/Checkbox';
+import TextField from '../../components/TextField';
+
+import RequiredIndicator from '../../components/RequiredIndicator';
+import StringUtil from '../../util/StringUtil';
+import Tooltip from '../../components/Tooltip';
+
+import { helpText } from '../../components/RadioField.module.scss';
+
+const RadioFieldHelpText = ({ help, className }) => {
+ const helpClasses = classNames('cf-form-radio-help', helpText, className);
+
+ return
{help}
;
+};
+
+RadioFieldHelpText.propTypes = {
+ help: PropTypes.string.isRequired,
+ className: PropTypes.string,
+};
+
+/**
+ * Intake radio button component.
+ *
+ * See StyleGuideRadioField.jsx for usage examples.
+ *
+ */
+
+export const IntakeRadioField = (props) => {
+ const {
+ id,
+ className,
+ label,
+ inputRef,
+ inputProps,
+ name,
+ options,
+ value,
+ onChange,
+ required,
+ errorMessage,
+ strongLabel,
+ hideLabel,
+ styling,
+ vertical,
+ totalElements,
+ renderMst,
+ renderPact,
+ renderJustification,
+ mstChecked,
+ setMstCheckboxFunction,
+ pactChecked,
+ setPactCheckboxFunction,
+ setVbmsMstCheckedFunction,
+ setVbmsPactCheckedFunction,
+ mstJustification,
+ mstJustificationOnChange,
+ pactJustification,
+ pactJustificationOnChange,
+ userCanEditIntakeIssues
+ } = props;
+
+ const isVertical = useMemo(() => props.vertical || props.options.length > 2, [
+ vertical,
+ options,
+ ]);
+
+ const intakeRadioClass = className.
+ concat(isVertical ? 'cf-form-radio' : 'cf-form-radio-inline').
+ concat(errorMessage ? 'usa-input-error' : '');
+
+ const labelClass = hideLabel ? 'usa-sr-only' : '';
+
+ // Since HTML5 IDs should not contain spaces...
+ const idPart = StringUtil.html5CompliantId(id || name);
+
+ const labelContents = (
+
+ {label || name} {required && }
+
+ );
+
+ // prepopulated MST and PACT checkbox values
+ let prePopulatedMst = options[value - totalElements]?.mst;
+ let prePopulatedPact = options[value - totalElements]?.pact;
+
+ // handle both MST and PACT pre-populated checkbox status on load
+ const handlePrepopulatedCheckboxes = (radioOptions, index) => {
+ // update the checkbox value
+ setMstCheckboxFunction(radioOptions[index - totalElements]?.mst);
+ setPactCheckboxFunction(radioOptions[index - totalElements]?.pact);
+
+ // handle update for pre-populated mst/pact status
+ setVbmsMstCheckedFunction(radioOptions[index - totalElements]?.mst);
+ setVbmsPactCheckedFunction(radioOptions[index - totalElements]?.pact);
+ };
+
+ const maybeAddTooltip = (option, radioField) => {
+ if (option.tooltipText) {
+ const idKey = `tooltip-${option.value}`;
+
+ return
+ {radioField}
+ ;
+ }
+
+ return radioField;
+ };
+
+ // Creating MST and PACT checkboxes, along with a text input for justification of change
+ const addMstPactCheckboxes = (option) => {
+ if (option.value === props.value) {
+ return (
+
+ { renderMst &&
+ setMstCheckboxFunction(checked)}
+ />
+ { (renderJustification && (mstChecked && !prePopulatedMst)) &&
+ mstJustificationOnChange(mstJustificationText)}
+ />
+ }
+
+ }
+ { renderPact &&
+ setPactCheckboxFunction(checked)}
+ />
+ { (renderJustification && (pactChecked && !prePopulatedPact)) &&
+ pactJustificationOnChange(pactJustificationText)}
+ />
+ }
+
+ }
+
+ );
+ }
+ };
+
+ const isDisabled = (option) => Boolean(option.disabled);
+
+ const handleChange = (event) => {
+ onChange?.(event.target.value);
+
+ // if the radio option has a pre-populated MST/PACT checkbox, update the value
+ handlePrepopulatedCheckboxes(props.options, event.target.value);
+ };
+
+ const controlled = useMemo(() => typeof value !== 'undefined', [value]);
+
+ return (
+
+ );
+};
+
+IntakeRadioField.defaultProps = {
+ required: false,
+ className: ['usa-fieldset-inputs'],
+};
+
+IntakeRadioField.propTypes = {
+ id: PropTypes.string,
+ className: PropTypes.arrayOf(PropTypes.string),
+ required: PropTypes.bool,
+
+ /**
+ * Pass a ref to the `input` element
+ */
+ inputRef: PropTypes.oneOfType([
+ // Either a function
+ PropTypes.func,
+ // Or the instance of a DOM native element (see the note about SSR)
+ PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
+ ]),
+
+ /**
+ * Props to be applied to the `input` element
+ */
+ inputProps: PropTypes.object,
+
+ /**
+ * Text to display in a `legend` element for the radio group fieldset
+ */
+ label: PropTypes.node,
+
+ /**
+ * String to be applied to the `name` attribute of all the `input` elements
+ */
+ name: PropTypes.string.isRequired,
+
+ /**
+ * Callback fired when value is changed
+ *
+ * @param {string} value The current value of the component
+ */
+ onChange: PropTypes.func,
+
+ /**
+ * An array of options used to define individual radio inputs
+ */
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+
+ /**
+ * Text to be used as label for individual radio input
+ */
+ displayText: PropTypes.node,
+
+ /**
+ * The `value` attribute for the radio input
+ */
+ value: PropTypes.string,
+
+ /**
+ * Help text to be displayed below the label
+ */
+ help: PropTypes.string,
+ })
+ ),
+
+ /**
+ * The value of the named `input` element(s); required for a controlled component
+ */
+ value: PropTypes.string,
+
+ /**
+ * Stack `input` elements vertically (automatic for more than two options)
+ */
+ vertical: PropTypes.bool,
+ errorMessage: PropTypes.string,
+ strongLabel: PropTypes.bool,
+ hideLabel: PropTypes.bool,
+ styling: PropTypes.object,
+ renderMst: PropTypes.bool,
+ renderPact: PropTypes.bool,
+ renderJustification: PropTypes.bool,
+ mstChecked: PropTypes.bool,
+ setMstCheckboxFunction: PropTypes.func,
+ pactChecked: PropTypes.bool,
+ pactJustification: PropTypes.string,
+ setVbmsMstCheckedFunction: PropTypes.func,
+ setVbmsPactCheckedFunction: PropTypes.func,
+ mstJustification: PropTypes.string,
+ pactJustificationOnChange: PropTypes.func,
+ mstJustificationOnChange: PropTypes.func,
+ setPactCheckboxFunction: PropTypes.func,
+ userCanEditIntakeIssues: PropTypes.bool,
+ totalElements: PropTypes.number,
+ prePopulatedMst: PropTypes.bool,
+ prePopulatedPact: PropTypes.bool
+};
+
+export default IntakeRadioField;
diff --git a/client/app/intake/components/IssueList.jsx b/client/app/intake/components/IssueList.jsx
index 8ae5c7e4d24..0529246c893 100644
--- a/client/app/intake/components/IssueList.jsx
+++ b/client/app/intake/components/IssueList.jsx
@@ -27,17 +27,23 @@ const nonEditableIssueStyling = css({
});
export default class IssuesList extends React.Component {
- generateIssueActionOptions = (issue, userCanWithdrawIssues, isDtaError) => {
+ generateIssueActionOptions = (issue, userCanWithdrawIssues, userCanEditIntakeIssues, isDtaError, docketType) => {
let options = [];
if (issue.correctionType && issue.endProductCleared) {
options.push({ displayText: 'Undo correction',
value: 'undo_correction' });
- } else if (issue.correctionType && !issue.examRequested) {
+ } else if (issue.correctionType && !issue.examRequested && docketType !== 'Legacy') {
options.push(
{ displayText: 'Remove issue',
value: 'remove' }
);
+ if (userCanEditIntakeIssues) {
+ options.push(
+ { displayText: 'Edit issue',
+ value: 'edit' }
+ );
+ }
} else if (issue.endProductCleared) {
options.push({ displayText: 'Correct issue',
value: 'correct' });
@@ -48,10 +54,18 @@ export default class IssuesList extends React.Component {
value: 'withdraw' }
);
}
- options.push(
- { displayText: 'Remove issue',
- value: 'remove' }
- );
+ if (docketType !== 'Legacy') {
+ options.push(
+ { displayText: 'Remove issue',
+ value: 'remove' }
+ );
+ }
+ if (userCanEditIntakeIssues) {
+ options.push(
+ { displayText: 'Edit issue',
+ value: 'edit' }
+ );
+ }
}
const isIssueWithdrawn = issue.withdrawalDate || issue.withdrawalPending;
@@ -77,7 +91,9 @@ export default class IssuesList extends React.Component {
onClickIssueAction,
withdrawReview,
userCanWithdrawIssues,
- editPage
+ userCanEditIntakeIssues,
+ editPage,
+ featureToggles
} = this.props;
return
@@ -93,7 +109,7 @@ export default class IssuesList extends React.Component {
editableIssueProperties);
const issueActionOptions = this.generateIssueActionOptions(
- issue, userCanWithdrawIssues, intakeData.isDtaError
+ issue, userCanWithdrawIssues, userCanEditIntakeIssues, intakeData.isDtaError, intakeData.docketType
);
const isIssueWithdrawn = issue.withdrawalDate || issue.withdrawalPending;
@@ -113,6 +129,7 @@ export default class IssuesList extends React.Component {
requestIssues={intakeData.requestIssues}
legacyOptInApproved={intakeData.legacyOptInApproved}
legacyAppeals={intakeData.legacyAppeals}
+ featureToggles={featureToggles}
formType={formType} />
{ !issue.editable &&
@@ -120,7 +137,7 @@ export default class IssuesList extends React.Component {
}
- {editPage && issue.editable && !_.isEmpty(issueActionOptions) &&
{
+ this.setState({
+ mstChecked
+ });
+ };
+ isPactChecked = (pactChecked) => {
+ this.setState({
+ pactChecked
+ });
+ };
+
categoryOnChange = (value) => {
this.setState({
category: value,
@@ -137,6 +152,8 @@ class NonratingRequestIssueModal extends React.Component {
ineligibleReason,
decisionReviewTitle,
isPreDocketNeeded,
+ mstChecked,
+ pactChecked,
} = this.state;
const currentIssue = {
@@ -149,6 +166,8 @@ class NonratingRequestIssueModal extends React.Component {
decisionReviewTitle,
isRating: false,
isPreDocketNeeded,
+ mstChecked,
+ pactChecked,
timely: isTimely(formType, decisionDate, intakeData.receiptDate)
};
@@ -277,10 +296,32 @@ class NonratingRequestIssueModal extends React.Component {
);
}
+ getSpecialIssues(mstIdentification, pactIdentification) {
+ return (
+
+
+ {mstIdentification && }
+ {pactIdentification && }
+
+ );
+ }
+
render() {
const { formType, intakeData, onCancel, featureToggles } = this.props;
const { benefitType, category, selectedNonratingIssueId, isPreDocketNeeded } = this.state;
const eduPreDocketAppeals = featureToggles.eduPreDocketAppeals;
+ const mstIdentification = featureToggles.mstIdentification && formType === 'appeal';
+ const pactIdentification = featureToggles.pactIdentification && formType === 'appeal';
const issueNumber = (intakeData.addedIssues || []).length + 1;
@@ -305,6 +346,9 @@ class NonratingRequestIssueModal extends React.Component {
formType === 'appeal' ? : null;
+ const getSpecialIssues = this.props.userCanEditIntakeIssues ?
+ this.getSpecialIssues(mstIdentification, pactIdentification) : null;
+
return (
@@ -334,6 +378,9 @@ class NonratingRequestIssueModal extends React.Component {
{(isPreDocketNeeded === 'true' && showPreDocketBanner) &&
}
+
+ {getSpecialIssues}
+
@@ -353,6 +400,9 @@ NonratingRequestIssueModal.propTypes = {
activeNonratingRequestIssues: PropTypes.object,
receiptDate: PropTypes.string,
addedIssues: PropTypes.array,
+ userCanEditIntakeIssues: PropTypes.bool,
+ mstChecked: PropTypes.bool,
+ pactChecked: PropTypes.bool,
featureToggles: PropTypes.object
};
diff --git a/client/app/intake/constants.js b/client/app/intake/constants.js
index 16a6ff8c72f..b920fa673c4 100644
--- a/client/app/intake/constants.js
+++ b/client/app/intake/constants.js
@@ -129,6 +129,7 @@ export const ACTIONS = {
TOGGLE_ISSUE_REMOVE_MODAL: 'TOGGLE_ISSUE_REMOVE_MODAL',
TOGGLE_CORRECTION_TYPE_MODAL: 'TOGGLE_CORRECTION_TYPE_MODAL',
TOGGLE_LEGACY_OPT_IN_MODAL: 'TOGGLE_LEGACY_OPT_IN_MODAL',
+ TOGGLE_EDIT_INTAKE_ISSUES_MODAL: 'TOGGLE_EDIT_INTAKE_ISSUES_MODAL',
SUBMIT_REVIEW_START: 'SUBMIT_REVIEW_START',
SUBMIT_REVIEW_SUCCEED: 'SUBMIT_REVIEW_SUCCEED',
SUBMIT_REVIEW_FAIL: 'SUBMIT_REVIEW_FAIL',
@@ -163,6 +164,7 @@ export const ACTIONS = {
NO_ISSUES_SELECTED_ERROR: 'NO_ISSUES_SELECTED_ERROR',
SET_EDIT_CONTENTION_TEXT: 'SET_EDIT_CONTENTION_TEXT',
SET_HOMELESSNESS_TYPE: 'SET_HOMELESSNESS_TYPE',
+ SET_MST_PACT_DETAILS: 'SET_MST_PACT_DETAILS',
SET_SPLIT_APPEAL: 'SET_SPLIT_APPEAL',
SPLIT_APPEAL_SUCCESS: 'SPLIT_APPEAL_SUCCESS',
SPLIT_APPEAL_FAILURE: 'SPLIT_APPEAL_FAILURE'
diff --git a/client/app/intake/pages/addIssues/addIssues.jsx b/client/app/intake/pages/addIssues/addIssues.jsx
index 0e95fd25860..52ed4cf7f5c 100644
--- a/client/app/intake/pages/addIssues/addIssues.jsx
+++ b/client/app/intake/pages/addIssues/addIssues.jsx
@@ -21,7 +21,11 @@ import DateSelector from '../../../components/DateSelector';
import ErrorAlert from '../../components/ErrorAlert';
import { REQUEST_STATE, PAGE_PATHS, VBMS_BENEFIT_TYPES, FORM_TYPES } from '../../constants';
import EP_CLAIM_TYPES from '../../../../constants/EP_CLAIM_TYPES';
-import { formatAddedIssues, formatRequestIssues, getAddIssuesFields, formatIssuesBySection } from '../../util/issues';
+import { formatAddedIssues,
+ formatRequestIssues,
+ getAddIssuesFields,
+ formatIssuesBySection,
+ formatLegacyAddedIssues } from '../../util/issues';
import Table from '../../../components/Table';
import issueSectionRow from './issueSectionRow/issueSectionRow';
@@ -34,17 +38,20 @@ import {
removeIssue,
withdrawIssue,
setIssueWithdrawalDate,
+ setMstPactDetails,
correctIssue,
undoCorrection,
toggleUnidentifiedIssuesModal,
toggleIssueRemoveModal,
toggleLegacyOptInModal,
- toggleCorrectionTypeModal
+ toggleCorrectionTypeModal,
+ toggleEditIntakeIssueModal
} from '../../actions/addIssues';
import { editEpClaimLabel } from '../../../intakeEdit/actions/edit';
import COPY from '../../../../COPY';
import { EditClaimLabelModal } from '../../../intakeEdit/components/EditClaimLabelModal';
import { ConfirmClaimLabelModal } from '../../../intakeEdit/components/ConfirmClaimLabelModal';
+import { EditIntakeIssueModal } from '../../../intakeEdit/components/EditIntakeIssueModal';
class AddIssuesPage extends React.Component {
constructor(props) {
@@ -96,6 +103,12 @@ class AddIssuesPage extends React.Component {
case 'undo_correction':
this.props.undoCorrection(index);
break;
+ case 'edit':
+ this.setState({
+ issueIndex: index
+ });
+ this.props.toggleEditIntakeIssueModal({ index });
+ break;
default:
this.props.undoCorrection(index);
}
@@ -117,6 +130,9 @@ class AddIssuesPage extends React.Component {
// eslint-disable-next-line class-methods-use-this
requestIssuesWithoutDecisionDates(intakeData) {
+ if (intakeData.docketType === 'Legacy') {
+ return false;
+ }
const requestIssues = formatRequestIssues(intakeData.requestIssues, intakeData.contestableIssues);
return !requestIssues.every((issue) => issue.ratingIssueReferenceId ||
@@ -214,7 +230,9 @@ class AddIssuesPage extends React.Component {
editPage,
addingIssue,
userCanWithdrawIssues,
- userCanSplitAppeal
+ userCanEditIntakeIssues,
+ userCanSplitAppeal,
+ isLegacy
} = this.props;
const intakeData = intakeForms[formType];
const appealInfo = intakeForms.appeal;
@@ -243,7 +261,10 @@ class AddIssuesPage extends React.Component {
(issue) => VBMS_BENEFIT_TYPES.includes(issue.benefitType) || issue.ratingIssueReferenceId
);
- const issues = formatAddedIssues(intakeData.addedIssues, useAmaActivationDate);
+ // eslint-disable-next-line max-len
+ const issues = intakeData.docketType === 'Legacy' ? formatLegacyAddedIssues(intakeData.requestIssues, intakeData.addedIssues) :
+ formatAddedIssues(intakeData.addedIssues, useAmaActivationDate);
+
const issuesPendingWithdrawal = issues.filter((issue) => issue.withdrawalPending);
const issuesBySection = formatIssuesBySection(issues);
@@ -419,6 +440,7 @@ class AddIssuesPage extends React.Component {
onClickIssueAction: this.onClickIssueAction,
sectionIssues,
userCanWithdrawIssues,
+ userCanEditIntakeIssues,
withdrawReview,
};
@@ -451,7 +473,8 @@ class AddIssuesPage extends React.Component {
additionalRowClasses = (rowObj) => (rowObj.field === '' ? 'intake-issue-flash' : '');
- const hideAddIssueButton = intakeData.isDtaError && _.isEmpty(intakeData.contestableIssues);
+ const hideAddIssueButton = (intakeData.isDtaError && _.isEmpty(intakeData.contestableIssues)) ||
+ intakeData.docketType === 'Legacy';
if (!hideAddIssueButton) {
rowObjects = rowObjects.concat({
@@ -466,6 +489,7 @@ class AddIssuesPage extends React.Component {
{
@@ -522,6 +546,26 @@ class AddIssuesPage extends React.Component {
loading={this.state.loading}
/>
)}
+ {intakeData.editIntakeIssueModalVisible && (
+ {
+ this.props.toggleEditIntakeIssueModal();
+ }}
+ onSubmit={(issueProps) => {
+ this.props.setMstPactDetails({
+ issueProps,
+ });
+ this.props.toggleEditIntakeIssueModal();
+ }}
+ />
+ )}
{messageHeader}
{requestState === REQUEST_STATE.FAILED && (
@@ -565,6 +609,7 @@ AddIssuesPage.propTypes = {
intakeForms: PropTypes.object,
removeIssue: PropTypes.func,
setIssueWithdrawalDate: PropTypes.func,
+ setMstPactDetails: PropTypes.func,
toggleAddDecisionDateModal: PropTypes.func,
toggleAddingIssue: PropTypes.func,
toggleAddIssuesModal: PropTypes.func,
@@ -574,11 +619,14 @@ AddIssuesPage.propTypes = {
toggleNonratingRequestIssueModal: PropTypes.func,
toggleUnidentifiedIssuesModal: PropTypes.func,
toggleUntimelyExemptionModal: PropTypes.func,
+ toggleEditIntakeIssueModal: PropTypes.func,
undoCorrection: PropTypes.func,
veteran: PropTypes.object,
withdrawIssue: PropTypes.func,
userCanWithdrawIssues: PropTypes.bool,
- userCanSplitAppeal: PropTypes.bool
+ userCanEditIntakeIssues: PropTypes.bool,
+ userCanSplitAppeal: PropTypes.bool,
+ isLegacy: PropTypes.bool
};
export const IntakeAddIssuesPage = connect(
@@ -604,7 +652,8 @@ export const IntakeAddIssuesPage = connect(
toggleLegacyOptInModal,
removeIssue,
withdrawIssue,
- setIssueWithdrawalDate
+ setIssueWithdrawalDate,
+ setMstPactDetails
},
dispatch
)
@@ -627,8 +676,9 @@ export const EditAddIssuesPage = connect(
activeIssue: state.activeIssue,
addingIssue: state.addingIssue,
userCanWithdrawIssues: state.userCanWithdrawIssues,
- userCanSplitAppeal: state.userCanSplitAppeal
-
+ userCanEditIntakeIssues: state.userCanEditIntakeIssues,
+ userCanSplitAppeal: state.userCanSplitAppeal,
+ isLegacy: state.isLegacy
}),
(dispatch) =>
bindActionCreators(
@@ -637,9 +687,11 @@ export const EditAddIssuesPage = connect(
toggleAddingIssue,
toggleIssueRemoveModal,
toggleCorrectionTypeModal,
+ toggleEditIntakeIssueModal,
removeIssue,
withdrawIssue,
setIssueWithdrawalDate,
+ setMstPactDetails,
correctIssue,
undoCorrection,
toggleUnidentifiedIssuesModal,
diff --git a/client/app/intake/pages/addIssues/issueSectionRow/issueSectionRow.jsx b/client/app/intake/pages/addIssues/issueSectionRow/issueSectionRow.jsx
index 894fb4f144b..21e348d8a16 100644
--- a/client/app/intake/pages/addIssues/issueSectionRow/issueSectionRow.jsx
+++ b/client/app/intake/pages/addIssues/issueSectionRow/issueSectionRow.jsx
@@ -17,6 +17,7 @@ const issueSectionRow = (
onClickIssueAction,
sectionIssues,
userCanWithdrawIssues,
+ userCanEditIntakeIssues,
withdrawReview
}) => {
const reviewHasPredocketVhaIssues = sectionIssues.some(
@@ -40,6 +41,7 @@ const issueSectionRow = (
onClickIssueAction={onClickIssueAction}
userCanWithdrawIssues={userCanWithdrawIssues}
withdrawReview={withdrawReview}
+ userCanEditIntakeIssues={userCanEditIntakeIssues}
/>
{showPreDocketBanner && }
@@ -60,4 +62,5 @@ issueSectionRow.propTypes = {
sectionIssues: PropTypes.arrayOf(PropTypes.object),
userCanWithdrawIssues: PropTypes.bool,
withdrawIssue: PropTypes.func,
+ userCanEditIntakeIssues: PropTypes.bool
};
diff --git a/client/app/intake/reducers/common.js b/client/app/intake/reducers/common.js
index 71bd494a9ec..cab696f26ec 100644
--- a/client/app/intake/reducers/common.js
+++ b/client/app/intake/reducers/common.js
@@ -56,6 +56,27 @@ export const commonReducers = (state, action) => {
});
};
+ actionsMap[ACTIONS.TOGGLE_EDIT_INTAKE_ISSUES_MODAL] = () => {
+ return update(state, {
+ $toggle: ['editIntakeIssueModalVisible']
+ });
+ };
+
+ actionsMap[ACTIONS.SET_MST_PACT_DETAILS] = () => {
+ const { editIssuesDetails } = action.payload;
+ const index = editIssuesDetails.issueProps.issueIndex;
+
+ listOfIssues[index].mstChecked = editIssuesDetails.issueProps.mstChecked;
+ listOfIssues[index].pactChecked = editIssuesDetails.issueProps.pactChecked;
+ listOfIssues[index].mstJustification = editIssuesDetails.issueProps.mstJustification;
+ listOfIssues[index].pactJustification = editIssuesDetails.issueProps.pactJustification;
+
+ return {
+ ...state,
+ addedIssues: listOfIssues
+ };
+ };
+
actionsMap[ACTIONS.TOGGLE_CORRECTION_TYPE_MODAL] = () => {
return update(state, {
$toggle: ['correctIssueModalVisible'],
@@ -206,11 +227,11 @@ export const commonStateFromServerIntake = (serverIntake) => {
claimantName: {
$set: serverIntake.claimantName
},
- claimantRelationship: {
+ claimantRelationship: {
$set: serverIntake.claimantRelationship
},
powerOfAttorneyName: {
- $set: serverIntake.powerOfAttorneyName
+ $set: serverIntake.powerOfAttorneyName
},
payeeCode: {
$set: serverIntake.payeeCode
diff --git a/client/app/intake/reducers/featureToggles.js b/client/app/intake/reducers/featureToggles.js
index 53a5aa4dbda..3d1d73f5856 100644
--- a/client/app/intake/reducers/featureToggles.js
+++ b/client/app/intake/reducers/featureToggles.js
@@ -20,6 +20,18 @@ const updateFromServerFeatures = (state, featureToggles) => {
eduPreDocketAppeals: {
$set: Boolean(featureToggles.eduPreDocketAppeals)
},
+ mstIdentification: {
+ $set: Boolean(featureToggles.mstIdentification)
+ },
+ pactIdentification: {
+ $set: Boolean(featureToggles.pactIdentification)
+ },
+ justificationReason: {
+ $set: Boolean(featureToggles.justificationReason)
+ },
+ legacyMstPactIdentification: {
+ $set: Boolean(featureToggles.legacyMstPactIdentification)
+ },
updatedAppealForm: {
$set: Boolean(featureToggles.updatedAppealForm)
},
@@ -40,6 +52,10 @@ export const mapDataToFeatureToggle = (data = { featureToggles: {} }) =>
filedByVaGovHlr: false,
updatedIntakeForms: false,
eduPreDocketAppeals: false,
+ mstIdentification: false,
+ pactIdentification: false,
+ legacyMstPactIdentification: false,
+ justificationReason: false,
updatedAppealForm: false,
hlrScUnrecognizedClaimants: false,
vhaClaimReviewEstablishment: false
diff --git a/client/app/intake/util/issues.js b/client/app/intake/util/issues.js
index 42624f7c8b9..c2afd8fa163 100644
--- a/client/app/intake/util/issues.js
+++ b/client/app/intake/util/issues.js
@@ -146,7 +146,13 @@ export const formatRequestIssues = (requestIssues, contestableIssues) => {
titleOfActiveReview: issue.title_of_active_review,
rampClaimId: issue.ramp_claim_id,
verifiedUnidentifiedIssue: issue.verified_unidentified_issue,
- isPreDocketNeeded: issue.is_predocket_needed
+ isPreDocketNeeded: issue.is_predocket_needed,
+ mstChecked: issue.mst_status,
+ pactChecked: issue.pact_status,
+ vbmsMstChecked: issue.vbms_mst_status,
+ vbmsPactChecked: issue.vbms_pact_status,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
}
);
@@ -195,7 +201,13 @@ const formatUnidentifiedIssues = (state) => {
ineligibleReason: issue.ineligibleReason,
vacols_id: issue.vacolsId,
vacols_sequence_id: issue.vacolsSequenceId,
- verified_unidentified_issue: issue.verifiedUnidentifiedIssue
+ verified_unidentified_issue: issue.verifiedUnidentifiedIssue,
+ mst_status: issue.mstChecked,
+ vbms_mst_status: issue.vbmsMstChecked,
+ pact_status: issue.pactChecked,
+ vbms_pact_status: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
});
};
@@ -224,7 +236,13 @@ const formatRatingRequestIssues = (state) => {
ineligible_due_to_id: issue.ineligibleDueToId,
withdrawal_date: issue.withdrawalPending ? state.withdrawalDate : null,
edited_description: issue.editedDescription,
- correction_type: issue.correctionType
+ correction_type: issue.correctionType,
+ mst_status: issue.mstChecked,
+ vbms_mst_status: issue.vbmsMstChecked,
+ pact_status: issue.pactChecked,
+ vbms_pact_status: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
});
};
@@ -251,7 +269,13 @@ const formatNonratingRequestIssues = (state) => {
edited_description: issue.editedDescription,
withdrawal_date: issue.withdrawalPending ? state.withdrawalDate : null,
correction_type: issue.correctionType,
- is_predocket_needed: issue.isPreDocketNeeded
+ is_predocket_needed: issue.isPreDocketNeeded,
+ mst_status: issue.mstChecked,
+ vbms_mst_status: issue.vbmsMstChecked,
+ pact_status: issue.pactChecked,
+ vbms_pact_status: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
});
};
@@ -350,10 +374,38 @@ export const formatIssuesBySection = (issues) => {
);
};
+export const formatLegacyAddedIssues = (issues = [], addedIssues = []) => {
+ return issues.map((issue, index) => {
+ return {
+ index,
+ id: issue.id,
+ benefitType: issue.labels[0].toLowerCase(),
+ description: `${issue.labels[1]} - ${issue.labels[2]} - ${issue.labels[3]}`,
+ text: `${issue.labels[1]} - ${issue.labels[2]} - ${issue.labels[3]}`,
+ vacolsSequenceId: issue.vacols_sequence_id,
+ mstChecked: addedIssues[index].mstChecked,
+ pactChecked: addedIssues[index].pactChecked
+ };
+ }
+ );
+};
+
export const formatAddedIssues = (issues = [], useAmaActivationDate = false) => {
const amaActivationDate = new Date(useAmaActivationDate ? DATES.AMA_ACTIVATION : DATES.AMA_ACTIVATION_TEST);
return issues.map((issue, index) => {
+
+ if (issue.vacols_sequence_id) {
+ return {
+ index,
+ id: issue.id,
+ benefitType: issue.labels[0].toLowerCase(),
+ description: `${issue.labels[1]} - ${issue.labels[2]} - ${issue.labels[3]}`,
+ text: `${issue.labels[1]} - ${issue.labels[2]} - ${issue.labels[3]}`,
+ vacolsSequenceId: issue.vacols_sequence_id
+ };
+ }
+
if (issue.isUnidentified || issue.verifiedUnidentifiedIssue) {
const issueText = issue.isUnidentified ?
`Unidentified issue: no issue matched for "${issue.description}"` :
@@ -383,7 +435,13 @@ export const formatAddedIssues = (issues = [], useAmaActivationDate = false) =>
vacolsId: issue.vacolsId,
vacolsSequenceId: issue.vacolsSequenceId,
vacolsIssue: issue.vacolsIssue,
- verifiedUnidentifiedIssue: issue.verifiedUnidentifiedIssue
+ verifiedUnidentifiedIssue: issue.verifiedUnidentifiedIssue,
+ mstChecked: issue.mstChecked,
+ pactChecked: issue.pactChecked,
+ vbmsMstChecked: issue.vbmsMstChecked,
+ vbmsPactChecked: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
} else if (issue.isRating) {
if (!issue.decisionDate && !issue.approxDecisionDate) {
@@ -424,7 +482,13 @@ export const formatAddedIssues = (issues = [], useAmaActivationDate = false) =>
examRequested: issue.examRequested,
decisionIssueId: issue.decisionIssueId,
ratingIssueReferenceId: issue.ratingIssueReferenceId,
- ratingDecisionReferenceId: issue.ratingDecisionReferenceId
+ ratingDecisionReferenceId: issue.ratingDecisionReferenceId,
+ mstChecked: issue.mstChecked,
+ pactChecked: issue.pactChecked,
+ vbmsMstChecked: issue.vbmsMstChecked,
+ vbmsPactChecked: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
}
@@ -460,7 +524,13 @@ export const formatAddedIssues = (issues = [], useAmaActivationDate = false) =>
editable: issue.editable,
examRequested: issue.examRequested,
decisionIssueId: issue.decisionIssueId,
- isPreDocketNeeded: issue.isPreDocketNeeded
+ isPreDocketNeeded: issue.isPreDocketNeeded,
+ mstChecked: issue.mstChecked,
+ pactChecked: issue.pactChecked,
+ vbmsMstChecked: issue.vbmsMstChecked,
+ vbmsPactChecked: issue.vbmsPactChecked,
+ mst_status_update_reason_notes: issue?.mstJustification,
+ pact_status_update_reason_notes: issue?.pactJustification
};
});
};
diff --git a/client/app/intakeEdit/IntakeEditFrame.jsx b/client/app/intakeEdit/IntakeEditFrame.jsx
index bdb37cd7a4a..0913db01846 100644
--- a/client/app/intakeEdit/IntakeEditFrame.jsx
+++ b/client/app/intakeEdit/IntakeEditFrame.jsx
@@ -279,6 +279,7 @@ IntakeEditFrame.propTypes = {
appeal: PropTypes.object,
claimId: PropTypes.string,
user: PropTypes.string,
+ isLegacy: PropTypes.bool,
routerTestProps: PropTypes.object,
router: PropTypes.object
};
diff --git a/client/app/intakeEdit/components/EditIntakeIssueModal.jsx b/client/app/intakeEdit/components/EditIntakeIssueModal.jsx
new file mode 100644
index 00000000000..2c8f02a6036
--- /dev/null
+++ b/client/app/intakeEdit/components/EditIntakeIssueModal.jsx
@@ -0,0 +1,217 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import Modal from 'app/components/Modal';
+import Checkbox from '../../components/Checkbox';
+import TextField from '../../components/TextField';
+import BENEFIT_TYPES from '../../../constants/BENEFIT_TYPES';
+import { formatDateStr } from '../../util/DateUtil';
+import {
+ INTAKE_EDIT_ISSUE_TITLE,
+ INTAKE_EDIT_ISSUE_SELECT_SPECIAL_ISSUES,
+ INTAKE_EDIT_ISSUE_CHANGE_MESSAGE,
+ INTAKE_EDIT_ISSUE_LABEL,
+ INTAKE_EDIT_ISSUE_BENEFIT_TYPE,
+ INTAKE_EDIT_ISSUE_DECISION_DATE,
+ MST_LABEL,
+ PACT_LABEL
+} from 'app/../COPY';
+
+export class EditIntakeIssueModal extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ // eslint-disable-next-line max-len
+ mstChecked: props.appealIsLegacy ? props.legacyIssues[props.issueIndex]?.mstChecked : props.currentIssue?.mstChecked,
+ // eslint-disable-next-line max-len
+ pactChecked: props.appealIsLegacy ? props.legacyIssues[props.issueIndex]?.pactChecked : props.currentIssue?.pactChecked,
+ mstJustification: '',
+ pactJustification: ''
+ };
+ }
+
+ handleMstCheckboxChange = () => {
+ this.setState((prevState) => ({
+ mstChecked: !prevState.mstChecked
+ }));
+ }
+
+ handlePactCheckboxChange = () => {
+ this.setState((prevState) => ({
+ pactChecked: !prevState.pactChecked
+ }));
+ }
+
+ handleMstJustification = (mstJustification) => this.setState({ mstJustification });
+
+ handlePactJustification = (pactJustification) => this.setState({ pactJustification });
+
+ render() {
+ const {
+ issueIndex,
+ currentIssue,
+ onCancel,
+ issue = this.props.legacyIssues[issueIndex],
+ currentIssueCategory = currentIssue.category,
+ currentIssueDescription = currentIssue.description,
+ currentIssueText = currentIssue.text || issue.description,
+ currentIssueBenefitType = BENEFIT_TYPES[currentIssue.benefitType] || BENEFIT_TYPES[issue.benefitType],
+ currentIssueDecisionDate = formatDateStr(currentIssue.decisionDate),
+ justificationReason,
+ mstIdentification,
+ pactIdentification
+ } = this.props;
+
+ const { mstChecked, pactChecked, mstJustification, pactJustification } = this.state;
+
+ return
+
{
+ if (justificationReason) {
+ if (!currentIssue?.mstChecked && mstChecked && mstJustification === '') {
+ return;
+ }
+ if (!currentIssue?.pactChecked && pactChecked && pactJustification === '') {
+ return;
+ }
+ }
+
+ this.props.onSubmit({
+ issueIndex,
+ mstChecked,
+ pactChecked,
+ mstJustification,
+ pactJustification,
+ });
+ }
+ }
+ ]}
+ visible
+ closeHandler={onCancel}
+ title={INTAKE_EDIT_ISSUE_TITLE}
+ >
+
+
+ { INTAKE_EDIT_ISSUE_LABEL }
+
+ { currentIssueCategory ? `${currentIssueCategory} - ${currentIssueDescription}` : currentIssueDescription}
+ { currentIssueText }
+
+
+
+
+ { currentIssueBenefitType ? INTAKE_EDIT_ISSUE_BENEFIT_TYPE : null }
+
+ { currentIssueBenefitType ? currentIssueBenefitType : null }
+
+
+
+
+ { currentIssueDecisionDate ? INTAKE_EDIT_ISSUE_DECISION_DATE : '' }
+
+ { currentIssueDecisionDate }
+
+
+ { INTAKE_EDIT_ISSUE_SELECT_SPECIAL_ISSUES }
+
+
+ -
+ { mstIdentification &&
+
+ }
+
+
+ {(justificationReason && (mstChecked)) && (
+
+
+
+ )}
+
+ -
+
+
+
+ {(justificationReason && (pactChecked)) && (
+
+
+
+ )}
+
+
+
;
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ mstChecked: state.mstChecked,
+ pactChecked: state.pactChecked,
+ mstJustification: state.mstJustification,
+ pactJustification: state.pactJustification
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ handleMstCheckboxChange: () => dispatch({ type: 'SET_MST_STATUS' }),
+ handlePactCheckboxChange: () => dispatch({ type: 'SET_PACT_STATUS' }),
+ handleMstJustification: () => dispatch({ type: 'SET_MST_JUSTIFICATION' }),
+ handlePactJustification: () => dispatch({ type: 'SET_PACT_JUSTIFICATION' })
+ };
+};
+
+EditIntakeIssueModal.propTypes = {
+ onCancel: PropTypes.func,
+ onSubmit: PropTypes.func,
+ issueIndex: PropTypes.number,
+ mstIdentification: PropTypes.bool,
+ pactIdentification: PropTypes.bool,
+ justificationReason: PropTypes.bool,
+ mstChecked: PropTypes.bool,
+ pactChecked: PropTypes.bool,
+ mstJustification: PropTypes.object,
+ pactJustification: PropTypes.object,
+ editedIssue: PropTypes.object,
+ intakeData: PropTypes.object,
+ currentIssue: PropTypes.object,
+ currentIssueCategory: PropTypes.string,
+ currentIssueDescription: PropTypes.string,
+ currentIssueBenefitType: PropTypes.string,
+ currentIssueDecisionDate: PropTypes.string,
+ currentIssueText: PropTypes.string,
+ issue: PropTypes.object,
+ appealIsLegacy: PropTypes.bool,
+ legacyIssues: PropTypes.object
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(EditIntakeIssueModal);
diff --git a/client/app/intakeEdit/components/EditIntakeIssueModal.stories.js b/client/app/intakeEdit/components/EditIntakeIssueModal.stories.js
new file mode 100644
index 00000000000..ff769211736
--- /dev/null
+++ b/client/app/intakeEdit/components/EditIntakeIssueModal.stories.js
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { EditIntakeIssueModal } from './EditIntakeIssueModal';
+export default {
+ title: 'Intake/Edit Issues/Edit Intake Issues',
+ component: EditIntakeIssueModal,
+ decorators: [],
+ parameters: {},
+ args: {
+ currentIssue: {
+ issueIndex: 1,
+ category: 'Delimiting Date Issues | 38 U.S.C. ch. 1606',
+ description: 'Test Issue',
+ decisionDate: '04/04/2023'
+ },
+ currentIssueBenefitType: 'Education',
+ mstCheckboxValue: false,
+ pactCheckboxValue: false,
+ },
+ argTypes: {
+ onCancel: { action: 'cancel' },
+ onSubmit: { action: 'submit' },
+ },
+};
+
+const Template = (args) => ;
+
+export const Basic = Template.bind({});
+
+Basic.parameters = {
+ docs: {
+ storyDescription:
+ 'This is used to edit intake issues on the Edit Issues page',
+ },
+
+};
diff --git a/client/app/intakeEdit/reducers/index.js b/client/app/intakeEdit/reducers/index.js
index e31e5dc07b8..fb8c64e89a4 100644
--- a/client/app/intakeEdit/reducers/index.js
+++ b/client/app/intakeEdit/reducers/index.js
@@ -7,7 +7,14 @@ import { formatRequestIssues, formatContestableIssues } from '../../intake/util/
import { formatRelationships } from '../../intake/util';
export const mapDataToInitialState = function(props = {}) {
- const { serverIntake, claimId, featureToggles, userCanWithdrawIssues, userCanSplitAppeal} = props;
+ const {
+ serverIntake,
+ claimId,
+ featureToggles,
+ userCanWithdrawIssues,
+ userCanEditIntakeIssues,
+ userCanSplitAppeal,
+ isLegacy } = props;
serverIntake.relationships = formatRelationships(serverIntake.relationships);
serverIntake.contestableIssues = formatContestableIssues(serverIntake.contestableIssuesByDate);
@@ -28,7 +35,9 @@ export const mapDataToInitialState = function(props = {}) {
claimId,
featureToggles,
userCanWithdrawIssues,
+ userCanEditIntakeIssues,
userCanSplitAppeal,
+ isLegacy,
addDecisionDateModalVisible: false,
addIssuesModalVisible: false,
nonRatingRequestIssueModalVisible: false,
diff --git a/client/app/nonComp/components/Alerts.jsx b/client/app/nonComp/components/Alerts.jsx
index 63e2386ae46..5accc6dc1a4 100644
--- a/client/app/nonComp/components/Alerts.jsx
+++ b/client/app/nonComp/components/Alerts.jsx
@@ -44,7 +44,15 @@ SuccessAlert.propTypes = {
successCode: PropTypes.string
};
+// method to list MST/PACT edits
+const listChanges = (editList) => {
+ const divStyle = { margin: '.1rem' };
+
+ return editList.map((value) => - {value}
);
+};
+
export class FlashAlerts extends React.PureComponent {
+
render() {
let alerts = this.props.flash.map((flash, idx) => {
let flashMsg;
@@ -57,6 +65,9 @@ export class FlashAlerts extends React.PureComponent {
flashMsg = {flash[1]};
} else if (flash[0] === 'edited') {
flashMsg = {flash[1]};
+ } else if (flash[0] === 'mst_pact_edited') {
+ // eslint-disable-next-line max-len
+ flashMsg = {listChanges(flash[1])};
} else if (flash[0] === 'show_vha_org_join_info') {
flashMsg = ;
}
diff --git a/client/app/queue/AddEditIssueView.jsx b/client/app/queue/AddEditIssueView.jsx
index d9a2934ac72..6e569b214e5 100644
--- a/client/app/queue/AddEditIssueView.jsx
+++ b/client/app/queue/AddEditIssueView.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable max-lines */
const PropTypes = require('prop-types');
import * as React from 'react';
@@ -32,6 +33,10 @@ import TextField from '../components/TextField';
import Button from '../components/Button';
import Modal from '../components/Modal';
import Alert from '../components/Alert';
+import Checkbox from '../components/Checkbox';
+import {
+ INTAKE_EDIT_ISSUE_CHANGE_MESSAGE
+} from 'app/../COPY';
import {
fullWidth,
@@ -46,8 +51,27 @@ const dropdownMarginTop = css({ marginTop: '2rem' });
const smallTopMargin = css({ marginTop: '1rem' });
const smallBottomMargin = css({ marginBottom: '1rem' });
const noLeftPadding = css({ paddingLeft: 0 });
+const checkboxStyle = css({ marginTop: '0', marginBottom: '0' });
class AddEditIssueView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ showMstJustification: false,
+ showPactJustification: false,
+ mstJustification: '',
+ pactJustification: ''
+ };
+ }
+
+ handleShowMstJustification = () => this.setState((prev) => ({ showMstJustification: !prev.showMstJustification }))
+
+ handleShowPactJustification = () => this.setState((prev) => ({ showPactJustification: !prev.showPactJustification }))
+
+ handleMstJustification = (mstJustification) => this.setState({ mstJustification });
+
+ handlePactJustification = (pactJustification) => this.setState({ pactJustification });
componentDidMount = () => {
const { issueId, appealId } = this.props;
@@ -100,7 +124,8 @@ class AddEditIssueView extends React.Component {
const {
issue,
appeal,
- appeal: { issues }
+ appeal: { issues },
+ action
} = this.props;
const params = {
data: {
@@ -109,8 +134,11 @@ class AddEditIssueView extends React.Component {
level_1: _.get(issue.codes, 0, null),
level_2: _.get(issue.codes, 1, null),
level_3: _.get(issue.codes, 2, null),
- ..._.pick(issue, 'note', 'program')
- }
+ ..._.pick(issue, 'note', 'program'),
+ mst_status: issue.mst_status ? 'Y' : 'N',
+ pact_status: issue.pact_status ? 'Y' : 'N'
+ },
+ action
}
};
const issueIndex = _.map(issues, 'id').indexOf(issue.id);
@@ -200,9 +228,15 @@ class AddEditIssueView extends React.Component {
highlight,
error,
deleteIssueModal,
+ justificationFeatureToggle,
+ legacyMstPactFeatureToggle,
+ mstFeatureToggle,
+ pactFeatureToggle,
...otherProps
} = this.props;
+ const { showMstJustification, showPactJustification } = this.state && justificationFeatureToggle;
+
const programs = ISSUE_INFO;
const issues = _.get(programs[issue.program], 'levels');
const issueLevels = this.getIssueLevelOptions();
@@ -329,6 +363,41 @@ class AddEditIssueView extends React.Component {
value={_.get(this.props.issue, 'note', '')}
maxLength={ISSUE_DESCRIPTION_MAX_LENGTH}
onChange={(value) => this.updateIssue({ note: value })} />
+ {legacyMstPactFeatureToggle && }
+ {(legacyMstPactFeatureToggle && mstFeatureToggle) && {
+ this.updateIssue({ mst_status: checked });
+ this.handleShowMstJustification();
+ }}
+ />}
+ {showMstJustification &&
+
+ }
+ {(legacyMstPactFeatureToggle && pactFeatureToggle) && {
+ this.updateIssue({ pact_status: checked });
+ this.handleShowPactJustification();
+ }}
+ />}
+ {showPactJustification &&
+ }
;
};
}
@@ -360,10 +429,16 @@ AddEditIssueView.propTypes = {
id: PropTypes.number,
type: PropTypes.string,
codes: PropTypes.arrayOf(PropTypes.string),
- program: PropTypes.string
+ program: PropTypes.string,
+ mst_status: PropTypes.bool,
+ pact_status: PropTypes.bool
}),
issueId: PropTypes.string,
issues: PropTypes.object,
+ justificationFeatureToggle: PropTypes.bool,
+ legacyMstPactFeatureToggle: PropTypes.bool,
+ mstFeatureToggle: PropTypes.bool,
+ pactFeatureToggle: PropTypes.bool,
requestDelete: PropTypes.func,
requestSave: PropTypes.func,
requestUpdate: PropTypes.func,
diff --git a/client/app/queue/CaseDetailsView.jsx b/client/app/queue/CaseDetailsView.jsx
index cb6ff3e6496..8e87d22828f 100644
--- a/client/app/queue/CaseDetailsView.jsx
+++ b/client/app/queue/CaseDetailsView.jsx
@@ -330,6 +330,7 @@ export const CaseDetailsView = (props) => {
appeal.contestedClaim)
);
+ const anyAppealsHaveMst = Boolean(
+ find(this.props.appeals, (appeal) => appeal.mst)
+ );
+
+ const anyAppealsHavePact = Boolean(
+ find(this.props.appeals, (appeal) => appeal.pact)
+ );
+
+ const specialIssuesIdentified = anyAppealsHavePact || anyAppealsHaveMst;
+
const badgeColumn = {
valueFunction: (appeal) =>
};
if (anyAppealsHaveHeldHearings || anyAppealsHaveOvertimeStatus ||
- anyAppealsHaveFnod || anyAppealsAreContestedClaims) {
+ anyAppealsHaveFnod || anyAppealsAreContestedClaims || specialIssuesIdentified) {
columns.unshift(badgeColumn);
}
diff --git a/client/app/queue/LegacySelectDispositionsView.jsx b/client/app/queue/LegacySelectDispositionsView.jsx
index f2cee421007..58c13c2f7ae 100644
--- a/client/app/queue/LegacySelectDispositionsView.jsx
+++ b/client/app/queue/LegacySelectDispositionsView.jsx
@@ -121,7 +121,8 @@ class LegacySelectDispositionsView extends React.PureComponent {
appeal,
taskId,
checkoutFlow,
- appealId
+ appealId,
+ legacyMstPactFeatureToggle
} = this.props;
const columns = [{
@@ -133,6 +134,7 @@ class LegacySelectDispositionsView extends React.PureComponent {
}}
idxToDisplay={idx + 1}
showDisposition={false}
+ legacyMstPactFeatureToggle={legacyMstPactFeatureToggle}
stretchToFullWidth />
}, {
header: 'Dispositions',
@@ -217,7 +219,8 @@ LegacySelectDispositionsView.propTypes = {
setDecisionOptions: PropTypes.func,
startEditingAppealIssue: PropTypes.func,
saveEditedAppealIssue: PropTypes.func,
- hideSuccessMessage: PropTypes.func
+ hideSuccessMessage: PropTypes.func,
+ legacyMstPactFeatureToggle: PropTypes.bool
};
const mapStateToProps = (state, ownProps) => ({
diff --git a/client/app/queue/QueueApp.jsx b/client/app/queue/QueueApp.jsx
index 31c01fdf65d..39319172d7b 100644
--- a/client/app/queue/QueueApp.jsx
+++ b/client/app/queue/QueueApp.jsx
@@ -1,5 +1,4 @@
-/* eslint-disable max-lines */
-/* eslint-disable max-len */
+/* eslint-disable max-lines, max-len */
import querystring from 'querystring';
import React from 'react';
@@ -220,6 +219,10 @@ class QueueApp extends React.PureComponent {
appealId={props.match.params.appealId}
taskId={props.match.params.taskId}
checkoutFlow={props.match.params.checkoutFlow}
+ justificationFeatureToggle={this.props.featureToggles.justificationReason}
+ mstFeatureToggle={this.props.featureToggles.mstIdentification}
+ pactFeatureToggle={this.props.featureToggles.pactIdentification}
+ legacyMstPactFeatureToggle={this.props.featureToggles.legacyMstPactIdentification}
/>
);
@@ -231,6 +234,8 @@ class QueueApp extends React.PureComponent {
@@ -245,6 +250,10 @@ class QueueApp extends React.PureComponent {
);
diff --git a/client/app/queue/SelectDispositionsView.jsx b/client/app/queue/SelectDispositionsView.jsx
index d747ec7ce0f..6831556225c 100644
--- a/client/app/queue/SelectDispositionsView.jsx
+++ b/client/app/queue/SelectDispositionsView.jsx
@@ -1,4 +1,4 @@
-/* eslint-disable max-lines */
+/* eslint-disable max-lines, camelcase, max-len */
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
@@ -13,6 +13,7 @@ import SelectIssueDispositionDropdown from './components/SelectIssueDispositionD
import Modal from '../components/Modal';
import TextareaField from '../components/TextareaField';
import SearchableDropdown from '../components/SearchableDropdown';
+import QueueCheckboxGroup from './components/QueueCheckboxGroup';
import COPY from '../../COPY';
import { COLORS } from '../constants/AppConstants';
@@ -23,8 +24,13 @@ import {
import { hideSuccessMessage } from './uiReducer/uiActions';
import {
VACOLS_DISPOSITIONS,
- ISSUE_DISPOSITIONS
+ ISSUE_DISPOSITIONS,
+ DECISION_SPECIAL_ISSUES,
+ DECISION_SPECIAL_ISSUES_WITH_PACT,
+ DECISION_SPECIAL_ISSUES_WITH_MST,
+ DECISION_SPECIAL_ISSUES_WITH_MST_PACT
} from './constants';
+import ApiUtil from '../util/ApiUtil';
import BENEFIT_TYPES from '../../constants/BENEFIT_TYPES';
import DIAGNOSTIC_CODE_DESCRIPTIONS from '../../constants/DIAGNOSTIC_CODE_DESCRIPTIONS';
@@ -48,6 +54,18 @@ const textAreaStyle = css({
maxWidth: '100%'
});
+const specialIssuesCheckboxStyling = css({
+ columnCount: '1',
+ marginTop: '2%',
+ maxWidth: '70%',
+ '& legend': {
+ marginBottom: '2%',
+ },
+ '& .checkbox': {
+ marginTop: '0',
+ },
+});
+
class SelectDispositionsView extends React.PureComponent {
constructor(props) {
super(props);
@@ -57,18 +75,71 @@ class SelectDispositionsView extends React.PureComponent {
decisionIssue: null,
editingExistingIssue: false,
highlightModal: false,
- deleteAddedDecisionIssue: null
+ deleteAddedDecisionIssue: null,
+ specialIssues: null,
+ mstJustification: '',
+ pactJustification: ''
};
}
-
decisionReviewCheckoutFlow = () => this.props.checkoutFlow === 'dispatch_decision';
componentDidMount = () => {
if (!this.decisionReviewCheckoutFlow()) {
this.props.setDecisionOptions({ work_product: 'Decision' });
}
+ ApiUtil.get(
+ `/appeals/${this.props.appealId}/special_issues`).then(
+ (response) => {
+ const { ...specialIssues } = response.body;
+
+ this.editStagedAppeal({ specialIssues });
+ this.setState({ specialIssues });
+ }
+ );
}
+ stageSpecialIssues = (decisionIssues) => {
+ const appealIsBlueWater = decisionIssues.filter(
+ // eslint-disable-next-line camelcase, no-unneeded-ternary
+ (decision) => decision.decisionSpecialIssue?.blue_water).length > 0;
+
+ const appealIsBurnPit = decisionIssues.filter(
+ // eslint-disable-next-line camelcase, no-unneeded-ternary
+ (decision) => decision.decisionSpecialIssue?.burn_pit).length > 0;
+
+ this.setState({ specialIssues: {
+ ...this.state.specialIssues,
+ blue_water: appealIsBlueWater,
+ burn_pit: appealIsBurnPit
+ } });
+
+ this.props.editStagedAppeal(
+ this.props.appeal.externalId, {
+ specialIssues: {
+ ...this.state.specialIssues,
+ blue_water: appealIsBlueWater,
+ burn_pit: appealIsBurnPit
+ }
+ }
+ );
+ }
+
+ createSpecialIssueList = (decisionIssues) => {
+ const blueWater = decisionIssues.filter(
+ // eslint-disable-next-line camelcase, no-unneeded-ternary
+ (decision) => decision.decisionSpecialIssue?.blue_water);
+
+ const burnPit = decisionIssues.filter(
+ // eslint-disable-next-line camelcase, no-unneeded-ternary
+ (decision) => decision.decisionSpecialIssue?.burn_pit);
+
+ return {
+ ...this.state.specialIssues,
+ blue_water: _.some(blueWater, (bW) => bW.decisionSpecialIssue.blue_water === true),
+ burn_pit: _.some(burnPit, (bP) => bP.decisionSpecialIssue.burn_pit === true)
+ };
+ };
+
getNextStepUrl = () => {
const {
appealId,
@@ -77,6 +148,11 @@ class SelectDispositionsView extends React.PureComponent {
appeal: { decisionIssues }
} = this.props;
+ ApiUtil.post(`/appeals/${appealId}/special_issues`,
+ {
+ data: { special_issues: this.createSpecialIssueList(decisionIssues) }
+ });
+
let nextStep;
const dispositions = decisionIssues.map((issue) => issue.disposition);
const remandedIssues = _.some(dispositions, (disp) => [
@@ -98,10 +174,14 @@ class SelectDispositionsView extends React.PureComponent {
const {
appealId,
taskId,
- checkoutFlow
+ checkoutFlow,
+ mstFeatureToggle,
+ pactFeatureToggle
} = this.props;
- return `/queue/appeals/${appealId}/tasks/${taskId}/${checkoutFlow}/special_issues`;
+ // route to case details instead of special issues for MST/PACT
+ return (mstFeatureToggle || pactFeatureToggle) ? `/queue/appeals/${appealId}` :
+ `/queue/appeals/${appealId}/tasks/${taskId}/${checkoutFlow}/special_issues`;
}
validateForm = () => {
@@ -123,6 +203,10 @@ class SelectDispositionsView extends React.PureComponent {
const benefitType = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).program;
const diagnosticCode = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).diagnostic_code;
const closedStatus = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).closed_status;
+ const mst_justification = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).mst_justification;
+ const pact_justification = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).pact_justification;
+ const mst_status = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).mst_status;
+ const pact_status = _.find(this.props.appeal.issues, (issue) => requestIssueId === issue.id).pact_status;
const newDecisionIssue = {
id: `temporary-id-${uuid.v4()}`,
@@ -130,14 +214,30 @@ class SelectDispositionsView extends React.PureComponent {
disposition: closedStatus,
benefit_type: benefitType,
diagnostic_code: diagnosticCode,
- request_issue_ids: [requestIssueId]
+ request_issue_ids: [requestIssueId],
+ mst_justification,
+ pact_justification,
+ mst_status,
+ mstOriginalStatus: mst_status,
+ pact_status,
+ pactOriginalStatus: pact_status,
+
+ /*
+ Burn Pit and Blue Water will still be tracked on the appeal level but,
+ SelectSpecialIssuesView.jsx is no longer utilized for AMA appeals.
+ So we must temporarily track it on the issue level. As long as one
+ decision has the issue checked, it will be applied to whole appeal.
+ */
+ decisionSpecialIssue: null,
};
this.setState({
openRequestIssueId: requestIssueId,
decisionIssue: decisionIssue || newDecisionIssue,
editingExistingIssue: Boolean(decisionIssue),
- deleteAddedDecisionIssue: null
+ deleteAddedDecisionIssue: null,
+ mstJustification: mst_justification,
+ pactJustification: pact_justification,
});
}
@@ -160,6 +260,23 @@ class SelectDispositionsView extends React.PureComponent {
return this.validBenefitType(decisionIssue.benefit_type) && decisionIssue.disposition && decisionIssue.description;
}
+ validateJustification = (justificationFeatureToggle) => {
+ const { decisionIssue } = this.state;
+ const mstHasChanged = decisionIssue.mstOriginalStatus !== decisionIssue.mst_status;
+ const pactHasChanged = decisionIssue.pactOriginalStatus !== decisionIssue.pact_status;
+
+ if (mstHasChanged && (decisionIssue.mst_justification === '' || decisionIssue.mst_justification === null) &&
+ justificationFeatureToggle) {
+ return false;
+ }
+ if (pactHasChanged && (decisionIssue.pact_justification === '' || decisionIssue.pact_justification === null) &&
+ justificationFeatureToggle) {
+ return false;
+ }
+
+ return true;
+ }
+
saveDecision = () => {
if (!this.validate()) {
this.setState({
@@ -169,6 +286,14 @@ class SelectDispositionsView extends React.PureComponent {
return;
}
+ if (!this.validateJustification()) {
+ this.setState({
+ highlightModal: true
+ });
+
+ return;
+ }
+
let newDecisionIssues;
if (this.state.editingExistingIssue) {
@@ -187,6 +312,7 @@ class SelectDispositionsView extends React.PureComponent {
this.props.appeal.externalId, { decisionIssues: newDecisionIssues }
);
+ this.stageSpecialIssues(this.props.appeal.decisionIssues);
this.handleModalClose();
}
@@ -199,9 +325,23 @@ class SelectDispositionsView extends React.PureComponent {
this.props.appeal.externalId, { decisionIssues: remainingDecisionIssues }
);
+ // Reverts special issues view to their original status when deleting decision
+ this.selectedIssuesToDelete()[0].mst_status = this.state.decisionIssue.mstOriginalStatus;
+ this.selectedIssuesToDelete()[0].pact_status = this.state.decisionIssue.pactOriginalStatus;
+
this.handleModalClose();
}
+ selectedIssuesToDelete = () => {
+ if (!this.state.requestIdToDelete) {
+ return [];
+ }
+
+ return this.props.appeal.issues.filter((issue) => {
+ return this.state.requestIdToDelete === issue.id;
+ });
+ }
+
selectedIssues = () => {
if (!this.state.openRequestIssueId) {
return [];
@@ -240,15 +380,73 @@ class SelectDispositionsView extends React.PureComponent {
});
}
+ onJustificationChange = (event, decision, type) => {
+
+ if (type === 'mst_status') {
+ this.setState({
+ decisionIssue: {
+ ...decision,
+ mst_justification: event
+ }
+ });
+ this.setState({ mstJustification: event });
+ } else if (type === 'pact_status') {
+ this.setState({
+ decisionIssue: {
+ ...decision,
+ pact_justification: event
+ }
+ });
+ this.setState({ pactJustification: event });
+ }
+ }
+
+ filterIssuesForJustification = (issues, idToFilter) => {
+ return issues.filter((issue) => {
+ return issue.id === idToFilter;
+ });
+ }
+
+ onCheckboxChange = (event, decision) => {
+ const checkboxId = event.target.getAttribute('id');
+
+ if (checkboxId === 'mst_status' || checkboxId === 'pact_status') {
+ this.setState({
+ decisionIssue: {
+ ...decision,
+ [checkboxId]: event.target.checked,
+ }
+ });
+ }
+ if (checkboxId === 'blue_water' || checkboxId === 'burn_pit') {
+ this.setState({
+ decisionIssue: {
+ ...decision,
+ decisionSpecialIssue: {
+ ...decision.decisionSpecialIssue,
+ [checkboxId]: event.target.checked,
+ }
+ }
+ });
+ }
+ };
+
render = () => {
- const { appeal, highlight, ...otherProps } = this.props;
+ const {
+ appeal,
+ highlight,
+ justificationFeatureToggle,
+ mstFeatureToggle,
+ pactFeatureToggle,
+ ...otherProps
+ } = this.props;
const {
highlightModal,
decisionIssue,
openRequestIssueId,
editingExistingIssue,
deleteAddedDecisionIssue,
- requestIdToDelete
+ requestIdToDelete,
} = this.state;
const connectedRequestIssues = appeal.issues.filter((issue) => {
return decisionIssue && decisionIssue.request_issue_ids.includes(issue.id);
@@ -256,6 +454,29 @@ class SelectDispositionsView extends React.PureComponent {
const connectedIssues = this.connectedRequestIssuesWithoutCurrentId(connectedRequestIssues, requestIdToDelete);
const toDeleteHasConnectedIssue = connectedIssues.length > 0;
+ // switch statement to show checkbox values depending on
+ // mst/pact feature toggles
+ const buildCheckboxValues = () => {
+ if (mstFeatureToggle && pactFeatureToggle) {
+ return DECISION_SPECIAL_ISSUES_WITH_MST_PACT;
+ } else if (mstFeatureToggle) {
+ return DECISION_SPECIAL_ISSUES_WITH_MST;
+ } else if (pactFeatureToggle) {
+ return DECISION_SPECIAL_ISSUES_WITH_PACT;
+ }
+
+ return DECISION_SPECIAL_ISSUES;
+ };
+
+ const specialIssuesValues = {
+ // eslint-disable-next-line camelcase
+ blue_water: decisionIssue?.decisionSpecialIssue?.blue_water,
+ // eslint-disable-next-line camelcase
+ burn_pit: decisionIssue?.decisionSpecialIssue?.burn_pit,
+ mst_status: decisionIssue?.mst_status,
+ pact_status: decisionIssue?.pact_status
+ };
+
// In order to determine whether or not to display error styling and an error message for each issue,
// determine if highlight is set to true and if there is not a decision issue
const issueErrors = {};
@@ -280,8 +501,12 @@ class SelectDispositionsView extends React.PureComponent {
@@ -381,6 +606,36 @@ class SelectDispositionsView extends React.PureComponent {
}
})}
/>
+ { (mstFeatureToggle || pactFeatureToggle) && this.onCheckboxChange(event, decisionIssue)}
+ errorState={{
+ highlightModal,
+ invalid: !this.validateJustification(justificationFeatureToggle),
+ }
+ }
+ filterIssuesForJustification={this.filterIssuesForJustification}
+ justificationFeatureToggle={justificationFeatureToggle}
+ mstFeatureToggle={mstFeatureToggle}
+ pactFeatureToggle={pactFeatureToggle}
+ justifications={[
+ {
+ id: 'mst_status',
+ justification: decisionIssue.mst_justification,
+ onJustificationChange: (event) => this.onJustificationChange(event, decisionIssue, 'mst_status'),
+ hasChanged: this.state.decisionIssue.mstOriginalStatus !== this.state.decisionIssue.mst_status
+ },
+ {
+ id: 'pact_status',
+ justification: decisionIssue.pact_justification,
+ onJustificationChange: (event) => this.onJustificationChange(event, decisionIssue, 'pact_status'),
+ hasChanged: this.state.decisionIssue.pactOriginalStatus !== this.state.decisionIssue.pact_status
+ },
+ ]}
+ />}
{COPY.DECISION_ISSUE_MODAL_CONNECTED_ISSUES_DESCRIPTION}
{COPY.DECISION_ISSUE_MODAL_CONNECTED_ISSUES_EXAMPLE}
{COPY.DECISION_ISSUE_MODAL_CONNECTED_ISSUES_TITLE}
@@ -432,6 +687,7 @@ class SelectDispositionsView extends React.PureComponent {
SelectDispositionsView.propTypes = {
appeal: PropTypes.shape({
decisionIssues: PropTypes.array,
+ specialIssues: PropTypes.object,
externalId: PropTypes.string,
isLegacyAppeal: PropTypes.bool,
issues: PropTypes.array
@@ -442,7 +698,10 @@ SelectDispositionsView.propTypes = {
hideSuccessMessage: PropTypes.func,
highlight: PropTypes.bool,
setDecisionOptions: PropTypes.func,
- taskId: PropTypes.string
+ taskId: PropTypes.string,
+ justificationFeatureToggle: PropTypes.bool,
+ mstFeatureToggle: PropTypes.bool,
+ pactFeatureToggle: PropTypes.bool
};
const mapStateToProps = (state, ownProps) => ({
@@ -458,3 +717,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SelectDispositionsView);
+
diff --git a/client/app/queue/SelectSpecialIssuesView.jsx b/client/app/queue/SelectSpecialIssuesView.jsx
index e919faf2a15..261bf7acc7c 100644
--- a/client/app/queue/SelectSpecialIssuesView.jsx
+++ b/client/app/queue/SelectSpecialIssuesView.jsx
@@ -24,6 +24,10 @@ const flexColumn = css({
width: '50%'
});
+const infoBannerStyling = css({
+ marginBottom: '2rem',
+});
+
class SelectSpecialIssuesView extends React.PureComponent {
constructor(props) {
super(props);
@@ -85,19 +89,23 @@ class SelectSpecialIssuesView extends React.PureComponent {
}
legacySpecialIssuesSections = () => {
+ const mstIdentification = this.props.mstIdentification && this.props.legacyMstIdentification;
+
return [
specialIssueFilters(true).noneSection(),
specialIssueFilters(true).aboutSection(),
specialIssueFilters(true).residenceSection(),
specialIssueFilters(true).benefitTypeSection(),
- specialIssueFilters(true).issuesOnAppealSection(),
- specialIssueFilters(true).dicOrPensionSection()
+ specialIssueFilters(true, mstIdentification).issuesOnAppealSection(),
+ specialIssueFilters(true).dicOrPensionSection(),
];
};
amaSpecialIssuesSections = () => {
+ const mstIdentification = this.props.mstIdentification || this.props.legacyMstIdentification;
+
return [
specialIssueFilters(true).noneSection(),
- specialIssueFilters(true).amaIssuesOnAppealSection()
+ specialIssueFilters(true, mstIdentification).amaIssuesOnAppealSection(),
];
};
@@ -126,6 +134,8 @@ class SelectSpecialIssuesView extends React.PureComponent {
renderSpecialIssuesPage = (appeal, sections, specialIssues) => {
const {
error,
+ mstIdentification,
+ legacyMstIdentification,
...otherProps
} = this.props;
const { allIssuesDisabled } = this.state;
@@ -138,6 +148,8 @@ class SelectSpecialIssuesView extends React.PureComponent {
{this.getPageNote()}
+ {(mstIdentification || legacyMstIdentification) &&
+ }
{error && }
@@ -236,7 +248,9 @@ SelectSpecialIssuesView.propTypes = {
setSpecialIssues: PropTypes.func,
clearSpecialIssues: PropTypes.func,
showErrorMessage: PropTypes.func,
- specialIssues: PropTypes.object
+ specialIssues: PropTypes.object,
+ mstIdentification: PropTypes.bool,
+ legacyMstIdentification: PropTypes.bool
};
const mapStateToProps = (state, ownProps) => ({
diff --git a/client/app/queue/components/CaseDetailsIssueList.jsx b/client/app/queue/components/CaseDetailsIssueList.jsx
index a24c795a069..49748fd2845 100644
--- a/client/app/queue/components/CaseDetailsIssueList.jsx
+++ b/client/app/queue/components/CaseDetailsIssueList.jsx
@@ -1,5 +1,7 @@
+/* eslint-disable react/prop-types, import/extensions */
import React from 'react';
import { css } from 'glamor';
+import PropTypes from 'prop-types';
import { getIssueDiagnosticCodeLabel } from '../utils';
import AmaIssueList from '../../components/AmaIssueList';
@@ -26,11 +28,35 @@ const headingStyling = css({
export default function CaseDetailsIssueList(props) {
if (!props.isLegacyAppeal) {
+ // Map props from redux store - values from queue.appeals.issues and queue.appeals.decisionissues
+ const updatedDecisionIssues = props.decisionIssues.map((decisionIssue) => {
+ // Get corresponding request_issue_id
+ const correspondingRequestIssueId = decisionIssue.request_issue_ids[0];
+ // Filter request issues for the corresponding request issue id
+ const correspondingRequestIssue = props.issues.filter((requestIssue) => {
+ return requestIssue.id === correspondingRequestIssueId;
+ })[0];
+ // Grab the mst and pact statuses to add to decision issues
+ const requestIssueMstStatus = correspondingRequestIssue.mst_status;
+ const requestIssuePactStatus = correspondingRequestIssue.pact_status;
+
+ return {
+ // spread operator - opening up and duplicating each key value pair of an object/hash/dictionary/array
+ ...decisionIssue,
+ mstStatus: requestIssueMstStatus,
+ pactStatus: requestIssuePactStatus,
+ };
+ });
+
return
+ decisionIssues={props.decisionIssues}
+ mstFeatureToggle={props.featureToggles.mstIdentification}
+ pactFeatureToggle={props.featureToggles.pactIdentification}>
+ mstFeatureToggle={props.featureToggles.mstIdentification}
+ pactFeatureToggle={props.featureToggles.pactIdentification}
+ decisionIssues={updatedDecisionIssues} />
;
}
@@ -38,13 +64,16 @@ export default function CaseDetailsIssueList(props) {
{props.issues.map((issue, i) =>
Issue {1 + i}
- { {issue} }
+
+ {issue}
+
)}
;
}
const LegacyIssueDetails = (props) => {
+ const legacyMstPactFeatureToggle = props.legacyMstPactFeatureToggle;
const issue = props.children;
const codes = issue.codes ? issue.codes.slice() : [];
const diagnosticCode = getIssueDiagnosticCodeLabel(codes[codes.length - 1]) ? codes.pop() : null;
@@ -57,6 +86,7 @@ const LegacyIssueDetails = (props) => {
{issue.note}
{issue.disposition}
{issue.closed_status}
+ {legacyMstPactFeatureToggle &&
{issue}}
;
};
@@ -89,6 +119,22 @@ const getDescriptionsFromCodes = (levels, codes, descriptions = []) => {
return descriptions;
};
+// format special issues to display 'None', 'PACT', 'MST', or 'MST and PACT'
+const specialIssuesFormatting = (props) => {
+ const mstStatus = props.mst_status;
+ const pactStatus = props.pact_status;
+
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST and PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
const IssueDescriptionsListItem = (props) => {
if (!props.program || !props.children) {
return null;
@@ -107,6 +153,35 @@ const IssueNoteListItem = (props) =>
;
+const SpecialIssueListItem = (props) =>
+ {specialIssuesFormatting(props.children)}
+;
+
const IssueDispositionListItem = (props) =>
{dispositionLabelForDescription(props.children)}
;
+
+CaseDetailsIssueList.propTypes = {
+ isLegacyAppeal: PropTypes.bool,
+ issues: PropTypes.array,
+ title: PropTypes.string,
+ decisionIssues: PropTypes.node,
+ featureToggles: PropTypes.object
+};
+
+SpecialIssueListItem.propTypes = {
+ children: PropTypes.object,
+ mst_status: PropTypes.bool,
+ pact_status: PropTypes.bool
+};
+
+LegacyIssueDetails.propTypes = {
+ legacyMstPactFeatureToggle: PropTypes.bool,
+ children: PropTypes.object
+};
+
+DescriptionListItem.propTypes = {
+ label: PropTypes.object,
+ children: PropTypes.object,
+ styling: PropTypes.object
+};
diff --git a/client/app/queue/components/ContestedIssues.jsx b/client/app/queue/components/ContestedIssues.jsx
index fc605137165..341710b5324 100644
--- a/client/app/queue/components/ContestedIssues.jsx
+++ b/client/app/queue/components/ContestedIssues.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/prop-types, import/extensions */
import * as React from 'react';
import { css } from 'glamor';
import { COLORS } from '../../constants/AppConstants';
@@ -36,6 +37,18 @@ const errorTextSpacing = css({
margin: TEXT_INDENTATION
});
+const specialIssuesFormatting = (mstStatus, pactStatus) => {
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST, PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
export default class ContestedIssues extends React.PureComponent {
render = () => {
const {
@@ -77,6 +90,10 @@ export default class ContestedIssues extends React.PureComponent {
{ issue.diagnostic_code &&
Diagnostic code: {issue.diagnostic_code}
}
+ {
+ specialIssuesFormatting(issue.mst_status, issue.pact_status) &&
+ Special Issues: {specialIssuesFormatting(issue.mst_status, issue.pact_status)}
+ }
{ issue.notes &&
Note: "{issue.notes}"
}
diff --git a/client/app/queue/components/DecisionIssues.jsx b/client/app/queue/components/DecisionIssues.jsx
index ed9f36fb067..5b11187b401 100644
--- a/client/app/queue/components/DecisionIssues.jsx
+++ b/client/app/queue/components/DecisionIssues.jsx
@@ -6,6 +6,7 @@ import Button from '../../components/Button';
import ISSUE_DISPOSITIONS_BY_ID from '../../../constants/ISSUE_DISPOSITIONS_BY_ID';
import { LinkIcon } from '../../components/icons/LinkIcon';
import HearingWorksheetAmaIssues from '../../hearings/components/hearingWorksheet/HearingWorksheetAmaIssues';
+import BENEFIT_TYPES from '../../../constants/BENEFIT_TYPES';
const TEXT_INDENTATION = '10px';
@@ -57,9 +58,27 @@ const noteDiv = css({
color: COLORS.GREY
});
+const specialIssuesFormatting = (mstStatus, pactStatus) => {
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST and PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
export default class DecisionIssues extends React.PureComponent {
static generateDecisionIssues = (requestIssue, props) => {
- const { decisionIssues, openDecisionHandler, openDeleteAddedDecisionIssueHandler, hideDelete, hideEdit } = props;
+ const { decisionIssues,
+ openDecisionHandler,
+ openDeleteAddedDecisionIssueHandler,
+ hideDelete,
+ hideEdit,
+ mstFeatureToggle,
+ pactFeatureToggle } = props;
return decisionIssues.
filter((decisionIssue) => {
@@ -103,6 +122,10 @@ export default class DecisionIssues extends React.PureComponent {
{decisionIssue.description}
{decisionIssue.diagnostic_code && Diagnostic code: {decisionIssue.diagnostic_code}
}
+ {decisionIssue.benefit_type && Benefit Type: {BENEFIT_TYPES[decisionIssue.benefit_type]}
}
+ {(pactFeatureToggle || mstFeatureToggle) &&
+ Special Issues: {specialIssuesFormatting(decisionIssue.mst_status, decisionIssue.pact_status)}
+
}
{ISSUE_DISPOSITIONS_BY_ID[decisionIssue.disposition]}
@@ -149,7 +172,9 @@ DecisionIssues.propTypes = {
notes: PropTypes.string,
diagnostic_code: PropTypes.string,
closed_status: PropTypes.string,
- remand_reasons: PropTypes.array
+ remand_reasons: PropTypes.array,
+ mst_status: PropTypes.bool,
+ pact_status: PropTypes.bool
}),
openDecisionHandler: PropTypes.func,
hearingWorksheet: PropTypes.object,
diff --git a/client/app/queue/components/IssueRemandReasonsOptions.jsx b/client/app/queue/components/IssueRemandReasonsOptions.jsx
index 0bd0ce0c643..526848d7c34 100644
--- a/client/app/queue/components/IssueRemandReasonsOptions.jsx
+++ b/client/app/queue/components/IssueRemandReasonsOptions.jsx
@@ -26,7 +26,13 @@ import Checkbox from '../../components/Checkbox';
import CheckboxGroup from '../../components/CheckboxGroup';
import RadioField from '../../components/RadioField';
-import { getIssueProgramDescription, getIssueTypeDescription, getIssueDiagnosticCodeLabel } from '../utils';
+import {
+ getIssueProgramDescription,
+ getIssueTypeDescription,
+ getIssueDiagnosticCodeLabel,
+ getMstPactStatus,
+ getLegacyMstPactStatus } from '../utils';
+
import {
fullWidth,
REMAND_REASONS,
@@ -382,7 +388,12 @@ class IssueRemandReasonsOptions extends React.PureComponent {
`Program: ${getIssueProgramDescription(issue)}` :
`Benefit type: ${BENEFIT_TYPES[issue.benefit_type]}`}
- {!appeal.isLegacyAppeal && Issue description: {issue.description}
}
+ {!appeal.isLegacyAppeal && (
+
+ Issue description: {issue.description}
+ Special Issues: {getMstPactStatus(issue)}
+
+ )}
{appeal.isLegacyAppeal && (
Issue: {getIssueTypeDescription(issue)}
@@ -391,6 +402,7 @@ class IssueRemandReasonsOptions extends React.PureComponent {
Certified: {formatDateStr(appeal.certificationDate)}
Note: {issue.note}
+ Special Issues: {getLegacyMstPactStatus(issue)}
)}
{highlight && !this.getChosenOptions().length && (
diff --git a/client/app/queue/components/LegacyIssueListItem.jsx b/client/app/queue/components/LegacyIssueListItem.jsx
index dd56fa9801e..8e22d8b4813 100644
--- a/client/app/queue/components/LegacyIssueListItem.jsx
+++ b/client/app/queue/components/LegacyIssueListItem.jsx
@@ -25,6 +25,21 @@ const leftAlignTd = css({
paddingRight: 0
});
+const specialIssuesFormatting = (props) => {
+ const mstStatus = props.mst_status;
+ const pactStatus = props.pact_status;
+
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST and PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
export const dispositionLabelForDescription = (disposition) => {
// Use the disposition description from constants in order to get the proper capitalization.
return disposition ? `${disposition} - ${VACOLS_DISPOSITIONS_BY_ID[disposition]}` : null;
@@ -90,7 +105,8 @@ export default class LegacyIssueListItem extends React.PureComponent {
note
},
issuesOnly,
- showDisposition
+ showDisposition,
+ legacyMstPactFeatureToggle
} = this.props;
let issueContent = ;
@@ -107,6 +123,9 @@ export default class LegacyIssueListItem extends React.PureComponent {
Note: {note}
+ {legacyMstPactFeatureToggle &&
+ Special Issues: {specialIssuesFormatting(issue)}
+
}
;
}
@@ -126,7 +145,8 @@ LegacyIssueListItem.propTypes = {
issue: PropTypes.object.isRequired,
issuesOnly: PropTypes.bool,
idx: PropTypes.number.isRequired,
- showDisposition: PropTypes.bool
+ showDisposition: PropTypes.bool,
+ legacyMstPactFeatureToggle: PropTypes.bool
};
LegacyIssueListItem.defaultProps = {
diff --git a/client/app/queue/components/QueueCheckboxGroup.jsx b/client/app/queue/components/QueueCheckboxGroup.jsx
new file mode 100644
index 00000000000..c98f5c271ea
--- /dev/null
+++ b/client/app/queue/components/QueueCheckboxGroup.jsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import TextField from '../../components/TextField';
+import {
+ INTAKE_EDIT_ISSUE_CHANGE_MESSAGE
+} from 'app/../COPY';
+
+const renderCheckbox = (option, onChange, values = {}, disabled = false, justifications,
+ // eslint-disable-next-line max-params
+ filterIssuesForJustification, errorState, justificationFeatureToggle) =>
+
+
+ {option.requiresJustification && filterIssuesForJustification(justifications, option.id)[0].hasChanged &&
+ justificationFeatureToggle &&
+
+
+ }
+
;
+
+export default class QueueCheckboxGroup extends React.Component {
+
+ // number of options that render horizontally by default
+ MAX = 2;
+
+ render() {
+ const {
+ label,
+ name,
+ required,
+ onChange,
+ options,
+ vertical,
+ hideLabel,
+ values,
+ errorMessage,
+ errorState,
+ getCheckbox,
+ styling,
+ strongLabel,
+ disableAll,
+ justifications,
+ filterIssuesForJustification,
+ justificationFeatureToggle
+ } = this.props;
+
+ const labelContents = (
+
+ {label || name}
+
+ );
+
+ let fieldClasses = `checkbox-wrapper-${name} cf-form-checkboxes cf-checkbox-group`;
+
+ if (options.length <= this.MAX && !vertical) {
+ fieldClasses += '-inline';
+ }
+
+ if (errorMessage) {
+ fieldClasses += ' usa-input-error';
+ }
+
+ const legendClasses = (hideLabel) ? 'hidden-field' : '';
+
+ return ;
+ }
+}
+
+QueueCheckboxGroup.defaultProps = {
+ required: false,
+ getCheckbox: renderCheckbox,
+ hideErrorMessage: false
+};
+
+QueueCheckboxGroup.propTypes = {
+ label: PropTypes.node,
+ hideLabel: PropTypes.bool,
+ name: PropTypes.string.isRequired,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string,
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.node
+ ]),
+ requiresJustification: PropTypes.bool
+ })
+ ).isRequired,
+ onChange: PropTypes.func.isRequired,
+ required: PropTypes.bool,
+ vertical: PropTypes.bool,
+ values: PropTypes.object,
+ errorMessage: PropTypes.string,
+ errorState: PropTypes.shape({
+ highlightModal: PropTypes.bool,
+ invalid: PropTypes.bool,
+ }),
+ getCheckbox: PropTypes.func,
+ styling: PropTypes.object,
+ strongLabel: PropTypes.bool,
+ disableAll: PropTypes.bool,
+ justifications: PropTypes.arrayOf(
+ PropTypes.shape({
+ pactJustification: PropTypes.string,
+ mstJustification: PropTypes.string,
+ pactJustificationOnChange: PropTypes.func,
+ mstJustificationOnChange: PropTypes.func,
+ })
+ ),
+ filterIssuesForJustification: PropTypes.func,
+ justificationFeatureToggle: PropTypes.bool
+};
diff --git a/client/app/queue/components/QueueFlowPage.jsx b/client/app/queue/components/QueueFlowPage.jsx
index ce079af1624..213163e8508 100644
--- a/client/app/queue/components/QueueFlowPage.jsx
+++ b/client/app/queue/components/QueueFlowPage.jsx
@@ -88,7 +88,11 @@ class QueueFlowPage extends React.PureComponent {
} = this.props;
this.props.hideModal('cancelCheckout');
- this.props.resetDecisionOptions();
+ if (location.href.includes('dispositions')) {
+ this.props.resetDecisionOptions();
+ } else {
+ this.props.goToNextStep();
+ }
_.each(stagedAppeals, this.props.checkoutStagedAppeal);
this.withUnblockedTransition(
diff --git a/client/app/queue/components/TaskRows.jsx b/client/app/queue/components/TaskRows.jsx
index 68582336160..491b3ac960c 100644
--- a/client/app/queue/components/TaskRows.jsx
+++ b/client/app/queue/components/TaskRows.jsx
@@ -70,6 +70,14 @@ const isCancelled = (task) => {
return task.status === TASK_STATUSES.cancelled;
};
+const issueUpdateTask = (task) => {
+ return task.type === 'IssuesUpdateTask';
+};
+
+const establishmentTask = (task) => {
+ return task.type === 'EstablishmentTask';
+};
+
const tdClassNames = (timeline, task) => {
const containerClass = timeline ? taskInfoWithIconTimelineContainer : '';
const closedAtClass = task.closedAt ? null : ;
@@ -172,7 +180,7 @@ class TaskRows extends React.PureComponent {
assignedToListItem = (task) => {
const assignee = task.assigneeName;
- return assignee ? (
+ return assignee && !establishmentTask(task) ? (
- {COPY.TASK_SNAPSHOT_TASK_ASSIGNEE_LABEL}
- {assignee}
@@ -304,10 +312,152 @@ class TaskRows extends React.PureComponent {
return text.replace(/
|(? {
+ if (mstText) {
+ return
+ Reason for Change (MST):
+ {mstText}
+ ;
+ }
+ };
+
+ const renderPactLabel = (pactText, style) => {
+ if (pactText) {
+ return
+ Reason for Change (PACT):
+ {pactText}
+ ;
+ }
+ };
+
+ // formatting used for IssueUpdate task instructions.
+ const formatIssueUpdateBreaks = (text = '') => {
+ const divStyle = { marginTop: '1rem' };
+ const hStyle = { marginTop: '1.5rem', marginBottom: '0rem', fontWeight: 'bold' };
+
+ if (Array.isArray(text)) {
+ // text array indexes
+ // 0: change_type,
+ // 1: benefit_type,
+ // 2: issue description,
+ // 3: original special issues list
+ // 4: updated special issues list
+ // 5: mst edit reason (not currently implemented)
+ // 6: pact edit reason (not currently implemented)
+ return (
+
+
{text[0]}:
+ {text[1] &&
+
+
+ Benefit type: {text[1]}
+
+ }
+
+ {text[4] ?
+
+ Original:
+
+ {text[3]}
+
+ Updated:
+
+ {text[4]}
+
+ :
+
+ {text[3]}
+
}
+ {renderMstLabel(text[5], hStyle)}
+ {renderPactLabel(text[6], hStyle)}
+
+ );
+ }
+ };
+
+ const formatEstablishmentBreaks = (text = '') => {
+ const divStyle = { marginTop: '1rem' };
+ const hStyle = { marginTop: '1rem', marginBottom: '0rem', fontWeight: 'bold' };
+
+ if (Array.isArray(text)) {
+ const content = text.map((issue, index) =>
+ // issue array indexes:
+ // 0: Issue description
+ // 1: Benefit Type
+ // 2: Original special issues (empty string unless issue originated in VBMS
+ // AND mst/pact designation changes by intake user)
+ // 3: Special issues (Either added by intake user or originating in VBMS - if left unaltered during intake)
+
+
+ Added Issue:
+
+
+ {issue[0]}
+
+ {issue.at(1) !== '' &&
+
+
+ Benefit type: {issue[1]}
+
+ }
+ {/* Condition where a prior decision from vbms with mst/pact designation was updated in intake process */}
+ {issue[2] ?
+
+ ORIGINAL:
+ {issue[2]}
+ UPDATED:
+ {issue[3]}
+
+ :
+
+ }
+ {/* No horizontal rule after the last issue */}
+ {index !== (text.length - 1) &&
+
+
+
+
+
+ }
+
+ );
+
+ return (
+
+ {content}
+
+ );
+ }
+ };
+
// We specify the same 2.4rem margin-bottom as paragraphs to each set of instructions
// to ensure a consistent margin between instruction content and the "Hide" button
const divStyles = { marginBottom: '2.4rem', marginTop: '1em' };
+ // eslint-disable-next-line no-shadow
+ const formatInstructions = (task, text) => {
+ if (issueUpdateTask(task)) {
+ return (
+
{formatIssueUpdateBreaks(text)}
+ );
+ } else if (establishmentTask(task)) {
+ return (
+
{formatEstablishmentBreaks(text)}
+ );
+ }
+
+ return (
+
{formatBreaks(text)}
+ );
+ };
+
return (
{task.instructions.map((text) => (
@@ -317,7 +467,9 @@ class TaskRows extends React.PureComponent {
style={divStyles}
className="task-instructions"
>
- {formatBreaks(text)}
+ {
+ formatInstructions(task, text)
+ }
))}
@@ -334,9 +486,11 @@ class TaskRows extends React.PureComponent {
{this.state.taskInstructionsIsVisible[task.uniqueId] && (
+ {!establishmentTask(task) &&
-
{COPY.TASK_SNAPSHOT_TASK_INSTRUCTIONS_LABEL}
+ }
-
{this.taskInstructionsWithLineBreaks(task)}
@@ -408,7 +562,7 @@ class TaskRows extends React.PureComponent {
return (
- {this.assignedToListItem(task)}
+ {task.type !== 'IssuesUpdateTask' && this.assignedToListItem(task)}
{this.assignedByListItem(task)}
{this.cancelledByListItem(task)}
{this.cancelReasonListItem(task)}
diff --git a/client/app/queue/constants.js b/client/app/queue/constants.js
index a2cae86e8e7..fdb3192709e 100644
--- a/client/app/queue/constants.js
+++ b/client/app/queue/constants.js
@@ -218,3 +218,75 @@ export const PAGE_TITLES = {
export const CUSTOM_HOLD_DURATION_TEXT = 'Custom';
export const COLOCATED_HOLD_DURATIONS = [15, 30, 45, 60, 90, 120, CUSTOM_HOLD_DURATION_TEXT];
export const VHA_HOLD_DURATIONS = [15, 30, 45, CUSTOM_HOLD_DURATION_TEXT];
+
+export const DECISION_SPECIAL_ISSUES_WITH_MST_PACT = [
+ {
+ id: 'blue_water',
+ label: 'Blue Water',
+ requiresJustification: false
+ },
+ {
+ id: 'burn_pit',
+ label: 'Burn Pit',
+ requiresJustification: false
+ },
+ {
+ id: 'mst_status',
+ label: 'Military Sexual Trauma (MST)',
+ requiresJustification: true
+ },
+ {
+ id: 'pact_status',
+ label: 'PACT Act',
+ requiresJustification: true
+ },
+];
+
+export const DECISION_SPECIAL_ISSUES_WITH_MST = [
+ {
+ id: 'blue_water',
+ label: 'Blue Water',
+ requiresJustification: false
+ },
+ {
+ id: 'burn_pit',
+ label: 'Burn Pit',
+ requiresJustification: false
+ },
+ {
+ id: 'mst_status',
+ label: 'Military Sexual Trauma (MST)',
+ requiresJustification: true
+ },
+];
+
+export const DECISION_SPECIAL_ISSUES_WITH_PACT = [
+ {
+ id: 'blue_water',
+ label: 'Blue Water',
+ requiresJustification: false
+ },
+ {
+ id: 'burn_pit',
+ label: 'Burn Pit',
+ requiresJustification: false
+ },
+ {
+ id: 'pact_status',
+ label: 'PACT Act',
+ requiresJustification: true
+ },
+];
+
+export const DECISION_SPECIAL_ISSUES = [
+ {
+ id: 'blue_water',
+ label: 'Blue Water',
+ requiresJustification: false
+ },
+ {
+ id: 'burn_pit',
+ label: 'Burn Pit',
+ requiresJustification: false
+ }
+];
diff --git a/client/app/queue/utils.js b/client/app/queue/utils.js
index 46e449be2aa..acd083a8f54 100644
--- a/client/app/queue/utils.js
+++ b/client/app/queue/utils.js
@@ -199,6 +199,8 @@ const appealAttributesFromRawTask = (task) => ({
veteranFullName: task.attributes.veteran_full_name,
veteranFileNumber: task.attributes.veteran_file_number,
isPaperCase: task.attributes.paper_case,
+ mst: task.attributes.mst,
+ pact: task.attributes.pact
});
const extractAppealsFromTasks = (tasks) => {
@@ -276,6 +278,8 @@ export const prepareLegacyTasksForStore = (tasks) => {
task.attributes.latest_informal_hearing_presentation_task
?.received_at,
},
+ mst: task.attributes.mst,
+ pact: task.attributes.pact
};
});
@@ -439,6 +443,8 @@ export const prepareAppealForStore = (appeals) => {
appeal.attributes.readable_original_hearing_request_type,
vacateType: appeal.attributes.vacate_type,
cavcRemandsWithDashboard: appeal.attributes.cavc_remands_with_dashboard,
+ mst: appeal.attributes.mst,
+ pact: appeal.attributes.pact
};
return accumulator;
@@ -513,6 +519,8 @@ export const prepareAppealForStore = (appeals) => {
remandJudgeName: appeal.attributes.remand_judge_name,
hasNotifications: appeal.attributes.has_notifications,
locationHistory: prepareLocationHistoryForStore(appeal),
+ mst: appeal.attributes.mst,
+ pact: appeal.attributes.pact
};
return accumulator;
@@ -685,6 +693,36 @@ export const getIssueDiagnosticCodeLabel = (code) => {
return `${code} - ${readableLabel.staff_description}`;
};
+export const getMstPactStatus = (issue) => {
+ const mstStatus = issue.mst_status;
+ const pactStatus = issue.pact_status;
+
+ if (!mstStatus && !pactStatus) {
+ return 'None';
+ } else if (mstStatus && pactStatus) {
+ return 'MST and PACT';
+ } else if (mstStatus) {
+ return 'MST';
+ } else if (pactStatus) {
+ return 'PACT';
+ }
+};
+
+export const getLegacyMstPactStatus = (issue) => {
+ const mstStatusLegacy = issue.mst_status;
+ const pactStatusLegacy = issue.pact_status;
+
+ if (!mstStatusLegacy && !pactStatusLegacy) {
+ return 'None';
+ } else if (mstStatusLegacy && pactStatusLegacy) {
+ return 'MST and PACT';
+ } else if (mstStatusLegacy) {
+ return 'MST';
+ } else if (pactStatusLegacy) {
+ return 'PACT';
+ }
+};
+
// Build case review payloads for attorney decision draft submissions as well as judge decision evaluations.
export const buildCaseReviewPayload = (
checkoutFlow,
diff --git a/client/app/styles/hearings/_hearings.scss b/client/app/styles/hearings/_hearings.scss
index c1cadc513ce..9a107ad2fa9 100644
--- a/client/app/styles/hearings/_hearings.scss
+++ b/client/app/styles/hearings/_hearings.scss
@@ -423,6 +423,11 @@ $form-width: 575px;
width: 25px;
}
+.badge-designation {
+ width: 85px;
+ align-content: center;
+}
+
@media screen and (min-width: 1224px) and (max-width: 1680px) {
.usa-width-three-fourths.assign-hearing-tabs {
width: 70%;
diff --git a/client/constants/TASK_ACTIONS.json b/client/constants/TASK_ACTIONS.json
index 9e3d9d8789e..3590ce8aed0 100644
--- a/client/constants/TASK_ACTIONS.json
+++ b/client/constants/TASK_ACTIONS.json
@@ -171,7 +171,7 @@
"label": "Ready for Dispatch",
"value": "dispatch_decision/dispositions"
},
- "JUDGE_AMA_CHECKOUT_SP_ISSUES": {
+ "JUDGE_AMA_CHECKOUT_SPECIAL_ISSUES": {
"label": "Ready for Dispatch",
"value": "dispatch_decision/special_issues"
},
diff --git a/client/test/app/hearings/components/ScheduleVeteran.test.js b/client/test/app/hearings/components/ScheduleVeteran.test.js
index c2acc050606..55c6f93d2dd 100644
--- a/client/test/app/hearings/components/ScheduleVeteran.test.js
+++ b/client/test/app/hearings/components/ScheduleVeteran.test.js
@@ -636,6 +636,7 @@ describe('ScheduleVeteran', () => {
regionalOffice: virtualHearing.regionalOfficeKey,
readableHearingRequestType: VIRTUAL_HEARING_LABEL,
}}
+ appealId={legacyAppealForTravelBoard.externalId}
/>,
{
wrappingComponent: queueWrapper,
diff --git a/client/test/app/hearings/components/__snapshots__/Details.test.js.snap b/client/test/app/hearings/components/__snapshots__/Details.test.js.snap
index 58ab26faa10..ee8dca40950 100644
--- a/client/test/app/hearings/components/__snapshots__/Details.test.js.snap
+++ b/client/test/app/hearings/components/__snapshots__/Details.test.js.snap
@@ -129652,6 +129652,126 @@ exports[`Details Displays VirtualHearing details when there is a virtual hearing
disposition={null}
docketName="hearing"
docketNumber="200628-4"
+ hearing={
+ Object {
+ "advanceOnDocketMotion": null,
+ "aod": false,
+ "appealExternalId": "005334f7-b5c6-490c-a310-7dc5db22c8c3",
+ "appealId": 4,
+ "appellantAddressLine1": "9999 MISSION ST",
+ "appellantCity": "SAN FRANCISCO",
+ "appellantEmailAddress": "tom.brady@caseflow.gov",
+ "appellantFirstName": "Bob",
+ "appellantIsNotVeteran": false,
+ "appellantLastName": "Smith",
+ "appellantState": "CA",
+ "appellantTz": "America/Los_Angeles",
+ "appellantZip": "94103",
+ "availableHearingLocations": Object {},
+ "bvaPoc": null,
+ "caseType": "Original",
+ "centralOfficeTimeString": "03:30",
+ "claimantId": 4,
+ "closestRegionalOffice": null,
+ "currentIssueCount": 1,
+ "disposition": null,
+ "dispositionEditable": true,
+ "docketName": "hearing",
+ "docketNumber": "200628-4",
+ "emailEvents": Array [
+ Object {
+ "emailAddress": "tom.brady@caseflow.gov",
+ "emailType": "confirmation",
+ "sentAt": "2020-06-29T14:55:12.620-04:00",
+ "sentBy": "BVASYELLOW",
+ "sentTo": "POA/Representative Email",
+ },
+ Object {
+ "emailAddress": "Bob.Smith@test.com",
+ "emailType": "confirmation",
+ "sentAt": "2020-06-29T14:55:12.501-04:00",
+ "sentBy": "BVASYELLOW",
+ "sentTo": "Appellant Email",
+ },
+ ],
+ "evidenceWindowWaived": false,
+ "externalId": "9bb8e27e-9b89-48cd-8b0b-2e75cfa5627a",
+ "hearingDayId": 4,
+ "id": 4,
+ "isVirtual": true,
+ "judge": Object {
+ "createdAt": "2020-06-25T11:00:43.257-04:00",
+ "cssId": "BVAAABSHIRE",
+ "displayName": "BVAAABSHIRE (VACO)",
+ "efolderDocumentsFetchedAt": null,
+ "email": null,
+ "fullName": "Aaron Judge_HearingsAndCases Abshire",
+ "id": 3,
+ "lastLoginAt": null,
+ "roles": Object {},
+ "selectedRegionalOffice": null,
+ "stationId": "101",
+ "status": "active",
+ "statusUpdatedAt": null,
+ "updatedAt": "2020-06-25T11:00:43.257-04:00",
+ },
+ "judgeId": "3",
+ "location": null,
+ "militaryService": "",
+ "notes": null,
+ "paperCase": false,
+ "poaName": "AMERICAN LEGION",
+ "prepped": null,
+ "readableLocation": "Washington, DC",
+ "readableRequestType": "Central",
+ "regionalOfficeKey": "C",
+ "regionalOfficeName": "Central",
+ "regionalOfficeTimezone": "America/New_York",
+ "representative": "Clarence Darrow",
+ "representativeEmailAddress": "tom.brady@caseflow.gov",
+ "representativeName": "PARALYZED VETERANS OF AMERICA, INC.",
+ "representativeTz": "America/Denver",
+ "room": "2",
+ "scheduledFor": "2020-07-06T06:00:00.000-04:00",
+ "scheduledForIsPast": false,
+ "scheduledTime": "2000-01-01T03:30:00.000-05:00",
+ "scheduledTimeString": "03:30",
+ "summary": null,
+ "transcriptRequested": null,
+ "transcriptSentDate": undefined,
+ "transcription": Object {
+ "expectedReturnDate": undefined,
+ "problemNoticeSentDate": undefined,
+ "sentToTranscriberDate": undefined,
+ "uploadedToVbmsDate": undefined,
+ },
+ "uuid": "9bb8e27e-9b89-48cd-8b0b-2e75cfa5627a",
+ "veteranAge": 85,
+ "veteranEmailAddress": "Bob.Smith@test.com",
+ "veteranFileNumber": "500000003",
+ "veteranFirstName": "Bob",
+ "veteranGender": "M",
+ "veteranLastName": "Smith",
+ "virtualHearing": Object {
+ "aliasWithHost": "BVA0000009@care.evn.va.gov",
+ "appellantEmail": "Bob.Smith@test.com",
+ "appellantTz": "America/Denver",
+ "clientHost": "care.evn.va.gov",
+ "guestLink": "https://care.evn.va.gov/bva-app/?join=1&media=&escalate=1&conference=BVA0000009@care.evn.va.gov&pin=2684353125#&role=guest",
+ "guestPin": "2684353125#",
+ "hostLink": "https://care.evn.va.gov/bva-app/?join=1&media=&escalate=1&conference=BVA0000009@care.evn.va.gov&pin=8600030#&role=host",
+ "hostPin": "8600030#",
+ "jobCompleted": true,
+ "representativeEmail": "tom.brady@caseflow.gov",
+ "representativeTz": "America/Los_Angeles",
+ "requestCancelled": false,
+ "status": "active",
+ },
+ "wasVirtual": false,
+ "witness": null,
+ "worksheetIssues": Object {},
+ }
+ }
hearingDayId={4}
isVirtual={true}
readableLocation="Washington, DC"
@@ -129839,6 +129959,258 @@ exports[`Details Displays VirtualHearing details when there is a virtual hearing
+
+
+
+
-
+
+ Abellona Valtas
+
+ >
+
+ 9999 MISSION ST
+SAN FRANCISCO, CA 94103
+
+
Issues
+ 0
+ 0
}
+ text={
+
+ }
>
@@ -681,7 +1143,11 @@ exports[`ScheduleVeteran Auto-selects virtual if a virtual hearing was requested
spacing={15}
text={
-
+
+ 200805-541
}
unformatted={true}
@@ -694,10 +1160,326 @@ exports[`ScheduleVeteran Auto-selects virtual if a virtual hearing was requested
Docket Number
-
+
+
+
+ L
+
+
+
+
+
+ Legacy
+
+
+
+
+
+ 200805-541
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
expect(document.getElementsByClassName('usa-width-three-fourths')).toHaveLength(1);
expect(document.getElementsByClassName('time-slot-card-time')).toHaveLength(1);
expect(document.getElementsByClassName('time-slot-card-label')).toHaveLength(2);
- expect(card.getAllByText('·')).toHaveLength(5);
+ expect(card.getAllByText('·')).toHaveLength(6);
// Row 1
expect(document.getElementsByClassName('time-slot-card-label')[1]).toHaveTextContent(headerLabel);
diff --git a/client/test/app/hearings/components/assignHearings/__snapshots__/AssignHearingsList.test.js.snap b/client/test/app/hearings/components/assignHearings/__snapshots__/AssignHearingsList.test.js.snap
index 96fe2024c5f..942fb4b7961 100644
--- a/client/test/app/hearings/components/assignHearings/__snapshots__/AssignHearingsList.test.js.snap
+++ b/client/test/app/hearings/components/assignHearings/__snapshots__/AssignHearingsList.test.js.snap
@@ -237,6 +237,12 @@ Object {
·
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
+ ·
+
+
+
9999 MISSION ST
@@ -800,6 +803,9 @@ exports[`DailyDocketRow renders correctly for non virtual, Transcriber 1`] = `
+
9999 MISSION ST
@@ -1383,6 +1389,9 @@ exports[`DailyDocketRow renders correctly for non virtual, VSO 1`] = `
+
9999 MISSION ST
@@ -1939,6 +1948,9 @@ exports[`DailyDocketRow renders correctly for non virtual, attorney 1`] = `
+
9999 MISSION ST
@@ -2525,6 +2537,9 @@ exports[`DailyDocketRow renders correctly for non virtual, hearing cooridnator 1
+
9999 MISSION ST
@@ -3139,6 +3154,9 @@ exports[`DailyDocketRow renders correctly for non virtual, judge 1`] = `
+
9999 MISSION ST
@@ -3725,6 +3743,9 @@ exports[`DailyDocketRow renders correctly for virtual 1`] = `
+
9999 MISSION ST
diff --git a/client/test/app/hearings/components/scheduleHearing/__snapshots__/AppealInformation.test.js.snap b/client/test/app/hearings/components/scheduleHearing/__snapshots__/AppealInformation.test.js.snap
index ac0924bf6a2..406864035f9 100644
--- a/client/test/app/hearings/components/scheduleHearing/__snapshots__/AppealInformation.test.js.snap
+++ b/client/test/app/hearings/components/scheduleHearing/__snapshots__/AppealInformation.test.js.snap
@@ -163,6 +163,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -336,6 +339,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -557,6 +563,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -731,6 +740,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -962,6 +974,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -1126,6 +1141,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -1363,6 +1381,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -1543,6 +1564,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -1774,6 +1798,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -1948,6 +1975,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -2169,6 +2199,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
@@ -2333,6 +2366,9 @@ SAN FRANCISCO, CA 94103
200805-541
+
diff --git a/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotButton.test.js.snap b/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotButton.test.js.snap
index ab2be5df3f0..58ac9bbc746 100644
--- a/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotButton.test.js.snap
+++ b/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotButton.test.js.snap
@@ -123,6 +123,12 @@ Object {
+
+ ·
+
+
@@ -264,6 +270,12 @@ Object {
+
+ ·
+
+
diff --git a/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotDetail.test.js.snap b/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotDetail.test.js.snap
index 61ea9e11c1d..7f9bcdc0b2b 100644
--- a/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotDetail.test.js.snap
+++ b/client/test/app/hearings/components/scheduleHearing/__snapshots__/TimeSlotDetail.test.js.snap
@@ -121,6 +121,12 @@ Object {
+
+ ·
+
+
@@ -348,6 +354,12 @@ Object {
+
+ ·
+
+
@@ -639,6 +651,12 @@ Object {
·
+
+ ·
+
+
+
+ ·
+
+
{
const formType = 'higher_level_review';
const intakeData = sample1.intakeData;
+ const featureToggles = { mstIdentification: true };
describe('renders', () => {
it('renders button text', () => {
- const wrapper = mount( null} />);
+ const wrapper = mount( null} />);
const cancelBtn = wrapper.find('.cf-modal-controls .close-modal');
const skipBtn = wrapper.find('.cf-modal-controls .no-matching-issues');
@@ -23,7 +25,7 @@ describe('AddIssuesModal', () => {
wrapper.setProps({
cancelText: 'cancel',
skipText: 'skip',
- submitText: 'submit'
+ submitText: 'submit',
});
expect(cancelBtn.text()).toBe('cancel');
@@ -32,7 +34,7 @@ describe('AddIssuesModal', () => {
});
it('skip button only with onSkip prop', () => {
- const wrapper = mount();
+ const wrapper = mount();
expect(wrapper.find('.cf-modal-controls .no-matching-issues').exists()).toBe(false);
@@ -41,7 +43,7 @@ describe('AddIssuesModal', () => {
});
it('disables button when nothing selected', () => {
- const wrapper = mount();
+ const wrapper = mount();
const submitBtn = wrapper.find('.cf-modal-controls .add-issue');
diff --git a/client/test/app/intake/util/__snapshots__/issues.test.js.snap b/client/test/app/intake/util/__snapshots__/issues.test.js.snap
index ef7b4938f14..808c450cfb9 100644
--- a/client/test/app/intake/util/__snapshots__/issues.test.js.snap
+++ b/client/test/app/intake/util/__snapshots__/issues.test.js.snap
@@ -18,7 +18,11 @@ Array [
"id": null,
"index": 0,
"ineligibleReason": null,
+ "mstChecked": undefined,
+ "mst_status_update_reason_notes": undefined,
"notes": null,
+ "pactChecked": undefined,
+ "pact_status_update_reason_notes": undefined,
"rampClaimId": null,
"ratingDecisionReferenceId": null,
"ratingIssueReferenceId": "def456",
@@ -32,6 +36,8 @@ Array [
"vacolsId": null,
"vacolsIssue": null,
"vacolsSequenceId": null,
+ "vbmsMstChecked": undefined,
+ "vbmsPactChecked": undefined,
"withdrawalDate": null,
"withdrawalPending": undefined,
},
@@ -55,6 +61,10 @@ Array [
"index": 1,
"ineligibleReason": null,
"isPreDocketNeeded": undefined,
+ "mstChecked": undefined,
+ "mst_status_update_reason_notes": undefined,
+ "pactChecked": undefined,
+ "pact_status_update_reason_notes": undefined,
"text": "Military Retired Pay - nonrating description",
"timely": undefined,
"untimelyExemption": undefined,
@@ -63,6 +73,8 @@ Array [
"vacolsId": null,
"vacolsIssue": null,
"vacolsSequenceId": null,
+ "vbmsMstChecked": undefined,
+ "vbmsPactChecked": undefined,
"withdrawalDate": null,
"withdrawalPending": undefined,
},
@@ -87,7 +99,11 @@ Array [
"id": "2",
"index": 0,
"ineligibleReason": null,
+ "mstChecked": undefined,
+ "mst_status_update_reason_notes": undefined,
"notes": null,
+ "pactChecked": undefined,
+ "pact_status_update_reason_notes": undefined,
"rampClaimId": null,
"ratingDecisionReferenceId": null,
"ratingIssueReferenceId": "def456",
@@ -101,6 +117,8 @@ Array [
"vacolsId": null,
"vacolsIssue": null,
"vacolsSequenceId": null,
+ "vbmsMstChecked": undefined,
+ "vbmsPactChecked": undefined,
"withdrawalDate": null,
"withdrawalPending": undefined,
},
@@ -124,6 +142,10 @@ Array [
"index": 1,
"ineligibleReason": null,
"isPreDocketNeeded": undefined,
+ "mstChecked": undefined,
+ "mst_status_update_reason_notes": undefined,
+ "pactChecked": undefined,
+ "pact_status_update_reason_notes": undefined,
"text": "Military Retired Pay - nonrating description",
"timely": undefined,
"untimelyExemption": undefined,
@@ -132,6 +154,8 @@ Array [
"vacolsId": null,
"vacolsIssue": null,
"vacolsSequenceId": null,
+ "vbmsMstChecked": undefined,
+ "vbmsPactChecked": undefined,
"withdrawalDate": null,
"withdrawalPending": undefined,
},
diff --git a/client/test/app/queue/CaseDetailsView.test.js b/client/test/app/queue/CaseDetailsView.test.js
index d802a23fc66..08f1303ee48 100644
--- a/client/test/app/queue/CaseDetailsView.test.js
+++ b/client/test/app/queue/CaseDetailsView.test.js
@@ -1,6 +1,6 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
-import CaseDetailsView from "../../../app/queue/CaseDetailsView";
+import CaseDetailsView from '../../../app/queue/CaseDetailsView';
import { queueWrapper as Wrapper } from 'test/data/stores/queueStore';
import { amaAppeal, legacyAppeal, powerOfAttorney } from '../../data/appeals';
import COPY from '../../../COPY';
@@ -13,6 +13,16 @@ const defaultProps = {
};
const renderCaseDetailsView = (hasNotifications, appealData) => {
+ const decisionIssues = [
+ { request_issue_ids: [1, 2, 3] }
+ ];
+
+ const requestIssue = { mst_status: true, pact_status: true, id: 1 };
+
+ const issues = [
+ requestIssue
+ ];
+
const stagedAppealData = {
[appealData.id]: {
...appealData,
@@ -20,7 +30,9 @@ const renderCaseDetailsView = (hasNotifications, appealData) => {
hasNotifications,
isPaperCase: true,
powerOfAttorney,
- appellantType: 'VeteranClaimant'
+ appellantType: 'VeteranClaimant',
+ decisionIssues,
+ issues
}
};
@@ -86,18 +98,18 @@ describe('NotificationsLink', () => {
describe('When there are\'not notifications', () => {
// ama without notifications
it('link does not appears with ama appeal', () => {
- const {container} = renderCaseDetailsView(false, amaAppeal);
+ const { container } = renderCaseDetailsView(false, amaAppeal);
const link = container.querySelector('#notification-link');
- expect(link).toBeNull()
+ expect(link).toBeNull();
// legacy without notifications
});
it('link does not appears with legacy appeal', () => {
- const {container} = renderCaseDetailsView(false, legacyAppeal);
+ const { container } = renderCaseDetailsView(false, legacyAppeal);
const link = container.querySelector('#notification-link');
expect(link).toBeNull();
});
});
-});
\ No newline at end of file
+});
diff --git a/db/migrate/20230412154615_add_mst_pact_to_request_issues.rb b/db/migrate/20230412154615_add_mst_pact_to_request_issues.rb
new file mode 100644
index 00000000000..dd273f0b176
--- /dev/null
+++ b/db/migrate/20230412154615_add_mst_pact_to_request_issues.rb
@@ -0,0 +1,6 @@
+class AddMstPactToRequestIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :request_issues, :mst_status, :boolean, default: false, comment: "Indicates if issue is related to Military Sexual Trauma (MST)"
+ add_column :request_issues, :pact_status, :boolean, default: false ,comment: "Indicates if issue is related to Promise to Address Comprehensive Toxics (PACT) Act"
+ end
+end
diff --git a/db/migrate/20230508182827_add_mst_and_pact_edited_request_issue_ids_to_request_issues_updates.rb b/db/migrate/20230508182827_add_mst_and_pact_edited_request_issue_ids_to_request_issues_updates.rb
new file mode 100644
index 00000000000..7126e84e719
--- /dev/null
+++ b/db/migrate/20230508182827_add_mst_and_pact_edited_request_issue_ids_to_request_issues_updates.rb
@@ -0,0 +1,6 @@
+class AddMstAndPactEditedRequestIssueIdsToRequestIssuesUpdates < Caseflow::Migration
+ def change
+ add_column :request_issues_updates, :mst_edited_request_issue_ids, :integer, comment: "An array of the request issue IDs that were updated to be associated with MST in request issues update", array: true
+ add_column :request_issues_updates, :pact_edited_request_issue_ids, :integer, comment: "An array of the request issue IDs that were updated to be associated with PACT in request issues update", array: true
+ end
+end
diff --git a/db/migrate/20230508190230_add_mst_pact_reasons_to_request_issues.rb b/db/migrate/20230508190230_add_mst_pact_reasons_to_request_issues.rb
new file mode 100644
index 00000000000..266dba76e83
--- /dev/null
+++ b/db/migrate/20230508190230_add_mst_pact_reasons_to_request_issues.rb
@@ -0,0 +1,6 @@
+class AddMstPactReasonsToRequestIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :request_issues, :mst_status_update_reason_notes, :text, comment: "The reason for why Request Issue is Military Sexual Trauma (MST)"
+ add_column :request_issues, :pact_status_update_reason_notes, :text, comment: "The reason for why Request Issue is Promise to Address Comprehensive Toxics (PACT) Act"
+ end
+end
diff --git a/db/migrate/20230531163349_add_vbms_mst_and_pact_to_request_issues.rb b/db/migrate/20230531163349_add_vbms_mst_and_pact_to_request_issues.rb
new file mode 100644
index 00000000000..7074dd16de8
--- /dev/null
+++ b/db/migrate/20230531163349_add_vbms_mst_and_pact_to_request_issues.rb
@@ -0,0 +1,6 @@
+class AddVbmsMstAndPactToRequestIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :request_issues, :vbms_mst_status, :boolean, default: false, comment: "Indicates if issue is related to Military Sexual Trauma (MST) and was imported from VBMS"
+ add_column :request_issues, :vbms_pact_status, :boolean, default: false ,comment: "Indicates if issue is related to Promise to Address Comprehensive Toxics (PACT) Act and was imported from VBMS"
+ end
+end
diff --git a/db/migrate/20230609124110_create_special_issue_changes.rb b/db/migrate/20230609124110_create_special_issue_changes.rb
new file mode 100644
index 00000000000..4ad762e058e
--- /dev/null
+++ b/db/migrate/20230609124110_create_special_issue_changes.rb
@@ -0,0 +1,21 @@
+class CreateSpecialIssueChanges < ActiveRecord::Migration[5.2]
+ def change
+ create_table :special_issue_changes do |t|
+ t.bigint :issue_id, null: false, comment: "ID of the issue that was changed"
+ t.bigint :appeal_id, null: false, comment: "AMA or Legacy Appeal ID that the issue is tied to"
+ t.string :appeal_type, null: false, comment: "Appeal Type (Appeal or LegacyAppeal)"
+ t.bigint :task_id, null: false, comment: "Task ID of the IssueUpdateTask or EstablishmentTask used to log this issue in the case timeline"
+ t.datetime "created_at", null: false, comment: "Date the special issue change was made"
+ t.bigint :created_by_id, null: false, comment: "User ID of the user that made the special issue change"
+ t.string :created_by_css_id, null: false, comment: "CSS ID of the user that made the special issue change"
+ t.boolean :original_mst_status, null: false, comment: "Original MST special issue status of the issue"
+ t.boolean :original_pact_status, null: false, comment: "Original PACT special issue status of the issue"
+ t.boolean :updated_mst_status, null: false, comment: "Updated MST special issue status of the issue"
+ t.boolean :updated_pact_status, null: false, comment: "Updated PACT special issue status of the issue"
+ t.boolean :mst_from_vbms, default: false, comment: "Indication that the MST status originally came from VBMS on intake"
+ t.boolean :pact_from_vbms, default: false, commment: "Indication that the PACT status originally came from VBMS on intake"
+ t.string :mst_reason_for_change, comment: "Reason for changing the MST status on an issue"
+ t.string :pact_reason_for_change, comment: "Reason for changing the PACT status on an issue"
+ end
+ end
+end
diff --git a/db/migrate/20230609153757_add_change_category_to_special_issue_changes.rb b/db/migrate/20230609153757_add_change_category_to_special_issue_changes.rb
new file mode 100644
index 00000000000..9b74e88bc45
--- /dev/null
+++ b/db/migrate/20230609153757_add_change_category_to_special_issue_changes.rb
@@ -0,0 +1,5 @@
+class AddChangeCategoryToSpecialIssueChanges < ActiveRecord::Migration[5.2]
+ def change
+ add_column :special_issue_changes, :change_category, :string, null: false, comment: "Type of change that occured to the issue (Established Issue, Added Issue, Edited Issue, Removed Issue)"
+ end
+end
diff --git a/db/migrate/20230609161731_update_special_issue_changes.rb b/db/migrate/20230609161731_update_special_issue_changes.rb
new file mode 100644
index 00000000000..8909033ba53
--- /dev/null
+++ b/db/migrate/20230609161731_update_special_issue_changes.rb
@@ -0,0 +1,19 @@
+class UpdateSpecialIssueChanges < ActiveRecord::Migration[5.2]
+ def up
+ safety_assured do
+ change_column :special_issue_changes, :updated_mst_status, :boolean, null: true, default: nil
+ change_column :special_issue_changes, :updated_pact_status, :boolean, null: true, default: nil
+ change_column :special_issue_changes, :mst_from_vbms, :boolean, null: true, default: nil
+ change_column :special_issue_changes, :pact_from_vbms, :boolean, null: true, default: nil
+ end
+ end
+
+ def down
+ safety_assured do
+ change_column :special_issue_changes, :updated_mst_status, :boolean, null: false
+ change_column :special_issue_changes, :updated_pact_status, :boolean, null: false
+ change_column :special_issue_changes, :mst_from_vbms, :boolean, null: false
+ change_column :special_issue_changes, :pact_from_vbms, :boolean, null: false
+ end
+ end
+end
diff --git a/db/migrate/20230623132146_add_mst_pact_to_decision_issues.rb b/db/migrate/20230623132146_add_mst_pact_to_decision_issues.rb
new file mode 100644
index 00000000000..5c93e95b56e
--- /dev/null
+++ b/db/migrate/20230623132146_add_mst_pact_to_decision_issues.rb
@@ -0,0 +1,6 @@
+class AddMstPactToDecisionIssues < Caseflow::Migration
+ def change
+ add_column :decision_issues, :mst_status, :boolean, default: false, comment: "Indicates if decision issue is related to Military Sexual Trauma (MST)"
+ add_column :decision_issues, :pact_status, :boolean, default: false, comment: "Indicates if decision issue is related to Promise to Address Comprehensive Toxics (PACT) Act"
+ end
+end
diff --git a/db/migrate/20230629114954_add_decision_id_to_special_issue_changes.rb b/db/migrate/20230629114954_add_decision_id_to_special_issue_changes.rb
new file mode 100644
index 00000000000..c55736ce6be
--- /dev/null
+++ b/db/migrate/20230629114954_add_decision_id_to_special_issue_changes.rb
@@ -0,0 +1,5 @@
+class AddDecisionIdToSpecialIssueChanges < ActiveRecord::Migration[5.2]
+ def change
+ add_column :special_issue_changes, :decision_issue_id, :bigint, null: true, comment: "ID of the decision issue that had a special issue change from its corresponding request issue"
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 210aa53f54e..7bb45da760f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -623,6 +623,8 @@
t.string "diagnostic_code", comment: "If a decision resulted in a rating, this is the rating issue's diagnostic code."
t.string "disposition", comment: "The disposition for a decision issue. Dispositions made in Caseflow and dispositions made in VBMS can have different values."
t.date "end_product_last_action_date", comment: "After an end product gets synced with a status of CLR (cleared), the end product's last_action_date is saved on any decision issues that are created as a result. This is used as a proxy for decision date for non-rating issues that are processed in VBMS because they don't have a rating profile date, and the exact decision date is not available."
+ t.boolean "mst_status", default: false, comment: "Indicates if decision issue is related to Military Sexual Trauma (MST)"
+ t.boolean "pact_status", default: false, comment: "Indicates if decision issue is related to Promise to Address Comprehensive Toxics (PACT) Act"
t.string "participant_id", null: false, comment: "The Veteran's participant id."
t.string "percent_number", comment: "percent_number from RatingIssue (prcntNo from Rating Profile)"
t.string "rating_issue_reference_id", comment: "Identifies the specific issue on the rating that resulted from the decision issue (a rating issue can be connected to multiple contentions)."
@@ -1545,9 +1547,13 @@
t.string "ineligible_reason", comment: "The reason for a Request Issue being ineligible. If a Request Issue has an ineligible_reason, it is still captured, but it will not get a contention in VBMS or a decision."
t.boolean "is_predocket_needed", comment: "Indicates whether or not an issue has been selected to go to the pre-docket queue opposed to normal docketing."
t.boolean "is_unidentified", comment: "Indicates whether a Request Issue is unidentified, meaning it wasn't found in the list of contestable issues, and is not a new nonrating issue. Contentions for unidentified issues are created on a rating End Product if processed in VBMS but without the issue description, and someone is required to edit it in Caseflow before proceeding with the decision."
+ t.boolean "mst_status", default: false, comment: "Indicates if issue is related to Military Sexual Trauma (MST)"
+ t.text "mst_status_update_reason_notes", comment: "The reason for why Request Issue is Military Sexual Trauma (MST)"
t.string "nonrating_issue_category", comment: "The category selected for nonrating request issues. These vary by business line."
t.string "nonrating_issue_description", comment: "The user entered description if the issue is a nonrating issue"
t.text "notes", comment: "Notes added by the Claims Assistant when adding request issues. This may be used to capture handwritten notes on the form, or other comments the CA wants to capture."
+ t.boolean "pact_status", default: false, comment: "Indicates if issue is related to Promise to Address Comprehensive Toxics (PACT) Act"
+ t.text "pact_status_update_reason_notes", comment: "The reason for why Request Issue is Promise to Address Comprehensive Toxics (PACT) Act"
t.string "ramp_claim_id", comment: "If a rating issue was created as a result of an issue intaken for a RAMP Review, it will be connected to the former RAMP issue by its End Product's claim ID."
t.datetime "rating_issue_associated_at", comment: "Timestamp when a contention and its contested rating issue are associated in VBMS."
t.string "split_issue_status", comment: "If a request issue is part of a split, on_hold status applies to the original request issues while active are request issues on splitted appeals"
@@ -1558,6 +1564,8 @@
t.datetime "updated_at", comment: "Automatic timestamp whenever the record changes."
t.string "vacols_id", comment: "The vacols_id of the legacy appeal that had an issue found to match the request issue."
t.integer "vacols_sequence_id", comment: "The vacols_sequence_id, for the specific issue on the legacy appeal which the Claims Assistant determined to match the request issue on the Decision Review. A combination of the vacols_id (for the legacy appeal), and vacols_sequence_id (for which issue on the legacy appeal), is required to identify the issue being opted-in."
+ t.boolean "vbms_mst_status", default: false, comment: "Indicates if issue is related to Military Sexual Trauma (MST) and was imported from VBMS"
+ t.boolean "vbms_pact_status", default: false, comment: "Indicates if issue is related to Promise to Address Comprehensive Toxics (PACT) Act and was imported from VBMS"
t.boolean "verified_unidentified_issue", comment: "A verified unidentified issue allows an issue whose rating data is missing to be intaken as a regular rating issue. In order to be marked as verified, a VSR needs to confirm that they were able to find the record of the decision for the issue."
t.string "veteran_participant_id", comment: "The veteran participant ID. This should be unique in upstream systems and used in the future to reconcile duplicates."
t.index ["closed_at"], name: "index_request_issues_on_closed_at"
@@ -1583,6 +1591,8 @@
t.integer "edited_request_issue_ids", comment: "An array of the request issue IDs that were edited during this request issues update", array: true
t.string "error", comment: "The error message if the last attempt at processing the request issues update was not successful."
t.datetime "last_submitted_at", comment: "Timestamp for when the processing for the request issues update was last submitted. Used to determine how long to continue retrying the processing job. Can be reset to allow for additional retries."
+ t.integer "mst_edited_request_issue_ids", comment: "An array of the request issue IDs that were updated to be associated with MST in request issues update", array: true
+ t.integer "pact_edited_request_issue_ids", comment: "An array of the request issue IDs that were updated to be associated with PACT in request issues update", array: true
t.datetime "processed_at", comment: "Timestamp for when the request issue update successfully completed processing."
t.bigint "review_id", null: false, comment: "The ID of the decision review edited."
t.string "review_type", null: false, comment: "The type of the decision review edited."
@@ -1631,6 +1641,26 @@
t.index ["sent_by_id"], name: "index_sent_hearing_email_events_on_sent_by_id"
end
+ create_table "special_issue_changes", force: :cascade do |t|
+ t.bigint "appeal_id", null: false, comment: "AMA or Legacy Appeal ID that the issue is tied to"
+ t.string "appeal_type", null: false, comment: "Appeal Type (Appeal or LegacyAppeal)"
+ t.string "change_category", null: false, comment: "Type of change that occured to the issue (Established Issue, Added Issue, Edited Issue, Removed Issue)"
+ t.datetime "created_at", null: false, comment: "Date the special issue change was made"
+ t.string "created_by_css_id", null: false, comment: "CSS ID of the user that made the special issue change"
+ t.bigint "created_by_id", null: false, comment: "User ID of the user that made the special issue change"
+ t.bigint "decision_issue_id", comment: "ID of the decision issue that had a special issue change from its corresponding request issue"
+ t.bigint "issue_id", null: false, comment: "ID of the issue that was changed"
+ t.boolean "mst_from_vbms", comment: "Indication that the MST status originally came from VBMS on intake"
+ t.string "mst_reason_for_change", comment: "Reason for changing the MST status on an issue"
+ t.boolean "original_mst_status", null: false, comment: "Original MST special issue status of the issue"
+ t.boolean "original_pact_status", null: false, comment: "Original PACT special issue status of the issue"
+ t.boolean "pact_from_vbms"
+ t.string "pact_reason_for_change", comment: "Reason for changing the PACT status on an issue"
+ t.bigint "task_id", null: false, comment: "Task ID of the IssueUpdateTask or EstablishmentTask used to log this issue in the case timeline"
+ t.boolean "updated_mst_status", comment: "Updated MST special issue status of the issue"
+ t.boolean "updated_pact_status", comment: "Updated PACT special issue status of the issue"
+ end
+
create_table "special_issue_lists", comment: "Associates special issues to an AMA or legacy appeal for Caseflow Queue. Caseflow Dispatch uses special issues stored in legacy_appeals. They are intentionally disconnected.", force: :cascade do |t|
t.bigint "appeal_id", comment: "The ID of the appeal associated with this record"
t.string "appeal_type", comment: "The type of appeal associated with this record"
diff --git a/db/seeds.rb b/db/seeds.rb
index e2eefd39a92..d591aff2cbd 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -11,7 +11,7 @@ class SeedDB
def clean_db
DatabaseCleaner.clean_with(:truncation)
cm = CacheManager.new
- CacheManager::BUCKETS.keys.each { |bucket| cm.clear(bucket) }
+ CacheManager::BUCKETS.each_key { |bucket| cm.clear(bucket) }
Fakes::EndProductStore.new.clear!
Fakes::RatingStore.new.clear!
Fakes::VeteranStore.new.clear!
@@ -33,6 +33,7 @@ def call_and_log_seed_step(step)
Rails.logger.debug(msg)
end
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def seed
RequestStore[:current_user]=User.system_user
call_and_log_seed_step :clean_db
@@ -64,10 +65,13 @@ def seed
call_and_log_seed_step Seeds::RemandedAmaAppeals
call_and_log_seed_step Seeds::RemandedLegacyAppeals
call_and_log_seed_step Seeds::VhaChangeHistory
+ call_and_log_seed_step Seeds::BgsServiceRecordMaker
+ call_and_log_seed_step Seeds::MstPactLegacyCaseAppeals
# Always run this as last one
call_and_log_seed_step Seeds::StaticTestCaseData
call_and_log_seed_step Seeds::StaticDispatchedAppealsTestData
end
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
end
SeedDB.new.seed
diff --git a/db/seeds/bgs_service_record_maker.rb b/db/seeds/bgs_service_record_maker.rb
new file mode 100644
index 00000000000..197105f9c0b
--- /dev/null
+++ b/db/seeds/bgs_service_record_maker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Seeds
+ class BgsServiceRecordMaker < Base
+ # :reek:UtilityFunction
+ def seed!
+ # run the BGSServiceMaker file located in lib/fakes
+ # seed data comes from a CSV file called 'bgs_setup.csv' located at local/vacols/bgs_setup.csv
+ Fakes::BGSServiceRecordMaker.new.call
+ end
+ end
+end
diff --git a/db/seeds/mst_pact_legacy_case_appeals.rb b/db/seeds/mst_pact_legacy_case_appeals.rb
new file mode 100644
index 00000000000..081a1dc4738
--- /dev/null
+++ b/db/seeds/mst_pact_legacy_case_appeals.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require "rake"
+
+module Seeds
+ class MstPactLegacyCaseAppeals < Base
+
+ USER_CSS_IDS = [
+ 'BVASRITCHIE',
+ 'BVASCASPER1',
+ 'BVAEBECKER',
+ 'BVAKKEELING'
+ ]
+
+ # :reek:UtilityFunction
+ def seed!
+ generate_legacy_appeals
+ end
+
+ # confirms the user CSS IDS are for valid users or skip
+ # :reek:UtilityFunction
+ def generate_legacy_appeals
+ USER_CSS_IDS.each do |id|
+ next unless User.find_by_css_id(id)
+
+ Rake::Task['db:generate_legacy_appeals'].invoke(true, id)
+ Rake::Task['db:generate_legacy_appeals'].reenable
+ end
+ end
+
+ end
+end
diff --git a/db/seeds/users.rb b/db/seeds/users.rb
index 6589e2f0089..738ab3e2f9f 100644
--- a/db/seeds/users.rb
+++ b/db/seeds/users.rb
@@ -396,17 +396,22 @@ def create_clerk_of_the_board_users
station_id: 101,
css_id: "COB_USER",
full_name: "Clark ClerkOfTheBoardUser Bard",
- roles: ["Hearing Prep"]
+ roles: ["Hearing Prep", "Mail Intake"]
)
ClerkOfTheBoard.singleton.add_user(atty)
- judge = create(:user, full_name: "Judith COTB Judge", css_id: "BVACOTBJUDGE", roles: ["Hearing Prep"])
+ judge = create(:user, full_name: "Judith COTB Judge", css_id: "BVACOTBJUDGE", roles: ["Hearing Prep", "Mail Intake"])
create(:staff, :judge_role, sdomainid: judge.css_id)
ClerkOfTheBoard.singleton.add_user(judge)
- admin = create(:user, full_name: "Ty ClerkOfTheBoardAdmin Cobb", css_id: "BVATCOBB", roles: ["Hearing Prep"])
+ admin = create(:user, full_name: "Ty ClerkOfTheBoardAdmin Cobb", css_id: "BVATCOBB", roles: ["Hearing Prep", "Mail Intake"])
ClerkOfTheBoard.singleton.add_user(admin)
OrganizationsUser.make_user_admin(admin, ClerkOfTheBoard.singleton)
+
+ # added to Bva Intake so they can intake
+ BvaIntake.singleton.add_user(atty)
+ BvaIntake.singleton.add_user(judge)
+ BvaIntake.singleton.add_user(admin)
end
def create_case_search_only_user
diff --git a/lib/fakes/bgs_service.rb b/lib/fakes/bgs_service.rb
index 4ffcd9db5bb..38fe29a845e 100644
--- a/lib/fakes/bgs_service.rb
+++ b/lib/fakes/bgs_service.rb
@@ -140,6 +140,287 @@ def select_end_products(file_number, code: nil, modifier: nil, payee_code: nil,
end
end
+ # returns example payload from VBMS dev test data (vet file number 011899903)
+ # for testing with vet file number 992190636 on local environment.
+ # rubocop:disable Metrics/MethodLength
+ def find_contentions_by_participant_id(participant_id)
+ [
+ {
+ call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:12:55 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS - CEST",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "BenefitClaim",
+ row_cnt: "4",
+ row_id: "32117",
+ bnft_clm_tc: "020NADIDESNO",
+ bnft_clm_tn: "IDES Non-AD Non-Original",
+ claim_rcvd_dt: "Fri, 01 May 2020 00:00:00 -0500",
+ claim_suspns_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ clm_id: "600401998",
+ clm_suspns_cd: "055",
+ contentions: { call_id: "17",
+ jrn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS-cp_auth_evnt_pkg.do_create",
+ jrn_stt_tc: "U",
+ jrn_user_id: "CF_AUTH",
+ name: "Contention",
+ parent_id: "32117",
+ parent_name: "CD_CLM",
+ row_cnt: "4",
+ row_id: "4",
+ begin_dt: "Sun, 30 Apr 2023 23:00:00 -0500",
+ clm_id: "600401998",
+ clmnt_txt: "Abscess, brain",
+ clsfcn_id: "8921",
+ clsfcn_txt: "Adhesions - Gynecological",
+ cntntn_id: "7781930",
+ cntntn_status_tc: "C",
+ cntntn_type_cd: "NEW",
+ create_dt: "Mon, 08 May 2023 09:14:15 -0500",
+ med_ind: "1",
+ special_issues: { call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:14:15 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "cd_spis_pkg.do_create",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "SpecialIssue",
+ parent_id: "4",
+ parent_name: "CD_CNTNTN",
+ row_cnt: "5",
+ row_id: "8",
+ clm_id: "600401998",
+ cntntn_id: "7781930",
+ cntntn_spis_id: "302061",
+ spis_tc: "MST",
+ spis_tn: "Military Sexual Trauma (MST)" },
+ wg_aplcbl_ind: "0" },
+ lc_stt_rsn_tc: "CLOSED",
+ lc_stt_rsn_tn: "Closed",
+ lctn_id: "123725",
+ non_med_clm_desc: "IDES Non-AD Non-Original",
+ notes_ind: "1",
+ prirty: "0",
+ ptcpnt_id_clmnt: participant_id,
+ ptcpnt_id_vet: participant_id,
+ ptcpnt_id_vsr: "601225005",
+ ptcpnt_suspns_id: "601225049",
+ soj_lctn_id: "360",
+ suspns_actn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ suspns_rsn_txt: "Closed"
+ },
+ { call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:15:09 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS - CEST",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "BenefitClaim",
+ row_id: "32118",
+ bnft_clm_tc: "310IIR",
+ bnft_clm_tn: "IU issue 4140 referred",
+ claim_rcvd_dt: "Sat, 02 May 2020 00:00:00 -0500",
+ claim_suspns_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ clm_id: "600402015",
+ clm_suspns_cd: "055",
+ contentions: { call_id: "17",
+ jrn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS-cp_auth_evnt_pkg.do_create",
+ jrn_stt_tc: "U",
+ jrn_user_id: "CF_AUTH",
+ name: "Contention",
+ parent_id: "32118",
+ parent_name: "CD_CLM",
+ row_id: "5",
+ begin_dt: "Mon, 01 May 2023 23:00:00 -0500",
+ clm_id: "600402015",
+ clmnt_txt: "Allergic or vasomotor rhinitis",
+ clsfcn_id: "8920",
+ clsfcn_txt: "Adhesions - Digestive",
+ cntntn_id: "7781930",
+ cntntn_status_tc: "C",
+ cntntn_type_cd: "NEW",
+ create_dt: "Mon, 08 May 2023 09:16:18 -0500",
+ med_ind: "1",
+ special_issues: { call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:16:18 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "cd_spis_pkg.do_create",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "SpecialIssue",
+ parent_id: "5",
+ parent_name: "CD_CNTNTN",
+ row_id: "9",
+ clm_id: "600402015",
+ cntntn_id: "7781930",
+ cntntn_spis_id: "302062",
+ spis_tc: "PACTDICRE",
+ spis_tn: "PACT ACT DIC Reevaluation" },
+ wg_aplcbl_ind: "0" },
+ lc_stt_rsn_tc: "CLOSED",
+ lc_stt_rsn_tn: "Closed",
+ lctn_id: "123725",
+ non_med_clm_desc: "IU issue 4140 referred",
+ notes_ind: "1",
+ prirty: "0",
+ ptcpnt_id_clmnt: participant_id,
+ ptcpnt_id_vet: participant_id,
+ ptcpnt_id_vsr: "601225005",
+ ptcpnt_suspns_id: "601225049",
+ soj_lctn_id: "360",
+ suspns_actn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ suspns_rsn_txt: "Closed" },
+ { call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:17:59 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS - CEST",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "BenefitClaim",
+ row_id: "32119",
+ bnft_clm_tc: "290FOWC",
+ bnft_clm_tn: "Federal Office of Workers' Compensation",
+ claim_rcvd_dt: "Sun, 03 May 2020 00:00:00 -0500",
+ claim_suspns_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ clm_id: "600402023",
+ clm_suspns_cd: "055",
+ contentions: { call_id: "17",
+ jrn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS-cp_auth_evnt_pkg.do_create",
+ jrn_stt_tc: "U",
+ jrn_user_id: "CF_AUTH",
+ name: "Contention",
+ parent_id: "32119",
+ parent_name: "CD_CLM",
+ row_id: "6",
+ begin_dt: "Tue, 02 May 2023 23:00:00 -0500",
+ clm_id: "600402023",
+ clmnt_txt: "Abdominal pain, etiology unknown",
+ clsfcn_id: "8923",
+ clsfcn_txt: "Adhesions - Neurological other System",
+ cntntn_id: "7781930",
+ cntntn_status_tc: "C",
+ cntntn_type_cd: "NEW",
+ create_dt: "Mon, 08 May 2023 09:18:54 -0500",
+ med_ind: "1",
+ special_issues: [{ call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:18:54 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "cd_spis_pkg.do_create",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "SpecialIssue",
+ parent_id: "6",
+ parent_name: "CD_CNTNTN",
+ row_id: "10",
+ clm_id: "600402023",
+ cntntn_id: "7781930",
+ cntntn_spis_id: "302063",
+ spis_tc: "PACT",
+ spis_tn: "PACT" },
+ { call_id: "17",
+ jrn_dt: "Mon, 08 May 2023 09:18:54 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "cd_spis_pkg.do_create",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "SpecialIssue",
+ parent_id: "6",
+ parent_name: "CD_CNTNTN",
+ row_id: "11",
+ clm_id: "600402023",
+ cntntn_id: "7781930",
+ cntntn_spis_id: "302064",
+ spis_tc: "MST",
+ spis_tn: "Military Sexual Trauma (MST)" }],
+ wg_aplcbl_ind: "0" },
+ lc_stt_rsn_tc: "CLOSED",
+ lc_stt_rsn_tn: "Closed",
+ lctn_id: "123725",
+ non_med_clm_desc: "Federal Office of Workers' Compensation",
+ notes_ind: "1",
+ prirty: "0",
+ ptcpnt_id_clmnt: participant_id,
+ ptcpnt_id_vet: participant_id,
+ ptcpnt_id_vsr: "601225005",
+ ptcpnt_suspns_id: "601225049",
+ soj_lctn_id: "360",
+ suspns_actn_dt: "Thu, 18 May 2023 13:34:50 -0500",
+ suspns_rsn_txt: "Closed" },
+ { call_id: "17",
+ jrn_dt: "Wed, 14 Jun 2023 07:52:18 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS - CEST",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "BenefitClaim",
+ row_id: "32120",
+ bnft_clm_tc: "290RNCMNT",
+ bnft_clm_tn: "Renouncement",
+ claim_rcvd_dt: "Wed, 31 May 2023 00:00:00 -0500",
+ claim_suspns_dt: "Wed, 14 Jun 2023 11:12:03 -0500",
+ clm_id: "600413139",
+ clm_suspns_cd: "055",
+ contentions: { call_id: "17",
+ jrn_dt: "Wed, 14 Jun 2023 11:12:03 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "VBMS-cp_auth_evnt_pkg.do_create",
+ jrn_stt_tc: "U",
+ jrn_user_id: "CF_AUTH",
+ name: "Contention",
+ parent_id: "32120",
+ parent_name: "CD_CLM",
+ row_id: "7",
+ begin_dt: "Tue, 30 May 2023 23:00:00 -0500",
+ clm_id: "600413139",
+ clmnt_txt: "Adenocarcinoma, prostate",
+ clsfcn_id: "8923",
+ clsfcn_txt: "Adhesions - Neurological other System",
+ cntntn_id: "7781930",
+ cntntn_status_tc: "C",
+ cntntn_type_cd: "NEW",
+ create_dt: "Wed, 14 Jun 2023 07:54:41 -0500",
+ med_ind: "1",
+ special_issues: { call_id: "17",
+ jrn_dt: "Wed, 14 Jun 2023 07:54:41 -0500",
+ jrn_lctn_id: "316",
+ jrn_obj_id: "cd_spis_pkg.do_create",
+ jrn_stt_tc: "I",
+ jrn_user_id: "CF_SSUPER",
+ name: "SpecialIssue",
+ parent_id: "7",
+ parent_name: "CD_CNTNTN",
+ row_id: "12",
+ clm_id: "600413139",
+ cntntn_id: "7781930",
+ cntntn_spis_id: "303955",
+ spis_tc: "FDPR",
+ spis_tn: "FY16 Drill Pay Reviews" },
+ wg_aplcbl_ind: "0" },
+ lc_stt_rsn_tc: "CLOSED",
+ lc_stt_rsn_tn: "Closed",
+ lctn_id: "123725",
+ non_med_clm_desc: "Renouncement",
+ notes_ind: "1",
+ prirty: "0",
+ ptcpnt_id_clmnt: participant_id,
+ ptcpnt_id_vet: participant_id,
+ ptcpnt_id_vsr: "601225005",
+ ptcpnt_suspns_id: "601225049",
+ soj_lctn_id: "360",
+ suspns_actn_dt: "Wed, 14 Jun 2023 11:12:03 -0500",
+ suspns_rsn_txt: "Closed" }
+ ]
+ end
+ # rubocop:enable Metrics/MethodLength
+
def find_contentions_by_claim_id(claim_id)
contentions = self.class.end_product_store.inflated_bgs_contentions_for(claim_id)
diff --git a/lib/fakes/bgs_service_record_maker.rb b/lib/fakes/bgs_service_record_maker.rb
index c8f6cc671b3..82380ced6c4 100644
--- a/lib/fakes/bgs_service_record_maker.rb
+++ b/lib/fakes/bgs_service_record_maker.rb
@@ -53,17 +53,82 @@ def map_corres_to_veteran(file_number)
end
# rubocop:disable Naming/PredicateName
+ # rubocop:disable Metrics/MethodLength
def has_rating(veteran)
Generators::PromulgatedRating.build(
participant_id: veteran.participant_id,
promulgation_date: Date.new(2019, 10, 11),
profile_date: Date.new(2019, 10, 11),
issues: [
- { decision_text: "Service connection is granted for PTSD at 10 percent, effective 10/11/2019." },
+ {
+ decision_text: "Service connection is granted for PTSD at 10 percent, effective 10/11/2019.",
+ dis_sn: "1224780"
+ },
{ decision_text: "Service connection is denied for right knee condition." }
+ ],
+ disabilities: [
+ {
+ name: "Disability",
+ parent_name: "CP_RBA_PRFIL2",
+ dis_dt: "Wed, 29 Mar 2023 10:28:11 -0500",
+ dis_sn: "1224780",
+ prfl_dt: "Wed, 29 Mar 2023 10:26:14 -0500",
+ ptcpnt_id_a: veteran.participant_id,
+ disability_special_issues: [
+ {
+ spis_basis_tc: "PTSD/10",
+ spis_basis_tn: "Sexual Trauma/Assault",
+ spis_tc: "PTSD/3",
+ spis_tn: "PTSD - Personal Trauma",
+ dis_sn: "1224780"
+ },
+ {
+ spis_basis_tc: "AO/14",
+ spis_basis_tn: "Peripheral Neuropathy",
+ spis_tc: "AOOV",
+ spis_tn: "Agent Orange - outside Vietnam or unknown",
+ dis_sn: "1224780"
+ }
+ ]
+ },
+ {
+ name: "Disability",
+ parent_name: "CP_RBA_PRFIL2",
+ dis_dt: "Wed, 14 Jun 2023 10:18:08 -0500",
+ dis_sn: "1230647",
+ disability_evaluations: {
+ name: "DisabilityEvaluation",
+ dgnstc_tc: "5000",
+ dgnstc_tn: "Osteomyelitis",
+ dgnstc_txt: "Adenocarcinoma, respiratory",
+ dgnstc_txt_clob: "Adenocarcinoma, respiratory",
+ dis_dt: "Wed, 14 Jun 2023 10:18:08 -0500",
+ dis_sn: "1230647",
+ prfl_dt: "Wed, 14 Jun 2023 09:28:22 -0500"
+ },
+ disability_special_issues: [
+ {
+ name: "DisabilitySpecialIssue",
+ dis_sn: "1230647",
+ spis_basis_tc: "PTSD/12",
+ spis_basis_tn: "Sexual Harassment",
+ spis_tc: "PTSD/3",
+ spis_tn: "PTSD - Personal Trauma"
+ },
+ {
+ spis_basis_tc: "AO/14",
+ spis_basis_tn: "Peripheral Neuropathy",
+ spis_tc: "AOOV",
+ spis_tn: "Agent Orange - outside Vietnam or unknown",
+ dis_sn: "1224780"
+ }
+ ],
+ ptcpnt_id_a: veteran.participant_id
+ }
]
)
end
+ # rubocop:enable Metrics/MethodLength
def has_two_ratings(veteran)
Generators::PromulgatedRating.build(
@@ -86,7 +151,7 @@ def has_many_ratings(veteran)
in_active_review_receipt_date = Time.zone.parse("2018-04-01")
completed_review_receipt_date = in_active_review_receipt_date - 30.days
completed_review_reference_id = "cleared-review-ref-id"
- contention = Generators::Contention.build
+ contention = Generators::BgsContention.build
Generators::PromulgatedRating.build(
participant_id: veteran.participant_id
@@ -100,7 +165,7 @@ def has_many_ratings(veteran)
{ decision_text: "Right knee" },
{ decision_text: "PTSD" },
{ decision_text: "This rating is in active review", reference_id: in_active_review_reference_id },
- { decision_text: "I am on a completed Higher Level Review", contention_reference_id: contention.id }
+ { decision_text: "I am on a completed Higher Level Review", contention_reference_id: contention.reference_id }
]
)
Generators::PromulgatedRating.build(
@@ -172,7 +237,7 @@ def has_many_ratings(veteran)
benefit_type: "compensation",
end_product_establishment: cleared_epe,
contested_rating_issue_reference_id: completed_review_reference_id,
- contention_reference_id: contention.id
+ contention_reference_id: contention.reference_id
) do |reqi|
reqi.contested_rating_issue_profile_date = Time.zone.today - 100
end
@@ -224,7 +289,103 @@ def has_supplemental_claim_with_vbms_claim_id(veteran)
sc
end
+ def build_promulgated_rating(veteran, contention_reference_id)
+ Generators::PromulgatedRating.build(
+ participant_id: veteran.participant_id,
+ promulgation_date: Time.zone.today - 40,
+ profile_date: Time.zone.today - 30,
+ issues: [
+ {
+ decision_text: "Higher Level Review was denied",
+ contention_reference_id: contention_reference_id
+ }
+ ]
+ )
+ end
+
+ def generate_mst_and_pact_contentions(veteran)
+ has_hlr_with_mst_contention(veteran)
+ has_hlr_with_pact_contention(veteran)
+ end
+
# rubocop:disable Metrics/MethodLength
+ def has_hlr_with_mst_contention(veteran)
+ claim_id = "600118959"
+ mst_contention = Generators::BgsContention.build_mst_contention(
+ claim_id: claim_id
+ )
+ contention_reference_id = mst_contention.reference_id
+
+ # if contention ID is already linked to a RequestIssue, generate a new contention
+ until RequestIssue.find_by(contention_reference_id: contention_reference_id).nil?
+ mst_contention = Generators::BgsContention.build_mst_contention(
+ claim_id: claim_id
+ )
+ contention_reference_id = mst_contention.reference_id
+ end
+
+ hlr = HigherLevelReview.find_or_create_by!(
+ veteran_file_number: veteran.file_number
+ )
+ epe = EndProductEstablishment.find_or_create_by!(
+ reference_id: claim_id,
+ veteran_file_number: veteran.file_number,
+ source: hlr,
+ payee_code: EndProduct::DEFAULT_PAYEE_CODE
+ )
+ RequestIssue.find_or_create_by!(
+ decision_review: hlr,
+ benefit_type: "compensation",
+ end_product_establishment: epe,
+ contention_reference_id: contention_reference_id
+ )
+ build_promulgated_rating(veteran, contention_reference_id)
+ Generators::EndProduct.build(
+ veteran_file_number: veteran.file_number,
+ bgs_attrs: { benefit_claim_id: claim_id }
+ )
+ hlr
+ end
+
+ def has_hlr_with_pact_contention(veteran)
+ claim_id = "600118960"
+ pact = Generators::BgsContention.build_pact_contention(
+ claim_id: claim_id
+ )
+ contention_reference_id = pact.id
+ # if contention ID is already linked to a RequestIssue, generate a new contention
+ until RequestIssue.find_by(contention_reference_id: contention_reference_id).nil?
+ mst_contention = Generators::BgsContention.build_mst_contention(
+ claim_id: claim_id
+ )
+ contention_reference_id = mst_contention.reference_id
+ end
+ hlr = HigherLevelReview.find_or_create_by!(
+ veteran_file_number: veteran.file_number
+ )
+ epe = EndProductEstablishment.find_or_create_by!(
+ reference_id: claim_id,
+ veteran_file_number: veteran.file_number,
+ source: hlr,
+ payee_code: EndProduct::DEFAULT_PAYEE_CODE
+ )
+ RequestIssue.find_or_create_by!(
+ decision_review: hlr,
+ benefit_type: "compensation",
+ end_product_establishment: epe,
+ contention_reference_id: contention_reference_id
+ )
+ build_promulgated_rating(veteran, contention_reference_id)
+ Generators::EndProduct.build(
+ veteran_file_number: veteran.file_number,
+ bgs_attrs: { benefit_claim_id: claim_id }
+ )
+ Generators::BgsContention.build_pact_contention(
+ claim_id: claim_id
+ )
+ hlr
+ end
+
def has_higher_level_review_with_vbms_claim_id(veteran)
claim_id = "600118951"
contention_reference_id = veteran.file_number[0..4] + "1234"
@@ -243,17 +404,7 @@ def has_higher_level_review_with_vbms_claim_id(veteran)
end_product_establishment: epe,
contention_reference_id: contention_reference_id
)
- Generators::PromulgatedRating.build(
- participant_id: veteran.participant_id,
- promulgation_date: Time.zone.today - 40,
- profile_date: Time.zone.today - 30,
- issues: [
- {
- decision_text: "Higher Level Review was denied",
- contention_reference_id: contention_reference_id
- }
- ]
- )
+ build_promulgated_rating(veteran, contention_reference_id)
Generators::EndProduct.build(
veteran_file_number: veteran.file_number,
bgs_attrs: { benefit_claim_id: claim_id }
diff --git a/lib/generators/bgs_contention.rb b/lib/generators/bgs_contention.rb
index 988e89c26db..e04311425b8 100644
--- a/lib/generators/bgs_contention.rb
+++ b/lib/generators/bgs_contention.rb
@@ -16,10 +16,60 @@ def default_attrs
}
end
+ def default_attrs_with_mst
+ {
+ reference_id: generate_external_id,
+ text: "Generic contention with MST claim",
+ type_code: "SUP",
+ medical_indicator: "1",
+ orig_source_type_code: "APP",
+ begin_date: Time.zone.today,
+ claim_id: generate_external_id,
+ special_issues: {
+ call_id: "12345",
+ jrn_dt: 5.days.ago,
+ name: "SpecialIssue",
+ spis_tc: "MST",
+ spis_tn: "Military Sexual Trauma (MST)"
+ }
+ }
+ end
+
+ def default_attrs_with_pact
+ {
+ reference_id: generate_external_id,
+ text: "Generic contention with PACT claim",
+ type_code: "SUP",
+ medical_indicator: "1",
+ orig_source_type_code: "APP",
+ begin_date: Time.zone.today,
+ claim_id: generate_external_id,
+ special_issues: {
+ call_id: "12345",
+ jrn_dt: 5.days.ago,
+ name: "SpecialIssue",
+ spis_tc: "PACT",
+ spis_tn: "PACT"
+ }
+ }
+ end
+
def build(attrs = {})
attrs = default_attrs.merge(attrs)
OpenStruct.new(attrs).tap { |contention| Fakes::BGSService.end_product_store.create_contention(contention) }
end
+
+ def build_mst_contention(attrs = {})
+ attrs = default_attrs_with_mst.merge(attrs)
+
+ OpenStruct.new(attrs).tap { |contention| Fakes::BGSService.end_product_store.create_contention(contention) }
+ end
+
+ def build_pact_contention(attrs = {})
+ attrs = default_attrs_with_pact.merge(attrs)
+
+ OpenStruct.new(attrs).tap { |contention| Fakes::BGSService.end_product_store.create_contention(contention) }
+ end
end
end
diff --git a/lib/generators/contention.rb b/lib/generators/contention.rb
index b436ed01b99..a2a2417f59c 100644
--- a/lib/generators/contention.rb
+++ b/lib/generators/contention.rb
@@ -14,11 +14,120 @@ def default_attrs
}
end
+ def default_attrs_with_mst
+ {
+ id: generate_external_id,
+ claim_id: generate_external_id,
+ text: "Generic contention with MST",
+ start_date: Time.zone.today,
+ submit_date: 5.days.ago,
+ special_issues: [{
+ issue_id: generate_external_id,
+ narrative: "Military Sexual Trauma (MST)",
+ code: "MST"
+ }]
+ }
+ end
+
+ def default_attrs_with_pact
+ {
+ id: generate_external_id,
+ claim_id: generate_external_id,
+ text: "Generic contention",
+ start_date: Time.zone.today,
+ submit_date: 5.days.ago,
+ special_issues: [{
+ issue_id: generate_external_id,
+ narrative: "PACT",
+ code: "PACT"
+ }]
+ }
+ end
+
+ def default_attrs_with_mst_and_pact
+ {
+ id: generate_external_id,
+ claim_id: generate_external_id,
+ text: "Generic contention",
+ start_date: Time.zone.today,
+ submit_date: 5.days.ago,
+ special_issues: [{
+ issue_id: generate_external_id,
+ narrative: "Military Sexual Trauma (MST)",
+ code: "MST"
+ }, {
+ issue_id: generate_external_id,
+ narrative: "PACT",
+ code: "PACT"
+ }]
+ }
+ end
+
+ # :reek:RepeatedConditionals
def build(attrs = {})
attrs = default_attrs.merge(attrs)
claim_id = attrs[:claim_id]
disposition = attrs.delete(:disposition)
+ OpenStruct.new(attrs).tap do |contention|
+ Fakes::BGSService.end_product_store.create_contention(contention)
+ if disposition
+ disposition_record = OpenStruct.new(
+ claim_id: claim_id,
+ contention_id: contention.id,
+ disposition: disposition
+ )
+ Fakes::BGSService.end_product_store.create_disposition(disposition_record)
+ end
+ end
+ end
+
+ # :reek:RepeatedConditionals
+ def build_mst_contention(attrs = {})
+ attrs = default_attrs_with_mst.merge(attrs)
+ claim_id = attrs[:claim_id]
+ disposition = attrs.delete(:disposition)
+
+ OpenStruct.new(attrs).tap do |contention|
+ Fakes::BGSService.end_product_store.create_contention(contention)
+
+ if disposition
+ disposition_record = OpenStruct.new(
+ claim_id: claim_id,
+ contention_id: contention.id,
+ disposition: disposition
+ )
+ Fakes::BGSService.end_product_store.create_disposition(disposition_record)
+ end
+ end
+ end
+
+ # :reek:RepeatedConditionals
+ def build_pact_contention(attrs = {})
+ attrs = default_attrs_with_pact.merge(attrs)
+ claim_id = attrs[:claim_id]
+ disposition = attrs.delete(:disposition)
+
+ OpenStruct.new(attrs).tap do |contention|
+ Fakes::BGSService.end_product_store.create_contention(contention)
+
+ if disposition
+ disposition_record = OpenStruct.new(
+ claim_id: claim_id,
+ contention_id: contention.id,
+ disposition: disposition
+ )
+ Fakes::BGSService.end_product_store.create_disposition(disposition_record)
+ end
+ end
+ end
+
+ # :reek:RepeatedConditionals
+ def build_mst_and_pact_contention(attrs = {})
+ attrs = default_attrs_with_mst_and_pact.merge(attrs)
+ claim_id = attrs[:claim_id]
+ disposition = attrs.delete(:disposition)
+
OpenStruct.new(attrs).tap do |contention|
Fakes::BGSService.end_product_store.create_contention(contention)
diff --git a/lib/generators/rating.rb b/lib/generators/rating.rb
index 698d84ee752..b8632baccf9 100644
--- a/lib/generators/rating.rb
+++ b/lib/generators/rating.rb
@@ -64,6 +64,7 @@ def bgs_rating_profile_data(attrs)
}
end
+ # rubocop:disable Metrics/MethodLength
def bgs_rating_decisions_data(attrs)
return nil unless attrs[:decisions]
@@ -80,9 +81,16 @@ def bgs_rating_decisions_data(attrs)
decn_tn: decision[:type_name],
dis_sn: decision[:disability_id],
dis_dt: decision[:disability_date],
- orig_denial_dt: decision[:original_denial_date]
+ orig_denial_dt: decision[:original_denial_date],
+ disability_special_issues: {
+ dist_dt: decision[:dis_dt],
+ dis_sn: decision[:dis_sn],
+ spis_basis_tn: decision[:spis_basis_tn],
+ spis_tn: decision[:spis_tn]
+ }
}
end
+ # rubocop:enable Metrics/MethodLength
(decisions_data.length == 1) ? decisions_data.first : decisions_data
end
diff --git a/lib/generators/vacols/case.rb b/lib/generators/vacols/case.rb
index 40ad4860f93..a32a717127b 100644
--- a/lib/generators/vacols/case.rb
+++ b/lib/generators/vacols/case.rb
@@ -38,12 +38,12 @@ def case_attrs
bfbsasgn: nil,
bfattid: "1286",
bfdasgn: nil,
- bfcclkid: "8927941",
+ bfcclkid: nil,
bfdqrsnt: nil,
bfdlocin: "2017-11-30 09:01:21 UTC",
bfdloout: "2017-11-30 09:01:21 UTC",
bfstasgn: nil,
- bfcurloc: "98",
+ bfcurloc: "CASEFLOW",
bfnrcopy: nil,
bfmemid: "909",
bfdmem: nil,
@@ -111,9 +111,11 @@ def create(attrs = {})
representative_attrs[:repkey] = custom_case_attrs[:bfkey]
Generators::Vacols::Representative.create(representative_attrs)
- correspondent_attrs = attrs[:correspondent_attrs].nil? ? {} : attrs[:correspondent_attrs]
- correspondent_attrs[:stafkey] = custom_case_attrs[:bfcorkey]
- Generators::Vacols::Correspondent.create(correspondent_attrs)
+ unless attrs[:corres_exists]
+ correspondent_attrs = attrs[:correspondent_attrs].nil? ? {} : attrs[:correspondent_attrs]
+ correspondent_attrs[:stafkey] = custom_case_attrs[:bfcorkey]
+ Generators::Vacols::Correspondent.create(correspondent_attrs)
+ end
note_attrs = attrs[:note_attrs].nil? ? {} : attrs[:note_attrs]
note_attrs[:tsktknm] = custom_case_attrs[:bfkey]
@@ -127,7 +129,10 @@ def create(attrs = {})
# Default to two issues
case_issue_attrs = attrs[:case_issue_attrs].nil? ? [{}, {}] : attrs[:case_issue_attrs]
- case_issue_attrs.each { |issue| issue[:isskey] = custom_case_attrs[:bfkey] }
+ case_issue_attrs.each_with_index do |issue, idx|
+ issue[:isskey] = custom_case_attrs[:bfkey]
+ issue[:issseq] = idx
+ end
Generators::Vacols::CaseIssue.create(case_issue_attrs)
# Default to zero hearings
diff --git a/lib/generators/veteran.rb b/lib/generators/veteran.rb
index 48071e6fdd0..342840c5f5c 100644
--- a/lib/generators/veteran.rb
+++ b/lib/generators/veteran.rb
@@ -105,7 +105,8 @@ def build(attrs = {})
last_name: attrs[:last_name],
middle_name: attrs[:middle_name],
date_of_death: attrs[:date_of_death],
- name_suffix: attrs[:suffix_name])
+ name_suffix: attrs[:suffix_name],
+ participant_id: attrs[:participant_id])
end
end
end
diff --git a/lib/tasks/seed_legacy_appeals.rake b/lib/tasks/seed_legacy_appeals.rake
new file mode 100644
index 00000000000..7b7805f5417
--- /dev/null
+++ b/lib/tasks/seed_legacy_appeals.rake
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+# to create legacy appeals with MST/PACT issues, run "bundle exec rake 'db:generate_legacy_appeals[true]'""
+# to create without, run "bundle exec rake db:generate_legacy_appeals"
+namespace :db do
+ desc "Generates a smattering of legacy appeals with VACOLS cases that have special issues assocaited with them"
+ task :generate_legacy_appeals, [:add_special_issues, :user_id] => :environment do |_, args|
+ ADD_SPECIAL_ISSUES = args.add_special_issues.to_s == "true"
+ USER_ID = args.user_id
+ class LegacyAppealFactory
+ class << self
+ # Stamping out appeals like mufflers!
+ # rubocop:disable Layout/LineLength, Metrics/AbcSize, Metrics/MethodLength
+ def stamp_out_legacy_appeals(num_appeals_to_create, file_number, user, docket_number)
+ veteran = Veteran.find_by_file_number(file_number)
+
+ fail ActiveRecord::RecordNotFound unless veteran
+
+ vacols_veteran_record = find_or_create_vacols_veteran(veteran)
+
+ cases = Array.new(num_appeals_to_create).each_with_index.map do |_element, idx|
+ key = VACOLS::Folder.maximum(:ticknum).next
+ Generators::Vacols::Case.create(
+ corres_exists: true,
+ case_issue_attrs: [
+ Generators::Vacols::CaseIssue.case_issue_attrs.merge(ADD_SPECIAL_ISSUES ? special_issue_types(idx) : {}),
+ Generators::Vacols::CaseIssue.case_issue_attrs.merge(ADD_SPECIAL_ISSUES ? special_issue_types(idx) : {}),
+ Generators::Vacols::CaseIssue.case_issue_attrs.merge(ADD_SPECIAL_ISSUES ? special_issue_types(idx) : {})
+ ],
+ folder_attrs: Generators::Vacols::Folder.folder_attrs.merge(
+ custom_folder_attributes(vacols_veteran_record, docket_number.to_s)
+ ),
+ case_attrs: {
+ bfcorkey: vacols_veteran_record.stafkey,
+ bfcorlid: vacols_veteran_record.slogid,
+ bfkey: key,
+ bfcurloc: VACOLS::Staff.find_by(sdomainid: user.css_id).slogid,
+ bfmpro: "ACT",
+ bfddec: nil
+ },
+ staff_attrs: {
+ sattyid: user.id,
+ sdomainid: user.css_id
+ },
+ decass_attrs: {
+ defolder: key,
+ deatty: user.id,
+ dereceive: "2020-11-17 00:00:00 UTC"
+ }
+ )
+ end.compact
+
+ build_the_cases_in_caseflow(cases)
+ end
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/MethodLength
+
+ def custom_folder_attributes(veteran, docket_number)
+ {
+ titrnum: veteran.slogid,
+ tiocuser: nil,
+ tinum: docket_number
+ }
+ end
+
+ # Generators::Vacols::Case will create new correspondents, and I think it'll just be easier to
+ # update the cases created rather than mess with the generator's internals.
+ def find_or_create_vacols_veteran(veteran)
+ # Being naughty and calling a private method (it'd be cool to have this be public...)
+ vacols_veteran_record = VACOLS::Correspondent.send(:find_veteran_by_ssn, veteran.ssn).first
+
+ return vacols_veteran_record if vacols_veteran_record
+
+ Generators::Vacols::Correspondent.create(
+ Generators::Vacols::Correspondent.correspondent_attrs.merge(
+ ssalut: veteran.name_suffix,
+ snamef: veteran.first_name,
+ snamemi: veteran.middle_name,
+ snamel: veteran.last_name,
+ slogid: LegacyAppeal.convert_file_number_to_vacols(veteran.file_number)
+ )
+ )
+ end
+
+ ########################################################
+ # Create Postgres LegacyAppeals based on VACOLS Cases
+ #
+ # AND
+ #
+ # Create Postgres Request Issues based on VACOLS Issues
+ def build_the_cases_in_caseflow(cases)
+ vacols_ids = cases.map(&:bfkey)
+
+ issues = VACOLS::CaseIssue.where(isskey: vacols_ids).group_by(&:isskey)
+
+ cases.map do |case_record|
+ AppealRepository.build_appeal(case_record).tap do |appeal|
+ appeal.issues = (issues[appeal.vacols_id] || []).map { |issue| Issue.load_from_vacols(issue.attributes) }
+ end.save!
+ end
+ end
+
+ # MST is true for even indexes, and indexes that are multiples of 5. False for all other numbers.
+ # PACT is true for odd idexes, and index that are also multiples of 5. False for all others.
+ def special_issue_types(idx)
+ {
+ issmst: ((idx % 2).zero? || (idx % 5).zero?) ? "Y" : "N",
+ isspact: (!(idx % 2).zero? || (idx % 5).zero?) ? "Y" : "N",
+ issdc: nil
+ }
+ end
+ end
+
+ if Rails.env.development? || Rails.env.test?
+ # grab 5 random veterans
+ vets = []
+
+ (1..5).each do |_|
+ vets << Veteran.find(Random.new.rand(1..Veteran.count))
+ end
+
+ veterans_with_like_45_appeals = vets[0..5].pluck(:file_number)
+
+ # veterans_with_250_appeals = vets.last(3).pluck(:file_number)
+
+ else
+ veterans_with_like_45_appeals = %w[011899917 011899918]
+
+ # veterans_with_250_appeals = %w[011899906 011899999]
+ end
+
+ # request CSS ID for task assignment if not given
+ if USER_ID.blank?
+ STDOUT.puts("Enter the CSS ID of the user that you want to assign these appeals to")
+ STDOUT.puts("Hint: an Attorney User for demo env is BVASCASPER1, and UAT is TCASEY_JUDGE and CGRAHAM_JUDGE")
+ css_id = STDIN.gets.chomp.upcase
+ user = User.find_by_css_id(css_id)
+ else
+ user = User.find_by_css_id(USER_ID)
+ end
+
+ fail ActiveRecord::RecordNotFound unless user
+
+ # increment docket number for each case
+ docket_number = 9_000_000
+
+ veterans_with_like_45_appeals.each do |file_number|
+ docket_number += 1
+ LegacyAppealFactory.stamp_out_legacy_appeals(5, file_number, user, docket_number)
+ end
+ # veterans_with_250_appeals.each do |file_number|
+ # LegacyAppealFactory.stamp_out_legacy_appeals(250, file_number, user)
+ # end
+ end
+ end
+end
diff --git a/local/vacols/bgs_setup.csv b/local/vacols/bgs_setup.csv
index f0ff968f567..b29cfb2c408 100644
--- a/local/vacols/bgs_setup.csv
+++ b/local/vacols/bgs_setup.csv
@@ -25,27 +25,27 @@ vbms_id,bgs_key
772437875S,has_higher_level_review_with_vbms_claim_id
808415990S,has_two_ratings
872958715S,has_many_ratings
-992190636S,
-107924012S,
-329146737S,
-981775045S,
-738464961S,
-245620198S,
-955197769S,
-473949326S,
-933747849S,
-696650437S,
-598042239S,
-787148925S,
-657332982S,
-416322111S,
-968514949S,
-779309925S,
-169397130S,
-276880282S,
-138034137S,
-192920593S,
-190595135S,
+992190636S,generate_mst_and_pact_contentions
+107924012S,generate_mst_and_pact_contentions
+329146737S,generate_mst_and_pact_contentions
+981775045S,generate_mst_and_pact_contentions
+738464961S,generate_mst_and_pact_contentions
+245620198S,generate_mst_and_pact_contentions
+955197769S,generate_mst_and_pact_contentions
+473949326S,generate_mst_and_pact_contentions
+933747849S,generate_mst_and_pact_contentions
+696650437S,generate_mst_and_pact_contentions
+598042239S,generate_mst_and_pact_contentions
+787148925S,generate_mst_and_pact_contentions
+657332982S,generate_mst_and_pact_contentions
+416322111S,generate_mst_and_pact_contentions
+968514949S,generate_mst_and_pact_contentions
+779309925S,generate_mst_and_pact_contentions
+169397130S,generate_mst_and_pact_contentions
+276880282S,generate_mst_and_pact_contentions
+138034137S,generate_mst_and_pact_contentions
+192920593S,generate_mst_and_pact_contentions
+190595135S,generate_mst_and_pact_contentions
209053627S,
692291635S,
188144266S,
diff --git a/local/vacols/vacols_copy_2_tables_dev.sql b/local/vacols/vacols_copy_2_tables_dev.sql
index 87895be895d..dfd707379c9 100644
--- a/local/vacols/vacols_copy_2_tables_dev.sql
+++ b/local/vacols/vacols_copy_2_tables_dev.sql
@@ -518,7 +518,9 @@
"ISSDESC" VARCHAR2(100),
"ISSSEL" VARCHAR2(1),
"ISSGR" VARCHAR2(1),
- "ISSDEV" VARCHAR2(2))
+ "ISSDEV" VARCHAR2(2),
+ "ISSMST" VARCHAR2(1),
+ "ISSPACT" VARCHAR2(1))
TABLESPACE "VACOLS_ISSUES" ;
diff --git a/local/vacols/vacols_copy_2_tables_test.sql b/local/vacols/vacols_copy_2_tables_test.sql
index 382c827874b..df9b9eb2a10 100644
--- a/local/vacols/vacols_copy_2_tables_test.sql
+++ b/local/vacols/vacols_copy_2_tables_test.sql
@@ -518,7 +518,9 @@
"ISSDESC" VARCHAR2(100),
"ISSSEL" VARCHAR2(1),
"ISSGR" VARCHAR2(1),
- "ISSDEV" VARCHAR2(2))
+ "ISSDEV" VARCHAR2(2),
+ "ISSMST" VARCHAR2(1),
+ "ISSPACT" VARCHAR2(1))
TABLESPACE "VACOLS_ISSUES" ;
diff --git a/scripts/enable_features_dev.rb b/scripts/enable_features_dev.rb
index ef1b30eddd4..b2f075c550b 100644
--- a/scripts/enable_features_dev.rb
+++ b/scripts/enable_features_dev.rb
@@ -63,6 +63,7 @@ def call
acd_disable_legacy_distributions
acd_disable_nonpriority_distributions
acd_disable_legacy_lock_ready_appeals
+ justification_reason
]
all_features = AllFeatureToggles.new.call.flatten.uniq
diff --git a/spec/controllers/appeals_controller_spec.rb b/spec/controllers/appeals_controller_spec.rb
index 89956996699..e81086e86ad 100644
--- a/spec/controllers/appeals_controller_spec.rb
+++ b/spec/controllers/appeals_controller_spec.rb
@@ -993,4 +993,65 @@ def allow_vbms_to_return_empty_array
end
end
end
+
+ describe "POST update" do
+ context "AMA Appeal" do
+ before do
+ User.authenticate!(roles: ["System Admin"])
+ Fakes::Initializer.load!
+ end
+
+ let(:ssn) { Generators::Random.unique_ssn }
+ let(:options) { { format: :html, appeal_id: appeal_url_identifier } }
+ let(:appeal) { create(:appeal, veteran_file_number: ssn) }
+ let(:appeal_url_identifier) { appeal.is_a?(LegacyAppeal) ? appeal.vacols_id : appeal.uuid }
+ let!(:request_issue1) { create(:request_issue, decision_review: appeal) }
+ let(:request_issue2) { create(:request_issue, decision_review: appeal) }
+ let(:request_issue3) { create(:request_issue, decision_review: appeal) }
+ let(:request_issue4) { create(:request_issue, decision_review: appeal) }
+ let(:organization) { create(:organization) }
+
+ subject do
+ post :update, params: {
+ request_issues: [
+ {
+ request_issue_id: request_issue4.id,
+ mst_status: true,
+ mst_status_update_reason_notes: "MST reason note",
+ pact_status_update_reason_notes: ""
+ },
+ {
+ request_issue_id: request_issue3.id,
+ pact_status: true,
+ mst_status_update_reason_notes: "",
+ pact_status_update_reason_notes: "PACT reason note"
+ },
+ {
+ request_issue_id: request_issue2.id,
+ mst_status: true,
+ pact_status: true,
+ mst_status_update_reason_notes: "MST note",
+ pact_status_update_reason_notes: "Pact note"
+ },
+ {
+ request_issue_id: request_issue1.id,
+ mst_status_update_reason_notes: "",
+ pact_status_update_reason_notes: ""
+ }
+ ],
+ controller: "appeals",
+ action: "update",
+ appeal_id: appeal.id
+ }
+ end
+
+ it "responds with a 200 status" do
+ allow_any_instance_of(AppealsController).to receive(:appeal).and_return(appeal)
+ allow(Organization).to receive(:find_by_url).and_return(organization)
+
+ subject
+ expect(response).to be_successful
+ end
+ end
+ end
end
diff --git a/spec/controllers/case_reviews_controller_spec.rb b/spec/controllers/case_reviews_controller_spec.rb
index 2f892bd3e97..0bc2f931e77 100644
--- a/spec/controllers/case_reviews_controller_spec.rb
+++ b/spec/controllers/case_reviews_controller_spec.rb
@@ -82,11 +82,15 @@
"issues": [{ "disposition": disposition,
"description": "wonderful life",
"benefit_type": "pension",
+ "mst_status": true,
+ "pact_status": false,
"diagnostic_code": diagnostic_code,
"request_issue_ids": request_issue_ids },
{ "disposition": "remanded",
"description": "great moments",
"benefit_type": "vha",
+ "mst_status": false,
+ "pact_status": true,
"diagnostic_code": "5002",
"request_issue_ids": [request_issue2.id],
"remand_reasons": [{ "code": "va_records", "post_aoj": true }] }]
@@ -127,6 +131,20 @@
context "when all parameters are present" do
it_behaves_like "valid params"
end
+
+ context "when mst or pact status are passed in params" do
+ it "it create the decision issues with the correct mst and pact status" do
+ subject
+
+ decision_issues = JSON.parse(response.body, symbolize_names: true)[:issues][:decision_issues]
+ .sort_by { |issue| issue[:id] }
+
+ expect(decision_issues[0][:mst_status]).to be(true)
+ expect(decision_issues[0][:pact_status]).to be(false)
+ expect(decision_issues[1][:mst_status]).to be(false)
+ expect(decision_issues[1][:pact_status]).to be(true)
+ end
+ end
end
end
diff --git a/spec/controllers/intakes_controller_spec.rb b/spec/controllers/intakes_controller_spec.rb
index 45130bdbd3b..7affd25f073 100644
--- a/spec/controllers/intakes_controller_spec.rb
+++ b/spec/controllers/intakes_controller_spec.rb
@@ -173,6 +173,74 @@
end
end
+ context "when intaking an AMA appeal with MST and PACT issues" do
+ let!(:veteran) { create(:veteran) }
+ let!(:appeal) { create(:appeal, veteran: veteran) }
+ let!(:intake) { create(:intake, user: current_user, detail: appeal, veteran: veteran) }
+ let(:params) do
+ {
+ id: intake.id,
+ request_issues:
+ [
+ {
+ benefit_type: "compensation",
+ nonrating_issue_category: "Unknown Issue Category",
+ decision_text: "issue",
+ decision_date: "2023-04-28",
+ ineligible_due_to_id: nil,
+ ineligible_reason: nil,
+ withdrawal_date: nil,
+ is_predocket_needed: nil,
+ mst_status: true,
+ pact_status: true
+ }
+ ]
+ }
+ end
+ it "should pass the MST and PACT status in the params and create an RI with mst/pact" do
+ post :complete, params: params
+
+ expect(response.status).to eq(200)
+ appeal.reload
+ expect(appeal.request_issues.first.mst_status).to eq true
+ expect(appeal.request_issues.first.pact_status).to eq true
+ end
+ end
+
+ context "when intaking an AMA appeal without MST and PACT issues" do
+ let!(:veteran) { create(:veteran) }
+ let!(:appeal) { create(:appeal, veteran: veteran) }
+ let!(:intake) { create(:intake, user: current_user, detail: appeal, veteran: veteran) }
+ let(:params) do
+ {
+ id: intake.id,
+ request_issues:
+ [
+ {
+ benefit_type: "compensation",
+ nonrating_issue_category: "Unknown Issue Category",
+ decision_text: "issue",
+ decision_date: "2023-04-28",
+ ineligible_due_to_id: nil,
+ ineligible_reason: nil,
+ withdrawal_date: nil,
+ is_predocket_needed: nil,
+ mst_status: false,
+ pact_status: false
+ }
+ ]
+ }
+ end
+ it "should pass the MST and PACT status in the params and create an RI with mst/pact" do
+ post :complete, params: params
+
+ expect(response.status).to eq(200)
+ appeal.reload
+ expect(appeal.request_issues.first.mst_status).to eq false
+ expect(appeal.request_issues.first.pact_status).to eq false
+ end
+ end
+
context "when intaking a processed_in_caseflow AMA HLR/SC" do
let(:veteran) { create(:veteran) }
diff --git a/spec/controllers/issues_controller_spec.rb b/spec/controllers/issues_controller_spec.rb
index d930e80c1af..011f7f9928c 100644
--- a/spec/controllers/issues_controller_spec.rb
+++ b/spec/controllers/issues_controller_spec.rb
@@ -26,7 +26,9 @@
level_1: "03",
level_2: "5252",
level_3: nil,
- note: "test"
+ note: "test",
+ mst_status: "Y",
+ pact_status: "Y"
}
end
@@ -82,7 +84,9 @@
level_1: "03",
level_2: "04",
level_3: nil,
- note: "test"
+ note: "test",
+ mst_status: "N",
+ pact_status: "N"
}
end
@@ -104,7 +108,9 @@
level_1: "03",
level_2: "5252",
level_3: nil,
- note: "test"
+ note: "test",
+ mst_status: "N",
+ pact_status: "Y"
}
end
@@ -129,7 +135,9 @@
level_1: "03",
level_2: "5252",
level_3: nil,
- note: "test"
+ note: "test",
+ mst_status: "N",
+ pact_status: "Y"
}
end
@@ -162,7 +170,9 @@
level_1: "03",
level_2: "04",
level_3: nil,
- note: "test"
+ note: "test",
+ mst_status: "N",
+ pact_status: "N"
}
end
diff --git a/spec/factories/special_issue_list.rb b/spec/factories/special_issue_list.rb
new file mode 100644
index 00000000000..afc76a147f5
--- /dev/null
+++ b/spec/factories/special_issue_list.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :special_issue_list do
+ military_sexual_trauma { true }
+ appeal_type { "Appeal" }
+ end
+end
diff --git a/spec/feature/intake/add_issues_spec.rb b/spec/feature/intake/add_issues_spec.rb
index 27762354c08..54d800c846e 100644
--- a/spec/feature/intake/add_issues_spec.rb
+++ b/spec/feature/intake/add_issues_spec.rb
@@ -6,7 +6,6 @@
before do
setup_intake_flags
end
-
let(:veteran_file_number) { "123412345" }
let(:veteran) do
Generators::Veteran.build(file_number: veteran_file_number, first_name: "Ed", last_name: "Merica")
@@ -19,6 +18,13 @@
last_name: "Attings",
participant_id: "44444444")
end
+
+ let(:veteran_vbms_mst_pact) do
+ Generators::Veteran.build(file_number: "66666666",
+ first_name: "Veeby",
+ last_name: "Emmess")
+ end
+
let!(:rating) do
Generators::PromulgatedRating.build(
participant_id: veteran.participant_id,
@@ -43,6 +49,10 @@
)
end
+ let!(:vbms_rating) do
+ generate_rating_with_mst_pact(veteran_vbms_mst_pact)
+ end
+
context "not service connected rating decision" do
before { FeatureToggle.enable!(:contestable_rating_decisions) }
after { FeatureToggle.disable!(:contestable_rating_decisions) }
@@ -59,6 +69,16 @@
add_intake_rating_issue(rating_decision_text)
expect(page).to have_content("1. #{rating_decision_text}\nDecision date: #{promulgation_date.mdY}")
end
+
+ scenario "MST and PACT checkboxes DO NOT appear after selecting decision in higher level review" do
+ start_higher_level_review(veteran)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ choose("rating-radio_0", allow_label_click: true)
+ expect(page).to have_no_content("Issue is related to Military Sexual Trauma (MST)")
+ expect(page).to have_no_content("Issue is related to PACT Act")
+ end
end
context "check for correct time zone" do
@@ -795,4 +815,190 @@ def add_contested_claim_issue
expect(page).to_not have_content("Hearing type")
end
end
+
+ context "for MST and PACT Act" do
+ # format date to be yesterday in month/day/year format
+ let!(:issue_date) do
+ Time.zone.yesterday.strftime("%m/%d/%Y")
+ end
+
+ before :each do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ BvaIntake.singleton.add_user(current_user)
+ OrganizationsUser.find_by(user_id: current_user.id).update(admin: true)
+ end
+
+ after :each do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ # FeatureToggle.disable!(:legacy_mst_pact_identification)
+ end
+
+ scenario "MST and PACT checkboxes appear after selecting decision" do
+ start_appeal(veteran)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ choose("rating-radio_0", allow_label_click: true)
+ expect(page).to have_content("Issue is related to Military Sexual Trauma (MST)")
+ expect(page).to have_content("Issue is related to PACT Act")
+ end
+
+ scenario "MST and PACT checkboxes render a justification field when checked" do
+ FeatureToggle.enable!(:justification_reason)
+ start_appeal(veteran)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ choose("rating-radio_0", allow_label_click: true)
+ expect(page).to have_content("Issue is related to Military Sexual Trauma (MST)")
+ expect(page).to have_content("Issue is related to PACT Act")
+ # check mst checkbox
+ find_by_id("MST", visible: false).check(allow_label_click: true)
+ expect(page).to have_content("Why was this change made?")
+ # uncheck mst checkbox
+ find_by_id("MST", visible: false).uncheck(allow_label_click: true)
+ expect(page).to_not have_content("Why was this change made?")
+ # check pact checbox
+ find_by_id("Pact", visible: false).check(allow_label_click: true)
+ expect(page).to have_content("Why was this change made?")
+ # uncheck pact checkbox
+ find_by_id("Pact", visible: false).uncheck(allow_label_click: true)
+ expect(page).to_not have_content("Why was this change made?")
+ FeatureToggle.disable!(:justification_reason)
+ end
+
+ scenario "MST designation added during AMA intake" do
+ start_appeal(veteran)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ click_intake_no_matching_issues
+ find_by_id("mst-checkbox", visible: false).check(allow_label_click: true)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Establish appeal"
+
+ appeal_id = Appeal.find_by(veteran_file_number: veteran.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST")
+ expect(page).to have_no_content("Special Issues: PACT")
+ end
+
+ scenario "Pact designation added during AMA intake" do
+ start_appeal(veteran_no_ratings)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ find_by_id("pact-checkbox", visible: false).check(allow_label_click: true)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Establish appeal"
+
+ appeal_id = Appeal.find_by(veteran_file_number: veteran_no_ratings.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ visit current_path
+ click_on "View task instructions"
+
+ expect(page).to have_content("Special Issues: PACT")
+ expect(page).to have_no_content("Special Issues: MST")
+ end
+
+ scenario "MST and Pact designation added during AMA intake" do
+ start_appeal(veteran_no_ratings)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ find_by_id("mst-checkbox", visible: false).check(allow_label_click: true)
+ find_by_id("pact-checkbox", visible: false).check(allow_label_click: true)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Establish appeal"
+
+ appeal_id = Appeal.find_by(veteran_file_number: veteran_no_ratings.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ visit current_path
+ click_on "View task instructions"
+
+ expect(page).to have_content("Special Issues: MST, PACT")
+ end
+
+ # rubocop:disable Layout/LineLength
+ scenario "Intake appeal with MST contention from VBMS" do
+ start_appeal_with_mst_pact_from_vbms(veteran_vbms_mst_pact)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ find_all("label", text: "Service connection is granted for PTSD at 10 percent, effective 10/11/2022.", minimum: 1).first.click
+ safe_click ".add-issue"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Establish appeal"
+ appeal_id = Appeal.find_by(veteran_file_number: veteran_vbms_mst_pact.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Service connection is granted for PTSD at 10 percent, effective 10/11/2022.")
+ expect(page).to have_content("Special Issues: MST")
+ end
+ # rubocop:enable Layout/LineLength
+
+ # rubocop:disable Layout/LineLength
+ scenario "Intake appeal with PACT contention from VBMS" do
+ start_appeal_with_mst_pact_from_vbms(veteran_vbms_mst_pact)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ find_all("label", text: "Service connection is granted for AOOV at 10 percent, effective 10/11/2022.", minimum: 1).first.click
+ safe_click ".add-issue"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Establish appeal"
+ appeal_id = Appeal.find_by(veteran_file_number: veteran_vbms_mst_pact.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Service connection is granted for AOOV at 10 percent, effective 10/11/2022.")
+ expect(page).to have_content("Special Issues: PACT")
+ end
+ # rubocop:enable Layout/LineLength
+
+ # rubocop:disable Layout/LineLength
+ scenario "Intake appeal with MST and PACT contentions from VBMS" do
+ start_appeal_with_mst_pact_from_vbms(veteran_vbms_mst_pact)
+ visit "/intake"
+ click_intake_continue
+ click_intake_add_issue
+ find_all("label", text: "Service connection is granted for PTSD, AOOV at 10 percent, effective 10/11/2022.", minimum: 1).first.click
+ safe_click ".add-issue"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Establish appeal"
+ appeal_id = Appeal.find_by(veteran_file_number: veteran_vbms_mst_pact.file_number).uuid
+ visit "/queue/appeals/#{appeal_id}"
+ # to prevent timeout
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Service connection is granted for PTSD, AOOV at 10 percent, effective 10/11/2022.")
+ expect(page).to have_content("Special Issues: MST, PACT")
+ end
+ # rubocop:enable Layout/LineLength
+ end
end
diff --git a/spec/feature/intake/appeal/edit_spec.rb b/spec/feature/intake/appeal/edit_spec.rb
index dd694b9a618..c24b143a793 100644
--- a/spec/feature/intake/appeal/edit_spec.rb
+++ b/spec/feature/intake/appeal/edit_spec.rb
@@ -1217,4 +1217,133 @@ def finished_all_ajax_requests?
end
end
end
+
+ context "with BVA Intake Admin user" do
+ # creates organization
+ let(:bva_intake) { BvaIntake.singleton }
+ # creates admin user
+ let(:bva_intake_admin_user) { create(:user, roles: ["Mail Intake"]) }
+
+ let(:legacy_appeal_mst_pact_unchecked) do
+ create(
+ :legacy_appeal,
+ :with_veteran,
+ vacols_case: create(
+ :case,
+ case_issues: [
+ create(:case_issue, issmst: "N", isspact: "N")
+ ]
+ )
+ )
+ end
+
+ let(:legacy_appeal_mst_pact_checked) do
+ create(
+ :legacy_appeal,
+ :with_veteran,
+ vacols_case: create(
+ :case,
+ case_issues: [
+ create(:case_issue, issmst: "Y", isspact: "Y")
+ ]
+ )
+ )
+ end
+
+ before do
+ # joins the user with the organization to grant access to role and org permissions
+ OrganizationsUser.make_user_admin(bva_intake_admin_user, bva_intake)
+ # authenticates and sets the user
+ User.authenticate!(user: bva_intake_admin_user)
+ end
+
+ def go_to_queue_edit_issues_page_with_legacy_appeal(legacy_appeal)
+ visit "/queue"
+ click_on "Search cases"
+ fill_in "search", with: legacy_appeal.veteran_file_number
+ click_on "Search"
+ click_on legacy_appeal.docket_number
+ click_on "Correct issues"
+ end
+
+ context "with Legacy MST/PACT identifications" do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ end
+
+ scenario "can add MST/PACT to issues" do
+ go_to_queue_edit_issues_page_with_legacy_appeal(legacy_appeal_mst_pact_unchecked)
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ find(:xpath, "//label[@for='PACT Act']").click(allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+
+ expect(page).to have_content("MST and PACT")
+ end
+
+ scenario "can remove MST/PACT issues" do
+ go_to_queue_edit_issues_page_with_legacy_appeal(legacy_appeal_mst_pact_checked)
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ find(:xpath, "//label[@for='PACT Act']").click(allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+
+ expect(page).to have_no_content("MST and PACT")
+ end
+
+ scenario "can add and remove only PACT to an issue" do
+ go_to_queue_edit_issues_page_with_legacy_appeal(legacy_appeal_mst_pact_unchecked)
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ find(:xpath, "//label[@for='PACT Act']").click(allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+ expect(page).to have_content("SPECIAL ISSUES\nPACT")
+
+ click_on "Correct issues"
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ find(:xpath, "//label[@for='PACT Act']").click(allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+ expect(page).to have_no_content("SPECIAL ISSUES\nPact")
+ end
+
+ scenario "can add and remove only MST to an issue" do
+ go_to_queue_edit_issues_page_with_legacy_appeal(legacy_appeal_mst_pact_unchecked)
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+ expect(page).to have_content("SPECIAL ISSUES\nMST")
+
+ click_on "Correct issues"
+ find("select", id: "issue-action-0").click
+ find("option", id: "issue-action-0_edit").click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Save"
+ expect(page).to have_no_content("SPECIAL ISSUES\nMST")
+ end
+ end
+ end
end
diff --git a/spec/feature/intake/appeal_spec.rb b/spec/feature/intake/appeal_spec.rb
index c4e6fd98589..dba5941c6a6 100644
--- a/spec/feature/intake/appeal_spec.rb
+++ b/spec/feature/intake/appeal_spec.rb
@@ -30,6 +30,37 @@
participant_id: "44444444")
end
+ let(:veteran_file_number2) { "123412345" }
+ let(:veteran_with_ratings) do
+ Generators::Veteran.build(file_number: veteran_file_number2, first_name: "Ed", last_name: "Merica")
+ end
+ let(:profile_date) { 10.days.ago }
+ let(:promulgation_date) { 9.days.ago.to_date }
+
+ let!(:rating2) do
+ Generators::PromulgatedRating.build(
+ participant_id: veteran_with_ratings.participant_id,
+ promulgation_date: promulgation_date,
+ profile_date: profile_date,
+ issues: [
+ { reference_id: "abc123", decision_text: "Left knee granted" },
+ { reference_id: "def456", decision_text: "PTSD denied" },
+ { reference_id: "def789", decision_text: "Looks like a VACOLS issue" }
+ ],
+ decisions: [
+ {
+ rating_issue_reference_id: nil,
+ original_denial_date: promulgation_date - 35.days,
+ diagnostic_text: "Broken arm",
+ diagnostic_type: "Bone",
+ disability_id: "123",
+ disability_date: promulgation_date - 10.days,
+ type_name: "Not Service Connected"
+ }
+ ]
+ )
+ end
+
let(:future_date) { (Time.zone.now + 30.days).to_date }
let(:receipt_date) { (post_ama_start_date - 30.days).to_date }
let(:untimely_days) { 372.days }
@@ -42,7 +73,11 @@
let!(:rating) { generate_rating(veteran, promulgation_date, profile_date) }
let!(:untimely_rating) { generate_untimely_rating(veteran, untimely_promulgation_date, untimely_profile_date) }
+ # rubocop:disable Layout/LineLength
+ let!(:untimely_rating2) { generate_untimely_rating(veteran_with_ratings, untimely_promulgation_date, untimely_profile_date) }
+ # rubocop:enable Layout/LineLength
let!(:before_ama_rating) { generate_pre_ama_rating(veteran) }
+ let!(:before_ama_rating2) { generate_pre_ama_rating(veteran_with_ratings) }
let(:no_ratings_err) { Rating::NilRatingProfileListError.new("none!") }
@@ -70,7 +105,7 @@
expect(page).to have_content(search_page_title)
- fill_in search_bar_title, with: veteran_file_number
+ fill_in search_bar_title, with: veteran_with_ratings.file_number
click_on "Search"
expect(page).to have_current_path("/intake/review_request")
@@ -109,9 +144,8 @@
click_intake_continue
- appeal = Appeal.find_by(veteran_file_number: veteran_file_number)
- intake = Intake.find_by(veteran_file_number: veteran_file_number)
-
+ appeal = Appeal.find_by(veteran_file_number: veteran_with_ratings.file_number)
+ intake = Intake.find_by(veteran_file_number: veteran_with_ratings.file_number)
expect(appeal).to_not be_nil
expect(appeal.receipt_date.to_date).to eq(receipt_date.to_date)
expect(appeal.docket_type).to eq(Constants.AMA_DOCKETS.evidence_submission)
@@ -289,14 +323,14 @@ def complete_appeal
let(:disability_profile_date) { profile_date - 1.day }
let!(:ratings_with_diagnostic_codes) do
generate_ratings_with_disabilities(
- veteran,
+ veteran_with_ratings,
disabiliity_receive_date,
disability_profile_date
)
end
scenario "saves diagnostic codes" do
- appeal, = start_appeal(veteran)
+ appeal, = start_appeal(veteran_with_ratings)
visit "/intake"
click_intake_continue
save_and_check_request_issues_with_diagnostic_codes(
@@ -332,9 +366,9 @@ def complete_appeal
promulgation_date = receipt_date - 40.days
rating_date = promulgation_date.mdY
- generate_timely_rating(veteran, receipt_date, duplicate_reference_id)
- generate_untimely_rating_from_ramp(veteran, receipt_date, old_reference_id)
- generate_rating_before_ama_from_ramp(veteran)
+ generate_timely_rating(veteran_with_ratings, receipt_date, duplicate_reference_id)
+ generate_untimely_rating_from_ramp(veteran_with_ratings, receipt_date, old_reference_id)
+ generate_rating_before_ama_from_ramp(veteran_with_ratings)
epe = create(:end_product_establishment, :active)
request_issue_in_progress = create(
@@ -344,13 +378,10 @@ def complete_appeal
contested_issue_description: "Old injury"
)
- appeal, = start_appeal(veteran)
+ appeal, = start_appeal(veteran_with_ratings)
visit "/intake/add_issues"
expect(page).to have_content("Add / Remove Issues")
- check_row("Review option", "Evidence Submission")
- check_row("Claimant", "Ed Merica, Veteran")
- check_row("SOC/SSOC Opt-in", "No")
# clicking the add issues button should bring up the modal
click_intake_add_issue
@@ -497,7 +528,7 @@ def complete_appeal
expect(Appeal.find_by(
id: appeal.id,
- veteran_file_number: veteran.file_number,
+ veteran_file_number: veteran_with_ratings.file_number,
established_at: Time.zone.now
)).to_not be_nil
@@ -579,7 +610,7 @@ def complete_appeal
end
context "when veteran chooses decision issue from a previous appeal" do
- let(:previous_appeal) { create(:appeal, :outcoded, veteran: veteran) }
+ let(:previous_appeal) { create(:appeal, :outcoded, veteran: veteran_with_ratings) }
let(:appeal_reference_id) { "appeal123" }
let!(:previous_appeal_request_issue) do
create(
@@ -596,7 +627,7 @@ def complete_appeal
decision_review: previous_appeal,
request_issues: [previous_appeal_request_issue],
rating_issue_reference_id: appeal_reference_id,
- participant_id: veteran.participant_id,
+ participant_id: veteran_with_ratings.participant_id,
description: "appeal decision issue",
decision_text: "appeal decision issue",
benefit_type: "compensation",
@@ -606,7 +637,7 @@ def complete_appeal
scenario "the issue is ineligible" do
start_appeal(
- veteran,
+ veteran_with_ratings,
veteran_is_not_claimant: false
)
visit "/intake/add_issues"
@@ -632,7 +663,7 @@ def complete_appeal
end
it "Shows a review error when something goes wrong" do
- start_appeal(veteran)
+ start_appeal(veteran_with_ratings)
visit "/intake/add_issues"
click_intake_add_issue
@@ -680,7 +711,7 @@ def complete_appeal
end
scenario "adding nonrating issue with non-comp benefit type" do
- _, intake = start_appeal(veteran)
+ _, intake = start_appeal(veteran_with_ratings)
visit "/intake/add_issues"
expect(page).to have_content("Add / Remove Issues")
@@ -722,14 +753,12 @@ def complete_appeal
context "with active legacy appeal" do
before do
- setup_legacy_opt_in_appeals(veteran.file_number)
+ setup_legacy_opt_in_appeals(veteran_with_ratings.file_number)
end
context "with legacy_opt_in_approved" do
- let(:receipt_date) { Time.zone.today }
-
scenario "adding issues" do
- start_appeal(veteran, legacy_opt_in_approved: true)
+ start_appeal(veteran_with_ratings, legacy_opt_in_approved: true)
visit "/intake/add_issues"
check_row("SOC/SSOC Opt-in", "Yes")
@@ -810,8 +839,11 @@ def complete_appeal
end
context "with legacy opt in not approved" do
+ let!(:promulgation_date) { receipt_date - untimely_days - 1.day }
+ let(:profile_date) { receipt_date - untimely_days - 3.days }
+
scenario "adding issues" do
- start_appeal(veteran, legacy_opt_in_approved: false)
+ start_appeal(veteran_with_ratings, legacy_opt_in_approved: false)
visit "/intake/add_issues"
click_intake_add_issue
add_intake_rating_issue("Left knee granted")
@@ -850,31 +882,36 @@ def complete_appeal
let(:prior_noncomp_decision_review) do
create(:higher_level_review,
benefit_type: "nca",
- veteran_file_number: veteran_no_ratings.file_number)
+ veteran_file_number: veteran_with_ratings.file_number)
end
+
# decision_issue_date needs to be before receipt date to show up
- let(:decision_issue_date) { receipt_date - 2.days }
+ let(:decision_issue_date) { receipt_date - 30.days }
let!(:decision_issues) do
[
# non comp decision issues do not have end_product_last_action date
# but do have promulgation date
- create(:decision_issue,
- disposition: "Granted",
- description: "granted issue",
- participant_id: veteran_no_ratings.participant_id,
- decision_review: prior_noncomp_decision_review,
- caseflow_decision_date: decision_issue_date),
- create(:decision_issue,
- disposition: "Dismissed",
- description: "dismissed issue",
- participant_id: veteran_no_ratings.participant_id,
- decision_review: prior_noncomp_decision_review,
- caseflow_decision_date: decision_issue_date)
+ create(
+ :decision_issue,
+ disposition: "Granted",
+ description: "granted issue",
+ participant_id: veteran_with_ratings.participant_id,
+ decision_review: prior_noncomp_decision_review,
+ caseflow_decision_date: decision_issue_date
+ ),
+ create(
+ :decision_issue,
+ disposition: "Dismissed",
+ description: "dismissed issue",
+ participant_id: veteran_with_ratings.participant_id,
+ decision_review: prior_noncomp_decision_review,
+ caseflow_decision_date: decision_issue_date
+ )
]
end
it "shows prior decision issues as contestable" do
- start_appeal(veteran_no_ratings)
+ start_appeal(veteran_with_ratings)
visit "/intake/add_issues"
click_intake_add_issue
@@ -887,16 +924,16 @@ def complete_appeal
context "has a chain of prior decision issues" do
let(:start_date) { Time.zone.today - 300.days }
before do
- prior_appeal = create(:appeal, :outcoded, veteran: veteran)
+ prior_appeal = create(:appeal, :outcoded, veteran: veteran_with_ratings)
request_issue = create(:request_issue,
contested_rating_issue_reference_id: "old123",
contested_rating_issue_profile_date: untimely_rating.profile_date,
decision_review: prior_appeal)
- setup_prior_decision_issue_chain(prior_appeal, request_issue, veteran, start_date)
+ setup_prior_decision_issue_chain(prior_appeal, request_issue, veteran_with_ratings, start_date)
end
it "disables prior contestable issues" do
- start_appeal(veteran)
+ start_appeal(veteran_with_ratings)
check_decision_issue_chain(start_date)
end
end
diff --git a/spec/feature/intake/nonrating_request_issue_modal_spec.rb b/spec/feature/intake/nonrating_request_issue_modal_spec.rb
index 4b9166d065e..b2e370f517f 100644
--- a/spec/feature/intake/nonrating_request_issue_modal_spec.rb
+++ b/spec/feature/intake/nonrating_request_issue_modal_spec.rb
@@ -3,8 +3,20 @@
feature "Nonrating Request Issue Modal", :postgres do
include IntakeHelpers
+ let(:bva_intake) { BvaIntake.singleton }
+ let(:bva_intake_admin_user) { create(:user, roles: ["Mail Intake"]) }
+
before do
Timecop.freeze(post_ama_start_date)
+ OrganizationsUser.make_user_admin(bva_intake_admin_user, bva_intake)
+ User.authenticate!(user: bva_intake_admin_user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
end
let(:veteran_file_number) { "123412345" }
@@ -12,7 +24,17 @@
Generators::Veteran.build(file_number: veteran_file_number, first_name: "Ed", last_name: "Merica")
end
- def test_issue_categories(decision_review_type:, benefit_type:, included_category:, excluded_category:)
+ def check_for_mst_pact
+ expect(page).to have_content("Military Sexual Trauma (MST)")
+ expect(page).to have_content("PACT Act")
+ end
+
+ def check_for_no_mst_pact
+ expect(page).to_not have_content("Military Sexual Trauma (MST)")
+ expect(page).to_not have_content("PACT Act")
+ end
+
+ def test_issue_categories(decision_review_type:, benefit_type:, included_category:, excluded_category:, mst_pact:)
case decision_review_type
when "higher_level_review"
start_higher_level_review(
@@ -28,13 +50,14 @@ def test_issue_categories(decision_review_type:, benefit_type:, included_categor
start_appeal(veteran)
end
- visit_and_test_categories(included_category, excluded_category, benefit_type)
+ visit_and_test_categories(included_category, excluded_category, benefit_type, mst_pact)
end
- def visit_and_test_categories(included_category, excluded_category, benefit_type)
+ def visit_and_test_categories(included_category, excluded_category, benefit_type, mst_pact)
visit "/intake"
click_intake_continue
click_intake_add_issue
+ mst_pact ? check_for_mst_pact : check_for_no_mst_pact
click_intake_nonrating_category_dropdown
expect(page).to have_content(included_category)
expect(page).to_not have_content(excluded_category)
@@ -43,8 +66,8 @@ def visit_and_test_categories(included_category, excluded_category, benefit_type
description: "I am a description",
date: Time.zone.today.mdY
)
- click_intake_finish
+ click_intake_finish
expect(page).to have_content("Intake completed") if %w[compensation pension].include?(benefit_type)
# hesitate just a little so non-comp background tasks can finish.
@@ -61,21 +84,24 @@ def visit_and_test_categories(included_category, excluded_category, benefit_type
decision_review_type: "higher_level_review",
benefit_type: "pension",
included_category: "Eligibility | Wartime Service",
- excluded_category: "Entitlement to Services"
+ excluded_category: "Entitlement to Services",
+ mst_pact: false
)
test_issue_categories(
decision_review_type: "higher_level_review",
benefit_type: "vha",
included_category: "Eligibility for Dental Treatment",
- excluded_category: "Entitlement to Services"
+ excluded_category: "Entitlement to Services",
+ mst_pact: false
)
test_issue_categories(
decision_review_type: "supplemental_claim",
benefit_type: "fiduciary",
included_category: "Appointment of a Fiduciary (38 CFR 13.100)",
- excluded_category: "Entitlement to Services"
+ excluded_category: "Entitlement to Services",
+ mst_pact: false
)
end
end
@@ -86,7 +112,8 @@ def visit_and_test_categories(included_category, excluded_category, benefit_type
decision_review_type: "appeal",
benefit_type: "not applicable to appeal",
included_category: "Unknown Issue Category",
- excluded_category: "Entitlement to Services"
+ excluded_category: "Entitlement to Services",
+ mst_pact: true
)
end
end
diff --git a/spec/feature/intake/supplemental_claim_spec.rb b/spec/feature/intake/supplemental_claim_spec.rb
index 37b7611ae06..46edc8bc7c1 100644
--- a/spec/feature/intake/supplemental_claim_spec.rb
+++ b/spec/feature/intake/supplemental_claim_spec.rb
@@ -385,7 +385,8 @@ def start_supplemental_claim(
VeteranClaimant.create!(
decision_review: supplemental_claim,
- participant_id: test_veteran.participant_id
+ participant_id: test_veteran.participant_id,
+ payee_code: "11"
)
supplemental_claim.start_review!
diff --git a/spec/feature/queue/ama_queue_spec.rb b/spec/feature/queue/ama_queue_spec.rb
index 2fc1e42826a..d186f5732f5 100644
--- a/spec/feature/queue/ama_queue_spec.rb
+++ b/spec/feature/queue/ama_queue_spec.rb
@@ -78,9 +78,17 @@ def valid_document_id
create(:ama_judge_assign_task, assigned_to: judge_user, parent: root_task)
end
+ # This task is for holding legacy appeals. The factory will create an attached legacy appeal.
+ # Attach an attorney task from :attorney task
+ let!(:legacy_appeal_task) do
+ build(:task, id: "1010", assigned_to: attorney_user, assigned_by_id: "3",
+ assigned_to_id: "2", assigned_to_type: "User", type: "AttorneyTask", created_at: 5.days.ago)
+ end
+
let(:poa_name) { "Test POA" }
let(:veteran_participant_id) { "600085544" }
let(:file_numbers) { Array.new(3) { Random.rand(999_999_999).to_s } }
+
let!(:appeals) do
[
create(
@@ -135,6 +143,13 @@ def valid_document_id
assigned_to: attorney_user,
assigned_by: judge_user,
appeal: appeals.third
+ ),
+ create(
+ :ama_attorney_task,
+ :in_progress,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ appeal: legacy_appeal_task.appeal
)
]
end
@@ -194,7 +209,6 @@ def valid_document_id
find(".cf-select__control", text: "Select a type").click
find("div", class: "cf-select__option", text: "Serious illness").click
-
click_on "Submit"
expect(page).to have_content("AOD status has been granted due to Serious illness")
@@ -205,6 +219,74 @@ def valid_document_id
expect(motion.reason).to eq(Constants.AOD_REASONS.serious_illness)
end
+ scenario "Appeal redirects to Draft Decisions page when 'Decision ready for review' is clicked." do
+ visit "/queue/appeals/#{appeals.first.uuid}"
+ # We reload the page because the page errors first load for some reason?
+ visit current_path
+
+ # pop the actions dropdown open and click the 'Decision ready for review' option.
+ find(".cf-select__control", text: "Select an action").click
+ click_dropdown(prompt: "Select an action", text: "Decision ready for review")
+
+ # Validate that the path changed to the expected location.
+ path_array = current_path.split("/")
+ expect(path_array[-1] == "dispositions")
+ expect(path_array[-2] == "draft_decision")
+ end
+
+ scenario "Appeal contains MST PACT labels in timeline." do
+ visit "/queue/appeals/#{appeals.first.uuid}"
+
+ # load in the timeline data
+ appeal = appeals[0]
+ iup = IssuesUpdateTask.create!(
+ appeal: appeal,
+ parent: appeal.root_task,
+ assigned_to: Organization.find_by_url("bva-intake"),
+ assigned_by: RequestStore[:current_user]
+ )
+ set = CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: "test category",
+ benefit_type: "benefit type",
+ original_mst: false,
+ original_pact: false,
+ edit_mst: true,
+ edit_pact: true,
+ mst_edit_reason: "MST reason",
+ pact_edit_reason: "PACT reason"
+ )
+ iup.format_instructions(set)
+ iup.completed!
+
+ # We reload the page because the page sometimes errors first load for some reason,
+ # also ensures that the timeline is refreshed with the current data.
+ visit current_path
+
+ click_on "View task instructions"
+
+ expect(page).to have_content("ORIGINAL")
+ expect(page).to have_content("Special Issues: None")
+ expect(page).to have_content("UPDATED")
+ expect(page).to have_content("Special Issues: MST, PACT")
+ end
+
+ scenario "Appeal redirects to special issues page when 'Decision ready for review' is clicked." do
+ visit "/queue/appeals/#{legacy_appeal_task.appeal.external_id}"
+
+ # We reload the page because the page sometimes errors first load for some reason?
+ visit current_path
+
+ # pop the actions dropdown open and click the 'Decision ready for review' option.
+ find(".cf-select__control", text: "Select an action").click
+ click_dropdown(prompt: "Select an action", text: "Decision ready for review")
+
+ # Validate that the path changed to the expected location.
+ path_array = current_path.split("/")
+ expect(path_array[-1] == "special_issues")
+ expect(path_array[-2] == "draft_decision")
+ end
+
context "when there is an error loading addresses" do
before do
allow_any_instance_of(Fakes::BGSService).to receive(:find_address_by_participant_id)
@@ -453,6 +535,9 @@ def valid_document_id
judgeteam.add_user(attorney_user)
User.authenticate!(user: judge_user)
+
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
end
def judge_assign_to_attorney
@@ -482,12 +567,6 @@ def judge_assign_to_attorney
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
- expect(page.has_no_content?("Select special issues")).to eq(true)
-
expect(page).to have_content("Add decisions")
# Add a first decision issue
@@ -560,11 +639,6 @@ def judge_assign_to_attorney
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
-
expect(page).to have_content("Add decisions")
expect(page).to have_content("Allowed")
expect(page).to have_content("Remanded")
@@ -610,11 +684,6 @@ def judge_assign_to_attorney
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
-
expect(page).to have_content("Add decisions")
# Add a first decision issue
@@ -684,11 +753,6 @@ def judge_assign_to_attorney
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
-
expect(page).to have_content("Add decisions")
click_on "Continue"
@@ -791,11 +855,6 @@ def judge_assign_to_attorney
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
-
expect(page).to have_content("Add decisions")
# Add a first decision issue
@@ -871,7 +930,11 @@ def judge_assign_to_attorney
it_behaves_like "Judge has a case to assign to an attorney"
context "overtime_revamp feature enabled with different overtime values" do
- before { FeatureToggle.enable!(:overtime_revamp) }
+ before do
+ FeatureToggle.enable!(:overtime_revamp)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
after { FeatureToggle.disable!(:overtime_revamp) }
it_behaves_like "Judge has a case to assign to an attorney" do
let(:overtime) { true }
@@ -897,6 +960,8 @@ def judge_assign_to_attorney
before do
org.add_user(user)
User.authenticate!(user: user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
end
it "successfully loads the individual queue " do
diff --git a/spec/feature/queue/ama_queue_workflow_spec.rb b/spec/feature/queue/ama_queue_workflow_spec.rb
new file mode 100644
index 00000000000..00f8ff8df94
--- /dev/null
+++ b/spec/feature/queue/ama_queue_workflow_spec.rb
@@ -0,0 +1,426 @@
+# frozen_string_literal: true
+
+feature "Attorney checkout flow", :all_dbs do
+ include IntakeHelpers
+
+ let!(:bva_intake_admin_user) { create(:user, roles: ["Mail Intake"]) }
+ let!(:bva_intake) { BvaIntake.singleton }
+
+ let!(:vanilla_vet) do
+ Generators::Veteran.build(
+ file_number: "67845673",
+ first_name: "Bryan",
+ last_name: "Libby",
+ participant_id: "23434565"
+ )
+ end
+ let!(:veteran) do
+ create(
+ :veteran,
+ first_name: "Vick",
+ bgs_veteran_record: {
+ first_name: "Vick",
+ address_line1: "1234 Main Street",
+ country: "USA",
+ zip_code: "12345",
+ state: "FL",
+ city: "Orlando",
+ file_number: file_numbers[0]
+ },
+ file_number: file_numbers[0]
+ )
+ end
+
+ let(:attorney_first_name) { "Robby" }
+ let(:attorney_last_name) { "McDobby" }
+ let!(:attorney_user) do
+ create(:user, full_name: "#{attorney_first_name} #{attorney_last_name}")
+ end
+
+ let!(:vacols_attorney) { create(:staff, :attorney_role, user: attorney_user) }
+
+ let(:judge_user) { create(:user, station_id: User::BOARD_STATION_ID, full_name: "Aaron Judge") }
+ let!(:vacols_judge) { create(:staff, :judge_role, user: judge_user) }
+
+ let!(:judge_team) do
+ JudgeTeam.create_for_judge(judge_user).tap { |jt| jt.add_user(attorney_user) }
+ end
+ let!(:vacols_atty) do
+ create(
+ :staff,
+ :attorney_role,
+ sdomainid: attorney_user.css_id,
+ snamef: attorney_first_name,
+ snamel: attorney_last_name
+ )
+ end
+ # creation of vet with contention
+ let(:file_numbers) { Array.new(3) { Random.rand(999_999_999).to_s } }
+ let!(:appeal) do
+ create(
+ :appeal,
+ :advanced_on_docket_due_to_age,
+ created_at: 1.day.ago,
+ veteran: veteran,
+ documents: create_list(:document, 5, file_number: file_numbers[0], upload_date: 4.days.ago),
+ request_issues: build_list(
+ :request_issue,
+ 3,
+ contested_issue_description: "Knee pain",
+ decision_date: 2.days.ago,
+ veteran_participant_id: veteran.participant_id
+ )
+ )
+ end
+ # Creation of vanilla vet. This is a vet without a contention.
+ let!(:appeal_vanilla_vet) do
+ create(
+ :appeal,
+ :advanced_on_docket_due_to_age,
+ created_at: 3.months.ago,
+ veteran:
+ vanilla_vet,
+ documents: create_list(:document, 5, file_number: file_numbers[0], upload_date: 4.days.ago),
+ request_issues: build_list(
+ :request_issue,
+ 3,
+ contested_issue_description: "Knee pain",
+ decision_date: 2.days.ago,
+ veteran_participant_id: veteran_participant_id
+ )
+ )
+ end
+
+ let!(:poa_address) { "123 Poplar St." }
+ let!(:participant_id) { "600153863" }
+
+ let!(:root_task) { create(:root_task, appeal: appeal) }
+ let!(:parent_task) do
+ create(:ama_judge_assign_task, appeal: appeal, assigned_to: judge_user, parent: root_task)
+ end
+
+ let(:poa_name) { "Test POA" }
+ let(:veteran_participant_id) { "600085544" }
+
+ let(:judge_task) do
+ create(
+ :ama_judge_decision_review_task,
+ appeal: appeal,
+ assigned_to: judge_user,
+ parent: root_task
+ )
+ end
+
+ let!(:attorney_tasks) do
+ create(
+ :ama_attorney_task,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ appeal: appeal,
+ parent: judge_task
+ )
+ end
+
+ let!(:root_task2) { create(:root_task, appeal: appeal_vanilla_vet) }
+ let!(:parent_task2) do
+ create(:ama_judge_assign_task, appeal: appeal_vanilla_vet, assigned_to: judge_user, parent: root_task2)
+ end
+
+ let(:poa_name2) { "Test POA" }
+ let(:veteran_participant_id2) { "600085544" }
+
+ let(:judge_task2) do
+ create(
+ :ama_judge_decision_review_task,
+ appeal: appeal_vanilla_vet,
+ assigned_to: judge_user,
+ parent: root_task2
+ )
+ end
+
+ let!(:attorney_tasks2) do
+ create(
+ :ama_attorney_task,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ appeal: appeal_vanilla_vet,
+ parent: judge_task2
+ )
+ end
+
+ let!(:colocated_team) do
+ Colocated.singleton.tap { |org| org.add_user(create(:user)) }
+ end
+
+ # format date to be yesterday in month/day/year format
+ let!(:issue_date) do
+ Time.zone.yesterday.strftime("%m/%d/%Y")
+ end
+
+ before do
+ # creates admin user
+ User.authenticate!(user: bva_intake_admin_user)
+ OrganizationsUser.make_user_admin(bva_intake_admin_user, bva_intake)
+ # joins the user with the organization to grant access to role and org permissions
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:acd_distribute_by_docket_date)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:acd_distribute_by_docket_date)
+ end
+
+ # Adding a new issue to appeal
+ context "AC 1.1 It passes the feature tests for adding a new issue appeal MST" do
+ scenario "Adding a new issue with MST" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST")
+ end
+ end
+
+ context "AC 1.2 It passes the feature tests for adding a new issue appeal PACT" do
+ scenario "Adding a new issue with PACT" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ check("PACT Act", allow_label_click: true, visible: false)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: PACT")
+ end
+ end
+ context "AC 1.3 It passes the feature tests for adding a new issue appeal MST + PACT" do
+ scenario "Adding a new issue with PACT" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ add_intake_nonrating_issue(date: issue_date)
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ end
+ end
+
+ # Adding a new issue to appeal coming from a contention
+ context " AC 1.4 It passes the feature tests for adding a new issue appeal MST" do
+ scenario "Adding a new issue with MST" do
+ generate_rating_with_mst_pact(veteran)
+ visit "/appeals/#{appeal.uuid}/edit"
+ visit "/appeals/#{appeal.uuid}/edit"
+ click_on "+ Add issue"
+ choose("rating-radio_3", allow_label_click: true)
+ check("Issue is related to Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Next"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST")
+ end
+ end
+
+ context " AC 1.5 It passes the feature tests for adding a new issue appeal PACT" do
+ scenario "Adding a new issue with PACT" do
+ generate_rating_with_mst_pact(veteran)
+ visit "/appeals/#{appeal.uuid}/edit"
+ visit "/appeals/#{appeal.uuid}/edit"
+ click_on "+ Add issue"
+ choose("rating-radio_3", allow_label_click: true)
+ check("Issue is related to PACT Act", allow_label_click: true, visible: false)
+ click_on "Next"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: PACT")
+ end
+ end
+
+ context " AC 1.6 It passes the feature tests for adding a new issue appeal MST & PACT" do
+ scenario "Adding a new issue with MST & PACT" do
+ generate_rating_with_mst_pact(veteran)
+ visit "/appeals/#{appeal.uuid}/edit"
+ visit "/appeals/#{appeal.uuid}/edit"
+ click_on "+ Add issue"
+ choose("rating-radio_3", allow_label_click: true)
+ check("Issue is related to Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("Issue is related to PACT Act", allow_label_click: true, visible: false)
+ click_on "Next"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ end
+ end
+
+ context " AC 2.5 adding a new issue appeal MST & PACT coming from a contention, then removing MST/PACT designation" do
+ scenario "Adding a new issue with MST & PACT" do
+ generate_rating_with_mst_pact(veteran)
+ visit "/appeals/#{appeal.uuid}/edit"
+ visit "/appeals/#{appeal.uuid}/edit"
+ click_on "+ Add issue"
+ choose("rating-radio_2", allow_label_click: true)
+ click_on "Next"
+
+ # vha modal check
+ radio_choices = page.all(".cf-form-radio-option > label")
+ radio_choices[1].click
+ click_on "Add this issue"
+
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ find("label[for='Military Sexual Trauma (MST)']").click
+ find("label[for='PACT Act']").click
+ find("#Edit-issue-button-id-1").click
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal.uuid}"
+ refresh
+ expect(page).to have_content("Special Issues: None")
+ end
+ end
+
+ # Editing an issue on an appeal
+ context " AC 2.1 It passes the feature tests for editing an issue on an appeal by adding MST" do
+ scenario "Editing an issue with MST" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ check("PACT Act", allow_label_click: true, visible: false)
+ add_intake_nonrating_issue(date: issue_date)
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ find("#Edit-issue-button-id-1").click
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ end
+ end
+
+ context "AC 2.2 It passes the feature tests for editing an issue on an appeal by adding PACT" do
+ scenario "Editing an issue with MST" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ add_intake_nonrating_issue(date: issue_date)
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ find("label[for='PACT Act']").click
+ find("#Edit-issue-button-id-1").click
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ end
+ end
+
+ context "AC 2.3 It passes the feature tests for editing an issue on an appeal by adding MST + PACT" do
+ scenario "Editing an issue with MST + PACT" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ add_intake_nonrating_issue(date: issue_date)
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ find("label[for='PACT Act']").click
+ find("#Edit-issue-button-id-1").click
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ end
+ end
+ context "AC 2.4 It passes the feature tests for editing an issue on an appeal by removing PACT" do
+ scenario "Editing an issue with MST" do
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ click_on "+ Add issue"
+ add_intake_nonrating_issue(date: issue_date)
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ find("label[for='PACT Act']").click
+ find("#Edit-issue-button-id-1").click
+ find("#issue-action-3").find(:xpath, "option[3]").select_option
+ find("label[for='PACT Act']").click
+ find("#Edit-issue-button-id-1").click
+ click_on "Save"
+ click_on "Yes, save"
+ visit "/queue/appeals/#{appeal_vanilla_vet.uuid}"
+ refresh
+ click_on "View task instructions"
+ expect(page).to have_content("Special Issues: MST")
+ end
+ end
+
+ # Removing an issue on an appeal
+ context "AC 3.1 ,3.2 ,3.3 It passes the feature tests for removing an issue on an appeal with MST + PACT" do
+ scenario "Removing an issue on an appeal with MST + PACT" do
+ appeal_vanilla_vet.request_issues[0].update(mst_status: true)
+ appeal_vanilla_vet.request_issues[1].update(pact_status: true)
+ appeal_vanilla_vet.request_issues[2].update(mst_status: true, pact_status: true)
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ visit "/appeals/#{appeal_vanilla_vet.uuid}/edit"
+ # Adding issue so entire appeal is deleted
+ click_on "+ Add issue"
+ add_intake_nonrating_issue(date: issue_date)
+ 3.times do
+ find("#issue-action-0").find(:xpath, "option[2]").select_option
+ click_on "Yes, remove issue"
+ end
+ click_on "Save"
+ click_on "Yes, save"
+ using_wait_time(10) do
+ expect(page).to have_content("You have successfully added 1 issue and removed 3 issues.")
+ end
+ end
+ end
+end
diff --git a/spec/feature/queue/attorney_checkout_flow_spec.rb b/spec/feature/queue/attorney_checkout_flow_spec.rb
index 79502856e63..6f1576a5b70 100644
--- a/spec/feature/queue/attorney_checkout_flow_spec.rb
+++ b/spec/feature/queue/attorney_checkout_flow_spec.rb
@@ -10,6 +10,19 @@
let(:valid_document_id) { "12345678.123" }
let(:invalid_document_id) { "222333" }
+ before do
+ User.authenticate!(user: attorney_user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ end
+
+ after do
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ end
+
context "given a valid ama appeal" do
before do
root_task = create(:root_task, appeal: appeal)
@@ -66,30 +79,25 @@
visit "/queue"
click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
- # Ensure the issue is on the case details screen
- expect(page).to have_content(issue_description)
- expect(page).to have_content(issue_note)
- expect(page).to have_content("Diagnostic code: #{diagnostic_code}")
- expect(page).to have_content "Correct issues"
+ using_wait_time(8) do
+ # Ensure the issue is on the case details screen
+ expect(page).to have_content(issue_description)
+ expect(page).to have_content(issue_note)
+ expect(page).to have_content("Diagnostic code: #{diagnostic_code}")
+ expect(page).to have_content "Correct issues"
+ end
click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
- expect(page).to have_content("Select special issues")
-
- expect(page.find("label[for=no_special_issues]")).to have_content("No Special Issues")
- expect(page).to have_content("Blue Water")
- expect(page).to have_content("Burn Pit")
- expect(page).to have_content("Military Sexual Trauma (MST)")
- find("label", text: "Blue Water").click
click_on "Continue"
- click_on "Continue"
+ using_wait_time(8) do
+ # Ensure the issue is on the select disposition screen
+ expect(page).to have_content(issue_description)
+ expect(page).to have_content(issue_note)
- # Ensure the issue is on the select disposition screen
- expect(page).to have_content(issue_description)
- expect(page).to have_content(issue_note)
-
- expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+ expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+ end
click_on "Continue"
expect(page).to have_content "You must add a decision before you continue."
@@ -97,6 +105,10 @@
# Add a first decision issue
all("button", text: "+ Add decision", count: 2)[0].click
expect(page).to have_content COPY::DECISION_ISSUE_MODAL_TITLE
+ expect(page).to have_content "Blue Water"
+ expect(page).to have_content "Burn Pit"
+ expect(page).to have_content "Military Sexual Trauma (MST)"
+ expect(page).to have_content "PACT Act"
click_on "Save"
@@ -262,16 +274,6 @@
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- expect(page).to have_content("Select special issues")
- expect(page.find("label[for=no_special_issues]")).to have_content("No Special Issues")
- expect(page).to have_content("Blue Water")
- expect(page).to have_content("Burn Pit")
- expect(page).to have_content("Military Sexual Trauma (MST)")
- find("label", text: "Burn Pit").click
- click_on "Continue"
-
- expect(page).to have_content("Added to 2 issues", count: 2)
-
expect(page).to have_content(decision_issue_text)
# Update the decision issue
@@ -304,7 +306,9 @@
expect(appeal.decision_issues.count).to eq 3
expect(appeal.request_decision_issues.count).to eq(4)
# The decision issue should have the new content the judge added
- expect(appeal.decision_issues.first.description).to eq(updated_decision_issue_text)
+ using_wait_time(5) do
+ expect(appeal.decision_issues.reload.first.description).to eq(updated_decision_issue_text)
+ end
remand_reasons = appeal.decision_issues.where(disposition: "remanded").map do |decision|
decision.remand_reasons.first.code
@@ -331,8 +335,6 @@
)
end
- before { User.authenticate!(user: attorney_user) }
-
context "with a single issue" do
let(:case_issues) { create_list(:case_issue, 1) }
@@ -358,6 +360,22 @@
expect(appeal.special_issue_list.vamc).to eq(true)
click_on "Continue"
+ # Checking if MST and PACT are shown under Edit Issue
+ click_on "Edit Issue"
+ expect(page).to have_content "Military Sexual Trauma (MST)"
+ expect(page).to have_content "PACT Act"
+
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ expect(page).to have_checked_field("Military Sexual Trauma (MST)", visible: false)
+ expect(page).to have_checked_field("PACT Act", visible: false)
+
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+ expect(page).not_to have_checked_field("Military Sexual Trauma (MST)", visible: false)
+ expect(page).not_to have_checked_field("PACT Act", visible: false)
+ click_on "Back"
+
expect(page).to have_content "Select Dispositions"
cancel_button = page.find "#button-cancel-button"
@@ -707,4 +725,426 @@ def select_issue_level_options(opts)
end
end
end
+
+ describe "MST/PACT identification on ama and legacy appeal issues" do
+ let(:issue_note) { "Test note" }
+ let(:issue_description) { "Test description" }
+ let(:other_issue_text) { "Decision issue text here" }
+ let(:allowed_issue_text) { "This is an allowed issue" }
+ let(:disposition_allowed) { "Allowed" }
+ let(:benefit_type) { "Compensation" }
+ let(:diagnostic_code) { "5008" }
+
+ let!(:appeal) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: build_list(
+ :request_issue, 1,
+ contested_issue_description: issue_description,
+ notes: issue_note,
+ contested_rating_issue_diagnostic_code: diagnostic_code
+ )
+ )
+ end
+
+ let(:appeal_with_mst_pact) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: [
+ create(
+ :request_issue,
+ benefit_type: "compensation",
+ mst_status: true,
+ pact_status: true,
+ nonrating_issue_description: issue_description,
+ notes: issue_note,
+ contested_rating_issue_diagnostic_code: diagnostic_code
+ )
+ ]
+ )
+ end
+
+ let!(:appeal_multiple_issues) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: build_list(
+ :request_issue, 3,
+ contested_issue_description: issue_description,
+ notes: issue_note,
+ contested_rating_issue_diagnostic_code: diagnostic_code
+ )
+ )
+ end
+
+ context " - AMA Appeals" do
+ context "given a single issue" do
+ before do
+ root_task = create(:root_task, appeal: appeal)
+ parent_task = create(
+ :ama_judge_decision_review_task,
+ assigned_to: judge_user,
+ parent: root_task
+ )
+
+ create(
+ :ama_attorney_task,
+ :in_progress,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ parent: parent_task
+ )
+
+ User.authenticate!(user: attorney_user)
+ BvaDispatch.singleton.add_user(create(:user))
+ end
+
+ it " - add both mst and pact to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
+
+ expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+
+ all("button", text: "+ Add decision", count: 1)[0].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Continue"
+
+ safe_click "#select-judge"
+ click_dropdown(index: 0)
+ fill_in "document_id", with: valid_document_id
+ fill_in "notes", with: "note"
+
+ click_on "Continue"
+
+ User.authenticate!(user: judge_user)
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+ expect(page).to have_selector(".cf-mst-badge")
+ expect(page).to have_selector(".cf-pact-badge")
+ expect(page).to have_content("Special Issues: MST and PACT")
+ expect(appeal.decision_issues.first.mst_status).to eq(true)
+ expect(appeal.decision_issues.first.pact_status).to eq(true)
+ end
+
+ it " - add mst to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
+
+ expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+
+ all("button", text: "+ Add decision", count: 1)[0].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Continue"
+
+ safe_click "#select-judge"
+ click_dropdown(index: 0)
+ fill_in "document_id", with: valid_document_id
+ fill_in "notes", with: "note"
+
+ click_on "Continue"
+
+ User.authenticate!(user: judge_user)
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+
+ expect(page).to have_selector(".cf-mst-badge")
+ expect(page).to_not have_selector(".cf-pact-badge")
+ expect(page).to have_content("Special Issues: MST")
+ expect(appeal.decision_issues.first.mst_status).to eq(true)
+ expect(appeal.decision_issues.first.pact_status).to eq(false)
+ end
+
+ it " - add pact to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
+
+ expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+
+ all("button", text: "+ Add decision", count: 1)[0].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Continue"
+
+ safe_click "#select-judge"
+ click_dropdown(index: 0)
+ fill_in "document_id", with: valid_document_id
+ fill_in "notes", with: "note"
+
+ click_on "Continue"
+
+ User.authenticate!(user: judge_user)
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.veteran_file_number})"
+
+ expect(page).to_not have_selector(".cf-mst-badge")
+ expect(page).to have_selector(".cf-pact-badge")
+ expect(page).to have_content("Special Issues: PACT")
+ expect(appeal.decision_issues.first.mst_status).to eq(false)
+ expect(appeal.decision_issues.first.pact_status).to eq(true)
+ end
+
+ context " - with mst and pact designated" do
+ before do
+ root_task = create(:root_task, appeal: appeal_with_mst_pact)
+ parent_task = create(
+ :ama_judge_decision_review_task,
+ assigned_to: judge_user,
+ parent: root_task
+ )
+
+ create(
+ :ama_attorney_task,
+ :in_progress,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ parent: parent_task
+ )
+
+ User.authenticate!(user: attorney_user)
+ BvaDispatch.singleton.add_user(create(:user))
+ end
+
+ it " removes mst and pact from the issue" do
+ visit "/queue"
+ click_on "#{appeal_with_mst_pact.veteran_full_name} (#{appeal_with_mst_pact.veteran_file_number})"
+ expect(page).to have_selector(".cf-mst-badge")
+ expect(page).to have_selector(".cf-pact-badge")
+ expect(page).to have_content("Special Issues: MST and PACT")
+
+ click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
+
+ all("button", text: "+ Add decision", count: 1)[0].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Continue"
+
+ safe_click "#select-judge"
+ click_dropdown(index: 0)
+ fill_in "document_id", with: valid_document_id
+ fill_in "notes", with: "note"
+
+ click_on "Continue"
+
+ User.authenticate!(user: judge_user)
+ visit "/queue"
+ click_on "#{appeal_with_mst_pact.veteran_full_name} (#{appeal_with_mst_pact.veteran_file_number})"
+
+ expect(page).to have_content("Special Issues: None")
+ expect(appeal_with_mst_pact.decision_issues.first.mst_status).to eq(false)
+ expect(appeal_with_mst_pact.decision_issues.first.pact_status).to eq(false)
+ end
+ end
+ end
+
+ context "given multiple issues" do
+ before do
+ root_task = create(:root_task, appeal: appeal_multiple_issues)
+ parent_task = create(
+ :ama_judge_decision_review_task,
+ assigned_to: judge_user,
+ parent: root_task
+ )
+
+ create(
+ :ama_attorney_task,
+ :in_progress,
+ assigned_to: attorney_user,
+ assigned_by: judge_user,
+ parent: parent_task
+ )
+
+ User.authenticate!(user: attorney_user)
+ BvaDispatch.singleton.add_user(create(:user))
+ end
+
+ it " - add one of each mst, pact, and both to issues" do
+ visit "/queue"
+ click_on "#{appeal_multiple_issues.veteran_full_name} (#{appeal_multiple_issues.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.label)
+
+ expect(page).to have_content COPY::DECISION_ISSUE_PAGE_TITLE
+
+ all("button", text: "+ Add decision", count: 3)[0].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ all("button", text: "+ Add decision", count: 3)[1].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ all("button", text: "+ Add decision", count: 3)[2].click
+ fill_in "Text Box", with: allowed_issue_text
+ find(".cf-select__control", text: "Select disposition").click
+ find("div", class: "cf-select__option", text: disposition_allowed).click
+ find(".cf-select__control", text: benefit_type).click
+ find("div", class: "cf-select__option", text: benefit_type).click
+ find(".cf-select__control", text: diagnostic_code).click
+ find("div", class: "cf-select__option", text: diagnostic_code).click
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ click_on "Continue"
+
+ safe_click "#select-judge"
+ click_dropdown(index: 0)
+ fill_in "document_id", with: valid_document_id
+ fill_in "notes", with: "note"
+
+ click_on "Continue"
+
+ User.authenticate!(user: judge_user)
+ visit "/queue"
+ click_on "#{appeal_multiple_issues.veteran_full_name} (#{appeal_multiple_issues.veteran_file_number})"
+
+ expect(page).to have_selector(".cf-mst-badge")
+ expect(page).to have_selector(".cf-pact-badge")
+ expect(page).to have_content("Special Issues: MST and PACT")
+ expect(page).to have_content("Special Issues: MST")
+ expect(page).to have_content("Special Issues: PACT")
+ expect(appeal_multiple_issues.decision_issues.first.mst_status).to eq(true)
+ expect(appeal_multiple_issues.decision_issues.first.pact_status).to eq(true)
+ expect(appeal_multiple_issues.decision_issues.second.mst_status).to eq(true)
+ expect(appeal_multiple_issues.decision_issues.second.pact_status).to eq(false)
+ expect(appeal_multiple_issues.decision_issues.third.mst_status).to eq(false)
+ expect(appeal_multiple_issues.decision_issues.third.pact_status).to eq(true)
+ end
+ end
+ end
+
+ context " - Legacy Appeals " do
+ let!(:appeal) do
+ create(
+ :legacy_appeal,
+ :with_veteran,
+ vacols_case: create(
+ :case,
+ :assigned,
+ user: attorney_user,
+ case_issues: [
+ create(:case_issue, issmst: "N", isspact: "N"),
+ create(:case_issue, issmst: "Y", isspact: "Y")
+ ]
+ )
+ )
+ end
+
+ it " - add mst to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Decision ready for review", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+ first("a", text: "Edit Issue").click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: MST")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "Y"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "N"
+ end
+
+ it " - add pact to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Decision ready for review", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+ first("a", text: "Edit Issue").click
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: PACT")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "Y"
+ end
+
+ it " - add mst and pact to an issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Decision ready for review", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+ first("a", text: "Edit Issue").click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "Y"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "Y"
+ end
+
+ it " - remove mst and pact from issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Decision ready for review", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+ all("a", text: "Edit Issue")[1].click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: None")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[1].issmst).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[1].isspact).to eq "N"
+ end
+ end
+ end
end
diff --git a/spec/feature/queue/bva_dispatch_return_flow_spec.rb b/spec/feature/queue/bva_dispatch_return_flow_spec.rb
index 646d5db4a40..21dab338a16 100644
--- a/spec/feature/queue/bva_dispatch_return_flow_spec.rb
+++ b/spec/feature/queue/bva_dispatch_return_flow_spec.rb
@@ -89,14 +89,14 @@ def attorney_checkout
visit "/queue"
click_on veteran_full_name
click_dropdown(prompt: "Select an action", text: "Decision ready for review")
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
+ find("#no_special_issues", visible: false).sibling("label").set(true)
click_on "Continue"
-
+ if page.has_content?("Choose at least one.")
+ find("#no_special_issues", visible: false).sibling("label").set(true)
+ click_on "Continue"
+ end
click_on "+ Add decision"
fill_in "Text Box", with: "test"
-
find(".cf-select__control", text: "Select disposition").click
find("div", class: "cf-select__option", text: "Allowed").click
click_on "Save"
@@ -112,7 +112,12 @@ def judge_checkout
visit "/queue"
click_on "(#{appeal.veteran_file_number})"
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- click_on "Continue" if page.has_content?("No Special Issues")
+ find("#no_special_issues", visible: false).sibling("label").set(true)
+ click_on "Continue"
+ if page.has_content?("Choose at least one.")
+ find("#no_special_issues", visible: false).sibling("label").set(true)
+ click_on "Continue"
+ end
click_on "Continue"
find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["COMPLEXITY"]["easy"]).click
text_to_click = "1 - #{Constants::JUDGE_CASE_REVIEW_OPTIONS['QUALITY']['does_not_meet_expectations']}"
diff --git a/spec/feature/queue/case_details_spec.rb b/spec/feature/queue/case_details_spec.rb
index 30d944880bc..579ab417382 100644
--- a/spec/feature/queue/case_details_spec.rb
+++ b/spec/feature/queue/case_details_spec.rb
@@ -679,7 +679,9 @@ def wait_for_page_render
scenario "access the appeal's case details" do
reload_case_detail_page(appeal.external_id)
- expect(page).to have_content(COPY::DUPLICATE_PHONE_NUMBER_TITLE)
+ using_wait_time(5) do
+ expect(page).to have_content(COPY::DUPLICATE_PHONE_NUMBER_TITLE)
+ end
bgs.inaccessible_appeal_vbms_ids = []
allow_any_instance_of(Fakes::BGSService).to receive(:fetch_veteran_info)
@@ -2208,6 +2210,95 @@ def wait_for_page_render
end
end
end
+
+ describe "MST and PACT issues" do
+ let!(:mst_appeal) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: [
+ create(
+ :request_issue,
+ benefit_type: "compensation",
+ mst_status: true,
+ pact_status: false,
+ nonrating_issue_description: "description here",
+ notes: "issue notes here"
+ )
+ ]
+ )
+ end
+ let!(:pact_appeal) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: [
+ create(
+ :request_issue,
+ benefit_type: "compensation",
+ mst_status: false,
+ pact_status: true,
+ nonrating_issue_description: "description here",
+ notes: "issue notes here"
+ )
+ ]
+ )
+ end
+
+ let(:intake_user) { create(:user, css_id: "BVA_INTAKE_USER", station_id: "101") }
+
+ context "when there is a pact issue prechecked" do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ BvaIntake.singleton.add_user(intake_user)
+ User.authenticate!(user: intake_user)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ end
+
+ it "the page shows the Special Issues: PACT Badge" do
+ visit "/queue/appeals/#{pact_appeal.external_id}"
+ page.find("a", text: "refresh the page").click if page.has_text?("Unable to load this case")
+ expect(page).to have_content("Special Issues: PACT")
+ end
+
+ it "the page does not show the Special Issues: MST Badge" do
+ visit "/queue/appeals/#{pact_appeal.external_id}"
+ page.find("a", text: "refresh the page").click if page.has_text?("Unable to load this case")
+ expect(page).to_not have_content("Special Issues: MST")
+ end
+ end
+
+ context "when there is an mst issue prechecked" do
+ before do
+ BvaIntake.singleton.add_user(intake_user)
+ User.authenticate!(user: intake_user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ end
+
+ it "the page shows the Special Issues: MST Badge" do
+ visit "/queue/appeals/#{mst_appeal.external_id}"
+ page.find("a", text: "refresh the page").click if page.has_text?("Unable to load this case")
+ expect(page).to have_content("Special Issues: MST")
+ end
+
+ it "the page does not show the Special Issues: PACT Badge" do
+ visit "/queue/appeals/#{mst_appeal.external_id}"
+ page.find("a", text: "refresh the page").click if page.has_text?("Unable to load this case")
+ expect(page).to_not have_content("Special Issues: PACT")
+ end
+ end
+ end
end
describe "task snapshot" do
diff --git a/spec/feature/queue/cavc_dashboard_spec.rb b/spec/feature/queue/cavc_dashboard_spec.rb
index a2547a9c1af..24702c1e328 100644
--- a/spec/feature/queue/cavc_dashboard_spec.rb
+++ b/spec/feature/queue/cavc_dashboard_spec.rb
@@ -135,8 +135,9 @@
it "user can add issues, edit dispsositions, and save changes" do
issue_description = "Test Issue Description"
Seeds::CavcDecisionReasonData.new.seed!
-
- go_to_dashboard(cavc_remand.remand_appeal.uuid)
+ using_wait_time(8) do
+ go_to_dashboard(cavc_remand.remand_appeal.uuid)
+ end
dropdowns = page.all("div.cf-select__placeholder", exact_text: "Select option")
diff --git a/spec/feature/queue/delete_request_issues_spec.rb b/spec/feature/queue/delete_request_issues_spec.rb
index b5494a06e04..90add585ed5 100644
--- a/spec/feature/queue/delete_request_issues_spec.rb
+++ b/spec/feature/queue/delete_request_issues_spec.rb
@@ -43,13 +43,15 @@
end
context "deleting a request issue that has a decision issue shared with another request issue" do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
it "deletes the request issue but not the shared decision issue" do
appeal = appeal_with_shared_decision_issues
create_judge_decision_review_task_for(appeal)
visit_appeals_page_as_judge(appeal)
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- find("label", text: "No Special Issues").click
- click_on "Continue"
expect(page).to have_content "Added to 2 issues"
expect(page).to have_content "decision with id 1"
expect(page).to have_content "decision with id 2"
@@ -67,10 +69,7 @@
expect(page).to_not have_content "Added to 2 issues"
expect(page).to_not have_content "decision with id 2"
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
+
expect(page).to have_content "decision with id 1"
expect(page).to have_content "another shared decision issue"
end
diff --git a/spec/feature/queue/judge_checkout_flow_spec.rb b/spec/feature/queue/judge_checkout_flow_spec.rb
index 18bdfcd2fdd..ae1c5212eed 100644
--- a/spec/feature/queue/judge_checkout_flow_spec.rb
+++ b/spec/feature/queue/judge_checkout_flow_spec.rb
@@ -16,6 +16,8 @@
# the BVA dispatch team so that the creation of that task (which round robin assigns org tasks) does not fail.
BvaDispatch.singleton.add_user(create(:user))
FeatureToggle.enable!(:das_case_timeliness)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
end
after { FeatureToggle.disable!(:das_case_timeliness) }
@@ -68,10 +70,6 @@
expect(page).to have_content("Select an action")
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- # Special Issues page
- expect(page).to have_content("Select special issues")
- find("label", text: "No Special Issues").click
- click_on "Continue"
# Decision Issues page
click_on "Continue"
@@ -136,6 +134,8 @@
before do
child_task.update!(status: Constants.TASK_STATUSES.completed)
User.authenticate!(user: judge_user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
end
scenario "starts dispatch checkout flow" do
@@ -143,9 +143,13 @@
click_on "(#{appeal.veteran_file_number})"
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- # Special Issues screen
- find("label", text: "No Special Issues").click
- click_on "Continue"
+
+ all("button", text: "+ Add decision")[0].click
+ expect(page).to have_content "Blue Water"
+ expect(page).to have_content "Burn Pit"
+ expect(page).to have_content "Military Sexual Trauma (MST)"
+ expect(page).to have_content "PACT Act"
+ click_on "Close"
# Decision Issues Screen
click_on "Continue"
@@ -184,24 +188,6 @@
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- # Special Issues page
- expect(page).to have_content("Select special issues")
-
- expect(page.find("label[for=no_special_issues]")).to have_content("No Special Issues")
-
- expect(page).to have_content("Blue Water")
- expect(page).to have_content("Burn Pit")
- expect(page).to have_content("Military Sexual Trauma (MST)")
- find("label", text: "Blue Water").click
- expect(page.find("#blue_water", visible: false).checked?).to eq true
- find("label", text: "No Special Issues").click
- expect(page.find("#blue_water", visible: false).checked?).to eq false
- expect(page.find("#blue_water", visible: false).disabled?).to eq true
- find("label", text: "No Special Issues").click
- expect(page.find("#blue_water", visible: false).checked?).to eq false
- find("label", text: "Blue Water").click
- click_on "Continue"
-
# Decision Issues screen
click_on "Continue"
expect(page).to have_content("Evaluate Decision")
@@ -255,6 +241,7 @@
before do
User.authenticate!(user: judge_user)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
end
context "where work product is draft decision" do
@@ -288,6 +275,20 @@
expect(page.find_all(".issue-disposition-dropdown").length).to eq 1
click_on "Edit Issue"
+ # Checking if MST and PACT are shown under Edit Issue
+ expect(page).to have_content "Military Sexual Trauma (MST)"
+ expect(page).to have_content "PACT Act"
+
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ expect(page).to have_checked_field("Military Sexual Trauma (MST)", visible: false)
+ expect(page).to have_checked_field("PACT Act", visible: false)
+
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+ expect(page).not_to have_checked_field("Military Sexual Trauma (MST)", visible: false)
+ expect(page).not_to have_checked_field("PACT Act", visible: false)
+
click_on "Delete Issue"
click_on "Delete issue"
@@ -364,10 +365,6 @@
step("Navigate from case details to decision issues by way of actions dropdown") do
visit("/queue/appeals/#{appeal.external_id}")
click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
expect(page).to have_content(COPY::DECISION_ISSUE_PAGE_TITLE)
end
@@ -507,4 +504,354 @@
expect(page).to have_current_path("/queue/appeals/#{appeal_id}")
end
end
+
+ describe " - mst/pact identification" do
+ context " - AMA appeals" do
+ let(:issue_note) { "Test note" }
+ let(:issue_description) { "Test description" }
+ let(:diagnostic_code) { "5008" }
+ let(:other_issue_text) { "Decision issue text here" }
+ let(:disposition_allowed) { "Allowed" }
+ let(:benefit_type) { "Compensation" }
+ let(:appeal) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: [
+ create(
+ :request_issue,
+ benefit_type: "compensation",
+ nonrating_issue_description: issue_description,
+ notes: issue_note,
+ contested_rating_issue_diagnostic_code: diagnostic_code
+ )
+ ],
+ decision_issues: create_list(:decision_issue, 1)
+ )
+ end
+
+ let(:appeal_with_mst_pact) do
+ create(
+ :appeal,
+ number_of_claimants: 1,
+ request_issues: [
+ create(
+ :request_issue,
+ benefit_type: "compensation",
+ mst_status: true,
+ pact_status: true,
+ nonrating_issue_description: issue_description,
+ notes: issue_note,
+ contested_rating_issue_diagnostic_code: diagnostic_code
+ )
+ ],
+ decision_issues: [
+ create(
+ :decision_issue,
+ disposition: "allowed",
+ benefit_type: "compensation",
+ diagnostic_code: diagnostic_code,
+ mst_status: true,
+ pact_status: true
+ )
+ ]
+ )
+ end
+
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
+
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ end
+
+ context " - given a single issue without mst or pact" do
+ let(:root_task) { create(:root_task, appeal: appeal) }
+ let(:judge_review_task) do
+ create(
+ :ama_judge_decision_review_task,
+ appeal: appeal,
+ parent: root_task,
+ assigned_to: judge_user
+ )
+ end
+ let!(:attorney_task) do
+ create(
+ :ama_attorney_task,
+ appeal: appeal,
+ parent: judge_review_task,
+ assigned_to: attorney_user
+ )
+ end
+
+ before do
+ attorney_task.update!(status: Constants.TASK_STATUSES.completed)
+ create(
+ :request_decision_issue,
+ request_issue: appeal.request_issues.first,
+ decision_issue: appeal.decision_issues.first
+ )
+ User.authenticate!(user: judge_user)
+ end
+
+ scenario " - adds mst to the decision" do
+ visit "/queue"
+ click_on "(#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
+
+ all("button", text: "Edit", count: 1)[0].click
+ fill_in "Text Box", with: other_issue_text
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ expect(page).to have_content "Special Issues: MST"
+
+ click_on "Continue"
+
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["TIMELINESS"]["timely"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["COMPLEXITY"]["easy"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["QUALITY"]["outstanding"]).click
+
+ dummy_note = generate_words 5
+ fill_in "additional-factors", with: dummy_note
+ click_on "Continue"
+
+ decision_issues = appeal.decision_issues
+
+ visit "queue/appeals/#{appeal.uuid}"
+
+ expect(decision_issues[0].mst_status).to eq(true)
+ expect(decision_issues[0].pact_status).to eq(false)
+ end
+
+ scenario " - adds pact to the decision" do
+ visit "/queue"
+ click_on "(#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
+
+ all("button", text: "Edit", count: 1)[0].click
+ fill_in "Text Box", with: other_issue_text
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ expect(page).to have_content "Special Issues: PACT"
+
+ click_on "Continue"
+
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["TIMELINESS"]["timely"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["COMPLEXITY"]["easy"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["QUALITY"]["outstanding"]).click
+
+ dummy_note = generate_words 5
+ fill_in "additional-factors", with: dummy_note
+ click_on "Continue"
+
+ decision_issues = appeal.decision_issues
+
+ visit "queue/appeals/#{appeal.uuid}"
+
+ expect(decision_issues[0].mst_status).to eq(false)
+ expect(decision_issues[0].pact_status).to eq(true)
+ end
+
+ scenario " - adds mst and pact to the decision" do
+ visit "/queue"
+ click_on "(#{appeal.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
+
+ all("button", text: "Edit", count: 1)[0].click
+ fill_in "Text Box", with: other_issue_text
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ expect(page).to have_content "Special Issues: MST and PACT"
+
+ click_on "Continue"
+
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["TIMELINESS"]["timely"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["COMPLEXITY"]["easy"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["QUALITY"]["outstanding"]).click
+
+ dummy_note = generate_words 5
+ fill_in "additional-factors", with: dummy_note
+ click_on "Continue"
+
+ decision_issues = appeal.decision_issues
+
+ visit "queue/appeals/#{appeal.uuid}"
+
+ expect(decision_issues[0].mst_status).to eq(true)
+ expect(decision_issues[0].pact_status).to eq(true)
+ end
+ end
+
+ context " - given a single issue with mst and pact" do
+ let(:root_task) { create(:root_task, appeal: appeal_with_mst_pact) }
+ let(:judge_review_task) do
+ create(
+ :ama_judge_decision_review_task,
+ appeal: appeal_with_mst_pact,
+ parent: root_task,
+ assigned_to: judge_user
+ )
+ end
+ let!(:attorney_task) do
+ create(
+ :ama_attorney_task,
+ appeal: appeal_with_mst_pact,
+ parent: judge_review_task,
+ assigned_to: attorney_user
+ )
+ end
+
+ before do
+ attorney_task.update!(status: Constants.TASK_STATUSES.completed)
+ create(
+ :request_decision_issue,
+ request_issue: appeal_with_mst_pact.request_issues.first,
+ decision_issue: appeal_with_mst_pact.decision_issues.first
+ )
+ User.authenticate!(user: judge_user)
+ end
+
+ scenario " - removes mst and pact from the decision" do
+ visit "/queue"
+ click_on "(#{appeal_with_mst_pact.veteran_file_number})"
+
+ click_dropdown(text: Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT.label)
+
+ all("button", text: "Edit", count: 1)[0].click
+ fill_in "Text Box", with: other_issue_text
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+ click_on "Save"
+
+ expect(page).to have_content "Special Issues: None"
+
+ click_on "Continue"
+
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["TIMELINESS"]["timely"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["COMPLEXITY"]["easy"]).click
+ find("label", text: Constants::JUDGE_CASE_REVIEW_OPTIONS["QUALITY"]["outstanding"]).click
+
+ dummy_note = generate_words 5
+ fill_in "additional-factors", with: dummy_note
+ click_on "Continue"
+
+ decision_issues = appeal_with_mst_pact.decision_issues
+
+ visit "queue/appeals/#{appeal.uuid}"
+
+ expect(decision_issues[0].mst_status).to eq(false)
+ expect(decision_issues[0].pact_status).to eq(false)
+ end
+ end
+ end
+
+ context " - Legacy Appeals" do
+ let!(:appeal) do
+ create(
+ :legacy_appeal,
+ :with_veteran,
+ vacols_case: create(
+ :case,
+ :assigned,
+ user: judge_user,
+ assigner: attorney_user,
+ case_issues: [
+ create(:case_issue, :disposition_allowed),
+ create(:case_issue, :disposition_allowed, issmst: "Y", isspact: "Y")
+ ]
+ )
+ )
+ end
+
+ before do
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ User.authenticate!(user: judge_user)
+ end
+ after { FeatureToggle.disable!(:legacy_mst_pact_identification) }
+
+ scenario " - adds mst to a legacy issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Ready for Dispatch", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+
+ all("a", text: "Edit Issue")[0].click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: MST")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "Y"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "N"
+ end
+
+ scenario " - adds pact to a legacy issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Ready for Dispatch", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+
+ all("a", text: "Edit Issue")[0].click
+ check("PACT Act", allow_label_click: true, visible: false)
+
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: PACT")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "Y"
+ end
+
+ scenario " - adds mst and pact to a legacy issue" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Ready for Dispatch", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+
+ all("a", text: "Edit Issue")[0].click
+ check("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ check("PACT Act", allow_label_click: true, visible: false)
+
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: MST and PACT")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "Y"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "Y"
+ end
+
+ scenario " - adds mst/pact to first legacy issue then remove mst/pact from all issues" do
+ visit "/queue"
+ click_on "#{appeal.veteran_full_name} (#{appeal.sanitized_vbms_id})"
+ click_dropdown(text: "Ready for Dispatch", visible: false)
+ find("label", text: "No Special Issues").click
+ click_on "Continue"
+
+ all("a", text: "Edit Issue")[0].click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+
+ click_on "Continue"
+
+ all("a", text: "Edit Issue")[1].click
+ uncheck("Military Sexual Trauma (MST)", allow_label_click: true, visible: false)
+ uncheck("PACT Act", allow_label_click: true, visible: false)
+
+ click_on "Continue"
+ expect(page).to have_content("Special Issues: None")
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].issmst).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[0].isspact).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[1].issmst).to eq "N"
+ expect(VACOLS::CaseIssue.where(isskey: appeal.vacols_id)[1].isspact).to eq "N"
+ end
+ end
+ end
end
diff --git a/spec/feature/queue/quality_review_flow_spec.rb b/spec/feature/queue/quality_review_flow_spec.rb
index 610bb161017..3abfd6a01f6 100644
--- a/spec/feature/queue/quality_review_flow_spec.rb
+++ b/spec/feature/queue/quality_review_flow_spec.rb
@@ -57,6 +57,9 @@
let!(:qr_instructions) { "Fix this case!" }
before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+
["Reba Janowiec", "Lee Jiang", "Pearl Jurs"].each do |judge_name|
create(
:staff,
@@ -141,9 +144,6 @@
find(".cf-select__control", text: "Select an action").click
find("div", class: "cf-select__option", text: Constants.TASK_ACTIONS.REVIEW_AMA_DECISION.to_h[:label]).click
- find("label", text: "No Special Issues").click
- click_on "Continue"
-
expect(page).to have_content("Add decisions")
all("button", text: "+ Add decision", count: 1)[0].click
expect(page).to have_content COPY::DECISION_ISSUE_MODAL_TITLE
diff --git a/spec/feature/queue/task_queue_spec.rb b/spec/feature/queue/task_queue_spec.rb
index 7cc99c467e6..d7ee0bacd77 100644
--- a/spec/feature/queue/task_queue_spec.rb
+++ b/spec/feature/queue/task_queue_spec.rb
@@ -973,6 +973,8 @@ def validate_pulac_cerullo_tasks_created(task_class, label)
before do
# force objects above to reload to ensure the visit doesn't fail to load them
judge_task.reload
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
# Add a user to the Colocated team so the task assignment will suceed.
Colocated.singleton.add_user(create(:user))
diff --git a/spec/feature/withdrawn_request_issues_spec.rb b/spec/feature/withdrawn_request_issues_spec.rb
index e66869603e2..90d7f910015 100644
--- a/spec/feature/withdrawn_request_issues_spec.rb
+++ b/spec/feature/withdrawn_request_issues_spec.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
feature "attorney checkout flow when appeal has withdrawn request issues", :all_dbs do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
it "displays withdrawn status on case details page" do
appeal = create(:appeal)
judge = create(:user, station_id: User::BOARD_STATION_ID, full_name: "Aaron Judge")
@@ -13,10 +17,6 @@
expect(page).to have_content("Disposition: Withdrawn", wait: 10)
select_decision_ready_for_review
- if !find("#no_special_issues", visible: false).checked?
- find("label", text: "No Special Issues").click
- end
- click_on "Continue"
click_add_decision_on_first_issue
expect_disposition_dropdown_to_be_preselected_with_withdrawn
diff --git a/spec/mappers/issue_mapper_spec.rb b/spec/mappers/issue_mapper_spec.rb
index bc7c1fc844c..fac84a28c60 100644
--- a/spec/mappers/issue_mapper_spec.rb
+++ b/spec/mappers/issue_mapper_spec.rb
@@ -14,7 +14,10 @@
level_2: "03",
level_3: nil,
note: "k" * 120,
- vacols_user_id: "TEST1"
+ vacols_user_id: "TEST1",
+ mst_status: "Y",
+ pact_status: "Y"
+
}
end
@@ -32,7 +35,9 @@
isslev3: nil,
issdesc: "k" * 100,
issaduser: "TEST1",
- issadtime: VacolsHelper.local_time_with_utc_timezone
+ issadtime: VacolsHelper.local_time_with_utc_timezone,
+ issmst: "Y",
+ isspact: "Y"
}
end
@@ -63,7 +68,9 @@
isslev3: nil,
issdesc: "k" * 100,
issmduser: "TEST1",
- issmdtime: VacolsHelper.local_time_with_utc_timezone
+ issmdtime: VacolsHelper.local_time_with_utc_timezone,
+ issmst: "Y",
+ isspact: "Y"
}
end
diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb
index 305168caf38..9ab2bde566c 100644
--- a/spec/models/appeal_spec.rb
+++ b/spec/models/appeal_spec.rb
@@ -1265,6 +1265,116 @@
end
end
+ describe "#mst?" do
+ subject { appeal.mst? }
+
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ end
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ end
+ let(:request_issues) do
+ [
+ create(:request_issue, mst_status: mst_status)
+ ]
+ end
+
+ context "when request issues with mst_status are associated with appeal" do
+ let(:appeal) { create(:appeal, request_issues: request_issues) }
+
+ context "when mst_status is enabled" do
+ let(:mst_status) { true }
+
+ it "returns true" do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context "when mst_status is disabled" do
+ let(:mst_status) { false }
+
+ it "returns false" do
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
+ context "when request issues with mst_status are not associated with appeal and has special_issue_list" do
+ let!(:appeal) { create(:appeal) }
+
+ before do
+ Timecop.freeze(Time.utc(2023, 4, 28, 12, 0, 0))
+ create(:special_issue_list, appeal_id: appeal.id, military_sexual_trauma: military_sexual_trauma)
+ end
+
+ after do
+ Timecop.return
+ end
+
+ context "when military_sexual_trauma is enabled" do
+ let(:military_sexual_trauma) { true }
+
+ it "returns true" do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context "when military_sexual_trauma is disabled" do
+ let(:military_sexual_trauma) { false }
+
+ it "returns false" do
+ expect(subject).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe "#pact?" do
+ subject { appeal.pact? }
+
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ end
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ end
+
+ let(:request_issues) do
+ [
+ create(:request_issue, pact_status: pact_status)
+ ]
+ end
+
+ context "when request issues with pact_status are associated with appeal" do
+ let(:appeal) { create(:appeal, request_issues: request_issues) }
+
+ context "when pact_status is enabled" do
+ let(:pact_status) { true }
+
+ it "returns true" do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context "when pact_status is disabled" do
+ let(:pact_status) { false }
+
+ it "returns false" do
+ expect(subject).to be_falsey
+ end
+ end
+ end
+ end
+
describe "#vacate_type" do
subject { appeal.vacate_type }
diff --git a/spec/models/case_timeline_instruction_set_spec.rb b/spec/models/case_timeline_instruction_set_spec.rb
new file mode 100644
index 00000000000..86f024519f0
--- /dev/null
+++ b/spec/models/case_timeline_instruction_set_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.describe CaseTimelineInstructionSet do
+ it "can be initialized" do
+ set = CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: "test category",
+ benefit_type: "benefit type",
+ original_mst: false,
+ original_pact: false,
+ edit_mst: true,
+ edit_pact: true,
+ mst_edit_reason: "MST reason",
+ pact_edit_reason: "PACT reason"
+ )
+ expect(set).to be_a(CaseTimelineInstructionSet)
+ end
+
+ it "validates attributes" do
+ expect do
+ CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: "test category"
+ )
+ end.to raise_error(ArgumentError)
+ end
+
+ it "has default values for the edit and reason attributes" do
+ expect do
+ @set = CaseTimelineInstructionSet.new(
+ change_type: "Edited Issue",
+ issue_category: "test category",
+ benefit_type: "benefit type",
+ original_mst: false,
+ original_pact: false
+ )
+ end.not_to raise_error
+
+ expect(@set.edit_mst).to eq(nil)
+ expect(@set.edit_pact).to eq(nil)
+ expect(@set.mst_edit_reason).to eq(nil)
+ expect(@set.pact_edit_reason).to eq(nil)
+ end
+end
diff --git a/spec/models/contestable_issue_spec.rb b/spec/models/contestable_issue_spec.rb
index c7987881b90..cc81560cad0 100644
--- a/spec/models/contestable_issue_spec.rb
+++ b/spec/models/contestable_issue_spec.rb
@@ -89,7 +89,9 @@
titleOfActiveReview: nil,
sourceReviewType: nil,
timely: true,
- latestIssuesInChain: [{ id: nil, approxDecisionDate: rating_decision.decision_date }]
+ latestIssuesInChain: [{ id: nil, approxDecisionDate: rating_decision.decision_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
end
@@ -123,7 +125,9 @@
titleOfActiveReview: nil,
sourceReviewType: nil,
timely: true,
- latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }]
+ latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
@@ -144,7 +148,9 @@
titleOfActiveReview: nil,
sourceReviewType: nil,
timely: false,
- latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }]
+ latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
end
@@ -179,7 +185,9 @@
titleOfActiveReview: nil,
sourceReviewType: "Appeal",
timely: true,
- latestIssuesInChain: [{ id: decision_issue.id, approxDecisionDate: caseflow_decision_date }]
+ latestIssuesInChain: [{ id: decision_issue.id, approxDecisionDate: caseflow_decision_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
@@ -200,7 +208,9 @@
titleOfActiveReview: nil,
sourceReviewType: "Appeal",
timely: false,
- latestIssuesInChain: [{ id: decision_issue.id, approxDecisionDate: caseflow_decision_date }]
+ latestIssuesInChain: [{ id: decision_issue.id, approxDecisionDate: caseflow_decision_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
end
diff --git a/spec/models/decision_review_spec.rb b/spec/models/decision_review_spec.rb
index 3da684a18aa..b4a386d812e 100644
--- a/spec/models/decision_review_spec.rb
+++ b/spec/models/decision_review_spec.rb
@@ -162,7 +162,9 @@ def find_serialized_issue(serialized_contestable_issues, ref_id_or_description)
titleOfActiveReview: nil,
sourceReviewType: "HigherLevelReview",
timely: true,
- latestIssuesInChain: [{ id: decision_issues.first.id, approxDecisionDate: promulgation_date }]
+ latestIssuesInChain: [{ id: decision_issues.first.id, approxDecisionDate: promulgation_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
expect(find_serialized_issue(serialized_contestable_issues, "456")).to eq(
@@ -178,7 +180,9 @@ def find_serialized_issue(serialized_contestable_issues, ref_id_or_description)
titleOfActiveReview: nil,
sourceReviewType: nil,
timely: true,
- latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }]
+ latestIssuesInChain: [{ id: nil, approxDecisionDate: promulgation_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
expect(find_serialized_issue(serialized_contestable_issues, "789")).to eq(
@@ -194,7 +198,9 @@ def find_serialized_issue(serialized_contestable_issues, ref_id_or_description)
titleOfActiveReview: nil,
sourceReviewType: "HigherLevelReview",
timely: true,
- latestIssuesInChain: [{ id: decision_issues.second.id, approxDecisionDate: promulgation_date + 1.day }]
+ latestIssuesInChain: [{ id: decision_issues.second.id, approxDecisionDate: promulgation_date + 1.day }],
+ mstAvailable: false,
+ pactAvailable: false
)
expect(find_serialized_issue(serialized_contestable_issues, "decision issue 3")).to eq(
@@ -210,7 +216,9 @@ def find_serialized_issue(serialized_contestable_issues, ref_id_or_description)
titleOfActiveReview: nil,
sourceReviewType: "HigherLevelReview",
timely: true,
- latestIssuesInChain: [{ id: decision_issues.third.id, approxDecisionDate: promulgation_date }]
+ latestIssuesInChain: [{ id: decision_issues.third.id, approxDecisionDate: promulgation_date }],
+ mstAvailable: false,
+ pactAvailable: false
)
end
diff --git a/spec/models/end_product_establishment_spec.rb b/spec/models/end_product_establishment_spec.rb
index a35debddca7..a9a4b3423e3 100644
--- a/spec/models/end_product_establishment_spec.rb
+++ b/spec/models/end_product_establishment_spec.rb
@@ -1095,7 +1095,46 @@
)
end
+ let(:veteran_assoc_rating) do
+ Generators::Veteran.create(
+ file_number: "43214321"
+ )
+ end
+
+ let(:epe) do
+ EndProductEstablishment.new(
+ source: source,
+ veteran_file_number: veteran_assoc_rating.file_number,
+ code: code,
+ payee_code: payee_code,
+ claim_date: 2.days.ago,
+ station: "397",
+ reference_id: reference_id,
+ claimant_participant_id: veteran_assoc_rating.participant_id,
+ synced_status: synced_status,
+ committed_at: committed_at,
+ benefit_type_code: benefit_type_code,
+ doc_reference_id: doc_reference_id,
+ development_item_reference_id: development_item_reference_id,
+ established_at: 30.days.ago,
+ user: current_user,
+ limited_poa_code: limited_poa_code,
+ limited_poa_access: limited_poa_access,
+ last_synced_at: last_synced_at
+ )
+ end
+
context "when ep is one of many associated to the rating" do
+ subject { epe.associated_rating }
+
+ let!(:rating) do
+ Generators::PromulgatedRating.build(
+ participant_id: veteran_assoc_rating.participant_id,
+ promulgation_date: epe.established_at,
+ associated_claims: associated_claims
+ )
+ end
+
let(:associated_claims) do
[
{ clm_id: "09123", bnft_clm_tc: end_product_establishment.code },
@@ -1112,6 +1151,16 @@
end
context "when associated rating only has 1 ep" do
+ subject { epe.associated_rating }
+
+ let!(:rating) do
+ Generators::PromulgatedRating.build(
+ participant_id: veteran_assoc_rating.participant_id,
+ promulgation_date: epe.established_at,
+ associated_claims: associated_claims
+ )
+ end
+
let(:associated_claims) do
[
{ clm_id: end_product_establishment.reference_id, bnft_clm_tc: end_product_establishment.code }
@@ -1126,6 +1175,7 @@
}
context "when rating is before established_at date" do
+ subject { end_product_establishment.associated_rating }
let!(:another_rating) do
Generators::PromulgatedRating.build(
profile_date: end_product_establishment.established_at + 1.day,
diff --git a/spec/models/issues_update_task_spec.rb b/spec/models/issues_update_task_spec.rb
new file mode 100644
index 00000000000..000d50af7bb
--- /dev/null
+++ b/spec/models/issues_update_task_spec.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+describe IssuesUpdateTask do
+ let(:user) { create(:user) }
+ let(:bva_intake) { BvaIntake.singleton }
+ let(:root_task) { create(:root_task) }
+ let(:distribution_task) { create(:distribution_task, parent: root_task) }
+ let(:task_class) { IssuesUpdateTask }
+
+ before do
+ bva_intake.add_user(user)
+ User.authenticate!(user: user)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ end
+
+ describe ".verify_user_can_create" do
+ let(:params) { { appeal: root_task.appeal, parent_id: distribution_task&.id, type: task_class.name } }
+
+ context "when no root task exists for appeal" do
+ let(:distribution_task) { nil }
+
+ it "throws an error" do
+ expect do
+ task_class.create!(
+ appeal: root_task.appeal,
+ parent_id: distribution_task&.id,
+ type: task_class.name,
+ assigned_to: bva_intake,
+ assigned_by: user
+ )
+ end.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
+ context "proper params are sent" do
+ it "creates the new task" do
+ expect do
+ task_class.create!(
+ appeal: root_task.appeal,
+ parent_id: distribution_task&.id,
+ type: task_class.name,
+ assigned_to: bva_intake,
+ assigned_by: user
+ )
+ end.to change { IssuesUpdateTask.count }.by(1)
+ end
+ end
+
+ # test contexts for successfully creating task when an appeal has a CC will go here once other tasks are made
+ end
+
+ describe ".format_instructions" do
+ let(:issues_update_task) do
+ task_class.create!(
+ appeal: root_task.appeal,
+ parent_id: distribution_task&.id,
+ type: task_class.name,
+ assigned_to: bva_intake,
+ assigned_by: user
+ )
+ end
+
+ subject do
+ set = CaseTimelineInstructionSet.new(
+ change_type: params[:change_type],
+ issue_category: params[:issue_category],
+ benefit_type: params[:benefit_type],
+ original_mst: params[:original_mst],
+ original_pact: params[:original_pact],
+ edit_mst: params[:edit_mst],
+ edit_pact: params[:edit_pact]
+ # mst_edit_reason: params[:_mst_edit_reason],
+ # pact_edit_reason: params[:_pact_edit_reason]
+ )
+ issues_update_task.format_instructions(set)
+ issues_update_task
+ end
+
+ # clear the instructions after each run
+ after do
+ issues_update_task.instructions.clear
+ issues_update_task.save!
+ end
+
+ # Note: MST/PACT edit reasons are removed on release and commented out in case they are needed in the future
+ context "changes occur to the MST status on an issue" do
+ let(:params) do
+ {
+ change_type: "test change",
+ issue_category: "test category",
+ benefit_type: "test benefit",
+ original_mst: false,
+ original_pact: false,
+ edit_mst: true,
+ edit_pact: false
+ # edit_reason: "reason for edit here...",
+ # _mst_edit_reason: "MST reason here",
+ # _pact_edit_reason: "PACT reason here"
+ }
+ end
+
+ it "formats the instructions with MST" do
+ expect(subject.instructions[0][0]).to eql("test change")
+ expect(subject.instructions[0][1]).to eql("test benefit")
+ expect(subject.instructions[0][2]).to eql("test category")
+ expect(subject.instructions[0][3]).to eql("Special Issues: None")
+ expect(subject.instructions[0][4]).to eql("Special Issues: MST")
+ # expect(issues_update_task.instructions[0][5]).to eql("MST reason here")
+ # expect(issues_update_task.instructions[0][6]).to eql("PACT reason here")
+ end
+ end
+
+ context "changes occur to the PACT status on an issue" do
+ let(:params) do
+ {
+ change_type: "test change",
+ issue_category: "test category",
+ benefit_type: "test benefit",
+ original_mst: false,
+ original_pact: false,
+ edit_mst: false,
+ edit_pact: true
+ # mst_reason: "MST reason here",
+ # pact_reason: "PACT reason here"
+ }
+ end
+
+ it "formats the instructions with PACT" do
+ expect(subject.instructions[0][0]).to eql("test change")
+ expect(subject.instructions[0][1]).to eql("test benefit")
+ expect(subject.instructions[0][2]).to eql("test category")
+ expect(subject.instructions[0][3]).to eql("Special Issues: None")
+ expect(subject.instructions[0][4]).to eql("Special Issues: PACT")
+ # expect(issues_update_task.instructions[0][5]).to eql("MST reason here")
+ # expect(issues_update_task.instructions[0][6]).to eql("PACT reason here")
+ end
+ end
+
+ context "changes occur to the MST and PACT status on an issue" do
+ let(:params) do
+ {
+ change_type: "test change",
+ issue_category: "test category",
+ benefit_type: "test benefit",
+ original_mst: false,
+ original_pact: false,
+ edit_mst: true,
+ edit_pact: true
+ # mst_reason: "MST reason here",
+ # pact_reason: "PACT reason here"
+ }
+ end
+
+ it "formats the instructions with MST and PACT" do
+ expect(subject.instructions[0][0]).to eql("test change")
+ expect(subject.instructions[0][1]).to eql("test benefit")
+ expect(subject.instructions[0][2]).to eql("test category")
+ expect(subject.instructions[0][3]).to eql("Special Issues: None")
+ expect(subject.instructions[0][4]).to eql("Special Issues: MST, PACT")
+ # expect(issues_update_task.instructions[0][5]).to eql("MST reason here")
+ # expect(issues_update_task.instructions[0][6]).to eql("PACT reason here")
+ end
+ end
+
+ context "MST and PACT status on an issue are removed" do
+ let(:params) do
+ {
+ change_type: "test change",
+ issue_category: "test category",
+ benefit_type: "test benefit",
+ original_mst: true,
+ original_pact: true,
+ edit_mst: false,
+ edit_pact: false
+ # mst_reason: "MST reason here",
+ # pact_reason: "PACT reason here"
+ }
+ end
+
+ it "formats the instructions from MST and PACT to None" do
+ expect(subject.instructions[0][0]).to eql("test change")
+ expect(subject.instructions[0][1]).to eql("test benefit")
+ expect(subject.instructions[0][2]).to eql("test category")
+ expect(subject.instructions[0][3]).to eql("Special Issues: MST, PACT")
+ expect(subject.instructions[0][4]).to eql("Special Issues: None")
+ # expect(issues_update_task.instructions[0][4]).to eql("MST reason here")
+ # expect(issues_update_task.instructions[0][5]).to eql("PACT reason here")
+ end
+ end
+ end
+end
diff --git a/spec/models/judge_case_review_spec.rb b/spec/models/judge_case_review_spec.rb
index 5d8131d4308..ca7e9a87954 100644
--- a/spec/models/judge_case_review_spec.rb
+++ b/spec/models/judge_case_review_spec.rb
@@ -152,8 +152,9 @@ def expect_case_review_to_match_params(case_review)
dedeadline: 6.days.ago)
end
let!(:vacols_case) { create(:case, bfkey: "123456") }
- let(:vacols_issue1) { create(:case_issue, isskey: vacols_case.bfkey) }
- let(:vacols_issue2) { create(:case_issue, isskey: vacols_case.bfkey) }
+ let(:vacols_issue1) { create(:case_issue, isskey: vacols_case.bfkey, issmst: "Y", isspact: "Y") }
+ let(:vacols_issue2) { create(:case_issue, isskey: vacols_case.bfkey, issmst: "N", isspact: "N") }
+ let!(:judge_staff) { create(:staff, :judge_role, slogid: "CFS456", sdomainid: judge.css_id, sattyid: "AA") }
context "when all parameters are present to sign a decision and VACOLS update is successful" do
before do
diff --git a/spec/models/request_issue_spec.rb b/spec/models/request_issue_spec.rb
index 8ac3a78b62e..618a6515ffa 100644
--- a/spec/models/request_issue_spec.rb
+++ b/spec/models/request_issue_spec.rb
@@ -4,9 +4,17 @@
before do
Timecop.freeze(Time.zone.now)
FeatureToggle.enable!(:use_ama_activation_date)
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
end
- after { FeatureToggle.disable!(:use_ama_activation_date) }
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ FeatureToggle.disable!(:use_ama_activation_date)
+ end
let(:contested_rating_issue_reference_id) { "abc123" }
let(:contested_rating_decision_reference_id) { nil }
@@ -1087,6 +1095,147 @@
end
end
+ context "._from_intake_data with mst and pact params" do
+ subject { RequestIssue.from_intake_data(data) }
+ context "when mst or pact is in the payload" do
+ let(:data) do
+ {
+ rating_issue_reference_id: rating_issue_reference_id,
+ decision_text: "decision text",
+ nonrating_issue_category: nonrating_issue_category,
+ is_unidentified: is_unidentified,
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ contested_decision_issue_id: contested_decision_issue_id,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ rating_issue_diagnostic_code: "2222",
+ is_predocket_needed: is_predocket_needed,
+ mst_status: true,
+ pact_status: false
+ }
+ end
+
+ let(:rating_issue_reference_id) { nil }
+ let(:contested_decision_issue_id) { nil }
+ let(:nonrating_issue_category) { nil }
+ let(:is_unidentified) { nil }
+
+ it "expects mst and pact are saved to the model" do
+ RequestIssue.from_intake_data(data)
+ is_expected.to have_attributes(
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ contested_rating_issue_diagnostic_code: "2222",
+ mst_status: true,
+ pact_status: false
+ )
+ end
+ end
+
+ context "pact is in the payload" do
+ let(:data) do
+ {
+ rating_issue_reference_id: rating_issue_reference_id,
+ decision_text: "decision text",
+ nonrating_issue_category: nonrating_issue_category,
+ is_unidentified: is_unidentified,
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ contested_decision_issue_id: contested_decision_issue_id,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ rating_issue_diagnostic_code: "2222",
+ is_predocket_needed: is_predocket_needed,
+ mst_status: false,
+ pact_status: true
+ }
+ end
+
+ let(:rating_issue_reference_id) { nil }
+ let(:contested_decision_issue_id) { nil }
+ let(:nonrating_issue_category) { nil }
+ let(:is_unidentified) { nil }
+
+ it "expect the pact status to return true" do
+ RequestIssue.from_intake_data(data)
+ is_expected.to have_attributes(
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ contested_rating_issue_diagnostic_code: "2222",
+ mst_status: false,
+ pact_status: true
+ )
+ end
+ end
+
+ context "mst and pact are in the payload" do
+ let(:data) do
+ {
+ rating_issue_reference_id: rating_issue_reference_id,
+ decision_text: "decision text",
+ nonrating_issue_category: nonrating_issue_category,
+ is_unidentified: is_unidentified,
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ contested_decision_issue_id: contested_decision_issue_id,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ rating_issue_diagnostic_code: "2222",
+ is_predocket_needed: is_predocket_needed,
+ mst_status: true,
+ pact_status: true
+ }
+ end
+
+ let(:rating_issue_reference_id) { nil }
+ let(:contested_decision_issue_id) { nil }
+ let(:nonrating_issue_category) { nil }
+ let(:is_unidentified) { nil }
+
+ it "expect mst and pact in the payload" do
+ RequestIssue.from_intake_data(data)
+ is_expected.to have_attributes(
+ decision_date: Time.zone.today,
+ notes: "notes",
+ untimely_exemption: true,
+ untimely_exemption_notes: "untimely notes",
+ ramp_claim_id: "ramp_claim_id",
+ vacols_sequence_id: 2,
+ ineligible_reason: "untimely",
+ ineligible_due_to_id: 345,
+ contested_rating_issue_diagnostic_code: "2222",
+ mst_status: true,
+ pact_status: true
+ )
+ end
+ end
+ end
+
context "#move_stream!" do
subject { request_issue.move_stream!(new_appeal_stream: new_appeal_stream, closed_status: closed_status) }
let(:closed_status) { "docket_switch" }
@@ -1370,6 +1519,185 @@
end
end
+ context "#mst_contention_status?, #pact_contention_status?" do
+ let(:claim_id) { "600118959" }
+ let(:end_prod_establishment) do
+ create(
+ :end_product_establishment,
+ reference_id: claim_id
+ )
+ end
+ context "when mst is available and pact is not available" do
+ let!(:mst_contention) do
+ Generators::Contention.build_mst_contention(
+ claim_id: claim_id
+ )
+ end
+ let(:mst_request_issue) do
+ create(
+ :request_issue,
+ contention_reference_id: mst_contention.id,
+ end_product_establishment: end_prod_establishment
+ )
+ end
+ let!(:bgs_contention) do
+ Generators::BgsContention.build_mst_contention(
+ reference_id: mst_contention.id,
+ claim_id: end_prod_establishment.reference_id
+ )
+ end
+
+ before do
+ end_prod_establishment.bgs_contentions << bgs_contention
+ end
+
+ after do
+ end_prod_establishment.bgs_contentions.clear
+ end
+
+ it "mst_contention_status? is true" do
+ expect(mst_request_issue.mst_contention_status?).to be true
+ end
+
+ it "pact_contention_status? is false" do
+ expect(mst_request_issue.pact_contention_status?).to be false
+ end
+ end
+
+ context "when pact is available and mst is not available" do
+ let(:pact_contention) do
+ Generators::Contention.build_pact_contention(
+ claim_id: claim_id
+ )
+ end
+
+ let(:pact_request_issue) do
+ create(
+ :request_issue,
+ contention_reference_id: pact_contention.id,
+ end_product_establishment: end_prod_establishment
+ )
+ end
+
+ let!(:bgs_contention) do
+ Generators::BgsContention.build_pact_contention(
+ reference_id: pact_contention.id,
+ claim_id: end_prod_establishment.reference_id
+ )
+ end
+
+ before do
+ end_prod_establishment.bgs_contentions << bgs_contention
+ end
+
+ after do
+ end_prod_establishment.bgs_contentions.clear
+ end
+
+ it "mst_contention_status? is false" do
+ expect(pact_request_issue.mst_contention_status?).to be false
+ end
+
+ it "pact_contention_status? is true" do
+ expect(pact_request_issue.pact_contention_status?).to be true
+ end
+ end
+
+ context "when pact and mst are available" do
+ let(:mst_and_pact_contention) do
+ Generators::Contention.build_mst_and_pact_contention(
+ claim_id: claim_id
+ )
+ end
+
+ let(:mst_and_pact_request_issue) do
+ create(
+ :request_issue,
+ contention_reference_id: mst_and_pact_contention.id,
+ end_product_establishment: end_prod_establishment
+ )
+ end
+
+ let!(:bgs_contention) do
+ Generators::BgsContention.build(
+ reference_id: mst_and_pact_contention.id,
+ claim_id: end_prod_establishment.reference_id,
+ special_issues: [
+ {
+ call_id: "12345",
+ jrn_dt: 5.days.ago,
+ name: "SpecialIssue",
+ spis_tc: "MST",
+ spis_tn: "Military Sexual Trauma (MST)"
+ },
+ {
+ call_id: "12345",
+ jrn_dt: 5.days.ago,
+ name: "SpecialIssue",
+ spis_tc: "PACT",
+ spis_tn: "PACT"
+ }
+ ]
+ )
+ end
+
+ before do
+ end_prod_establishment.bgs_contentions << bgs_contention
+ end
+
+ after do
+ end_prod_establishment.bgs_contentions.clear
+ end
+
+ it "mst_contention_status? is true" do
+ expect(mst_and_pact_request_issue.mst_contention_status?).to be true
+ end
+
+ it "pact_contention_status? is true" do
+ expect(mst_and_pact_request_issue.pact_contention_status?).to be true
+ end
+ end
+
+ context "when pact and mst are not available" do
+ let(:contention) do
+ Generators::Contention.build(
+ claim_id: claim_id
+ )
+ end
+
+ let(:request_issue) do
+ create(
+ :request_issue,
+ contention_reference_id: contention.id,
+ end_product_establishment: end_prod_establishment
+ )
+ end
+
+ let!(:bgs_contention) do
+ Generators::BgsContention.build(
+ reference_id: contention.id,
+ claim_id: end_prod_establishment.reference_id
+ )
+ end
+
+ before do
+ end_prod_establishment.bgs_contentions << bgs_contention
+ end
+
+ after do
+ end_prod_establishment.bgs_contentions.clear
+ end
+
+ it "mst_contention_status? is false" do
+ expect(request_issue.mst_contention_status?).to be false
+ end
+
+ it "pact_contention_status? is false" do
+ expect(request_issue.pact_contention_status?).to be false
+ end
+ end
+ end
+
context "#valid?" do
subject { request_issue.valid? }
let(:request_issue) do
diff --git a/spec/models/request_issues_update_spec.rb b/spec/models/request_issues_update_spec.rb
index fd8b97ce246..f5afe3a04f0 100644
--- a/spec/models/request_issues_update_spec.rb
+++ b/spec/models/request_issues_update_spec.rb
@@ -59,6 +59,10 @@ def allow_update_contention
let!(:vacols_issue) { create(:case_issue, issseq: vacols_sequence_id) }
let!(:vacols_case) { create(:case, bfkey: vacols_id, case_issues: [vacols_issue, create(:case_issue, issseq: 2)]) }
let(:edited_description) { "I am an edited description" }
+ let(:mst_status) { true }
+ let(:pact_status) { true }
+ let(:mst_status_update_reason_notes) { "I am the mst status update reason notes" }
+ let(:pact_status_update_reason_notes) { "I am the pact status update reason notes" }
let(:legacy_appeal) do
create(:legacy_appeal, vacols_case: vacols_case)
end
@@ -288,6 +292,48 @@ def allow_update_contention
expect(existing_request_issue.reload.edited_description).to eq(edited_description)
end
+ context "when an issue's mst status is updated" do
+ let(:request_issues_data) do
+ [{ request_issue_id: existing_legacy_opt_in_request_issue.id },
+ { request_issue_id: existing_request_issue.id,
+ mst_status: mst_status,
+ mst_status_update_reason_notes: mst_status_update_reason_notes }]
+ end
+
+ before { FeatureToggle.enable!(:mst_identification) }
+ after { FeatureToggle.disable!(:mst_identification) }
+
+ it "updates the request issue's mst status and mst status update reason notes" do
+ allow_any_instance_of(RequestIssuesUpdate).to receive(:create_issue_update_task).and_return(true)
+ expect(subject).to be_truthy
+ expect(existing_request_issue.reload.mst_status).to eq(true)
+ expect(
+ existing_request_issue.reload.mst_status_update_reason_notes
+ ).to eq("I am the mst status update reason notes")
+ end
+ end
+
+ context "when an issue's pact status is updated" do
+ let(:request_issues_data) do
+ [{ request_issue_id: existing_legacy_opt_in_request_issue.id },
+ { request_issue_id: existing_request_issue.id,
+ pact_status: pact_status,
+ pact_status_update_reason_notes: pact_status_update_reason_notes }]
+ end
+
+ before { FeatureToggle.enable!(:pact_identification) }
+ after { FeatureToggle.disable!(:pact_identification) }
+
+ it "updates the request issue's pact status and pact status update reason notes" do
+ allow_any_instance_of(RequestIssuesUpdate).to receive(:create_issue_update_task).and_return(true)
+ expect(subject).to be_truthy
+ expect(existing_request_issue.reload.pact_status).to eq(true)
+ expect(
+ existing_request_issue.reload.pact_status_update_reason_notes
+ ).to eq("I am the pact status update reason notes")
+ end
+ end
+
context "if the contention text has been updated in VBMS before" do
let(:contention_updated_at) { 1.day.ago }
diff --git a/spec/models/serializers/idt/appeal_details_serializer_spec.rb b/spec/models/serializers/idt/appeal_details_serializer_spec.rb
index 163aa954f24..5ca29af37c7 100644
--- a/spec/models/serializers/idt/appeal_details_serializer_spec.rb
+++ b/spec/models/serializers/idt/appeal_details_serializer_spec.rb
@@ -14,8 +14,9 @@
context "legacy appeals " do
let(:appeal) { create(:legacy_appeal, vacols_case: create(:case)) }
- it "returns nil for legacy appeals" do
- expect(subject).to be nil
+ it "returns mst/pact hash for legacy appeals" do
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
end
end
@@ -29,6 +30,8 @@
expect(subject[:fnod]).to be false
expect(subject[:hearing]).to be false
expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
end
context "contested claims" do
@@ -51,6 +54,8 @@
expect(subject[:fnod]).to be false
expect(subject[:hearing]).to be false
expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
end
end
@@ -66,6 +71,38 @@
expect(subject[:fnod]).to be false
expect(subject[:hearing]).to be false
expect(subject[:overtime]).to be true
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
+ end
+ end
+
+ context "mst" do
+ let(:appeal) { create(:appeal, request_issues: [create(:request_issue, mst_status: true)]) }
+ before { FeatureToggle.enable!(:mst_identification) }
+ after { FeatureToggle.disable!(:mst_identification) }
+
+ it "sets mst key value to true" do
+ expect(subject[:contested_claim]).to be false
+ expect(subject[:fnod]).to be false
+ expect(subject[:hearing]).to be false
+ expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be true
+ expect(subject[:pact]).to be false
+ end
+ end
+
+ context "pact" do
+ let(:appeal) { create(:appeal, request_issues: [create(:request_issue, pact_status: true)]) }
+ before { FeatureToggle.enable!(:pact_identification) }
+ after { FeatureToggle.disable!(:pact_identification) }
+
+ it "sets pact key value to true" do
+ expect(subject[:contested_claim]).to be false
+ expect(subject[:fnod]).to be false
+ expect(subject[:hearing]).to be false
+ expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be true
end
end
@@ -84,6 +121,8 @@
expect(subject[:fnod]).to be true
expect(subject[:hearing]).to be false
expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
end
end
@@ -109,6 +148,8 @@
expect(subject[:fnod]).to be false
expect(subject[:hearing]).to be true
expect(subject[:overtime]).to be false
+ expect(subject[:mst]).to be false
+ expect(subject[:pact]).to be false
end
end
end
diff --git a/spec/models/tasks/judge_dispatch_return_task_spec.rb b/spec/models/tasks/judge_dispatch_return_task_spec.rb
index 251c09739d4..80af1dd05d9 100644
--- a/spec/models/tasks/judge_dispatch_return_task_spec.rb
+++ b/spec/models/tasks/judge_dispatch_return_task_spec.rb
@@ -15,6 +15,11 @@
subject { judge_dispatch_task.available_actions(judge) }
context "when judge dispatch return task is assigned to judge" do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ end
+
let(:expected_actions) do
[
Constants.TASK_ACTIONS.ADD_ADMIN_ACTION.to_h,
diff --git a/spec/models/tasks/judge_task_spec.rb b/spec/models/tasks/judge_task_spec.rb
index f06924ab49d..3825aa80cb8 100644
--- a/spec/models/tasks/judge_task_spec.rb
+++ b/spec/models/tasks/judge_task_spec.rb
@@ -163,7 +163,7 @@
Constants.TASK_ACTIONS.ADD_ADMIN_ACTION.to_h,
Constants.TASK_ACTIONS.PLACE_TIMED_HOLD.to_h,
Constants.TASK_ACTIONS.REASSIGN_TO_JUDGE.to_h,
- Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SP_ISSUES.to_h,
+ Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SPECIAL_ISSUES.to_h,
Constants.TASK_ACTIONS.JUDGE_RETURN_TO_ATTORNEY.to_h
].map { |action| subject_task.build_action_hash(action, judge) }
)
@@ -206,7 +206,7 @@
it "should show pulac cerullo task action along with special actions" do
expect(task.additional_available_actions(user)).to eq(
[Constants.TASK_ACTIONS.LIT_SUPPORT_PULAC_CERULLO.to_h,
- Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SP_ISSUES.to_h,
+ Constants.TASK_ACTIONS.JUDGE_AMA_CHECKOUT_SPECIAL_ISSUES.to_h,
Constants.TASK_ACTIONS.JUDGE_RETURN_TO_ATTORNEY.to_h]
)
end
diff --git a/spec/seeds/intake_spec.rb b/spec/seeds/intake_spec.rb
index 0e47259074c..f3e35ba2216 100644
--- a/spec/seeds/intake_spec.rb
+++ b/spec/seeds/intake_spec.rb
@@ -7,10 +7,10 @@
before do
Fakes::BGSServiceRecordMaker.new.call
end
-
it "creates all kinds of decision reviews" do
expect { subject }.to_not raise_error
- expect(HigherLevelReview.count).to eq(10)
+
+ expect(HigherLevelReview.count).to be >= 10
end
end
end
diff --git a/spec/support/intake_helpers.rb b/spec/support/intake_helpers.rb
index adb90227d4b..84b2802967f 100644
--- a/spec/support/intake_helpers.rb
+++ b/spec/support/intake_helpers.rb
@@ -162,6 +162,51 @@ def start_appeal(
[appeal, intake]
end
+
+ def start_appeal_with_mst_pact_from_vbms(
+ test_veteran,
+ receipt_date: 1.day.ago,
+ claim_participant_id: nil,
+ legacy_opt_in_approved: false,
+ no_claimant: false,
+ intake_user: User.authenticate!(roles: ["Admin Intake"]),
+ docket_type: Constants.AMA_DOCKETS.direct_review,
+ original_hearing_request_type: nil
+ )
+
+ appeal = Appeal.create!(
+ veteran_file_number: test_veteran.file_number,
+ receipt_date: receipt_date,
+ docket_type: docket_type,
+ original_hearing_request_type: original_hearing_request_type,
+ legacy_opt_in_approved: legacy_opt_in_approved,
+ veteran_is_not_claimant: claim_participant_id.present?,
+ filed_by_va_gov: false
+ )
+
+ intake = AppealIntake.create!(
+ veteran_file_number: test_veteran.file_number,
+ user: intake_user,
+ started_at: 5.minutes.ago,
+ detail: appeal
+ )
+
+ BvaIntake.singleton.add_user(intake.user)
+
+ unless no_claimant
+ stub_valid_address
+ participant_id = claim_participant_id || test_veteran.participant_id
+ claimant_class = claim_participant_id.present? ? DependentClaimant : VeteranClaimant
+ claimant_class.create!(
+ decision_review: appeal,
+ participant_id: participant_id
+ )
+ end
+
+ appeal.start_review!
+
+ [appeal, intake]
+ end
# rubocop: enable Metrics/ParameterLists
def start_claim_review(
@@ -861,6 +906,90 @@ def generate_rating_with_old_decisions(veteran, receipt_date)
)
end
+ def generate_rating_with_mst_pact(veteran)
+ Generators::PromulgatedRating.build(
+ participant_id: veteran.participant_id,
+ promulgation_date: Date.new(2022, 10, 11),
+ profile_date: Date.new(2022, 10, 11),
+ issues: [
+ {
+ decision_text: "Service connection is granted for PTSD at 10 percent, effective 10/11/2022.",
+ dis_sn: "1224780"
+ },
+ {
+ decision_text: "Service connection is granted for AOOV at 10 percent, effective 10/11/2022.",
+ dis_sn: "1224781"
+ },
+ {
+ decision_text: "Service connection is granted for PTSD, AOOV at 10 percent, effective 10/11/2022.",
+ dis_sn: "1224782"
+ },
+ {
+ decision_text: "Service connection is denied for right knee condition."
+ }
+ ],
+ disabilities: [
+ {
+ name: "Disability 1",
+ parent_name: "CP_RBA_PRFIL2",
+ dis_dt: "Wed, 29 Mar 2023 10:28:11 -0500",
+ dis_sn: "1224780",
+ prfl_dt: "Wed, 29 Mar 2023 10:26:14 -0500",
+ ptcpnt_id_a: veteran.participant_id,
+ disability_special_issues: [
+ {
+ spis_basis_tc: "PTSD/10",
+ spis_basis_tn: "Sexual Trauma/Assault",
+ spis_tc: "PTSD/3",
+ spis_tn: "PTSD - Personal Trauma",
+ dis_sn: "1224780"
+ }
+ ]
+ },
+ {
+ name: "Disablilty 2",
+ dis_dt: "Wed, 29 Mar 2023 11:28:11 -0500",
+ dis_sn: "1224781",
+ prfl_dt: "Wed, 29 Mar 2023 11:26:14 -0500",
+ ptcpnt_id_a: veteran.participant_id,
+ disability_special_issues: [
+ {
+ spis_basis_tc: "AO/14",
+ spis_basis_tn: "Peripheral Neuropathy",
+ spis_tc: "AOOV",
+ spis_tn: "Agent Orange - outside Vietnam or unknown",
+ dis_sn: "1224781"
+ }
+ ]
+ },
+ {
+ name: "Disability",
+ parent_name: "CP_RBA_PRFIL2",
+ dis_dt: "Wed, 29 Mar 2023 12:28:11 -0500",
+ dis_sn: "1224782",
+ prfl_dt: "Wed, 29 Mar 2023 12:26:14 -0500",
+ ptcpnt_id_a: veteran.participant_id,
+ disability_special_issues: [
+ {
+ spis_basis_tc: "PTSD/10",
+ spis_basis_tn: "Sexual Trauma/Assault",
+ spis_tc: "PTSD/3",
+ spis_tn: "PTSD - Personal Trauma",
+ dis_sn: "1224782"
+ },
+ {
+ spis_basis_tc: "AO/14",
+ spis_basis_tn: "Peripheral Neuropathy",
+ spis_tc: "AOOV",
+ spis_tn: "Agent Orange - outside Vietnam or unknown",
+ dis_sn: "1224782"
+ }
+ ]
+ }
+ ]
+ )
+ end
+
def save_and_check_request_issues_with_diagnostic_codes(form_name, decision_review)
click_intake_add_issue
expect(page).to have_content("this is a disability")
diff --git a/spec/workflows/initial_tasks_factory_spec.rb b/spec/workflows/initial_tasks_factory_spec.rb
index 14b40c33010..8ff4f4a456f 100644
--- a/spec/workflows/initial_tasks_factory_spec.rb
+++ b/spec/workflows/initial_tasks_factory_spec.rb
@@ -157,6 +157,72 @@
end
end
end
+
+ context "related to MST/PACT work" do
+ before do
+ FeatureToggle.enable!(:mst_identification)
+ FeatureToggle.enable!(:pact_identification)
+ FeatureToggle.enable!(:legacy_mst_pact_identification)
+ BvaIntake.singleton.add_user(intake_user)
+ User.authenticate!(user: intake_user)
+ end
+ after do
+ FeatureToggle.disable!(:mst_identification)
+ FeatureToggle.disable!(:pact_identification)
+ FeatureToggle.disable!(:legacy_mst_pact_identification)
+ end
+
+ let(:intake_user) { create(:user, css_id: "BVADWISE", station_id: "101") }
+ let(:docket_type) { Constants.AMA_DOCKETS.direct_review }
+ let(:appeal_with_mst_pact_issues) do
+ create(
+ :appeal,
+ docket_type: docket_type,
+ request_issues: build_list(
+ :request_issue, 4,
+ benefit_type: "compensation",
+ nonrating_issue_category: "Unknown Issue Category",
+ decision_date: "2023-04-28",
+ mst_status: true,
+ pact_status: true
+ ),
+ claimants: [
+ create(:claimant, participant_id: participant_id_with_pva)
+ ],
+ number_of_claimants: 1
+ )
+ end
+
+ let(:appeal_with_no_mst_or_pact_issues) do
+ create(
+ :appeal,
+ docket_type: docket_type,
+ request_issues: build_list(
+ :request_issue, 1,
+ benefit_type: "compensation",
+ nonrating_issue_category: "Unknown Issue Category",
+ decision_date: "2023-04-28",
+ mst_status: false,
+ pact_status: false
+ ),
+ claimants: [
+ create(:claimant, participant_id: participant_id_with_pva)
+ ],
+ number_of_claimants: 1
+ )
+ end
+
+ it "creates an EstablishmentTask when mst/pact is on an issue" do
+ InitialTasksFactory.new(appeal_with_mst_pact_issues).create_root_and_sub_tasks!
+ expect(appeal_with_mst_pact_issues.tasks.map(&:type)).to include("EstablishmentTask")
+ expect(appeal_with_mst_pact_issues.tasks.count { |task| task.is_a?(EstablishmentTask) }).to eq(1)
+ end
+
+ it "does not create an EstablishmentTask when mst/pact is not on an issue" do
+ InitialTasksFactory.new(appeal_with_no_mst_or_pact_issues).create_root_and_sub_tasks!
+ expect(appeal_with_no_mst_or_pact_issues.tasks.count { |task| task.is_a?(EstablishmentTask) }).to eq(0)
+ end
+ end
end
context "when it has multiple ihp-writing vsos" do