diff --git a/app/models/serializers/work_queue/appeal_search_serializer.rb b/app/models/serializers/work_queue/appeal_search_serializer.rb new file mode 100644 index 00000000000..d72ff4547a8 --- /dev/null +++ b/app/models/serializers/work_queue/appeal_search_serializer.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +class WorkQueue::AppealSearchSerializer + include FastJsonapi::ObjectSerializer + extend Helpers::AppealHearingHelper + + set_type :appeal + + attribute :contested_claim, &:contested_claim? + + attribute :issues do |object| + object.request_issues.active_or_decided_or_withdrawn.includes(:remand_reasons).map do |issue| + { + id: issue.id, + program: issue.benefit_type, + description: issue.description, + notes: issue.notes, + diagnostic_code: issue.contested_rating_issue_diagnostic_code, + remand_reasons: issue.remand_reasons, + closed_status: issue.closed_status, + decision_date: issue.decision_date + } + end + end + + attribute :status + + attribute(:hearings) do |object, params| + # For substitution appeals after death dismissal, we need to show hearings from the source appeal + # in addition to those on the new/target appeal; this avoids copying them to new appeal stream + associated_hearings = [] + + if object.separate_appeal_substitution? + associated_hearings = hearings(object.appellant_substitution.source_appeal, params) + end + + associated_hearings + hearings(object, params) + end + + attribute :withdrawn, &:withdrawn? + + attribute :removed, &:removed? + + attribute :overtime, &:overtime? + + attribute :veteran_appellant_deceased, &:veteran_appellant_deceased? + + attribute :assigned_to_location + + attribute :distributed_to_a_judge, &:distributed_to_a_judge? + + attribute :appellant_full_name do |object| + object.claimant&.name + end + + attribute :veteran_death_date + + attribute :veteran_file_number + + attribute :veteran_full_name do |object| + object.veteran ? object.veteran.name.formatted(:readable_full) : "Cannot locate" + end + + attribute :external_id, &:uuid + + attribute :type + attribute :vacate_type + attribute :aod, &:advanced_on_docket? + attribute :docket_name + attribute :docket_number + attribute :docket_range_date + attribute :decision_date + attribute :nod_date, &:receipt_date + attribute :withdrawal_date + + attribute :caseflow_veteran_id do |object| + object.veteran ? object.veteran.id : nil + end + + attribute :docket_switch do |object| + if object.docket_switch + WorkQueue::DocketSwitchSerializer.new(object.docket_switch).serializable_hash[:data][:attributes] + end + end +end diff --git a/app/models/serializers/work_queue/appeal_serializer.rb b/app/models/serializers/work_queue/appeal_serializer.rb index bfa23c51fe4..62409533333 100644 --- a/app/models/serializers/work_queue/appeal_serializer.rb +++ b/app/models/serializers/work_queue/appeal_serializer.rb @@ -103,7 +103,15 @@ class WorkQueue::AppealSerializer attribute :veteran_appellant_deceased, &:veteran_appellant_deceased? - attribute :assigned_to_location + attribute :assigned_to_location do |object, params| + if object&.status&.status == :distributed_to_judge + if params[:user]&.judge? || params[:user]&.attorney? || User.list_hearing_coordinators.include?(params[:user]) + object.assigned_to_location + end + else + object.assigned_to_location + end + end attribute :distributed_to_a_judge, &:distributed_to_a_judge? diff --git a/app/models/serializers/work_queue/legacy_appeal_search_serializer.rb b/app/models/serializers/work_queue/legacy_appeal_search_serializer.rb new file mode 100644 index 00000000000..7cd374f20e6 --- /dev/null +++ b/app/models/serializers/work_queue/legacy_appeal_search_serializer.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class WorkQueue::LegacyAppealSearchSerializer + include FastJsonapi::ObjectSerializer + extend Helpers::AppealHearingHelper + + set_type :legacy_appeal + + attribute :assigned_attorney + attribute :assigned_judge + + attribute :issues do |object| + object.issues.map do |issue| + WorkQueue::LegacyIssueSerializer.new(issue).serializable_hash[:data][:attributes] + end + end + + attribute :hearings do |object, params| + hearings(object, params) + end + + attribute :completed_hearing_on_previous_appeal? + + attribute :appellant_is_not_veteran, &:appellant_is_not_veteran + + attribute :appellant_full_name, &:appellant_name + + attribute :appellant_address, &:appellant_address + + attribute :appellant_tz, &:appellant_tz + + attribute :appellant_relationship + attribute :assigned_to_location + attribute :vbms_id, &:sanitized_vbms_id + attribute :veteran_full_name + attribute :veteran_death_date + attribute :veteran_appellant_deceased, &:veteran_appellant_deceased? + # Aliasing the vbms_id to make it clear what we're returning. + attribute :veteran_file_number, &:sanitized_vbms_id + attribute :veteran_participant_id do |object| + object&.veteran&.participant_id + end + attribute :efolder_link do + ENV["CLAIM_EVIDENCE_EFOLDER_BASE_URL"] + end + attribute :external_id, &:vacols_id + attribute :type + attribute :aod + attribute :docket_number + attribute :docket_range_date, &:docket_date + attribute :status + attribute :decision_date + attribute :form9_date + attribute :nod_date + attribute :certification_date + attribute :paper_case, &:paper_case? + attribute :overtime, &:overtime? + attribute :caseflow_veteran_id do |object| + object.veteran ? object.veteran.id : nil + end + + attribute(:available_hearing_locations) { |object| available_hearing_locations(object) } + + attribute :docket_name do + "legacy" + end + + attribute :document_id do |object| + latest_vacols_attorney_case_review(object)&.document_id + end + + attribute :can_edit_document_id do |object, params| + LegacyDocumentIdPolicy.new( + user: params[:user], + case_review: latest_vacols_attorney_case_review(object) + ).editable? + end + + attribute :attorney_case_review_id do |object| + latest_vacols_attorney_case_review(object)&.vacols_id + end + + attribute :current_user_email do |_, params| + params[:user]&.email + end + + attribute :current_user_timezone do |_, params| + params[:user]&.timezone + end + + attribute :location_history do |object| + object.location_history.map do |location| + WorkQueue::PriorlocSerializer.new(location).serializable_hash[:data][:attributes] + end + end + + def self.latest_vacols_attorney_case_review(object) + VACOLS::CaseAssignment.latest_task_for_appeal(object.vacols_id) + end +end diff --git a/app/services/appeal_finder.rb b/app/services/appeal_finder.rb index 30829666e7c..30e586b0798 100644 --- a/app/services/appeal_finder.rb +++ b/app/services/appeal_finder.rb @@ -8,12 +8,21 @@ def find_appeals_with_file_numbers(file_numbers) MetricsService.record("VACOLS: Get appeal information for file_numbers #{file_numbers}", service: :queue, name: "VeteranFinderQuery.find_appeals_with_file_numbers") do - appeals = Appeal.established.where(veteran_file_number: file_numbers).to_a + ## appeals = Appeal.established.where(veteran_file_number: file_numbers).to_a + ama_appeals = Appeal.established + .includes(:docket_switch, :available_hearing_locations, :tasks, :work_mode, + :request_issues, :hearings, :appellant_substitution, :nod_date_updates, + :decision_issues) + .where(veteran_file_number: file_numbers) + .to_a begin - appeals.concat(LegacyAppeal.fetch_appeals_by_file_number(*file_numbers)) + legacy_appeals = LegacyAppeal.fetch_appeals_by_file_number(*file_numbers) rescue ActiveRecord::RecordNotFound # file number could not be found. don't raise exception and not return, just ignore. + legacy_appeals = [] end + appeals = ama_appeals + legacy_appeals + appeals end end diff --git a/app/workflows/case_search_results_base.rb b/app/workflows/case_search_results_base.rb index 46f05c018ee..81e4f3a224d 100644 --- a/app/workflows/case_search_results_base.rb +++ b/app/workflows/case_search_results_base.rb @@ -60,11 +60,11 @@ def veterans_user_can_access def json_appeals(appeals) ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } - ama_hash = WorkQueue::AppealSerializer.new( + ama_hash = WorkQueue::AppealSearchSerializer.new( ama_appeals, is_collection: true, params: { user: user } ).serializable_hash - legacy_hash = WorkQueue::LegacyAppealSerializer.new( + legacy_hash = WorkQueue::LegacyAppealSearchSerializer.new( legacy_appeals, is_collection: true, params: { user: user } ).serializable_hash diff --git a/client/app/queue/CaseList/CaseListActions.js b/client/app/queue/CaseList/CaseListActions.js index b9d7b8d82ec..8a9264b3edc 100644 --- a/client/app/queue/CaseList/CaseListActions.js +++ b/client/app/queue/CaseList/CaseListActions.js @@ -4,7 +4,7 @@ import * as Constants from './actionTypes'; import { get, size } from 'lodash'; import { onReceiveAppealDetails, onReceiveClaimReviewDetails } from '../QueueActions'; -import { prepareAppealForStore, prepareClaimReviewForStore } from '../utils'; +import { prepareAppealForStore, prepareAppealForSearchStore, prepareClaimReviewForStore } from '../utils'; import ValidatorsUtil from '../../util/ValidatorsUtil'; const { validSSN, validFileNum, validDocketNum } = ValidatorsUtil; @@ -54,7 +54,7 @@ export const fetchedNoAppeals = (searchQuery) => ({ }); export const onReceiveAppeals = (appeals) => (dispatch) => { - dispatch(onReceiveAppealDetails(prepareAppealForStore(appeals))); + dispatch(onReceiveAppealDetails(prepareAppealForSearchStore(appeals))); dispatch({ type: Constants.RECEIVED_APPEALS_USING_VETERAN_ID_SUCCESS }); diff --git a/client/app/queue/CaseListView.jsx b/client/app/queue/CaseListView.jsx index a01a23b83a4..0f951cdbdc2 100644 --- a/client/app/queue/CaseListView.jsx +++ b/client/app/queue/CaseListView.jsx @@ -105,7 +105,6 @@ class CaseListView extends React.PureComponent {

