diff --git a/app/controllers/intakes_controller.rb b/app/controllers/intakes_controller.rb index ca08b861167..bb93838ea92 100644 --- a/app/controllers/intakes_controller.rb +++ b/app/controllers/intakes_controller.rb @@ -144,6 +144,7 @@ def intake_ui_hash { unread_messages: unread_messages? } end + # rubocop:disable Metrics/AbcSize def feature_toggle_ui_hash { useAmaActivationDate: FeatureToggle.enabled?(:use_ama_activation_date, user: current_user), @@ -160,9 +161,11 @@ def feature_toggle_ui_hash 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), - metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user) + metricsBrowserError: FeatureToggle.enabled?(:metrics_browser_error, user: current_user), + removeCompAndPenIntake: FeatureToggle.enabled?(:remove_comp_and_pen_intake, user: current_user) } end + # rubocop:enable Metrics/AbcSize def user_information_ui_hash { diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index fa6d8052b65..34f4569dbf4 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -17,7 +17,6 @@ class IssuesController < ApplicationController handle_non_critical_error("issues", e) end - # rubocop:disable Layout/LineLength def create return record_not_found unless appeal @@ -27,21 +26,24 @@ def create 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]) + 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 + # rubocop:disable 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]) + create_legacy_issue_update_task(issue) if FeatureToggle.enabled?( + :legacy_mst_pact_identification, user: RequestStore[:current_user] + ) end Issue.update_in_vacols!( @@ -55,7 +57,7 @@ def update render json: { issues: json_issues }, status: :ok end - # rubocop:enable Layout/LineLength, Metrics/AbcSize + # rubocop:enable Metrics/AbcSize def destroy return record_not_found unless appeal @@ -69,10 +71,7 @@ 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 @@ -83,63 +82,98 @@ def create_legacy_issue_update_task(issue) appeal: appeal, parent: appeal.root_task, assigned_to: SpecialIssueEditTeam.singleton, - assigned_by: user, - completed_by: user + assigned_by: current_user, + completed_by: current_user ) + task_instructions_helper(issue, task) + end + # rubocop:disable Metrics/MethodLength + def task_instructions_helper(issue, task) # 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" + instruction_params = { + issue: issue, + task: task, + updated_mst_status: updated_mst_status, + updated_pact_status: updated_pact_status, + change_category: change_category + } + format_instructions(instruction_params) + + # 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: current_user.id, + created_by_css_id: current_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 + # formats and saves task instructions + # rubocop:disable Metrics/AbcSize + # :reek:FeatureEnvy + def format_instructions(inst_params) 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"] + + issue_code_message = build_issue_code_message(issue_code, param_issue) # format the task instructions and close out set = CaseTimelineInstructionSet.new( - change_type: change_category, + change_type: inst_params[: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" + "Code: #{issue_code_message}\n", + "Note: #{note}\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 + original_mst: inst_params[:issue].mst_status, + original_pact: inst_params[:issue].pact_status, + edit_mst: inst_params[:updated_mst_status], + edit_pact: inst_params[:updated_pact_status] ) + inst_params[:task].format_instructions(set) + inst_params[:task].completed! + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + # builds issue code on IssuesUpdateTask for MST/PACT changes + def build_issue_code_message(issue_code, param_issue) + level_1_code = params[:issues][:level_1] + diagnostic_code = params[:issues][:level_2] + + # use diagnostic code message if it exists + if !diagnostic_code.blank? + diagnostic_description = Constants::DIAGNOSTIC_CODE_DESCRIPTIONS[diagnostic_code]["staff_description"] + [diagnostic_code, diagnostic_description].join(" - ") + # use level 1 message if it exists + elsif !level_1_code.blank? + level_1_description = param_issue["levels"][issue_code]["levels"][level_1_code]["description"] + [level_1_code, level_1_description].join(" - ") + # return N/A if none exist + else + "N/A" + end end - # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity def convert_to_bool(status) status == "Y" diff --git a/app/mailers/dispatch_mailer.rb b/app/mailers/dispatch_mailer.rb index 87da8af8e23..0c026bfca65 100644 --- a/app/mailers/dispatch_mailer.rb +++ b/app/mailers/dispatch_mailer.rb @@ -6,7 +6,7 @@ ## # rubocop:disable Rails/ApplicationMailer class DispatchMailer < ActionMailer::Base - default from: "Board of Veterans' Appeals " + default from: "Board of Veterans' Appeals " layout "dispatch_mailer" helper VirtualHearings::LinkHelper diff --git a/app/models/distribution.rb b/app/models/distribution.rb index 37045d03fbf..8c483b511ee 100644 --- a/app/models/distribution.rb +++ b/app/models/distribution.rb @@ -2,11 +2,7 @@ class Distribution < CaseflowRecord include ActiveModel::Serializers::JSON - if FeatureToggle.enabled?(:acd_distribute_by_docket_date, user: RequestStore.store[:current_user]) - include ByDocketDateDistribution - else - include AutomaticCaseDistribution - end + include ByDocketDateDistribution has_many :distributed_cases belongs_to :judge, class_name: "User" diff --git a/app/models/request_issues_update.rb b/app/models/request_issues_update.rb index 622c347298e..70ba1f7eede 100644 --- a/app/models/request_issues_update.rb +++ b/app/models/request_issues_update.rb @@ -237,9 +237,6 @@ 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 diff --git a/app/queries/appeals_ready_for_distribution.rb b/app/queries/appeals_ready_for_distribution.rb index 5f6ce4c7a99..0406ab1834a 100644 --- a/app/queries/appeals_ready_for_distribution.rb +++ b/app/queries/appeals_ready_for_distribution.rb @@ -44,43 +44,62 @@ def self.ready_appeals .flat_map do |sym, docket| appeals = docket.ready_to_distribute_appeals if sym == :legacy - legacy_rows(appeals, docket, sym) + legacy_rows(appeals, sym) else ama_rows(appeals, docket, sym) end end end - def self.legacy_rows(appeals, docket, sym) + def self.legacy_rows(appeals, sym) appeals.map do |appeal| - veteran_name = FullName.new(appeal["snamef"], nil, appeal["snamel"]).to_s - vlj_name = FullName.new(appeal["vlj_namef"], nil, appeal["vlj_namel"]).to_s - hearing_judge = vlj_name.empty? ? nil : vlj_name - appeal_affinity = AppealAffinity.find_by(case_id: appeal["bfkey"], case_type: "VACOLS::Case") - - { - docket_number: appeal["tinum"], - docket: sym.to_s, - aod: appeal["aod"] == 1, - cavc: appeal["cavc"] == 1, - receipt_date: appeal["bfd19"], - ready_for_distribution_at: appeal["bfdloout"], - target_distro_date: target_distro_date(appeal["bfd19"], docket), - days_before_goal_date: days_before_goal_date(appeal["bfd19"], docket), - hearing_judge: hearing_judge, - veteran_file_number: appeal["ssn"] || appeal["bfcorlid"], - veteran_name: veteran_name, - affinity_start_date: appeal_affinity&.affinity_start_date - } + build_appeal_row(appeal, sym) end end + def self.build_appeal_row(appeal, sym) + veteran_name = format_veteran_name(appeal["snamef"], appeal["snamel"]) + hearing_judge = format_vlj_name(appeal["vlj_namef"], appeal["vlj_namel"]) + appeal_affinity = fetch_affinity_start_date(appeal["bfkey"]) + + { + docket_number: appeal["tinum"], + docket: sym.to_s, + aod: appeal["aod"] == 1, + cavc: appeal["cavc"] == 1, + receipt_date: appeal["bfd19"], + ready_for_distribution_at: appeal["bfdloout"], + target_distro_date: target_distro_date(appeal["bfd19"], sym), + days_before_goal_date: days_before_goal_date(appeal["bfd19"], sym), + hearing_judge: hearing_judge, + original_judge: legacy_original_deciding_judge(appeal), + veteran_file_number: appeal["ssn"] || appeal["bfcorlid"], + veteran_name: veteran_name, + affinity_start_date: appeal_affinity + } + end + + def self.format_vlj_name(first_name, last_name) + name = FullName.new(first_name, nil, last_name).to_s + name.empty? ? nil : name + end + + def self.format_veteran_name(first_name, last_name) + FullName.new(first_name, nil, last_name).to_s + end + + def self.fetch_affinity_start_date(case_id) + appeal_affinity = AppealAffinity.find_by(case_id: case_id, case_type: "VACOLS::Case") + appeal_affinity&.affinity_start_date + end + def self.ama_rows(appeals, docket, sym) appeals.map do |appeal| # This comes from the DistributionTask's assigned_at date ready_for_distribution_at = distribution_task_query(appeal) # only look for hearings that were held hearing_judge = with_held_hearings(appeal) + priority_appeal = appeal.aod || appeal.cavc { docket_number: appeal.docket_number, docket: sym.to_s, @@ -88,8 +107,8 @@ def self.ama_rows(appeals, docket, sym) cavc: appeal.cavc, receipt_date: appeal.receipt_date, ready_for_distribution_at: ready_for_distribution_at, - target_distro_date: target_distro_date(appeal.receipt_date, docket), - days_before_goal_date: days_before_goal_date(appeal.receipt_date, docket), + target_distro_date: priority_appeal ? "N/A" : target_distro_date(appeal.receipt_date, docket), + days_before_goal_date: priority_appeal ? "N/A" : days_before_goal_date(appeal.receipt_date, docket), hearing_judge: hearing_judge, veteran_file_number: appeal.veteran_file_number, veteran_name: appeal.veteran&.name.to_s, diff --git a/client/COPY.json b/client/COPY.json index e3b596368c3..bbc5dc80ad9 100644 --- a/client/COPY.json +++ b/client/COPY.json @@ -965,6 +965,7 @@ "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.", + "INTAKE_REMOVE_COMP_AND_PEN": "Higher Level Reviews and Supplemental Claims with Compensation and Pension & Survivor's Benefits are now handled through VBMS and are no longer established through Caseflow", "VHA_BENEFIT_EMAIL_ADDRESS": "VHABENEFITAPPEALS@va.gov", "VHA_CAREGIVER_SUPPORT_EMAIL_ADDRESS": "VHA.CSPAppeals@va.gov", "VHA_PAYMENT_OPERATIONS_EMAIL_ADDRESS": "VHA10D1B3R2Appeals@va.gov", diff --git a/client/app/intake/components/BenefitType.jsx b/client/app/intake/components/BenefitType.jsx index e9cc8e80cc9..ab04d9e09e8 100644 --- a/client/app/intake/components/BenefitType.jsx +++ b/client/app/intake/components/BenefitType.jsx @@ -19,13 +19,15 @@ export default class BenefitType extends React.PureComponent { // If the feature toggle is off then all users should be able to select vha const canSelectVhaBenefit = featureToggles.vhaClaimReviewEstablishment ? userCanSelectVha : true; + const canSelectCompAndPen = !featureToggles.removeCompAndPenIntake; + return
{ + /* eslint-disable max-params */ + generateIssueActionOptions = ( + issue, + userCanWithdrawIssues, + userCanEditIntakeIssues, + isDtaError, + docketType, + formType + ) => { let options = []; if (issue.correctionType && issue.endProductCleared) { @@ -38,7 +46,7 @@ export default class IssuesList extends React.Component { { label: 'Remove issue', value: 'remove' } ); - if (userCanEditIntakeIssues) { + if (userCanEditIntakeIssues && (formType === FORM_TYPES.APPEAL.key)) { options.push( { label: 'Edit issue', value: 'edit' } @@ -60,7 +68,7 @@ export default class IssuesList extends React.Component { value: 'remove' } ); } - if (userCanEditIntakeIssues) { + if (userCanEditIntakeIssues && (formType === FORM_TYPES.APPEAL.key)) { options.push( { label: 'Edit issue', value: 'edit' } @@ -84,6 +92,7 @@ export default class IssuesList extends React.Component { value: 'requestWithdrawal' } ); } + /* eslint-enable max-params */ const isIssueWithdrawn = issue.withdrawalDate || issue.withdrawalPending; @@ -127,7 +136,12 @@ export default class IssuesList extends React.Component { editableIssueProperties); const issueActionOptions = this.generateIssueActionOptions( - issue, userCanWithdrawIssues, userCanEditIntakeIssues, intakeData.isDtaError, intakeData.docketType + issue, + userCanWithdrawIssues, + userCanEditIntakeIssues, + intakeData.isDtaError, + intakeData.docketType, + formType ); const isIssueWithdrawn = issue.withdrawalDate || issue.withdrawalPending; diff --git a/client/app/intake/components/NonratingRequestIssueModal.jsx b/client/app/intake/components/NonratingRequestIssueModal.jsx index 35967d8c804..933c3f223fa 100644 --- a/client/app/intake/components/NonratingRequestIssueModal.jsx +++ b/client/app/intake/components/NonratingRequestIssueModal.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { css } from 'glamor'; import { COLORS } from 'app/constants/AppConstants'; +import { FORM_TYPES } from '../constants'; import BenefitType from '../components/BenefitType'; import PreDocketRadioField from '../components/PreDocketRadioField'; @@ -368,7 +369,7 @@ class NonratingRequestIssueModal extends React.Component { formType === 'appeal' ? : null; - const getSpecialIssues = this.props.userCanEditIntakeIssues ? + const getSpecialIssues = (this.props.userCanEditIntakeIssues && (formType === FORM_TYPES.APPEAL.key)) ? this.getSpecialIssues(mstIdentification, pactIdentification) : null; return ( diff --git a/client/app/intake/pages/formGenerator.jsx b/client/app/intake/pages/formGenerator.jsx index d20bbf29b0f..e359f3cf655 100644 --- a/client/app/intake/pages/formGenerator.jsx +++ b/client/app/intake/pages/formGenerator.jsx @@ -7,7 +7,8 @@ import { Redirect } from 'react-router-dom'; import { reject, map } from 'lodash'; import RadioField from '../../components/RadioField'; import ReceiptDateInput from './receiptDateInput'; -import { setDocketType, setOriginalHearingRequestType, setHomelessnessType +import { + setDocketType, setOriginalHearingRequestType, setHomelessnessType } from '../actions/appeal'; import { setReceiptDate, setOptionSelected } from '../actions/intake'; import { setAppealDocket, confirmIneligibleForm } from '../actions/rampRefiling'; @@ -117,14 +118,14 @@ const formFieldMapping = (props) => {
), 'original-hearing-request-type': - props.docketType === 'hearing' && props.featureToggles.updatedAppealForm ? hearingTypeDropdown : <>, + props.docketType === 'hearing' && props.featureToggles.updatedAppealForm ? hearingTypeDropdown : <>, 'legacy-opt-in': ( @@ -142,7 +143,7 @@ const formFieldMapping = (props) => { onChange={props.setBenefitType} errorMessage={ props.benefitTypeError || - props.errors?.['benefit-type-options']?.message + props.errors?.['benefit-type-options']?.message } register={props.register} formName={props.formName} @@ -162,7 +163,7 @@ const formFieldMapping = (props) => { }} errorMessage={ props.informalConferenceError || - props.errors?.['informal-conference']?.message + props.errors?.['informal-conference']?.message } value={renderBooleanValue('informalConference')} inputRef={props.register} @@ -221,7 +222,7 @@ const formFieldMapping = (props) => { onChange={props.setOptionSelected} errorMessage={ props.optionSelectedError || - props.errors?.['opt-in-election']?.message + props.errors?.['opt-in-election']?.message } value={props.optionSelected} inputRef={props.register} @@ -235,7 +236,7 @@ const formFieldMapping = (props) => { onChange={props.setAppealDocket} errorMessage={ props.appealDocketError || - props.errors?.['appeal-docket']?.message + props.errors?.['appeal-docket']?.message } value={props.appealDocket} inputRef={props.register} @@ -286,6 +287,7 @@ const FormGenerator = (props) => { )} + {props.reviewIntakeError && } {showInvalidVeteranError && ( { errorData={props.veteranInvalidFields} /> )} - {!props.userIsVhaEmployee && isHlrOrScForm && props.featureToggles.vhaClaimReviewEstablishment && ( + + {isHlrOrScForm && !props.userIsVhaEmployee && props.featureToggles.vhaClaimReviewEstablishment && !props.featureToggles.removeCompAndPenIntake && (
)} + + {isHlrOrScForm && !props.userIsVhaEmployee && props.featureToggles.vhaClaimReviewEstablishment && props.featureToggles.removeCompAndPenIntake && ( +
+ +
    +
  • +
  • {COPY.INTAKE_REMOVE_COMP_AND_PEN}
  • +
+
+
+ )} + + {isHlrOrScForm && props.featureToggles.removeCompAndPenIntake && ((props.featureToggles.vhaClaimReviewEstablishment && props.userIsVhaEmployee) || (!props.featureToggles.vhaClaimReviewEstablishment && !props.userIsVhaEmployee)) && ( +
+ +
    +
  • {COPY.INTAKE_REMOVE_COMP_AND_PEN}
  • +
+
+
+ )} + {Object.keys(props.schema.fields).map((field) => formFieldMapping(props)[field])} ); diff --git a/client/app/intake/reducers/featureToggles.js b/client/app/intake/reducers/featureToggles.js index 3d1d73f5856..e5705f0cc0c 100644 --- a/client/app/intake/reducers/featureToggles.js +++ b/client/app/intake/reducers/featureToggles.js @@ -40,6 +40,9 @@ const updateFromServerFeatures = (state, featureToggles) => { }, vhaClaimReviewEstablishment: { $set: Boolean(featureToggles.vhaClaimReviewEstablishment) + }, + removeCompAndPenIntake: { + $set: Boolean(featureToggles.removeCompAndPenIntake) } }); }; @@ -58,7 +61,8 @@ export const mapDataToFeatureToggle = (data = { featureToggles: {} }) => justificationReason: false, updatedAppealForm: false, hlrScUnrecognizedClaimants: false, - vhaClaimReviewEstablishment: false + vhaClaimReviewEstablishment: false, + removeCompAndPenIntake: false }, data.featureToggles ); diff --git a/client/app/intake/util/index.js b/client/app/intake/util/index.js index 97436c487ec..01b48cbdf48 100644 --- a/client/app/intake/util/index.js +++ b/client/app/intake/util/index.js @@ -4,7 +4,8 @@ import { sprintf } from 'sprintf-js'; import { REVIEW_OPTIONS, REVIEW_DATA_FIELDS, CLAIMANT_ERRORS } from '../constants'; import { INTAKE_VHA_CLAIM_REVIEW_REQUIREMENT, - VHA_BENEFIT_EMAIL_ADDRESS + VHA_BENEFIT_EMAIL_ADDRESS, + INTAKE_REMOVE_COMP_AND_PEN } from '../../../COPY'; import DATES from '../../../constants/DATES'; import { formatDateStr } from '../../util/DateUtil'; @@ -91,7 +92,7 @@ export const getDefaultPayeeCode = (state, claimant) => { return null; }; -export const formatBenefitTypeRadioOptions = (options, userCanSelectVha) => { +export const formatBenefitTypeRadioOptions = (options, userCanSelectVha, userCanSelectCompAndPen) => { return _.map(options, (value, key) => { const radioData = { value: key, displayText: value }; @@ -101,6 +102,12 @@ export const formatBenefitTypeRadioOptions = (options, userCanSelectVha) => { disabled: true, tooltipText: sprintf(INTAKE_VHA_CLAIM_REVIEW_REQUIREMENT, VHA_BENEFIT_EMAIL_ADDRESS) }; + } else if ((key === 'compensation' || key === 'pension') && !userCanSelectCompAndPen) { + return { + ...radioData, + disabled: true, + tooltipText: sprintf(INTAKE_REMOVE_COMP_AND_PEN) + }; } return radioData; diff --git a/client/test/app/intake/components/BenefitType.test.js b/client/test/app/intake/components/BenefitType.test.js index a90ae4a1428..4ce2794db33 100644 --- a/client/test/app/intake/components/BenefitType.test.js +++ b/client/test/app/intake/components/BenefitType.test.js @@ -18,12 +18,15 @@ const defaultProps = { }; const vhaTooltipText = sprintf(COPY.INTAKE_VHA_CLAIM_REVIEW_REQUIREMENT, COPY.VHA_BENEFIT_EMAIL_ADDRESS); +const penAndCompTooltipText = sprintf(COPY.INTAKE_REMOVE_COMP_AND_PEN); const renderBenefitType = (props) => { return render(); }; const getVhaRadioOption = () => screen.getByRole('radio', { name: BENEFIT_TYPES.vha }); +const getCompRadioOption = () => screen.getByRole('radio', { name: BENEFIT_TYPES.compensation }); +const getPenRadioOption = () => screen.getByRole('radio', { name: BENEFIT_TYPES.pension }); const getVhaOptionTooltip = () => { return screen.getByRole( @@ -73,6 +76,70 @@ describe('BenefitType', () => { }); }); + describe('when remove comp and pen intake is disabled', () => { + const props = { + ...defaultProps, + featureToggles: { removeCompAndPenIntake: false } + + }; + + beforeEach(() => { + renderBenefitType(props); + }); + + it('The "Veterans Health Administration" option is enabled', () => { + expect(getCompRadioOption()).not.toBeDisabled(); + expect(getPenRadioOption()).not.toBeDisabled(); + }); + + it('Tooltip does not appear whenever Compensation option is hovered over', () => { + hoverOverRadioOption(getCompRadioOption()); + + expect( + screen.queryByText(vhaTooltipText) + ).not.toBeInTheDocument(); + }); + + it('Tooltip does not appear whenever Pension option is hovered over', () => { + hoverOverRadioOption(getPenRadioOption()); + + expect( + screen.queryByText(vhaTooltipText) + ).not.toBeInTheDocument(); + }); + }); + + describe('when remove comp and pen intake is enabled', () => { + const props = { + ...defaultProps, + featureToggles: { removeCompAndPenIntake: true } + + }; + + beforeEach(() => { + renderBenefitType(props); + }); + + it('The "Veterans Health Administration" option is enabled', () => { + expect(getCompRadioOption()).toBeDisabled(); + expect(getPenRadioOption()).toBeDisabled(); + }); + + it('Tooltip appear whenever Comp and Pen option is hovered over', async () => { + + await waitFor(() => { + const toolTipElements = screen.getAllByRole('tooltip'); + + toolTipElements.forEach((toolTipElement) => { + if (toolTipElement.id === 'tooltip-pension' || toolTipElement.id === 'tooltip-compensation') { + expect(toolTipElement).toBeInTheDocument(); + expect(toolTipElement).toHaveTextContent(penAndCompTooltipText); + } + }); + }); + }); + }); + describe('when the user is a VHA staff member with feature toggle disabled on Higher Level Review Form', () => { const props = { ...defaultProps, diff --git a/client/test/app/testSeeds/components/CustomSeeds.test.js b/client/test/app/testSeeds/components/CustomSeeds.test.js index 8ac682d198e..2e4077dc48d 100644 --- a/client/test/app/testSeeds/components/CustomSeeds.test.js +++ b/client/test/app/testSeeds/components/CustomSeeds.test.js @@ -1,16 +1,22 @@ import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import CustomSeeds from 'app/testSeeds/components/CustomSeeds'; import CUSTOM_SEEDS from '../../../../constants/CUSTOM_SEEDS'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import rootReducer from 'app/testSeeds/reducers/root'; -import { addCustomSeed } from 'app/testSeeds/reducers/seeds/seedsActions'; +import { addCustomSeed, resetCustomSeeds, removeCustomSeed, saveCustomSeeds } from 'app/testSeeds/reducers/seeds/seedsActions'; import thunk from 'redux-thunk'; import ApiUtil from 'app/util/ApiUtil'; jest.mock('app/util/ApiUtil', () => ({ - get: jest.fn() + get: jest.fn(), + post: jest.fn(), +})); + +jest.mock('app/testSeeds/reducers/seeds/seedsActions', () => ({ + ...jest.requireActual('app/testSeeds/reducers/seeds/seedsActions'), // Use actual implementation for other functions + saveCustomSeeds: jest.fn(), // Mock the saveCustomSeeds function })); describe('Custom Seeds', () => { @@ -20,6 +26,19 @@ describe('Custom Seeds', () => { applyMiddleware(thunk) ); + beforeAll(() => { + // Mock window.location.assign + Object.defineProperty(window, 'location', { + writable: true, + value: { assign: jest.fn() } + }); + }); + + afterAll(() => { + jest.restoreAllMocks(); // Restore original window.location.assign after all tests + }); + + afterEach(() => { jest.clearAllMocks(); }); @@ -159,6 +178,180 @@ describe('Custom Seeds', () => { ApiUtil.get.mockResolvedValueOnce({ data: 'Success' }); fireEvent.click(button); - expect(ApiUtil.get).toHaveBeenCalledWith(`/seeds/reset_all_appeals`); + expect(ApiUtil.get).toHaveBeenCalledWith('/seeds/reset_all_appeals'); + }); + + it('downloads the template', () => { + const store = getStore(); + + render( + + + + ); + + const downloadButton = screen.getByText('Download Template'); + fireEvent.click(downloadButton); + + expect(window.location.href).toContain('sample_custom_seeds.csv'); + }); + + it('handles file upload and parses CSV correctly', () => { + const store = getStore(); + + const { container } = render( + + + + ); + + const fileInput = container.querySelector('#seed_file_upload'); + const file = new File( + ['Case(s) Type,Amount,Days Ago,Associated Judge\nType1,10,5,Judge1'], + 'test.csv', + { type: 'text/csv' } + ); + + fireEvent.change(fileInput, { + target: { files: [file] } + }); + + const reader = new FileReader(); + reader.onload = jest.fn((e) => { + const result = e.target.result; + fireEvent.load(fileInput, { target: { result } }); + }); + + const base64 = btoa('Case(s) Type,Amount,Days Ago,Associated Judge\nType1,10,5,Judge1\nType2,20,15,Judge2'); + const mockFile = { + split: () => ['data:text/csv;base64', base64], + }; + + fireEvent.load(fileInput, { target: { result: mockFile } }); + + waitFor(() => { + expect(screen.getByText('Create 2 test cases')).toBeInTheDocument(); + }); + }); + + it('handles file upload and parses invalid CSV', () => { + const store = getStore(); + + const { container } = render( + + + + ); + + const fileInput = container.querySelector('#seed_file_upload'); + const file = new File( + ['Type,Price,Days,Judge\nType1,10,5,Judge1'], + 'test.csv', + { type: 'text/csv' } + ); + + fireEvent.change(fileInput, { + target: { files: [file] } + }); + + const reader = new FileReader(); + reader.onload = jest.fn((e) => { + const result = e.target.result; + fireEvent.load(fileInput, { target: { result } }); + }); + + const base64 = btoa('Type,Price,Days,Judge\nType1,10,5,Judge1'); + const mockFile = { + split: () => ['data:text/csv;base64', base64], + }; + + fireEvent.load(fileInput, { target: { result: mockFile } }); + + waitFor(() => { + expect(screen.getByText('Create 0 test cases')).toBeInTheDocument(); + }); + }); + + it('saves seeds correctly', async () => { + const store = getStore(); + + const { container } = render( + + + + ); + + const firstSeed = seedTypes[0]; + const caseCountInput = screen.getByLabelText(`seed-count-${firstSeed}`); + const daysAgoInput = screen.getByLabelText(`days-ago-${firstSeed}`); + const cssIdInput = screen.getByLabelText(`css-id-${firstSeed}`); + fireEvent.change(caseCountInput, { target: { value: '10' } }); + fireEvent.change(daysAgoInput, { target: { value: '5' } }); + fireEvent.change(cssIdInput, { target: { value: '12345' } }); + const button = container.querySelector(`#btn-${firstSeed}`); + fireEvent.click(button); + + waitFor(() => { + expect(screen.getByText('Create 1 test cases')).toBeInTheDocument(); + }); + + waitFor(() => { + const saveButton = container.querySelector('.cf-btn-link.lever-right.test-seed-button-style.cf-right-side Button'); + expect(saveButton.innerText).toBe('Create 1 test cases'); + fireEvent.click(saveButton); + }); + + // const saveButton = screen.getByRole('button', { name: 'Create 1 test cases' }); + // fireEvent.click(saveButton); + + // const saveButton = container.querySelector('#button-Create-1-test-cases'); + // fireEvent.click(saveButton); + + waitFor(() => { + expect(saveCustomSeeds).toHaveBeenCalledTimes(1); + const actions = store.getActions(); + const expectedAction = saveCustomSeeds(store.getState().testSeeds.seeds); + expect(actions).toContainEqual(expectedAction); + }); + + waitFor(() => { + expect(ApiUtil.post).toHaveBeenCalledWith('/seeds/save', expect.any(Object)); + }); + + waitFor(() => { + expect(screen.getByText('Test seeds have been saved')).toBeInTheDocument(); + }); + }); + + it('handles empty CSV file gracefully', () => { + const store = getStore(); + + const { container } = render( + + + + ); + + const fileInput = container.querySelector('#seed_file_upload'); + const file = new File([''], 'empty.csv', { type: 'text/csv' }); + + fireEvent.change(fileInput, { + target: { files: [file] } + }); + + const reader = new FileReader(); + reader.onload = jest.fn((e) => { + const result = e.target.result; + fireEvent.load(fileInput, { target: { result } }); + }); + + const base64 = btoa(''); + const mockFile = { + split: () => ['data:text/csv;base64', base64], + }; + + fireEvent.load(fileInput, { target: { result: mockFile } }); + + expect(screen.queryByText('Create 1 test cases')).not.toBeInTheDocument(); }); }); diff --git a/client/test/app/testSeeds/reducers/seeds/seedsReducer.test.js b/client/test/app/testSeeds/reducers/seeds/seedsReducer.test.js index 72ea77467cd..df422f7e0a2 100644 --- a/client/test/app/testSeeds/reducers/seeds/seedsReducer.test.js +++ b/client/test/app/testSeeds/reducers/seeds/seedsReducer.test.js @@ -2,13 +2,21 @@ import seedsReducer from 'app/testSeeds/reducers/seeds/seedsReducer'; import { ACTIONS } from 'app/testSeeds/reducers/seeds/seedsActionTypes'; -describe('Seed reducer', () => { +describe('seedsReducer function declaration', () => { let initialState = { seeds: [], displayBanner: false }; + it('should initialize the reducer with initialState', () => { + const state = seedsReducer(undefined, {}); + expect(state).toEqual(initialState); + }); +}); + +describe('Seed reducer', () => { + let seed = { "seed_count": 1, "days_ago": 12, @@ -16,6 +24,32 @@ describe('Seed reducer', () => { "seed_type": "ama-aod-hearing-seeds" }; + let seed1 = { + "seed_count": 1, + "days_ago": 12, + "judge_css_id": "keeling", + "seed_type": "ama-aod-hearing-seeds" + }; + + let seed2 = { + "seed_count": 1, + "days_ago": 20, + "judge_css_id": "keeling1", + "seed_type": "ama-aod-hearing-seeds" + }; + + let seed3 = { + "seed_count": 1, + "days_ago": 25, + "judge_css_id": "keeling2", + "seed_type": "ama-aod-hearing-seeds" + }; + + let initialState = { + seeds: [seed1, seed2, seed3], + displayBanner: false + }; + afterEach(() => { jest.clearAllMocks(); }); @@ -28,45 +62,52 @@ describe('Seed reducer', () => { } }; + const newState = seedsReducer(initialState, action); + expect(newState.seeds).toContain(seed); + }); + + it('Should handle RESET_CUSTOM_SEEDS action', () => { + const action = { + type: ACTIONS.RESET_CUSTOM_SEEDS, + }; + const expectedState = { ...initialState, - seeds: [action.payload.seed] + seeds: [] }; const newState = seedsReducer(initialState, action); expect(newState).toEqual(expectedState); + }); - const removeAction = { - type: ACTIONS.REMOVE_CUSTOM_SEED, - payload: { - seed: seed, - index: 0 - } + it('Should set displayBanner to true when dispatched SAVE_CUSTOM_SEEDS action', () => { + const action = { + type: ACTIONS.SAVE_CUSTOM_SEEDS }; - const expectedRemoveState = { - ...initialState, - seeds: [] - }; + const newState = seedsReducer(initialState, action); + expect(newState.displayBanner).toBe(true); + }); - const newStateFixed = seedsReducer(initialState, removeAction); + it('should not modify the state for unknown action', () => { + const action = { type: 'UNKNOWN_ACTION' }; - expect(newStateFixed).toEqual(expectedRemoveState); + const newState = seedsReducer(initialState, action); + expect(newState).toEqual(initialState); }); - it('Should handle RESET_CUSTOM_SEEDS action', () => { + it('should remove a custom seed when dispatche REMOVE_CUSTOM_SEED action', () => { + const indexToRemove = 1; const action = { - type: ACTIONS.RESET_CUSTOM_SEEDS, - }; - - const expectedState = { - ...initialState, - seeds: [] + type: ACTIONS.REMOVE_CUSTOM_SEED, + payload: { + index: indexToRemove + } }; const newState = seedsReducer(initialState, action); - - expect(newState).toEqual(expectedState); + expect(newState.seeds).toHaveLength(initialState.seeds.length - 1); + expect(newState.seeds).not.toContain(initialState.seeds[indexToRemove]); }); }); diff --git a/spec/controllers/legacy_tasks_controller_spec.rb b/spec/controllers/legacy_tasks_controller_spec.rb index e34027b2859..54b8e2570a3 100644 --- a/spec/controllers/legacy_tasks_controller_spec.rb +++ b/spec/controllers/legacy_tasks_controller_spec.rb @@ -72,6 +72,19 @@ expect(response.status).to eq 200 end end + + context "when rendering json format" do + it "records metric with MetricsService" do + allow(LegacyWorkQueue).to receive(:tasks_for_user).and_return([]) + expect(MetricsService).to receive(:record).with( + "VACOLS: Get all tasks with appeals for #{user.css_id}", + name: "LegacyTasksController.index" + ).and_call_original + get :index, format: :json, params: { user_id: user.css_id, rest: "/assign" } + expect(response.status).to eq 200 + end + end + context "CSS_ID in URL is invalid" do it "returns 404" do [-1, "BAD_CSS_ID", ""].each do |user_id_path| diff --git a/spec/controllers/test_docket_seeds_controller_spec.rb b/spec/controllers/test_docket_seeds_controller_spec.rb index 91847bc3fe3..98d269992e3 100644 --- a/spec/controllers/test_docket_seeds_controller_spec.rb +++ b/spec/controllers/test_docket_seeds_controller_spec.rb @@ -8,6 +8,22 @@ end let!(:authenticated_user) { User.authenticate!(css_id: "RSPEC", roles: ["System Admin"]) } + let(:root_task) { create(:root_task) } + let(:distribution_task) do + DistributionTask.create!( + appeal: root_task.appeal, + assigned_to: Bva.singleton, + status: "assigned" + ) + end + + let(:bfcurloc_keys) { %w[77 81 83] } + let!(:cases) do + bfcurloc_keys.map do |bfcurloc_key| + create(:case, bfcurloc: bfcurloc_key) + end + end + describe "POST run-demo?seed_type=ii?seed_count=x&days_ago=y&judge_css_id=zzz" do before(:all) do Rake::Task.define_task(:environment) @@ -596,5 +612,34 @@ end end end + + it "should reset all appeals" do + expect(distribution_task.status).to eq("assigned") + expect(VACOLS::Case.where(bfcurloc: %w[81 83]).count).to eq(2) + expect(VACOLS::Case.where(bfcurloc: "testing").count).to eq(0) + get :reset_all_appeals + expect(response.status).to eq 200 + expect(distribution_task.reload.status).to eq("on_hold") + expect(VACOLS::Case.where(bfcurloc: %w[81 83]).count).to eq(0) + expect(VACOLS::Case.where(bfcurloc: "testing").count).to eq(2) + end + + context "check environment when non prod environments is true" do + before { allow(Rails).to receive(:deploy_env?).with(:demo).and_return(true) } + + it "allows access without redirecting" do + get :reset_all_appeals + expect(response.status).to eq 200 + end + end + + context "check environment when in other environments" do + before { allow(Rails).to receive(:deploy_env?).and_return(false) } + + it "redirects to /unauthorized" do + get :reset_all_appeals + expect(response).to redirect_to("/unauthorized") + end + end end end diff --git a/spec/factories/case_distribution_lever.rb b/spec/factories/case_distribution_lever.rb index a216d6e6716..30789851b51 100644 --- a/spec/factories/case_distribution_lever.rb +++ b/spec/factories/case_distribution_lever.rb @@ -83,30 +83,6 @@ lever_group_order { 101 } end - trait :disable_legacy_priority do - item { "disable_legacy_priority" } - title { "ACD Disable Legacy Priority" } - description { "" } - data_type { "boolean" } - value { false } - unit { "" } - algorithms_used { %w[docket proportion] } - lever_group { "docket_levers" } - lever_group_order { 105 } - end - - trait :disable_ama_non_priority_direct_review do - item { "disable_ama_non_priority_direct_review" } - title { "ACD Disable AMA Non-Priority Direct Review" } - description { "" } - data_type { "boolean" } - value { false } - unit { "" } - algorithms_used { %w[docket proportion] } - lever_group { "docket_levers" } - lever_group_order { 103 } - end - trait :ama_hearings_start_distribution_prior_to_goals do item { "ama_hearings_start_distribution_prior_to_goals" } title { "AMA Hearings Start Distribution Prior to Goals" } @@ -412,5 +388,117 @@ lever_group_order { 103 } control_group { "non_priority" } end + + trait :disable_legacy_priority do + item { "disable_legacy_priority" } + title { "ACD Disable Legacy Priority" } + data_type { "boolean" } + options do + [ + { + displayText: "On", + name: Constants.DISTRIBUTION.disable_legacy_priority, + value: "true", + disabled: false + }, + { + displayText: "Off", + name: Constants.DISTRIBUTION.disable_legacy_priority, + value: "false", + disabled: false + } + ] + end + value { false } + unit { "days" } + algorithms_used { %w(proportion docket) } + lever_group { "docket_levers" } + lever_group_order { 103 } + control_group { "priority" } + end + + trait :disable_ama_priority_hearing do + item { "disable_ama_priority_hearing" } + title { "ACD Disable AMA Priority Hearing" } + data_type { "boolean" } + options do + [ + { + displayText: "On", + name: Constants.DISTRIBUTION.disable_ama_priority_hearing, + value: "true", + disabled: false + }, + { + displayText: "Off", + name: Constants.DISTRIBUTION.disable_ama_priority_hearing, + value: "false", + disabled: false + } + ] + end + value { false } + unit { "days" } + algorithms_used { %w(proportion docket) } + lever_group { "docket_levers" } + lever_group_order { 103 } + control_group { "priority" } + end + + trait :disable_ama_priority_direct_review do + item { "disable_ama_priority_direct_review" } + title { "ACD Disable AMA Priority Direct Review" } + data_type { "boolean" } + options do + [ + { + displayText: "On", + name: Constants.DISTRIBUTION.disable_ama_priority_direct_review, + value: "true", + disabled: false + }, + { + displayText: "Off", + name: Constants.DISTRIBUTION.disable_ama_priority_direct_review, + value: "false", + disabled: false + } + ] + end + value { false } + unit { "days" } + algorithms_used { %w(proportion docket) } + lever_group { "docket_levers" } + lever_group_order { 103 } + control_group { "priority" } + end + + trait :disable_ama_priority_evidence_submission do + item { "disable_ama_priority_direct_review" } + title { "ACD Disable AMA Priority Evidence Submission" } + data_type { "boolean" } + options do + [ + { + displayText: "On", + name: Constants.DISTRIBUTION.disable_ama_priority_evidence_submission, + value: "true", + disabled: false + }, + { + displayText: "Off", + name: Constants.DISTRIBUTION.disable_ama_priority_evidence_submission, + value: "false", + disabled: false + } + ] + end + value { false } + unit { "days" } + algorithms_used { %w(proportion docket) } + lever_group { "docket_levers" } + lever_group_order { 103 } + control_group { "priority" } + end end end diff --git a/spec/feature/intake/review_page_spec.rb b/spec/feature/intake/review_page_spec.rb index f4c4cff8285..0d7a80316ff 100644 --- a/spec/feature/intake/review_page_spec.rb +++ b/spec/feature/intake/review_page_spec.rb @@ -486,6 +486,8 @@ def select_claimant(index = 0) shared_examples "Claim review intake with VHA benefit type" do let(:benefit_type_label) { Constants::BENEFIT_TYPES["vha"] } + let(:compensation_type_label) { Constants::BENEFIT_TYPES["compensation"] } + let(:pension_type_label) { Constants::BENEFIT_TYPES["pension"] } let(:email_href) do "mailto:VHABENEFITAPPEALS@va.gov?subject=Potential%20VHA%20Higher-Level%20Review%20or%20Supplemental%20Claim" end @@ -547,6 +549,79 @@ def select_claimant(index = 0) end end + context "Correct banner shows when user is NOT a member of the VHA business line" do + let(:vha_business_line) { create(:business_line, name: benefit_type_label, url: "vha") } + let(:current_user) { create(:user, roles: ["Admin Intake"]) } + + before do + User.authenticate!(user: current_user) + end + + it "display both REMOVE_INTAKE_COMP_AND_PEN and VHA messages" do + FeatureToggle.enable!(:remove_comp_and_pen_intake) + FeatureToggle.enable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + + it "display REMOVE_INTAKE_COMP_AND_PEN message and doesn't display VHA message" do + FeatureToggle.enable!(:remove_comp_and_pen_intake) + FeatureToggle.disable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to_not have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + + it "Display VHA message and doesn't display REMOVE_INTAKE_COMP_AND_PEN message" do + FeatureToggle.disable!(:remove_comp_and_pen_intake) + FeatureToggle.enable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to_not have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + + it "Doesn't display VHA message nor REMOVE_INTAKE_COMP_AND_PEN message if these features are disabled" do + FeatureToggle.disable!(:remove_comp_and_pen_intake) + FeatureToggle.disable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to_not have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to_not have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + end + + context "Correct banner shows when user IS a member of the VHA business line" do + let(:vha_business_line) { VhaBusinessLine.singleton } + let(:current_user) { create(:user, roles: ["Admin Intake"]) } + + before do + vha_business_line.add_user(current_user) + User.authenticate!(user: current_user) + end + + it "display REMOVE_INTAKE_COMP_AND_PEN message and doesn't display VHA message" do + FeatureToggle.enable!(:remove_comp_and_pen_intake) + FeatureToggle.enable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to_not have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + + it "Doesn't display VHA message nor REMOVE_INTAKE_COMP_AND_PEN message if these features are disabled" do + FeatureToggle.disable!(:remove_comp_and_pen_intake) + FeatureToggle.disable!(:vha_claim_review_establishment) + navigate_to_review_page(form_type) + + expect(page).to_not have_content(COPY::INTAKE_REMOVE_COMP_AND_PEN) + expect(page).to_not have_link(COPY::VHA_BENEFIT_EMAIL_ADDRESS, href: email_href) + end + end + context "Current user is not a member of the VHA business line with feature toggle disabled" do let(:vha_business_line) { create(:business_line, name: benefit_type_label, url: "vha") } let(:current_user) { create(:user, roles: ["Admin Intake"]) } @@ -566,6 +641,46 @@ def select_claimant(index = 0) expect(page).to have_field benefit_type_label, disabled: false, visible: false end end + + context "Current user has Compensation and Pension form selections disabled" do + let(:current_user) { create(:user, roles: ["Admin Intake"]) } + + before do + FeatureToggle.enable!(:remove_comp_and_pen_intake) + User.authenticate!(user: current_user) + navigate_to_review_page(form_type) + end + + after do + FeatureToggle.disable!(:remove_comp_and_pen_intake) + end + + it "Compensation benefit type radio option is disabled" do + expect(page).to have_field compensation_type_label, disabled: true, visible: false + end + + it "Pension benefit type radio option is disabled" do + expect(page).to have_field pension_type_label, disabled: true, visible: false + end + + it "The tooltip appears whenenver Compensation radio field is hovered over" do + find("label", text: compensation_type_label).hover + + # Checks for tooltip text + expect(page).to have_content( + format(COPY::INTAKE_REMOVE_COMP_AND_PEN) + ) + end + + it "The tooltip appears whenenver Pension radio field is hovered over" do + find("label", text: pension_type_label).hover + + # Checks for tooltip text + expect(page).to have_content( + format(COPY::INTAKE_REMOVE_COMP_AND_PEN) + ) + end + end end describe "Intaking a claim review" do diff --git a/spec/feature/queue/ama_queue_workflow_spec.rb b/spec/feature/queue/ama_queue_workflow_spec.rb index cc9b675238c..898e2bcf0ba 100644 --- a/spec/feature/queue/ama_queue_workflow_spec.rb +++ b/spec/feature/queue/ama_queue_workflow_spec.rb @@ -164,12 +164,14 @@ # joins the user with the organization to grant access to role and org permissions FeatureToggle.enable!(:mst_identification) FeatureToggle.enable!(:pact_identification) + FeatureToggle.enable!(:legacy_mst_pact_identification) FeatureToggle.enable!(:acd_distribute_by_docket_date) end after do FeatureToggle.disable!(:mst_identification) FeatureToggle.disable!(:pact_identification) + FeatureToggle.enable!(:legacy_mst_pact_identification) FeatureToggle.disable!(:acd_distribute_by_docket_date) end diff --git a/spec/jobs/push_priority_appeals_to_judges_job_spec.rb b/spec/jobs/push_priority_appeals_to_judges_job_spec.rb index f46cb7d0826..9d52ecaabed 100644 --- a/spec/jobs/push_priority_appeals_to_judges_job_spec.rb +++ b/spec/jobs/push_priority_appeals_to_judges_job_spec.rb @@ -13,7 +13,6 @@ create(:case_distribution_lever, :ama_hearing_case_aod_affinity_days) create(:case_distribution_lever, :ama_direct_review_start_distribution_prior_to_goals) create(:case_distribution_lever, :disable_legacy_non_priority) - create(:case_distribution_lever, :disable_legacy_priority) end def to_judge_hash(arr) @@ -222,10 +221,11 @@ def to_judge_hash(arr) context "using Automatic Case Distribution module" do before do + create(:case_distribution_lever, :disable_legacy_priority) allow_any_instance_of(PushPriorityAppealsToJudgesJob).to receive(:eligible_judges).and_return(eligible_judges) end - it "should only distribute the ready priority cases tied to a judge" do + xit "should only distribute the ready priority cases tied to a judge" do expect(subject.count).to eq eligible_judges.count expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [2, 2, 0, 0] @@ -252,27 +252,78 @@ def to_judge_hash(arr) FeatureToggle.disable!(:acd_distribute_by_docket_date) FeatureToggle.enable!(:acd_exclude_from_affinity) end + context "without using Docket Levers" do + before do + create(:case_distribution_lever, :disable_legacy_priority, value: "false") + end - it "should only distribute the ready priority cases tied to a judge" do - expect(subject.count).to eq eligible_judges.count - expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [2, 2, 0, 0] + xit "should only distribute the ready priority cases tied to a judge" do + expect(subject.count).to eq eligible_judges.count + expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [2, 2, 0, 0] - # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge - distributed_cases = DistributedCase.where(distribution: subject) - expect(distributed_cases.count).to eq 4 - expected_array = [ready_priority_bfkey, ready_priority_bfkey2, ready_priority_uuid, ready_priority_uuid2] - expect(distributed_cases.map(&:case_id)).to match_array expected_array - # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases - expected_array2 = %w[hearing hearing legacy legacy] - expect(distributed_cases.map(&:docket)).to match_array expected_array2 - expect(distributed_cases.map(&:priority).uniq).to match_array [true] - expect(distributed_cases.map(&:genpop).uniq).to match_array [false, true] + # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq 4 + expected_array = [ready_priority_bfkey, ready_priority_bfkey2, ready_priority_uuid, ready_priority_uuid2] + expect(distributed_cases.map(&:case_id)).to match_array expected_array + # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases + expected_array2 = %w[hearing hearing legacy legacy] + expect(distributed_cases.map(&:docket)).to match_array expected_array2 + expect(distributed_cases.map(&:priority).uniq).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq).to match_array [false, true] + end + end + + context "using Excluding Appeals by Docket Type and Priority from Automatic Case Distribution levers" do + context "all Exluding levers turned to Include" do + before do + create(:case_distribution_lever, :disable_legacy_priority, value: "false") + create(:case_distribution_lever, :disable_ama_priority_hearing, value: "false") + create(:case_distribution_lever, :disable_ama_priority_direct_review, value: "false") + create(:case_distribution_lever, :disable_ama_priority_evidence_submission, value: "false") + end + + xit "should distribute the ready priority cases" do + expect(subject.count).to eq eligible_judges.count + expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [2, 2, 0, 0] + + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq 4 + expected_array = [ready_priority_bfkey, ready_priority_bfkey2, ready_priority_uuid, ready_priority_uuid2] + expect(distributed_cases.map(&:case_id)).to match_array expected_array + # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases + expected_array2 = %w[hearing hearing legacy legacy] + expect(distributed_cases.map(&:docket)).to match_array expected_array2 + expect(distributed_cases.map(&:priority).uniq).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq).to match_array [false, true] + end + end + + context "all Exluding levers turned to Exclude" do + before do + create(:case_distribution_lever, :disable_legacy_priority, value: "true") + create(:case_distribution_lever, :disable_ama_priority_hearing, value: "true") + create(:case_distribution_lever, :disable_ama_priority_direct_review, value: "true") + create(:case_distribution_lever, :disable_ama_priority_evidence_submission, value: "true") + end + + xit "should not distribute the ready priority cases" do + expect(subject.count).to eq eligible_judges.count + expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [0, 0, 0, 0] + + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq 0 + expected_array = [] + expect(distributed_cases).to match_array expected_array + end + end end end end context ".distribute_genpop_priority_appeals" do before do + create(:case_distribution_lever, :disable_legacy_priority) allow_any_instance_of(DirectReviewDocket) .to receive(:nonpriority_receipts_per_year) .and_return(100) diff --git a/spec/mailers/dispatch_mailer_spec.rb b/spec/mailers/dispatch_mailer_spec.rb index 74422e993b8..4e6cf0cb1ed 100644 --- a/spec/mailers/dispatch_mailer_spec.rb +++ b/spec/mailers/dispatch_mailer_spec.rb @@ -14,7 +14,7 @@ subject { DispatchMailer.dispatch(email_address: email_address, appeal: appeal) } describe "#dispatch" do it "has the correct from" do - expect(subject.from).to include("BoardofVeteransAppealsHearings@messages.va.gov") + expect(subject.from).to include("BoardofVeteransAppealsDecisions@messages.va.gov") end it "has the correct subject line" do diff --git a/spec/models/distribution_spec.rb b/spec/models/distribution_spec.rb index 66aebd9302d..3e3b2e5124d 100644 --- a/spec/models/distribution_spec.rb +++ b/spec/models/distribution_spec.rb @@ -186,7 +186,7 @@ end it "updates status to error if an error is thrown and sends slack notification" do - allow_any_instance_of(LegacyDocket).to receive(:distribute_appeals).and_raise(StandardError) + allow(new_distribution).to receive(:num_oldest_priority_appeals_for_judge_by_docket).and_raise(StandardError) expect_any_instance_of(SlackService).to receive(:send_notification).exactly(1).times expect { new_distribution.distribute! }.to raise_error(StandardError) @@ -224,103 +224,4 @@ end end end - - # The following are specifically testing the priority push code in the AutomaticCaseDistribution module - # ByDocketDateDistribution tests are in their own file, by_docket_date_distribution_spec.rb - context "priority push distributions" do - let(:priority_push) { true } - - context "when there is no limit" do - let(:limit) { nil } - - it "distributes priority appeals on the legacy and hearing dockets" do - expect_any_instance_of(LegacyDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: nil, priority: true, genpop: "not_genpop", style: "push") - .and_return([]) - - expect_any_instance_of(HearingRequestDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: nil, priority: true, genpop: "not_genpop", style: "push") - .and_return([]) - - new_distribution.distribute!(limit) - end - end - - context "when there is a limit set" do - let(:limit) { 10 } - - let(:stubbed_appeals) do - { - legacy: 5, - direct_review: 3, - evidence_submission: 1, - hearing: 1 - } - end - - it "distributes only up to the limit" do - expect(new_distribution).to receive(:num_oldest_priority_appeals_by_docket) - .with(limit) - .and_return stubbed_appeals - - expect_any_instance_of(LegacyDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: 5, priority: true, style: "push") - .and_return(create_list(:appeal, 5)) - - expect_any_instance_of(DirectReviewDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: 3, priority: true, style: "push") - .and_return(create_list(:appeal, 3)) - - expect_any_instance_of(EvidenceSubmissionDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: 1, priority: true, style: "push") - .and_return(create_list(:appeal, 1)) - - expect_any_instance_of(HearingRequestDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: 1, priority: true, style: "push") - .and_return(create_list(:appeal, 1)) - - new_distribution.distribute!(limit) - end - end - end - - context "requested distributions" do - context "when priority_acd is enabled" do - let(:limit) { 10 } - let(:batch_size) { CaseDistributionLever.alternative_batch_size } - - before { FeatureToggle.enable!(:priority_acd) } - - it "calls distribute_appeals with bust_backlog set along with the other calls" do - expect_any_instance_of(LegacyDocket).to receive(:distribute_nonpriority_appeals) - .with(new_distribution, limit: batch_size, genpop: "not_genpop", bust_backlog: true, style: "request") - .and_return([]) - - expect_any_instance_of(LegacyDocket).to receive(:distribute_priority_appeals) - .with(new_distribution, limit: batch_size, genpop: "not_genpop", style: "request") - .and_return([]) - - expect_any_instance_of(HearingRequestDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: batch_size, priority: true, genpop: "not_genpop", style: "request") - .and_return([]) - - expect_any_instance_of(LegacyDocket).to receive(:distribute_nonpriority_appeals) - .with(new_distribution, limit: batch_size, genpop: "not_genpop", range: 0, style: "request") - .and_return([]) - - expect_any_instance_of(HearingRequestDocket).to receive(:distribute_appeals) - .with(new_distribution, limit: batch_size, priority: false, genpop: "not_genpop", style: "request") - .and_return([]) - - expect(new_distribution).to receive(:distribute_limited_priority_appeals_from_all_dockets) - .with(15, style: "request") - .and_return([]) - - expect(new_distribution).to receive(:deduct_distributed_actuals_from_remaining_docket_proportions) - .with(:legacy, :hearing) - - new_distribution.distribute!(limit) - end - end - end end