diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d89fe2a19d5..53b685d2640 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -87,6 +87,7 @@ jobs: CI: true REDIS_URL_CACHE: redis://redis:6379/0/cache/ TEST_VACOLS_HOST: facols_db + HEADLESS: true steps: - uses: actions/checkout@v3 diff --git a/app/models/legacy_tasks/judge_legacy_decision_review_task.rb b/app/models/legacy_tasks/judge_legacy_decision_review_task.rb index 1451a4de96e..1a794dce88c 100644 --- a/app/models/legacy_tasks/judge_legacy_decision_review_task.rb +++ b/app/models/legacy_tasks/judge_legacy_decision_review_task.rb @@ -8,12 +8,18 @@ def review_action def available_actions(current_user, _role) # This must check judge_in_vacols? rather than role as judge, otherwise acting # VLJs cannot check out - return [] if current_user != assigned_to || !current_user.judge_in_vacols? + scm = current_user.can_act_on_behalf_of_judges? - [ - Constants.TASK_ACTIONS.ADD_ADMIN_ACTION.to_h, - review_action - ] + actions = [] + + if scm && FeatureToggle.enabled?(:legacy_case_movement_vlj_to_atty_for_rewrite) + actions << Constants.TASK_ACTIONS.LEGACY_RETURN_TO_ATTORNEY.to_h + end + + return actions if current_user != assigned_to || !current_user.judge_in_vacols? + + actions << Constants.TASK_ACTIONS.ADD_ADMIN_ACTION.to_h + actions << review_action end def label diff --git a/app/queries/ineligible_judge_list.rb b/app/queries/ineligible_judge_list.rb index 7c5b7d3dc45..739282331f1 100644 --- a/app/queries/ineligible_judge_list.rb +++ b/app/queries/ineligible_judge_list.rb @@ -12,8 +12,6 @@ class IneligibleJudgeList }.freeze EMPTY_KEY_VALUE = "No Key Present" - INACTIVE_VACOLS = CaseDistributionIneligibleJudges.ineligible_vacols_judges - INACTIVE_CASEFLOW = CaseDistributionIneligibleJudges.ineligible_caseflow_judges def self.generate_rows(record) HEADERS.keys.map { |key| record[key] } @@ -47,9 +45,17 @@ def self.parse_record(record) } end + def self.inactive_caseflow + @inactive_caseflow ||= CaseDistributionIneligibleJudges.ineligible_caseflow_judges + end + + def self.inactive_vacols + @inactive_vacols ||= CaseDistributionIneligibleJudges.ineligible_vacols_judges + end + def self.get_reason_for_ineligibility(css_id_value, sdomainid_value) - inactive_caseflow_user = INACTIVE_CASEFLOW.find { |caseflow_user| caseflow_user[:css_id] == css_id_value } - inactive_vacols_user = INACTIVE_VACOLS.find { |vacols_user| vacols_user[:sdomainid] == sdomainid_value } + inactive_caseflow_user = inactive_caseflow.find { |caseflow_user| caseflow_user[:css_id] == css_id_value } + inactive_vacols_user = inactive_vacols.find { |vacols_user| vacols_user[:sdomainid] == sdomainid_value } @reason = if inactive_caseflow_user && inactive_vacols_user "BOTH" diff --git a/app/repositories/task_action_repository.rb b/app/repositories/task_action_repository.rb index e5171aaf9f0..4f41f02f7c9 100644 --- a/app/repositories/task_action_repository.rb +++ b/app/repositories/task_action_repository.rb @@ -200,6 +200,14 @@ def judge_qr_return_to_attorney_data(task, _user = nil) } end + def legacy_return_to_attorney_data(_task, _user = nil) + { + selected: nil, + options: users_to_options(Attorney.list_all), + type: AttorneyLegacyTask.name + } + end + def assign_to_privacy_team_data(_task, _user = nil) org = PrivacyTeam.singleton diff --git a/client/app/queue/AssignToView.jsx b/client/app/queue/AssignToView.jsx index 2b4b481265d..6fc5afd3d25 100644 --- a/client/app/queue/AssignToView.jsx +++ b/client/app/queue/AssignToView.jsx @@ -16,7 +16,7 @@ import SearchableDropdown from '../components/SearchableDropdown'; import TextareaField from '../components/TextareaField'; import QueueFlowModal from './components/QueueFlowModal'; -import { requestPatch, requestSave, resetSuccessMessages } from './uiReducer/uiActions'; +import { requestPatch, requestSave, resetSuccessMessages, highlightInvalidFormItems } from './uiReducer/uiActions'; import { taskActionData } from './utils'; @@ -76,6 +76,7 @@ class AssignToView extends React.Component { }; submit = () => { + const { appeal, task, isReassignAction, isTeamAssign } = this.props; const action = getAction(this.props); @@ -270,7 +271,7 @@ class AssignToView extends React.Component { } render = () => { - const { assigneeAlreadySelected, task } = this.props; + const { assigneeAlreadySelected, highlightFormItems, task } = this.props; const action = getAction(this.props); const actionData = taskActionData(this.props); @@ -327,11 +328,13 @@ class AssignToView extends React.Component { name="Assign to selector" searchable hideLabel={actionData.drop_down_label ? null : true} + errorMessage={highlightFormItems && this.state.selectedValue === null ? 'This field is required' : null} label={this.determineDropDownLabel(actionData)} placeholder={this.determinePlaceholder(this.props, actionData)} value={this.state.selectedValue} onChange={(option) => this.setState({ selectedValue: option ? option.value : null })} options={this.determineOptions(actionData)} + required={this.props.task.type === 'JudgeLegacyDecisionReviewTask'} /> )} {this.isVHAAssignToRegional() && @@ -353,6 +356,8 @@ class AssignToView extends React.Component { onChange={(value) => this.setState({ instructions: value })} value={this.state.instructions} optional={actionData.body_optional} + errorMessage={highlightFormItems && !validInstructions(this.state.instructions) ? 'Instructions field is required' : null} + required={this.props.task.type === 'JudgeLegacyDecisionReviewTask'} /> )} {isPulacCerullo && ( @@ -373,6 +378,8 @@ AssignToView.propTypes = { veteranFullName: PropTypes.string }), assigneeAlreadySelected: PropTypes.bool, + highlightInvalidFormItems: PropTypes.func, + highlightFormItems: PropTypes.bool, isReassignAction: PropTypes.bool, isTeamAssign: PropTypes.bool, onReceiveAmaTasks: PropTypes.func, @@ -392,6 +399,7 @@ AssignToView.propTypes = { const mapStateToProps = (state, ownProps) => { return { + highlightFormItems: state.ui.highlightFormItems, task: taskById(state, { taskId: ownProps.taskId }), appeal: appealWithDetailSelector(state, ownProps) }; @@ -403,6 +411,7 @@ const mapDispatchToProps = (dispatch) => requestPatch, requestSave, onReceiveAmaTasks, + highlightInvalidFormItems, legacyReassignToJudge, setOvertime, resetSuccessMessages diff --git a/client/app/queue/QueueApp.jsx b/client/app/queue/QueueApp.jsx index 06d50aa6530..a40a562fbb2 100644 --- a/client/app/queue/QueueApp.jsx +++ b/client/app/queue/QueueApp.jsx @@ -946,6 +946,11 @@ class QueueApp extends React.PureComponent { }`} render={this.routedAssignToUser} /> + 09d", n: @scenario_6_file_number + 1)) + @scenario_6_file_number += 2000 + @scenario_6_participant_id += 2000 + end + end + + def initial_scenario_7_id_values + @scenario_7_file_number ||= 790_000_000 + @scenario_7_participant_id ||= 590_000_000 + while Veteran.find_by(file_number: format("%09d", n: @scenario_7_file_number + 1)) + @scenario_7_file_number += 2000 + @scenario_7_participant_id += 2000 + end + end + + def create_scenario_6_veteran(options = {}) + @scenario_6_file_number += 1 + @scenario_6_participant_id += 1 + params = { + file_number: format("%09d", n: @scenario_6_file_number), + participant_id: format("%09d", n: @scenario_6_participant_id) + } + create(:veteran, params.merge(options)) + end + + def create_scenario_7_veteran(options = {}) + @scenario_7_file_number += 1 + @scenario_7_participant_id += 1 + params = { + file_number: format("%09d", n: @scenario_7_file_number), + participant_id: format("%09d", n: @scenario_7_participant_id) + } + create(:veteran, params.merge(options)) + end + + def scenario_6_seeds + wqjudge_one = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "One") + wqjudge_two = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Two") + wqjudge_three = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Three") + wqjudge_four = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Four") + wqjudge_five = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Five") + wqjudge_six = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Six") + wqjudge_control = create_scenario_6_veteran(first_name: "Wqjudge", last_name: "Control") + + # Case 1: Wqjudge One (Priority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_cavc_remand, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVALSHIELDS"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_one.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation) + )) + + # Case 2: Wqjudge Two (Nonpriority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVACOTBJUDGE"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_two.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 3: Wqjudge Three (Nonpriority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVALSHIELDS"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_three.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation))) + + # Case 4: Wqjudge Four (Priority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_cavc_remand, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAJWEHNER1"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_four.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 5: Wqjudge Five (Priority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAOTRANTOW"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_five.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation) + )) + + # Case 6: Wqjudge Six (Nonpriority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAGSPORER"), + as_judge_assign_task: false, + bfcorlid: "#{wqjudge_six.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 7: Wqjudge Control (priority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + :aod, + bfcorlid: "#{wqjudge_control.file_number}S", + user: User.find_by_css_id("BVAOTRANTOW"), + assigner: User.find_by_css_id("BVAGSPORER") + )) + end + + def scenario_7_seeds + rewrite_one = create_scenario_7_veteran(first_name: "Rewrite", last_name: "One") + rewrite_two = create_scenario_7_veteran(first_name: "Rewrite", last_name: "Two") + rewrite_three = create_scenario_7_veteran(first_name: "Rewrite", last_name: "Three") + rewrite_four = create_scenario_7_veteran(first_name: "Rewrite", last_name: "Four") + rewrite_five = create_scenario_7_veteran(first_name: "Rewrite", last_name: "Five") + rewrite_six = create_scenario_7_veteran(first_name: "Rewrite", last_name: "Six") + no_rewrite_control = create_scenario_7_veteran(first_name: "NoRewrite ", last_name: "Control") + + # Case 1: Rewrite One (Priority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_cavc_remand, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVALSHIELDS"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_one.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation) + )) + + # Case 2: Rewrite Two (Nonpriority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVACOTBJUDGE"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_two.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 3: Rewrite Three (Nonpriority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVACOTBJUDGE"), + assigner: User.find_by_css_id("BVALSHIELDS"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_three.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation) + )) + + # Case 4: Rewrite Four (Priority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_cavc_remand, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAJWEHNER1"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_four.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 5: Rewrite Five (Priority, 2 issues) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :aod, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAOTRANTOW"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_five.file_number}S", + case_issues: create_list(:case_issue, 2, :compensation) + )) + + # Case 6: Rewrite Six (Nonpriority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + user: User.find_by_css_id("BVAGSPORER"), + assigner: User.find_by_css_id("BVAGSPORER"), + as_judge_assign_task: false, + bfcorlid: "#{rewrite_six.file_number}S", + case_issues: create_list(:case_issue, 1, :compensation) + )) + + # Case 7: NoRewrite Control (priority, 1 issue) + create(:legacy_appeal, :with_veteran, vacols_case: create( + :case_with_form_9, + :type_original, + :status_active, + :assigned, + :aod, + assigner: User.find_by_css_id("BVAGSPORER"), + bfcorlid: "#{no_rewrite_control.file_number}S", + user: User.find_by_css_id("BVAOTRANTOW"), + case_issues: create_list(:case_issue, 1, :compensation) + )) + end end end diff --git a/spec/feature/hearings/virtual_hearings/daily_docket_spec.rb b/spec/feature/hearings/virtual_hearings/daily_docket_spec.rb index ede34af5705..eaf07f3b392 100644 --- a/spec/feature/hearings/virtual_hearings/daily_docket_spec.rb +++ b/spec/feature/hearings/virtual_hearings/daily_docket_spec.rb @@ -40,12 +40,15 @@ def check_email_events(hearing, current_user) context "Formerly Video Virtual Hearing" do let(:expected_central_office_time) do + time_str = "#{updated_hearing_time} #{hearing.hearing_day.scheduled_for} America/New_York" + tz_abbr = Time.zone.parse(time_str).dst? ? "ET" : "EST" + Time .parse(updated_hearing_time) .strftime("%F %T") .in_time_zone(regional_office_timezone) # cast the updated hearing time to the ro timezone .in_time_zone(HearingTimeService::CENTRAL_OFFICE_TIMEZONE) # convert it to the central office timezone - .strftime("%-l:%M %p ET") # and render it in the format expected in the modal + .strftime("%-l:%M %p #{tz_abbr}") # and render it in the format expected in the modal end scenario "Virtual hearing time is updated" do @@ -55,6 +58,7 @@ def check_email_events(hearing, current_user) click_dropdown(name: "optionalHearingTime0", text: updated_video_hearing_time) expect(page).to have_content(COPY::VIRTUAL_HEARING_MODAL_CHANGE_HEARING_TIME_TITLE) expect(page).to have_content(COPY::VIRTUAL_HEARING_MODAL_CHANGE_HEARING_TIME_BUTTON) + expect(page).to have_content("Time: #{expected_central_office_time} / #{expected_regional_office_time}") click_button(COPY::VIRTUAL_HEARING_MODAL_CHANGE_HEARING_TIME_BUTTON) @@ -180,6 +184,8 @@ def check_email_events(hearing, current_user) end context "Updating a hearing's time" do + let(:fall_date) { "#{1.year.from_now.year}-11-11" } + shared_examples "The hearing time is updated correctly" do scenario do visit "hearings/schedule/docket/" + hearing.hearing_day.id.to_s @@ -195,7 +201,7 @@ def check_email_events(hearing, current_user) let(:initial_hearing) { create(:legacy_hearing, case_hearing: case_hearing) } # Ensure that the times are always in standard time. - before { initial_hearing.hearing_day.update!(scheduled_for: "2024-11-11") } + before { initial_hearing.hearing_day.update!(scheduled_for: fall_date) } context "With a pre-existing scheduled_in_timezone value" do let(:hearing_time_selection_string) { "10:00 AM Central Time (US & Canada)" } @@ -203,7 +209,7 @@ def check_email_events(hearing, current_user) let(:expected_post_update_time) { "10:00 AM CST" } before do - hearing.hearing_day.update!(regional_office: "RO30", request_type: "V", scheduled_for: "2024-11-11") + hearing.hearing_day.update!(regional_office: "RO30", request_type: "V", scheduled_for: fall_date) end include_examples "The hearing time is updated correctly" @@ -215,7 +221,7 @@ def check_email_events(hearing, current_user) let(:expected_post_update_time) { "3:00 PM CST" } before do - hearing.hearing_day.update!(regional_office: "RO30", request_type: "T", scheduled_for: "2024-11-11") + hearing.hearing_day.update!(regional_office: "RO30", request_type: "T", scheduled_for: fall_date) end include_examples "The hearing time is updated correctly" diff --git a/spec/models/hearings/forms/hearing_update_form_spec.rb b/spec/models/hearings/forms/hearing_update_form_spec.rb index bd5cc15a416..ff8744beb7d 100644 --- a/spec/models/hearings/forms/hearing_update_form_spec.rb +++ b/spec/models/hearings/forms/hearing_update_form_spec.rb @@ -114,13 +114,18 @@ end it "should update scheduled_datetime if it is not null already" do + date_str = "#{hearing.hearing_day.scheduled_for} America/New_York" + is_dst = Time.zone.parse(date_str).dst? + hearing.update( - scheduled_datetime: "2021-04-23T11:30:00-04:00", scheduled_in_timezone: "America/New_York" + scheduled_datetime: "2021-04-23T11:30:00#{is_dst ? '-04:00' : '-05:00'}", + scheduled_in_timezone: "America/New_York" ) subject.update updated_scheduled_datetime = hearing.scheduled_datetime + expect(updated_scheduled_datetime.strftime("%Y-%m-%d %H:%M %z")) - .to eq "#{hearing.hearing_day.scheduled_for.strftime('%Y-%m-%d')} 21:45 -0400" + .to eq "#{hearing.hearing_day.scheduled_for.strftime('%Y-%m-%d')} 21:45 #{is_dst ? '-0400' : '-0500'}" end it "should not update scheduled_datetime if it is null already" do diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 811a8a2d4ff..df5073476f7 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -93,7 +93,7 @@ Capybara.javascript_driver = :logging_selenium_chrome -Capybara.default_driver = ENV["CI"] ? :sniffybara_headless : :parallel_sniffybara +Capybara.default_driver = ENV["HEADLESS"] ? :sniffybara_headless : :parallel_sniffybara # the default default_max_wait_time is 2 seconds Capybara.default_max_wait_time = 5 # Capybara uses puma by default, but for some reason, some of our tests don't