{COPY.CASE_LIST_TABLE_TITLE}

-

{COPY.OTHER_REVIEWS_TABLE_TITLE}

diff --git a/client/app/queue/utils.js b/client/app/queue/utils.js index 757cb3a0e44..ba2f6605c68 100644 --- a/client/app/queue/utils.js +++ b/client/app/queue/utils.js @@ -398,6 +398,7 @@ const prepareLocationHistoryForStore = (appeal) => { return locationHistory; }; + export const prepareAppealForStore = (appeals) => { const appealHash = appeals.reduce((accumulator, appeal) => { const { @@ -523,6 +524,61 @@ export const prepareAppealForStore = (appeals) => { }; }; +export const prepareAppealForSearchStore = (appeals) => { + const appealHash = appeals.reduce((accumulator, appeal) => { + const { + attributes: { issues }, + } = appeal; + + accumulator[appeal.attributes.external_id] = { + id: appeal.id, + externalId: appeal.attributes.external_id, + docketName: appeal.attributes.docket_name, + withdrawn: appeal.attributes.withdrawn, + overtime: appeal.attributes.overtime, + contestedClaim: appeal.attributes.contested_claim, + veteranAppellantDeceased: appeal.attributes.veteran_appellant_deceased, + withdrawalDate: formatDateStrUtc(appeal.attributes.withdrawal_date), + isLegacyAppeal: appeal.attributes.docket_name === 'legacy', + caseType: appeal.attributes.type, + isAdvancedOnDocket: appeal.attributes.aod, + issueCount: (appeal.attributes.docket_name === 'legacy' ? + getUndecidedIssues(issues) : + issues + ).length, + docketNumber: appeal.attributes.docket_number, + distributedToJudge: appeal.attributes.distributed_to_a_judge, + veteranFullName: appeal.attributes.veteran_full_name, + veteranFileNumber: appeal.attributes.veteran_file_number, + vacateType: appeal.attributes.vacate_type, + }; + + return accumulator; + }, {}); + + const appealDetailsHash = appeals.reduce((accumulator, appeal) => { + accumulator[appeal.attributes.external_id] = { + hearings: prepareAppealHearingsForStore(appeal), + appellantFullName: appeal.attributes.appellant_full_name, + contestedClaim: appeal.attributes.contested_claim, + assignedToLocation: appeal.attributes.assigned_to_location, + veteranParticipantId: appeal.attributes.veteran_participant_id, + externalId: appeal.attributes.external_id, + status: appeal.attributes.status, + decisionDate: appeal.attributes.decision_date, + caseflowVeteranId: appeal.attributes.caseflow_veteran_id, + locationHistory: prepareLocationHistoryForStore(appeal), + }; + + return accumulator; + }, {}); + + return { + appeals: appealHash, + appealDetails: appealDetailsHash, + }; +}; + export const prepareClaimReviewForStore = (claimReviews) => { const claimReviewHash = claimReviews.reduce((accumulator, claimReview) => { const key = `${claimReview.review_type}-${claimReview.claim_id}`; diff --git a/client/constants/REGIONAL_OFFICE_FACILITY_ADDRESS.json b/client/constants/REGIONAL_OFFICE_FACILITY_ADDRESS.json index 95c468b2aed..558448003fa 100644 --- a/client/constants/REGIONAL_OFFICE_FACILITY_ADDRESS.json +++ b/client/constants/REGIONAL_OFFICE_FACILITY_ADDRESS.json @@ -721,6 +721,15 @@ "zip" : "39531", "timezone" : "America/Chicago" }, + "vc_0742V" :{ + "address_1" : "1118 Burlington Street", + "address_2" : null, + "address_3" : null, + "city" : "Holdrege", + "state" : "NE", + "zip" : "68949-1705", + "timezone" : "America/New_York" + }, "vha_402GA" : { "address_1" : "163 Van Buren Road", "address_2" : null, diff --git a/client/constants/REGIONAL_OFFICE_INFORMATION.json b/client/constants/REGIONAL_OFFICE_INFORMATION.json index df1489c956a..c2cf28a682d 100644 --- a/client/constants/REGIONAL_OFFICE_INFORMATION.json +++ b/client/constants/REGIONAL_OFFICE_INFORMATION.json @@ -157,7 +157,7 @@ "timezone": "America/New_York", "hold_hearings": true, "facility_locator_id": "vba_317", - "alternate_locations": ["vba_317a", "vc_0742V"] + "alternate_locations": ["vc_0742V"] }, "RO18": { "label": "Winston-Salem regional office", diff --git a/spec/fixes/assigned_to_search_results_spec.rb b/spec/fixes/assigned_to_search_results_spec.rb index 3d0e3414fbb..75765fdddd3 100644 --- a/spec/fixes/assigned_to_search_results_spec.rb +++ b/spec/fixes/assigned_to_search_results_spec.rb @@ -71,4 +71,72 @@ expect(appeal.root_task.status).to eq "completed" end end + + context "appeal status is distributed to judge" do + let!(:appeal) { create(:appeal, :assigned_to_judge) } + let!(:default_user) { create(:default_user) } + let!(:hearings_coordinator_user) do + coordinator = create(:hearings_coordinator) + HearingsManagement.singleton.add_user(coordinator) + coordinator + end + let!(:attorney) do + attorney = create(:user) + create(:staff, :attorney_role, sdomainid: attorney.css_id) + attorney + end + let!(:judge) do + judge = create(:user) + create(:staff, :judge_role, sdomainid: judge.css_id) + judge + end + + before do + allow_any_instance_of(BGSService).to receive(:fetch_file_number_by_ssn) + .with(appeal.veteran.ssn.to_s) + .and_return(appeal.veteran.file_number) + end + context "user is not an attorney, judge, or hearing coordinator" do + scenario "current user is a system admin" do + visit "/search?veteran_ids=#{appeal.veteran.id}" + expect(appeal.status.status).to eq :distributed_to_judge + expect(appeal.assigned_to_location).to eq "BVAAABSHIRE" # css_id is part of assigned_to + expect(page).not_to have_content("BVAAABSHIRE") # but css_id is not displayed in the page + end + + scenario "current user is a default user" do + User.authenticate!(user: default_user) + visit "/search?veteran_ids=#{appeal.veteran.id}" + expect(appeal.status.status).to eq :distributed_to_judge + expect(appeal.assigned_to_location).to eq "BVAAABSHIRE" # css_id is part of assigned_to + expect(page).not_to have_content("BVAAABSHIRE") # but css_id is not displayed in the page + end + end + + context "user is an attorney, a judge, or a hearing coordinator" do + scenario "user is an attorney" do + User.authenticate!(user: attorney) + visit "/search?veteran_ids=#{appeal.veteran.id}" + expect(appeal.status.status).to eq :distributed_to_judge + expect(appeal.assigned_to_location).to eq "BVAAABSHIRE" # css_id is part of assigned_to + expect(page).to have_content("BVAAABSHIRE") # and css_id is displayed in the page + end + + scenario "user is an judge" do + User.authenticate!(user: judge) + visit "/search?veteran_ids=#{appeal.veteran.id}" + expect(appeal.status.status).to eq :distributed_to_judge + expect(appeal.assigned_to_location).to eq "BVAAABSHIRE" # css_id is part of assigned_to + expect(page).to have_content("BVAAABSHIRE") # and css_id is displayed in the page + end + + scenario "user is an hearings coordinator" do + User.authenticate!(user: hearings_coordinator_user) + visit "/search?veteran_ids=#{appeal.veteran.id}" + expect(appeal.status.status).to eq :distributed_to_judge + expect(appeal.assigned_to_location).to eq "BVAAABSHIRE" # css_id is part of assigned_to + expect(page).to have_content("BVAAABSHIRE") # and css_id is displayed in the page + end + end + end end diff --git a/spec/models/tasks/hearing_admin_action_verify_address_task_spec.rb b/spec/models/tasks/hearing_admin_action_verify_address_task_spec.rb index e8f31318ea2..70ccabcad90 100644 --- a/spec/models/tasks/hearing_admin_action_verify_address_task_spec.rb +++ b/spec/models/tasks/hearing_admin_action_verify_address_task_spec.rb @@ -54,9 +54,9 @@ ahls = appeal.class.first.available_hearing_locations.map(&:facility_id).uniq - # St. Petersburg RO facility id, and 2 alternate facilities - expect(ahls.count).to eq 3 - expect(ahls).to match_array(%w[vba_317 vba_317a vc_0742V]) + # St. Petersburg RO facility id, and 1 alternate facility + expect(ahls.count).to eq 2 + expect(ahls).to match_array(%w[vba_317 vc_0742V]) end it "throws an access error trying to update from params with random user" do