From eece0e258f4cec4ec9e9ff2c0cf13d100e318a8d Mon Sep 17 00:00:00 2001 From: Robert Travis Pierce Date: Mon, 15 Jul 2024 06:18:50 -0500 Subject: [PATCH 01/19] Start feature work tracking PR with a TODO comment --- app/models/decision_issue.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/decision_issue.rb b/app/models/decision_issue.rb index 95ef1f19422..922b2d35d08 100644 --- a/app/models/decision_issue.rb +++ b/app/models/decision_issue.rb @@ -250,6 +250,7 @@ def create_remand_supplemental_claim! # Checking our assumption that approx_decision_date will always be populated for Decision Issues fail "approx_decision_date is required to create a DTA Supplemental Claim" unless approx_decision_date + # TODO: For APPEALS-41559 feature work we will need to modify this to create a Remand for the appeal instead of a Supplemental Claim sc = SupplementalClaim.create!( veteran_file_number: veteran_file_number, decision_review_remanded: decision_review, From 66256b369960addc6a75aa58fe52493678b640d0 Mon Sep 17 00:00:00 2001 From: Robert Travis Pierce Date: Thu, 18 Jul 2024 10:18:44 -0500 Subject: [PATCH 02/19] Remove TODO comment for CodeClimate --- app/models/decision_issue.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/decision_issue.rb b/app/models/decision_issue.rb index 922b2d35d08..95ef1f19422 100644 --- a/app/models/decision_issue.rb +++ b/app/models/decision_issue.rb @@ -250,7 +250,6 @@ def create_remand_supplemental_claim! # Checking our assumption that approx_decision_date will always be populated for Decision Issues fail "approx_decision_date is required to create a DTA Supplemental Claim" unless approx_decision_date - # TODO: For APPEALS-41559 feature work we will need to modify this to create a Remand for the appeal instead of a Supplemental Claim sc = SupplementalClaim.create!( veteran_file_number: veteran_file_number, decision_review_remanded: decision_review, From f6f8c74d8846d254530ef4b9fcedbedf3f467cca Mon Sep 17 00:00:00 2001 From: Sean Craig <110493538+seancva@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:22:51 -0500 Subject: [PATCH 03/19] SeanC/APPEALS-42315 | Create separate Remand table, model and seed data (#22220) * added migration file for adding type column to supplemental claims table * created stub for remand model * added type to supplemental claim factory and new remand trait * added remand sc's to vha seeds * updated migration name and fixed typo * Changed migration to inherit from Caseflow::Mirgration * schema changes * reverting schema * changed the setting of the type column * fixed schema and renamed migration file * fixed migration and schema * updated the remands factory and seeds * fixed comment in schema --------- Co-authored-by: Robert Travis Pierce --- app/models/remand.rb | 4 ++++ ...x_to_supplemental_claims_for_remand_inheritance.rb | 9 +++++++++ db/schema.rb | 4 +++- db/seeds/veterans_health_administration.rb | 11 +++++++++++ spec/factories/supplemental_claim.rb | 6 ++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/models/remand.rb create mode 100644 db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb diff --git a/app/models/remand.rb b/app/models/remand.rb new file mode 100644 index 00000000000..987220c7c3e --- /dev/null +++ b/app/models/remand.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Remand < SupplementalClaim +end diff --git a/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb b/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb new file mode 100644 index 00000000000..da38b8cd52a --- /dev/null +++ b/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb @@ -0,0 +1,9 @@ +class AddTypeColumnAndIndexToSupplementalClaimsForRemandInheritance < Caseflow::Migration + def change + safety_assured do + add_column :supplemental_claims, :type, :string, default: "SupplementalClaim", null: false, comment: "The class name for the single table inheritance type of Supplemental Claim for example Remand" + end + + add_safe_index :supplemental_claims, [:type], name: "index_supplemental_claims_on_type" + end +end diff --git a/db/schema.rb b/db/schema.rb index ba28966c8dd..dccc1be67f0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_06_13_202232) do +ActiveRecord::Schema.define(version: 2024_07_16_143816) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1831,11 +1831,13 @@ t.boolean "filed_by_va_gov", comment: "Indicates whether or not this form came from VA.gov" t.boolean "legacy_opt_in_approved", comment: "Indicates whether a Veteran opted to withdraw their Supplemental Claim request issues from the legacy system if a matching issue is found. If there is a matching legacy issue and it is not withdrawn, then that issue is ineligible to be a new request issue and a contention will not be created for it." t.date "receipt_date", comment: "The date that the Supplemental Claim form was received by central mail. Only issues decided prior to the receipt date will show up as contestable issues. It is also the claim date for any associated end products that are established. Supplemental Claims do not have the same timeliness restriction on contestable issues as Appeals and Higher Level Reviews." + t.string "type", default: "SupplementalClaim", null: false, comment: "The class name for the single table inheritance type of Supplemental Claim for example Remand" t.datetime "updated_at" t.uuid "uuid", default: -> { "uuid_generate_v4()" }, null: false, comment: "The universally unique identifier for the Supplemental Claim. Can be used to link to the claim after it is completed." t.string "veteran_file_number", null: false, comment: "PII. The file number of the Veteran that the Supplemental Claim is for." t.boolean "veteran_is_not_claimant", comment: "Indicates whether the Veteran is the claimant on the Supplemental Claim form, or if the claimant is someone else like a spouse or a child. Must be TRUE if the Veteran is deceased." t.index ["decision_review_remanded_type", "decision_review_remanded_id"], name: "index_decision_issues_on_decision_review_remanded" + t.index ["type"], name: "index_supplemental_claims_on_type" t.index ["updated_at"], name: "index_supplemental_claims_on_updated_at" t.index ["uuid"], name: "index_supplemental_claims_on_uuid" t.index ["veteran_file_number"], name: "index_supplemental_claims_on_veteran_file_number" diff --git a/db/seeds/veterans_health_administration.rb b/db/seeds/veterans_health_administration.rb index 9183c93352e..f99c4192da9 100644 --- a/db/seeds/veterans_health_administration.rb +++ b/db/seeds/veterans_health_administration.rb @@ -54,6 +54,7 @@ def create_supplemental_claims 3.times do CLAIMANT_TYPES.each do |claimant_type| create_sc_with_claimant(benefit_type, claimant_type) + create_sc_remand(benefit_type, claimant_type) end end end @@ -83,6 +84,16 @@ def create_sc_with_claimant(benefit_type, claimant_type) sc.create_business_line_tasks! end + def create_sc_remand(benefit_type, claimant_type) + sc = create( + :remand, + benefit_type: benefit_type, + claimant_type: claimant_type, + number_of_claimants: 1 + ) + sc.create_business_line_tasks! + end + # :reek:NestedIterators # this method is creating most of the data, but we can't get around it because of how many PO/VISN combos there are def create_vha_visn_pre_docket_queue diff --git a/spec/factories/supplemental_claim.rb b/spec/factories/supplemental_claim.rb index 39ed9b6df54..05841c7f9d3 100644 --- a/spec/factories/supplemental_claim.rb +++ b/spec/factories/supplemental_claim.rb @@ -7,6 +7,7 @@ benefit_type { "compensation" } uuid { SecureRandom.uuid } veteran_is_not_claimant { true } + type { SupplementalClaim.name } transient do number_of_claimants { nil } @@ -282,5 +283,10 @@ decision_review: sc) end end + + factory :remand, class: Remand do + type { Remand.name } + decision_review_remanded { create(:appeal) } + end end end From 96c1cc81742c632cbc9729fb45fd67f25a6e9703 Mon Sep 17 00:00:00 2001 From: almorbah <149511814+almorbah@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:14:37 -0500 Subject: [PATCH 04/19] Al/APPEALS-45335 (#22228) * added additional option to check box list * fixed some tests and code clean up * fixed more tests * fixed jest tests * changes per Pr feedback * added custom error display * fixed style for error message --------- Co-authored-by: Sean Craig --- client/app/nonComp/pages/ReportPage.jsx | 159 ++++++++++++++---- .../REPORT_PAGE_VALIDATION_ERRORS.json | 1 + client/constants/REPORT_TYPE_CONSTANTS.json | 128 +++++++++----- .../test/app/nonComp/pages/ReportPage.test.js | 21 ++- 4 files changed, 228 insertions(+), 81 deletions(-) diff --git a/client/app/nonComp/pages/ReportPage.jsx b/client/app/nonComp/pages/ReportPage.jsx index 3e3a7ba89a8..cedae7187b3 100644 --- a/client/app/nonComp/pages/ReportPage.jsx +++ b/client/app/nonComp/pages/ReportPage.jsx @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import React, { useEffect } from 'react'; import { useController, useForm, FormProvider, useFormContext } from 'react-hook-form'; import { useDispatch, useSelector } from 'react-redux'; @@ -5,6 +6,7 @@ import { downloadReportCSV } from 'app/nonComp/actions/changeHistorySlice'; import { css } from 'glamor'; import PropTypes from 'prop-types'; +import Alert from 'app/components/Alert'; import Button from 'app/components/Button'; import NonCompLayout from '../components/NonCompLayout'; import { conditionsSchema, ReportPageConditions } from '../components/ReportPage/ReportPageConditions'; @@ -27,7 +29,7 @@ import { RADIO_STATUS_OPTIONS, RADIO_STATUS_REPORT_TYPE_OPTIONS, SPECIFIC_STATUS_OPTIONS, - SPECTIFIC_EVENT_OPTIONS + SPECIFIC_EVENT_OPTIONS } from 'constants/REPORT_TYPE_CONSTANTS'; import * as ERRORS from 'constants/REPORT_PAGE_VALIDATION_ERRORS'; @@ -42,6 +44,13 @@ const buttonOuterContainerStyling = css({ marginTop: '4rem', }); +const outerContainerStyling = css({ + display: 'flex', + justifyContent: 'space-between', + paddingLeft: '30px', + gap: '3em', +}); + const specificEventTypeSchema = yup.lazy((value) => { // eslint-disable-next-line no-undefined if (value === undefined) { @@ -52,13 +61,22 @@ const specificEventTypeSchema = yup.lazy((value) => { added_decision_date: yup.boolean(), added_issue: yup.boolean(), added_issue_no_decision_date: yup.boolean(), + removed_issue: yup.boolean(), + withdrew_issue: yup.boolean(), + completed_disposition: yup.boolean(), claim_created: yup.boolean(), claim_closed: yup.boolean(), claim_status_incomplete: yup.boolean(), + claim_status_pending: yup.boolean(), claim_status_inprogress: yup.boolean(), - completed_disposition: yup.boolean(), - removed_issue: yup.boolean(), - withdrew_issue: yup.boolean(), + requested_issue_modification: yup.boolean(), + requested_issue_addition: yup.boolean(), + requested_issue_removal: yup.boolean(), + requested_issue_withdrawal: yup.boolean(), + approval_of_request: yup.boolean(), + rejection_of_request: yup.boolean(), + cancellation_of_request: yup.boolean(), + edit_of_request: yup.boolean(), }).test('at-least-one-true', ERRORS.AT_LEAST_ONE_OPTION, (obj) => { return Object.values(obj).some((val) => val === true); }); @@ -130,28 +148,11 @@ const ReportPageButtons = ({ ); }; -const RHFCheckboxGroup = ({ options, name, control }) => { - const { field } = useController({ - control, - name - }); - - const { errors } = useFormContext(); - - const [value, setValue] = React.useState({}); - - let fieldClasses = 'checkbox'; - - const errorMessage = get(errors, name)?.message; - - if (errorMessage) { - fieldClasses += ' usa-input-error'; - fieldClasses += ' less-error-padding'; - } +const EventCheckboxGroup = ({ header, options, name, onChange }) => { return ( -
- {errorMessage ?
{ errorMessage }
: null} +
+ { header &&

{header}

} {options.map((option) => (
{ key={`${name}.${option.id}`} label={option.label} stronglabel - onChange={(val) => { - value[option.id] = val; - field.onChange(value); - setValue(value); - }} unpadded + onChange={(val) => onChange({ option, val })} />
))} -
+ + ); +}; + +const RHFCheckboxGroup = ({ options, name, control }) => { + const { errors } = useFormContext(); + + let fieldClasses = 'checkbox'; + const errorMessage = get(errors, name)?.message; + + const { field } = useController({ + control, + name + }); + + if (errorMessage) { + fieldClasses += ' usa-input-error'; + fieldClasses += ' less-error-padding'; + } + + const [value, setValue] = React.useState({}); + + const onCheckboxClick = ({ option, val }) => { + value[option.id] = val; + field.onChange(value); + setValue(value); + }; + + const messageStyling = css({ + fontSize: '17px !important', + paddingTop: '2px !important' + }); + + return ( +
+ {name === 'specificEventType' ? +
+ {errorMessage && + + } +
+ + + +
+
: +
+ {errorMessage ?
{ errorMessage }
: null} + +
+ } +
); }; @@ -210,20 +281,30 @@ const ReportPage = ({ history }) => { specificStatus: { incomplete: '', in_progress: '', + pending: '', completed: '', cancelled: '' }, specificEventType: { - added_decision_date: '', - added_issue: '', - added_issue_no_decision_date: '', claim_created: '', claim_closed: '', claim_status_incomplete: '', + claim_status_pending: '', claim_status_inprogress: '', - completed_disposition: '', + added_decision_date: '', + added_issue: '', + added_issue_no_decision_date: '', removed_issue: '', withdrew_issue: '', + completed_disposition: '', + requested_issue_modification: '', + requested_issue_addition: '', + requested_issue_removal: '', + requested_issue_withdrawal: '', + approval_of_request: '', + rejection_of_request: '', + cancellation_of_request: '', + edit_of_request: '', } }; @@ -374,7 +455,7 @@ const ReportPage = ({ history }) => { {watchReportType === 'event_type_action' && watchRadioEventAction === 'specific_events_action' ? ( @@ -411,6 +492,13 @@ RHFCheckboxGroup.propTypes = { errorMessage: PropTypes.string }; +EventCheckboxGroup.propTypes = { + options: PropTypes.array, + onChange: PropTypes.func, + header: PropTypes.string, + name: PropTypes.string +}; + RHFRadioButton.propTypes = { options: PropTypes.array, control: PropTypes.object, @@ -419,3 +507,4 @@ RHFRadioButton.propTypes = { }; export default ReportPage; +/* eslint-enable max-lines */ diff --git a/client/constants/REPORT_PAGE_VALIDATION_ERRORS.json b/client/constants/REPORT_PAGE_VALIDATION_ERRORS.json index 6757f9945d4..17ec7ce7621 100644 --- a/client/constants/REPORT_PAGE_VALIDATION_ERRORS.json +++ b/client/constants/REPORT_PAGE_VALIDATION_ERRORS.json @@ -8,5 +8,6 @@ "END_DATE_SMALL": "End date must be greater than the start date", "DATE_FUTURE": "Date cannot be in the future", "AT_LEAST_ONE_OPTION": "Please select at least one option", + "AT_LEAST_ONE_CHECKBOX_OPTION": "Please select at least one checkbox from one of the sections below.", "MISSING_PERSONNEL": "Please select at least one team member" } diff --git a/client/constants/REPORT_TYPE_CONSTANTS.json b/client/constants/REPORT_TYPE_CONSTANTS.json index f84e99bb1c2..739bf7caf22 100644 --- a/client/constants/REPORT_TYPE_CONSTANTS.json +++ b/client/constants/REPORT_TYPE_CONSTANTS.json @@ -46,6 +46,10 @@ "label": "Incomplete", "id": "incomplete" }, + { + "label": "Pending", + "id": "pending" + }, { "label": "In Progress", "id": "in_progress" @@ -59,46 +63,90 @@ "id": "cancelled" } ], - "SPECTIFIC_EVENT_OPTIONS": [ - { - "label": "Added decision date", - "id": "added_decision_date" - }, - { - "label": "Added issue", - "id": "added_issue" - }, - { - "label": "Added issue - No decision date", - "id":"added_issue_no_decision_date" - }, - { - "label": "Claim created", - "id":"claim_created" - }, - { - "label": "Claim closed", - "id":"claim_closed" - }, - { - "label": "Claim status - Incomplete", - "id":"claim_status_incomplete" - }, - { - "label": "Claim status - In progress", - "id":"claim_status_inprogress" - }, - { - "label": "Completed disposition", - "id":"completed_disposition" - }, - { - "label": "Removed issue", - "id":"removed_issue" - }, - { - "label": "Withdrew issue", - "id":"withdrew_issue" + "SPECIFIC_EVENT_OPTIONS": [ + { + "system": [ + { + "label": "Claim created", + "id":"claim_created" + }, + { + "label": "Claim closed", + "id":"claim_closed" + }, + { + "label": "Claim status - Incomplete", + "id":"claim_status_incomplete" + }, + { + "label": "Claim status - Pending", + "id":"claim_status_pending" + }, + { + "label": "Claim status - In progress", + "id":"claim_status_inprogress" + } + ], + "general": [ + { + "label": "Added decision date", + "id": "added_decision_date" + }, + { + "label": "Added issue", + "id": "added_issue" + }, + { + "label": "Added issue - No decision date", + "id":"added_issue_no_decision_date" + }, + { + "label": "Removed issue", + "id":"removed_issue" + }, + { + "label": "Withdrew issue", + "id":"withdrew_issue" + }, + { + "label": "Completed disposition", + "id":"completed_disposition" + } + ], + "requests": [ + { + "label": "Requested issue modification", + "id":"requested_issue_modification" + }, + { + "label": "Requested issue addition", + "id":"requested_issue_addition" + }, + { + "label": "Requested issue removal", + "id":"requested_issue_removal" + }, + { + "label": "Requested issue withdrawal", + "id":"requested_issue_withdrawal" + }, + { + "label": "Approval of request", + "id":"approval_of_request" + }, + { + "label": "Rejection of request", + "id":"rejection_of_request" + }, + { + "label": "Cancellation of request", + "id":"cancellation_of_request" + }, + { + "label": "Edit of request", + "id":"edit_of_request" + } + ] } ], "TIMING_SPECIFIC_OPTIONS": [ diff --git a/client/test/app/nonComp/pages/ReportPage.test.js b/client/test/app/nonComp/pages/ReportPage.test.js index 502d9832392..000efd127af 100644 --- a/client/test/app/nonComp/pages/ReportPage.test.js +++ b/client/test/app/nonComp/pages/ReportPage.test.js @@ -13,6 +13,7 @@ import { getVhaUsers } from 'test/helpers/reportPageHelper'; import CombinedNonCompReducer from 'app/nonComp/reducers'; import REPORT_TYPE_CONSTANTS from 'constants/REPORT_TYPE_CONSTANTS'; +import * as ERRORS from 'constants/REPORT_PAGE_VALIDATION_ERRORS'; describe('ReportPage', () => { const setup = (storeValues = {}) => { @@ -375,7 +376,7 @@ describe('ReportPage', () => { }); - it('should add 10 checkboxes when radio Specific Events/ Actions is clicked', async () => { + it('should add 19 checkboxes when radio Specific Events/ Actions is clicked', async () => { setup(); await selectEvent.select(screen.getByLabelText('Report Type'), ['Status', 'Event / Action']); @@ -386,9 +387,17 @@ describe('ReportPage', () => { expect(specificEvents.length).toBe(1); fireEvent.click(screen.getByLabelText('Specific Events / Actions')); - expect(screen.getAllByRole('checkbox').length).toBe(10); + expect(screen.getAllByRole('checkbox').length).toBe(19); - REPORT_TYPE_CONSTANTS.SPECTIFIC_EVENT_OPTIONS.forEach((option) => { + REPORT_TYPE_CONSTANTS.SPECIFIC_EVENT_OPTIONS[0].system.forEach((option) => { + expect(screen.getAllByText(option.label)).toBeTruthy(); + }); + + REPORT_TYPE_CONSTANTS.SPECIFIC_EVENT_OPTIONS[0].general.forEach((option) => { + expect(screen.getAllByText(option.label)).toBeTruthy(); + }); + + REPORT_TYPE_CONSTANTS.SPECIFIC_EVENT_OPTIONS[0].requests.forEach((option) => { expect(screen.getAllByText(option.label)).toBeTruthy(); }); }); @@ -407,14 +416,14 @@ describe('ReportPage', () => { fireEvent.click(screen.getByLabelText('Specific Events / Actions')); - expect(screen.getAllByRole('checkbox').length).toBe(10); + expect(screen.getAllByRole('checkbox').length).toBe(19); const generateTaskReport = screen.getByRole('button', { name: /Generate task report/i }); await userEvent.click(generateTaskReport); await waitFor(() => { - expect(screen.getAllByText('Please select at least one option').length).toBe(1); + expect(screen.getAllByText(ERRORS.AT_LEAST_ONE_CHECKBOX_OPTION).length).toBe(1); }); }); @@ -430,7 +439,7 @@ describe('ReportPage', () => { expect(specificEvents.length).toBe(1); fireEvent.click(screen.getByLabelText('Specific Status')); - expect(screen.getAllByRole('checkbox').length).toBe(4); + expect(screen.getAllByRole('checkbox').length).toBe(5); REPORT_TYPE_CONSTANTS.SPECIFIC_STATUS_OPTIONS.map((option) => expect(screen.getAllByText(option.label)).toBeTruthy() From 794035441a7aedeb851cd56fb62c9b855570ca03 Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:19:30 -0400 Subject: [PATCH 05/19] TYLERB/APPEALS-42455: Display new and migrated Remands in Decision Review Queue tabs (#22206) * Added support for remands to the business_line.rb model and the index.js util file that parses the decision review type filters for the decision review queue. * Updated the business line spec file, the decision reviews controller spec tests, and the reviews spec feature test to support remands. * Added a decision review polymorphic helper to DRY up some of the polymorphic appeal concerns. * Overrode the remand model association to request issues so active record joins will work correctly. * Added another helper for sti polymorphic relationships. Fixed filtering for STI decision review types for remands. Updated tests and added factories for testing. * Code climate fixes and test fixes. * Fixed more test failures. Removed the remand request issues relationship since it was incorrectly implemented. Added a scope to supplemental claims for remands. Fixed an issue where board grant effectuation tasks without request issues were missing from the new task type count method. * Changed the UNION ALL to a UNION in the new task_type_count method to prevent duplicate row counts. Updated tests and fixed an issue where the board grant effectuation tasks were being counted for pending task queries. --- ...e_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...w_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...s_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...t_belongs_to_polymorphic_appeal_concern.rb | 24 +---- ...t_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...e_belongs_to_polymorphic_appeal_concern.rb | 21 +--- .../decision_review_polymorphic_helper.rb | 30 ++++++ .../decision_review_polymorphic_sti_helper.rb | 29 ++++++ ...t_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...w_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ..._belongs_to_polymorphic_hearing_concern.rb | 14 +-- ...t_belongs_to_polymorphic_appeal_concern.rb | 14 +-- ...k_belongs_to_polymorphic_appeal_concern.rb | 26 +---- ...t_belongs_to_polymorphic_appeal_concern.rb | 14 +-- app/models/organizations/business_line.rb | 97 ++++++++++++------ app/models/supplemental_claim.rb | 2 + client/app/nonComp/util/index.js | 5 + .../decision_reviews_controller_spec.rb | 99 +++++++++++++++---- spec/factories/task.rb | 18 ++++ spec/feature/non_comp/reviews_spec.rb | 84 +++++++++++----- spec/lib/helpers/association_wrapper_spec.rb | 9 +- spec/models/business_line_spec.rb | 54 +++++++--- 22 files changed, 370 insertions(+), 254 deletions(-) create mode 100644 app/models/concerns/decision_review_polymorphic_helper.rb create mode 100644 app/models/concerns/decision_review_polymorphic_sti_helper.rb diff --git a/app/models/concerns/appeal_state_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/appeal_state_belongs_to_polymorphic_appeal_concern.rb index 963b6e26371..7d3a575c7b7 100644 --- a/app/models/concerns/appeal_state_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/appeal_state_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module AppealStateBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(appeal_states: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(appeal_states: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :appeal_states, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/attorney_case_review_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/attorney_case_review_belongs_to_polymorphic_appeal_concern.rb index 6ee39b146d2..70039fbc321 100644 --- a/app/models/concerns/attorney_case_review_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/attorney_case_review_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module AttorneyCaseReviewBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(attorney_case_reviews: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(attorney_case_reviews: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :attorney_case_reviews, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/available_hearing_locations_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/available_hearing_locations_belongs_to_polymorphic_appeal_concern.rb index 901c94706de..926a75fba65 100644 --- a/app/models/concerns/available_hearing_locations_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/available_hearing_locations_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module AvailableHearingLocationsBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(available_hearing_locations: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(available_hearing_locations: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :available_hearing_locations, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/claimant_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/claimant_belongs_to_polymorphic_appeal_concern.rb index dd7de661815..b32a1b9b521 100644 --- a/app/models/concerns/claimant_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/claimant_belongs_to_polymorphic_appeal_concern.rb @@ -2,29 +2,9 @@ module ClaimantBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :decision_review, polymorphic: true - - belongs_to :ama_appeal, - -> { where(claimants: { decision_review_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "decision_review_id", optional: true - - belongs_to :legacy_appeal, - -> { where(claimants: { decision_review_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "decision_review_id", optional: true - - belongs_to :higher_level_review, - -> { where(claimants: { decision_review_type: "HigherLevelReview" }) }, - class_name: "HigherLevelReview", foreign_key: "decision_review_id", optional: true - - belongs_to :supplemental_claim, - -> { where(claimants: { decision_review_type: "SupplementalClaim" }) }, - class_name: "SupplementalClaim", foreign_key: "decision_review_id", optional: true - - scope :ama, -> { where(decision_review_type: "Appeal") } - scope :legacy, -> { where(decision_review_type: "LegacyAppeal") } - scope :higher_level_review, -> { where(decision_review_type: "HigherLevelReview") } - scope :supplemental_claim, -> { where(decision_review_type: "SupplementalClaim") } + define_polymorphic_decision_review_associations(:decision_review, :claimants) end end diff --git a/app/models/concerns/decision_document_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/decision_document_belongs_to_polymorphic_appeal_concern.rb index 264029a5212..92f07fa0516 100644 --- a/app/models/concerns/decision_document_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/decision_document_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module DecisionDocumentBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(decision_documents: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(decision_documents: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :decision_documents, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/decision_issue_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/decision_issue_belongs_to_polymorphic_appeal_concern.rb index ae4cd6ba075..f4355794a27 100644 --- a/app/models/concerns/decision_issue_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/decision_issue_belongs_to_polymorphic_appeal_concern.rb @@ -2,24 +2,11 @@ module DecisionIssueBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :decision_review, polymorphic: true - - belongs_to :ama_appeal, - -> { where(decision_issues: { decision_review_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "decision_review_id", optional: true - - belongs_to :higher_level_review, - -> { where(decision_issues: { decision_review_type: "HigherLevelReview" }) }, - class_name: "HigherLevelReview", foreign_key: "decision_review_id", optional: true - - belongs_to :supplemental_claim, - -> { where(decision_issues: { decision_review_type: "SupplementalClaim" }) }, - class_name: "SupplementalClaim", foreign_key: "decision_review_id", optional: true - - scope :ama, -> { where(decision_review_type: "Appeal") } - scope :higher_level_review, -> { where(decision_review_type: "HigherLevelReview") } - scope :supplemental_claim, -> { where(decision_review_type: "SupplementalClaim") } + define_polymorphic_decision_review_associations(:decision_review, + :decision_issues, + %w[Appeal HigherLevelReview SupplementalClaim]) end end diff --git a/app/models/concerns/decision_review_polymorphic_helper.rb b/app/models/concerns/decision_review_polymorphic_helper.rb new file mode 100644 index 00000000000..4082a2314de --- /dev/null +++ b/app/models/concerns/decision_review_polymorphic_helper.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module DecisionReviewPolymorphicHelper + extend ActiveSupport::Concern + + class_methods do + def define_polymorphic_decision_review_associations(association_name, from_association_name, types = nil) + belongs_to association_name, polymorphic: true + + # Specific association mappings that are uniquely different from the calculated class name to underscored symbol + association_name_mapping = { "Appeal" => :ama_appeal, "Hearing" => :ama_hearing } + scope_mapping = { "Appeal" => :ama, "LegacyAppeal" => :legacy, "LegacyHearing" => :legacy, "Hearing" => :ama } + + # LegacyAppeals + all of the non abstract subtypes of DecisionReview not incuding child types for STI + types ||= %w[Appeal LegacyAppeal HigherLevelReview SupplementalClaim] + + types.each do |type| + type_symbol = type.underscore.to_sym + belongs_to_association_name = association_name_mapping[type] || type_symbol + scope_name = scope_mapping[type] || type_symbol + + belongs_to belongs_to_association_name, + -> { where(from_association_name => { "#{association_name}_type": type }) }, + class_name: type, foreign_key: "#{association_name}_id", optional: true + + scope scope_name.to_sym, -> { where("#{association_name}_type": type) } + end + end + end +end diff --git a/app/models/concerns/decision_review_polymorphic_sti_helper.rb b/app/models/concerns/decision_review_polymorphic_sti_helper.rb new file mode 100644 index 00000000000..b7a1af23778 --- /dev/null +++ b/app/models/concerns/decision_review_polymorphic_sti_helper.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module DecisionReviewPolymorphicSTIHelper + extend ActiveSupport::Concern + + class_methods do + def define_polymorphic_decision_review_sti_associations(association_name, from_association_name, types = nil) + # Mappings between STI types and their associated parent type and parent database table + sti_table_mapping = { "Remand" => :supplemental_claims } + sti_type_mapping = { "Remand" => "SupplementalClaim" } + + types ||= %w[Remand] + + types.each do |type| + type_symbol = type.underscore.to_sym + belongs_to_association_name = type_symbol + sti_type = sti_type_mapping[type] || type + sti_table_name = sti_table_mapping[type] || association_name + + belongs_to belongs_to_association_name, + lambda { + where(from_association_name => { "#{association_name}_type": sti_type }) + .where(Arel::Table.new(sti_table_name)[:type].eq(type)) + }, + class_name: type, foreign_key: "#{association_name}_id", optional: true + end + end + end +end diff --git a/app/models/concerns/hearing_email_recipient_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/hearing_email_recipient_belongs_to_polymorphic_appeal_concern.rb index 772a47edb85..213b3edb166 100644 --- a/app/models/concerns/hearing_email_recipient_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/hearing_email_recipient_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module HearingEmailRecipientBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(hearing_email_recipients: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(hearing_email_recipients: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :hearing_email_recipients, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/judge_case_review_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/judge_case_review_belongs_to_polymorphic_appeal_concern.rb index 9400c88b5f1..8fae1ccf999 100644 --- a/app/models/concerns/judge_case_review_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/judge_case_review_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module JudgeCaseReviewBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(judge_case_reviews: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(judge_case_reviews: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :judge_case_reviews, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/sent_hearing_email_event_belongs_to_polymorphic_hearing_concern.rb b/app/models/concerns/sent_hearing_email_event_belongs_to_polymorphic_hearing_concern.rb index d0cc44c4cb9..3c3f3453cf7 100644 --- a/app/models/concerns/sent_hearing_email_event_belongs_to_polymorphic_hearing_concern.rb +++ b/app/models/concerns/sent_hearing_email_event_belongs_to_polymorphic_hearing_concern.rb @@ -2,19 +2,9 @@ module SentHearingEmailEventBelongsToPolymorphicHearingConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :hearing, polymorphic: true - - belongs_to :ama_hearing, - -> { where(sent_hearing_email_events: { hearing_type: "Hearing" }) }, - class_name: "Hearing", foreign_key: "hearing_id", optional: true - - belongs_to :legacy_hearing, - -> { where(sent_hearing_email_events: { hearing_type: "LegacyHearing" }) }, - class_name: "LegacyHearing", foreign_key: "hearing_id", optional: true - - scope :ama, -> { where(hearing_type: "Hearing") } - scope :legacy, -> { where(hearing_type: "LegacyHearing") } + define_polymorphic_decision_review_associations(:hearing, :sent_hearing_email_events, %w[Hearing LegacyHearing]) end end diff --git a/app/models/concerns/special_issue_list_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/special_issue_list_belongs_to_polymorphic_appeal_concern.rb index ed19b084975..44bf31e835a 100644 --- a/app/models/concerns/special_issue_list_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/special_issue_list_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module SpecialIssueListBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(special_issue_lists: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(special_issue_lists: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :special_issue_lists, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/concerns/task_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/task_belongs_to_polymorphic_appeal_concern.rb index 56a321d80e4..d1666b1ff93 100644 --- a/app/models/concerns/task_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/task_belongs_to_polymorphic_appeal_concern.rb @@ -2,29 +2,11 @@ module TaskBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper + include DecisionReviewPolymorphicSTIHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(tasks: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(tasks: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - belongs_to :higher_level_review, - -> { where(tasks: { appeal_type: "HigherLevelReview" }) }, - class_name: "HigherLevelReview", foreign_key: "appeal_id", optional: true - - belongs_to :supplemental_claim, - -> { where(tasks: { appeal_type: "SupplementalClaim" }) }, - class_name: "SupplementalClaim", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } - scope :higher_level_review, -> { where(appeal_type: "HigherLevelReview") } - scope :supplemental_claim, -> { where(appeal_type: "SupplementalClaim") } + define_polymorphic_decision_review_associations(:appeal, :tasks) + define_polymorphic_decision_review_sti_associations(:appeal, :tasks) end end diff --git a/app/models/concerns/vbms_uploaded_document_belongs_to_polymorphic_appeal_concern.rb b/app/models/concerns/vbms_uploaded_document_belongs_to_polymorphic_appeal_concern.rb index 6e332469f35..e2f89793d40 100644 --- a/app/models/concerns/vbms_uploaded_document_belongs_to_polymorphic_appeal_concern.rb +++ b/app/models/concerns/vbms_uploaded_document_belongs_to_polymorphic_appeal_concern.rb @@ -2,19 +2,9 @@ module VbmsUploadedDocumentBelongsToPolymorphicAppealConcern extend ActiveSupport::Concern + include DecisionReviewPolymorphicHelper included do - belongs_to :appeal, polymorphic: true - - belongs_to :ama_appeal, - -> { where(vbms_uploaded_documents: { appeal_type: "Appeal" }) }, - class_name: "Appeal", foreign_key: "appeal_id", optional: true - - belongs_to :legacy_appeal, - -> { where(vbms_uploaded_documents: { appeal_type: "LegacyAppeal" }) }, - class_name: "LegacyAppeal", foreign_key: "appeal_id", optional: true - - scope :ama, -> { where(appeal_type: "Appeal") } - scope :legacy, -> { where(appeal_type: "LegacyAppeal") } + define_polymorphic_decision_review_associations(:appeal, :vbms_uploaded_documents, %w[Appeal LegacyAppeal]) end end diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 5b43066bce2..c4b65345b50 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -106,6 +106,9 @@ class QueryBuilder "SupplementalClaim" => Task.arel_table[:appeal_type] .eq("SupplementalClaim") .and(Task.arel_table[:type].eq(DecisionReviewTask.name)) + .and(Arel.sql("sub_type").not_eq("Remand")), + "Remand" => Arel.sql("sub_type").eq("Remand") + .and(Task.arel_table[:type].eq(DecisionReviewTask.name)) }.freeze DEFAULT_ORDERING_HASH = { @@ -140,35 +143,69 @@ def build_query .order(order_clause) end - def task_type_count - Task.select(Task.arel_table[:type]) - .from(combined_decision_review_tasks_query) - .group(Task.arel_table[:type], Task.arel_table[:appeal_type]) - .count - end - - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/AbcSize - def issue_type_count - shared_select_statement = "tasks.id as tasks_id, request_issues.nonrating_issue_category as issue_category" - appeals_query = Task.send(parent.tasks_query_type[query_type]) - .select(shared_select_statement) - .joins(ama_appeal: :request_issues) + def task_type_query_helper(join_association) + Task.send(parent.tasks_query_type[query_type]) + .select("tasks.id AS task_id, tasks.type AS task_type") + .joins(join_association) .joins(issue_modification_request_join) .where(query_constraints) .where(issue_modification_request_filter) - hlr_query = Task.send(parent.tasks_query_type[query_type]) - .select(shared_select_statement) - .joins(supplemental_claim: :request_issues) + end + + def task_type_board_grant_helper + Task.send(parent.tasks_query_type[query_type]) + .select("tasks.id AS task_id, tasks.type AS task_type, 'Appeal' AS decision_review_type") + .joins(board_grant_effectuation_task_appeals_requests_join) .joins(issue_modification_request_join) - .where(query_constraints) + .where(board_grant_effectuation_task_constraints) .where(issue_modification_request_filter) - sc_query = Task.send(parent.tasks_query_type[query_type]) - .select(shared_select_statement) - .joins(higher_level_review: :request_issues) + end + + def issue_type_query_helper(join_association) + Task.send(parent.tasks_query_type[query_type]) + .select("tasks.id as task_id, request_issues.nonrating_issue_category AS issue_category") + .joins(join_association) .joins(issue_modification_request_join) .where(query_constraints) .where(issue_modification_request_filter) + end + + # rubocop:disable Metrics/MethodLength + def task_type_count + appeals_query = task_type_query_helper(ama_appeal: :request_issues) + .select("'Appeal' AS decision_review_type") + hlr_query = task_type_query_helper(higher_level_review: :request_issues) + .select("'HigherLevelReview' AS decision_review_type") + sc_query = task_type_query_helper(supplemental_claim: :request_issues) + .select("supplemental_claims.type AS decision_review_type") + board_grant_query = task_type_board_grant_helper + + task_count = ActiveRecord::Base.connection.execute <<-SQL + WITH task_review_issues AS ( + #{hlr_query.to_sql} + UNION + #{sc_query.to_sql} + UNION + #{appeals_query.to_sql} + UNION + #{board_grant_query.to_sql} + ) + SELECT task_type, decision_review_type, COUNT(1) + FROM task_review_issues + GROUP BY task_type, decision_review_type; + SQL + + task_count.reduce({}) do |acc, item| + key = [item["task_type"], item["decision_review_type"]] + acc[key] = (acc[key] || 0) + item["count"] + acc + end + end + + def issue_type_count + appeals_query = issue_type_query_helper(ama_appeal: :request_issues) + hlr_query = issue_type_query_helper(higher_level_review: :request_issues) + sc_query = issue_type_query_helper(supplemental_claim: :request_issues) nonrating_issue_count = ActiveRecord::Base.connection.execute <<-SQL WITH task_review_issues AS ( @@ -197,7 +234,6 @@ def issue_type_count issue_count_options end - # rubocop:enable Metrics/AbcSize def change_history_rows # Generate all of the filter queries to be used in both the HLR and SC block @@ -628,7 +664,7 @@ def search_all_clause def group_by_columns "tasks.id, uuid, veterans.participant_id, veterans.ssn, veterans.first_name, veterans.last_name, "\ "unrecognized_party_details.name, unrecognized_party_details.last_name, people.first_name, people.last_name, "\ - "veteran_is_not_claimant, bgs_attorneys.name" + "veteran_is_not_claimant, bgs_attorneys.name, sub_type" end # Uses an array to insert the searched text into all of the searchable fields since it's the same text for all @@ -638,21 +674,26 @@ def search_values end def higher_level_reviews_on_request_issues - decision_reviews_on_request_issues(higher_level_review: :request_issues) + sub_type_alias = "'HigherLevelReview' AS sub_type" + decision_reviews_on_request_issues({ higher_level_review: :request_issues }, sub_type_alias) end def supplemental_claims_on_request_issues - decision_reviews_on_request_issues(supplemental_claim: :request_issues) + sub_type_alias = "supplemental_claims.type AS sub_type" + decision_reviews_on_request_issues({ supplemental_claim: :request_issues }, sub_type_alias) end def appeals_on_request_issues - decision_reviews_on_request_issues(ama_appeal: :request_issues) + sub_type_alias = "'Appeal' as sub_type" + decision_reviews_on_request_issues({ ama_appeal: :request_issues }, sub_type_alias) end # Specific case for BoardEffectuationGrantTasks to include them in the result set # if the :board_grant_effectuation_task FeatureToggle is enabled for the current user. def board_grant_effectuation_tasks + sub_type_alias = "'Appeal' as sub_type" decision_reviews_on_request_issues(board_grant_effectuation_task_appeals_requests_join, + sub_type_alias, board_grant_effectuation_task_constraints) end @@ -667,8 +708,8 @@ def ama_appeals_query appeals_on_request_issues end - def decision_reviews_on_request_issues(join_constraint, where_constraints = query_constraints) - Task.select(union_select_statements) + def decision_reviews_on_request_issues(join_constraint, subclass_type_alias, where_constraints = query_constraints) + Task.select(union_select_statements.append(subclass_type_alias)) .send(parent.tasks_query_type[query_type]) .joins(join_constraint) .joins(*union_query_join_clauses) diff --git a/app/models/supplemental_claim.rb b/app/models/supplemental_claim.rb index cc33992cb6c..93dc068c52d 100644 --- a/app/models/supplemental_claim.rb +++ b/app/models/supplemental_claim.rb @@ -11,6 +11,8 @@ class SupplementalClaim < ClaimReview .where("#{table_name}.decision_review_remanded_type='Appeal'") } + scope :remand, -> { where(type: Remand.name) } + attr_accessor :appeal_split_process def ui_hash diff --git a/client/app/nonComp/util/index.js b/client/app/nonComp/util/index.js index e0f14eb7587..b554aca56b5 100644 --- a/client/app/nonComp/util/index.js +++ b/client/app/nonComp/util/index.js @@ -109,6 +109,11 @@ const parseDecisionReviewTypeFilterOptions = (taskCounts, enabledFilters) => value: 'BoardGrantEffectuationTask', displayText: `Board Grant (${taskCount})` }; + } else if (key.includes('Remand')) { + taskInfo = { + value: 'Remand', + displayText: `Remand (${taskCount})` + }; } return { ...taskInfo, checked: enabledFilters?.includes(taskInfo.value) || false }; diff --git a/spec/controllers/decision_reviews_controller_spec.rb b/spec/controllers/decision_reviews_controller_spec.rb index e16465f0362..41bdf0ad618 100644 --- a/spec/controllers/decision_reviews_controller_spec.rb +++ b/spec/controllers/decision_reviews_controller_spec.rb @@ -306,6 +306,23 @@ end end + let!(:in_progress_remand_tasks) do + (0...10).map do |task_num| + task = create( + :remand_task, + assigned_to: non_comp_org, + assigned_at: task_num.minutes.ago + 1.minute.ago + ) + task.appeal.update!(veteran_file_number: veteran.file_number) + create(:request_issue, :nonrating, decision_review: task.appeal, benefit_type: non_comp_org.url) + + # Generate some random request issues for testing issue type filters + generate_request_issues(task, non_comp_org) + + task + end + end + let!(:completed_hlr_tasks) do (1..20).map do |task_num| task = create( @@ -352,6 +369,29 @@ end end + let!(:completed_remand_tasks) do + (1..10).map do |task_num| + task = create( + :remand_task, + assigned_to: non_comp_org, + assigned_at: task_num.days.ago, + closed_at: (2 * task_num).hours.ago + ) + task.completed! + # Explicitly set the closed_at time again to try to avoid test flakiness + task.closed_at = Time.zone.now - (2 * task_num).hours + task.appeal.update!(veteran_file_number: veteran.file_number) + create(:request_issue, :nonrating, decision_review: task.appeal, benefit_type: non_comp_org.url) + + # Generate some random request issues for testing issue type filters + generate_request_issues(task, non_comp_org) + + task.save + # Attempt to reload after save to avoid potential test flakiness + task.reload + end + end + before { non_comp_org.add_user(user) } subject { get :index, params: query_params, format: :json } @@ -390,6 +430,23 @@ end ).to be true end + + it "Only Remand Tasks are shown when filtered" do + get :index, + params: query_params.merge( + filter: ["col=decisionReviewType&val=Remand"], + page: 1 + ), + format: :json + + response_body = JSON.parse(response.body) + + expect( + response_body["tasks"]["data"].all? do |task| + task["type"] == "decision_review_task" && task["attributes"]["type"] == "Remand" + end + ).to be true + end end shared_examples "issue type query filtering" do @@ -438,7 +495,9 @@ } end - let(:in_progress_tasks) { in_progress_hlr_tasks + on_hold_hlr_tasks + in_progress_sc_tasks } + let(:in_progress_tasks) do + in_progress_hlr_tasks + on_hold_hlr_tasks + in_progress_sc_tasks + in_progress_remand_tasks + end include_examples "task query filtering" include_examples "issue type query filtering" @@ -451,30 +510,30 @@ expect(response.status).to eq(200) response_body = JSON.parse(response.body) - expect(response_body["total_task_count"]).to eq 84 + expect(response_body["total_task_count"]).to eq 94 expect(response_body["tasks_per_page"]).to eq 15 - expect(response_body["task_page_count"]).to eq 6 + expect(response_body["task_page_count"]).to eq 7 expect( task_ids_from_response_body(response_body) ).to match_array task_ids_from_seed(in_progress_tasks, (0...15), :assigned_at) end - it "page 6 displays last 9 tasks" do - query_params[:page] = 6 + it "page 7 displays last 4 tasks" do + query_params[:page] = 7 subject expect(response.status).to eq(200) response_body = JSON.parse(response.body) - expect(response_body["total_task_count"]).to eq 84 + expect(response_body["total_task_count"]).to eq 94 expect(response_body["tasks_per_page"]).to eq 15 - expect(response_body["task_page_count"]).to eq 6 + expect(response_body["task_page_count"]).to eq 7 expect( task_ids_from_response_body(response_body) - ).to match_array task_ids_from_seed(in_progress_tasks, (-9..in_progress_tasks.size), :assigned_at) + ).to match_array task_ids_from_seed(in_progress_tasks, (-4..in_progress_tasks.size), :assigned_at) end end @@ -486,7 +545,7 @@ } end - let(:completed_tasks) { completed_sc_tasks + completed_hlr_tasks } + let(:completed_tasks) { completed_sc_tasks + completed_hlr_tasks + completed_remand_tasks } include_examples "task query filtering" include_examples "issue type query filtering" @@ -499,34 +558,34 @@ expect(response.status).to eq(200) response_body = JSON.parse(response.body) - expect(response_body["total_task_count"]).to eq 40 + expect(response_body["total_task_count"]).to eq 50 expect(response_body["tasks_per_page"]).to eq 15 - expect(response_body["task_page_count"]).to eq 3 + expect(response_body["task_page_count"]).to eq 4 expect( task_ids_from_response_body(response_body) ).to match_array task_ids_from_seed(completed_tasks, (0...15), :closed_at) end - it "page 3 displays last 10 tasks" do - query_params[:page] = 3 + it "page 4 displays last 5 tasks" do + query_params[:page] = 4 subject expect(response.status).to eq(200) response_body = JSON.parse(response.body) - expect(response_body["total_task_count"]).to eq 40 + expect(response_body["total_task_count"]).to eq 50 expect(response_body["tasks_per_page"]).to eq 15 - expect(response_body["task_page_count"]).to eq 3 + expect(response_body["task_page_count"]).to eq 4 expect( task_ids_from_response_body(response_body) - ).to match_array task_ids_from_seed(completed_tasks, (-10..completed_tasks.size), :closed_at) + ).to match_array task_ids_from_seed(completed_tasks, (-5..completed_tasks.size), :closed_at) end end - context "vha org incomplete_tasks" do + context "vha org with incomplete tasks" do let(:non_comp_org) { VhaBusinessLine.singleton } context "incomplete_tasks" do @@ -604,7 +663,7 @@ end # The Vha Businessline in_progress should not include on_hold since it uses active for the tasks query - let(:in_progress_tasks) { in_progress_hlr_tasks + in_progress_sc_tasks } + let(:in_progress_tasks) { in_progress_hlr_tasks + in_progress_sc_tasks + in_progress_remand_tasks } it "page 1 displays first 15 tasks" do query_params[:page] = 1 @@ -614,7 +673,7 @@ expect(response.status).to eq(200) response_body = JSON.parse(response.body) - expect(response_body["total_task_count"]).to eq 64 + expect(response_body["total_task_count"]).to eq 74 expect(response_body["tasks_per_page"]).to eq 15 expect(response_body["task_page_count"]).to eq 5 @@ -942,7 +1001,7 @@ params = { business_line_slug: non_comp_org.url }.merge(generate_report_filters.except("report_type")) get :generate_report, format: :csv, params: params expect(response).to have_http_status(:bad_request) - expect(response.content_type).to eq("application/json") + expect(response.media_type).to eq("application/json") json_response = JSON.parse(response.body) expect(json_response["error"]).to eq("param is missing or the value is empty: reportType") end diff --git a/spec/factories/task.rb b/spec/factories/task.rb index a184edca648..a921dad4360 100644 --- a/spec/factories/task.rb +++ b/spec/factories/task.rb @@ -431,6 +431,11 @@ def self.find_first_task_or_create(appeal, task_type, **kwargs) assigned_by { nil } end + factory :remand_task, class: DecisionReviewTask do + appeal { create(:remand, benefit_type: "nca") } + assigned_by { nil } + end + factory :supplemental_claim_poa_task, class: DecisionReviewTask do appeal do create(:supplemental_claim, @@ -511,6 +516,19 @@ def self.find_first_task_or_create(appeal, task_type, **kwargs) assigned_to { VhaBusinessLine.singleton } end + factory :remand_vha_task, class: DecisionReviewTask do + appeal do + create( + :remand, + :with_vha_issue, + benefit_type: "vha", + claimant_type: :veteran_claimant + ) + end + assigned_by { nil } + assigned_to { VhaBusinessLine.singleton } + end + factory :supplemental_claim_vha_task_incomplete, class: DecisionReviewTask do appeal do create( diff --git a/spec/feature/non_comp/reviews_spec.rb b/spec/feature/non_comp/reviews_spec.rb index f250c84ca98..1cf36ba917c 100644 --- a/spec/feature/non_comp/reviews_spec.rb +++ b/spec/feature/non_comp/reviews_spec.rb @@ -10,6 +10,7 @@ let(:veteran_b_on_hold) { create(:veteran, first_name: "Gaius", participant_id: "601172", ssn: "191039395") } let(:veteran_a_pending) { create(:veteran, first_name: "Dave", participant_id: "55667788", ssn: "123456789") } let(:veteran_c) { create(:veteran, first_name: "Ccc", participant_id: "1002345", ssn: "128455943") } + let(:veteran_remand) { create(:veteran, first_name: "B. Stark", participant_id: "100234567", ssn: "128455999") } let(:claimant_type) { :veteran_claimant } let(:hlr_a_on_hold) do create(:higher_level_review, veteran_file_number: veteran_a_on_hold.file_number, claimant_type: claimant_type) @@ -24,6 +25,7 @@ let(:hlr_b) { create(:higher_level_review, veteran_file_number: veteran_b.file_number, claimant_type: claimant_type) } let(:hlr_c) { create(:higher_level_review, veteran_file_number: veteran_c.file_number, claimant_type: claimant_type) } let(:appeal) { create(:appeal, veteran: veteran_c) } + let(:remand) { create(:remand, veteran_file_number: veteran_remand.file_number, claimant_type: claimant_type) } let!(:request_issue_a) do create(:request_issue, :nonrating, nonrating_issue_category: "Caregiver | Other", decision_review: hlr_a) @@ -57,6 +59,10 @@ create(:request_issue, :nonrating, nonrating_issue_category: "Other", decision_review: hlr_a_pending) end + let!(:remand_request_issue) do + create(:request_issue, :nonrating, nonrating_issue_category: "Beneficiary Travel", decision_review: remand) + end + let!(:modification_request_a) do create(:issue_modification_request, decision_review: hlr_a_pending, requestor: user) end @@ -107,7 +113,12 @@ :in_progress, appeal: appeal, assigned_to: non_comp_org, - assigned_at: 1.day.ago) + assigned_at: 1.day.ago), + create(:higher_level_review_task, + :in_progress, + appeal: remand, + assigned_to: non_comp_org, + assigned_at: today) ] end @@ -188,9 +199,9 @@ def current_table_rows scenario "displays tasks page with decision_review_queue_ssn_column feature toggle disabled" do visit BASE_URL expect(page).to have_content("Veterans Health Administration") - expect(page).to have_content("Incomplete Tasks") - expect(page).to have_content("Pending Tasks") - expect(page).to have_content("In Progress Tasks") + expect(page).to have_content("Incomplete Tasks (2)") + expect(page).to have_content("Pending Tasks (1)") + expect(page).to have_content("In Progress Tasks (4)") expect(page).to have_content("Completed Tasks") # default is the in progress page if no tab is specified in the url @@ -198,21 +209,24 @@ def current_table_rows expect(page).to have_content("Issues") expect(page).to have_content("Issue Type") expect(page).to have_content("Higher-Level Review", count: 2) + expect(page).to have_content("Remand", count: 1) expect(page).to have_content("Board Grant") expect(page).to have_content(veteran_a.name) expect(page).to have_content(veteran_b.name) expect(page).to have_content(veteran_c.name) + expect(page).to have_content(veteran_remand.name) expect(page).to_not have_content(veteran_a_on_hold.name) expect(page).to_not have_content(veteran_b_on_hold.name) expect(page).to have_content(vet_id_column_header) expect(page).to have_content(vet_a_id_column_value) expect(page).to have_content(vet_b_id_column_value) expect(page).to have_content(vet_c_id_column_value) + expect(page).to have_no_content(search_box_label) # ordered by assigned_at descending expect(page).to have_content( - /#{veteran_b.name}.+\s#{veteran_c.name}.+\s#{veteran_a.name}/ + /#{veteran_b.name}.+\s#{veteran_remand.name}.+\s#{veteran_c.name}.+\s#{veteran_a.name}/ ) click_on "Incomplete Tasks" @@ -241,7 +255,6 @@ def current_table_rows expect(page).to have_content("Higher-Level Review", count: 2) expect(page).to have_content("Date Completed") - # decision_date = hlr_b.request_issues.first.decision_date.strftime("%m\/%d\/%y") decision_date = hlr_b.tasks.first.closed_at.strftime("%m/%d/%y") # ordered by closed_at descending expect(page).to have_content( @@ -262,9 +275,9 @@ def current_table_rows scenario "displays tasks page" do visit BASE_URL expect(page).to have_content("Veterans Health Administration") - expect(page).to have_content("Incomplete Tasks") - expect(page).to have_content("Pending Tasks") - expect(page).to have_content("In Progress Tasks") + expect(page).to have_content("Incomplete Tasks (2)") + expect(page).to have_content("Pending Tasks (1)") + expect(page).to have_content("In Progress Tasks (4)") expect(page).to have_content("Completed Tasks") # default is the in progress page if no tab is specified in the url @@ -374,7 +387,7 @@ def current_table_rows table_rows = current_table_rows expect(table_rows.last.include?("Caregiver | Other\nCHAMPVA\n")).to eq true - expect(table_rows.first.include?(" Camp Lejune Family Member ")).to eq true + expect(table_rows.first.include?(" Beneficiary Travel ")).to eq true # Issue Types desc order_buttons[:issues_type].click @@ -383,7 +396,7 @@ def current_table_rows ) table_rows = current_table_rows - expect(table_rows.last.include?(" Camp Lejune Family Member ")).to eq true + expect(table_rows.last.include?(" Beneficiary Travel ")).to eq true expect(table_rows.first.include?("Caregiver | Other\nCHAMPVA")).to eq true # Days waiting asc @@ -515,22 +528,30 @@ def current_table_rows end end - scenario "filtering reviews by appeal type", skip: true do + scenario "filtering reviews by appeal type" do visit BASE_URL + + expect(page).to have_content("Higher-Level Review") + expect(page).to have_content("Board Grant") + expect(page).to have_content("Remand") + find("[aria-label='Filter by type']").click # Check that task counts are being transmitted correctly from backend expect(page).to have_content("Board Grant (1)") expect(page).to have_content("Higher-Level Review (2)") + expect(page).to have_content("Remand (1)") find("label", text: "Higher-Level Review").click - expect(page).to have_content("Higher-Level Review") + expect(page).to have_content("Higher-Level Review", count: 2) expect(page).to_not have_content("Board Grant") + expect(page).to_not have_content("Remand") find(".cf-clear-filters-link").click expect(page).to have_content("Board Grant") + expect(page).to have_content("Remand") end - scenario "filtering reviews by issue type", skip: true do + scenario "filtering reviews by issue type" do visit BASE_URL find("[aria-label='Filter by issue type']").click @@ -538,6 +559,7 @@ def current_table_rows expect(page).to have_content("Caregiver | Other (1)") expect(page).to have_content("Camp Lejune Family Member (1)") expect(page).to have_content("CHAMPVA (1)") + expect(page).to have_content("Beneficiary Travel (1)") find("label", text: "Caregiver | Other").click expect(page).to have_content("Caregiver | Other") @@ -547,16 +569,19 @@ def current_table_rows # Verify the filter counts for the incomplete tab click_on "Incomplete Tasks" + expect(page).to have_content(COPY::VHA_INCOMPLETE_TAB_DESCRIPTION) find("[aria-label='Filter by issue type']").click expect(page).to have_content("Clothing Allowance (1)") expect(page).to have_content("Other (1)") click_on "Pending Tasks" + expect(page).to have_content(COPY::VHA_PENDING_REQUESTS_TAB_DESCRIPTION) find("[aria-label='Filter by issue type']").click expect(page).to have_content("Other (1)") # Verify the filter counts for the completed tab click_on "Completed Tasks" + expect(page).to have_content(COPY::QUEUE_PAGE_COMPLETE_LAST_SEVEN_DAYS_TASKS_DESCRIPTION) find("[aria-label='Filter by issue type']").click expect(page).to have_content("Apportionment (1)") expect(page).to have_content("Camp Lejune Family Member (1)") @@ -750,17 +775,17 @@ def current_table_rows assigned_at: last_week) ] end - scenario "Duplicate issue types like Beneficiary Travel should be removed from the visible list of issue types" do - visit BASE_URL - hlr_c_regex = /#{veteran_c.name} #{veteran_c.ssn} 3\nBeneficiary Travel\nEligibility for Dental Treatment\n6 days Higher-Level Review/ # rubocop:disable Layout/LineLength - expect(page).to have_content( - hlr_c_regex - ) - end scenario "Ordering issue types should ignore duplicates when ordering" do visit BASE_URL + step "Duplicate issue types like Beneficiary Travel should be removed from the visible list of issue types" do + hlr_c_regex = /#{veteran_c.name} #{veteran_c.ssn} 3\nBeneficiary Travel\nEligibility for Dental Treatment\n6 days Higher-Level Review/ # rubocop:disable Layout/LineLength + expect(page).to have_content( + hlr_c_regex + ) + end + issues_type_sort_button = find(:xpath, '//*[@id="case-table-description"]/thead/tr/th[4]/span/span[2]') issues_type_sort_button.click @@ -788,7 +813,7 @@ def current_table_rows expect(table_rows.first.include?("B Veteran")).to eq true end - scenario "The Issue type column should orderable and filterable at the same time", skip: true do + scenario "The Issue type column should orderable and filterable at the same time" do visit BASE_URL issues_type_sort_button = find(:xpath, '//*[@id="case-table-description"]/thead/tr/th[4]/span/span[2]') @@ -816,6 +841,9 @@ def current_table_rows table_rows = current_table_rows + sort_header = find('th[aria-labelledby="header-appealIssueTypes"]') + expect(sort_header[:'aria-sort']).to eq("ascending") + expect(table_rows.first.include?("A Veteran")).to eq true expect(table_rows.last.include?("D Veteran")).to eq true @@ -823,6 +851,9 @@ def current_table_rows table_rows = current_table_rows + sort_header = find('th[aria-labelledby="header-appealIssueTypes"]') + expect(sort_header[:'aria-sort']).to eq("descending") + expect(table_rows.first.include?("D Veteran")).to eq true expect(table_rows.last.include?("A Veteran")).to eq true @@ -830,6 +861,9 @@ def current_table_rows expect(page).to have_content("Spina Bifida Treatment (Non-Compensation)") table_rows = current_table_rows + + sort_header = find('th[aria-labelledby="header-appealIssueTypes"]') + expect(sort_header[:'aria-sort']).to eq("descending") expect(table_rows.first.include?("B Veteran")).to eq true expect(table_rows.last.include?("A Veteran")).to eq true end @@ -965,7 +999,7 @@ def current_table_rows pagination = page.find(class: "cf-pagination-pages", match: :first) pagination.find("Button", text: "2", match: :first).click - expect(page).to have_content("Viewing 16-30 of 33 total") + expect(page).to have_content("Viewing 16-30 of 34 total") # Navigate to another tab click_button("Incomplete Tasks") @@ -997,9 +1031,11 @@ def current_table_rows expect(page).to have_content("Issue Type") expect(page).to have_content("Higher-Level Review", count: 4) expect(page).to have_content("Board Grant") + expect(page).to have_content("Remand") expect(page).to have_content(veteran_a.name) expect(page).to have_content(veteran_b.name) expect(page).to have_content(veteran_c.name) + expect(page).to have_content(veteran_remand.name) expect(page).to have_content(veteran_a_on_hold.name) expect(page).to have_content(veteran_b_on_hold.name) expect(page).to have_content(vet_id_column_header) @@ -1010,7 +1046,7 @@ def current_table_rows # ordered by assigned_at descending expect(page).to have_content( - /#{veteran_b.name}.+\s#{veteran_c.name}.+\s#{veteran_a.name}/ + /#{veteran_b.name}.+\s#{veteran_remand.name}.+\s#{veteran_c.name}.+\s#{veteran_a.name}/ ) click_on "Completed Tasks" diff --git a/spec/lib/helpers/association_wrapper_spec.rb b/spec/lib/helpers/association_wrapper_spec.rb index 5acf5ae0cb1..3536be6603b 100644 --- a/spec/lib/helpers/association_wrapper_spec.rb +++ b/spec/lib/helpers/association_wrapper_spec.rb @@ -26,6 +26,7 @@ [:legacy_appeal, "LegacyAppeal", "LegacyAppeal", nil, nil], [:supplemental_claim, "SupplementalClaim", "SupplementalClaim", nil, nil], [:higher_level_review, "HigherLevelReview", "HigherLevelReview", nil, nil], + [:remand, "Remand", "Remand", nil, nil], [:attorney_case_reviews, "AttorneyCaseReview", nil, nil, nil], [:task_timers, "TaskTimer", nil, nil, nil], [:cached_appeal, "CachedAppeal", nil, nil, nil] @@ -45,7 +46,8 @@ [:ama_appeal, nil], [:legacy_appeal, nil], [:higher_level_review, nil], - [:supplemental_claim, nil] + [:supplemental_claim, nil], + [:remand, nil] ] expect(subject.select_associations.map { |assoc| [assoc.name, assoc.options[:foreign_key]] }).to match_array [ [:versions, nil], @@ -62,7 +64,8 @@ [:ama_appeal, "appeal_id"], [:legacy_appeal, "appeal_id"], [:higher_level_review, "appeal_id"], - [:supplemental_claim, "appeal_id"] + [:supplemental_claim, "appeal_id"], + [:remand, "appeal_id"] ] map_foreign_keys = lambda { |assoc| @@ -86,6 +89,8 @@ [:legacy_appeal, true, false, "appeal_id", "legacy_appeal_id", nil], [:higher_level_review, true, false, "appeal_id", "higher_level_review_id", nil], [:supplemental_claim, true, false, "appeal_id", "supplemental_claim_id", nil], + # Polymorphic `belongs_to :appeal`-related STI associations + [:remand, true, false, "appeal_id", "remand_id", nil], # has_many declared in Task # Note: JudgeCaseReview is not listed; that `belongs_to` association can be traced from JudgeCaseReview [:attorney_case_reviews, false, false, "task_id", "attorney_case_review_id", nil], diff --git a/spec/models/business_line_spec.rb b/spec/models/business_line_spec.rb index f9e382d4742..a567c6bc04f 100644 --- a/spec/models/business_line_spec.rb +++ b/spec/models/business_line_spec.rb @@ -29,6 +29,18 @@ end end + context "Remand tasks" do + let!(:task_filters) { ["col=decisionReviewType&val=Remand"] } + + it "Returning only Remand tasks" do + expect( + subject.all? do |task| + task.type == DecisionReviewTask.name && task.appeal.type == Remand.name + end + ).to eq true + end + end + context "Veteran Record Request tasks" do let!(:task_filters) { ["col=decisionReviewType&val=VeteranRecordRequest"] } @@ -55,7 +67,6 @@ it "Attempting to return only Board Grant Effectuation tasks amounts to either only completed tasks or any empty result" do tasks = subject - expect(tasks.empty? || tasks.all?(&:completed?)).to eq true end end @@ -127,6 +138,10 @@ create_list(:higher_level_review_task, 5, assigned_to: business_line) end + let!(:remand_tasks_on_active_decision_reviews) do + create_list(:remand_vha_task, 5, assigned_to: business_line) + end + let!(:board_grant_effectuation_tasks) do tasks = create_list(:board_grant_effectuation_task, 5, assigned_to: business_line) @@ -165,12 +180,13 @@ after { FeatureToggle.disable!(:board_grant_effectuation_task) } it "All tasks associated with active decision reviews and BoardGrantEffectuationTasks are included" do - expect(subject.size).to eq 20 + expect(subject.size).to eq 25 expect(subject.map(&:id)).to match_array( (veteran_record_request_on_active_appeals + board_grant_effectuation_tasks + hlr_tasks_on_active_decision_reviews + - sc_tasks_on_active_decision_reviews + sc_tasks_on_active_decision_reviews + + remand_tasks_on_active_decision_reviews ).pluck(:id) ) end @@ -182,11 +198,12 @@ before { FeatureToggle.disable!(:board_grant_effectuation_task) } it "All tasks associated with active decision reviews are included, but not BoardGrantEffectuationTasks" do - expect(subject.size).to eq 15 + expect(subject.size).to eq 20 expect(subject.map(&:id)).to match_array( (veteran_record_request_on_active_appeals + hlr_tasks_on_active_decision_reviews + - sc_tasks_on_active_decision_reviews + sc_tasks_on_active_decision_reviews + + remand_tasks_on_active_decision_reviews ).pluck(:id) ) end @@ -245,6 +262,14 @@ ) end + let!(:completed_remand_tasks) do + add_veteran_and_request_issues_to_decision_reviews( + complete_all_tasks( + create_list(:remand_task, 5, assigned_to: business_line) + ) + ) + end + let!(:open_sc_tasks) do add_veteran_and_request_issues_to_decision_reviews( create_list(:supplemental_claim_task, 5, assigned_to: business_line) @@ -295,12 +320,13 @@ let!(:task_filters) { nil } it "All completed tasks are included in results" do - expect(subject.size).to eq 20 + expect(subject.size).to eq 25 expect(subject.map(&:id)).to match_array( (completed_hlr_tasks + completed_sc_tasks + completed_board_grant_effectuation_tasks + - completed_veteran_record_requests + completed_veteran_record_requests + + completed_remand_tasks ).pluck(:id) ) end @@ -394,6 +420,10 @@ create_list(:supplemental_claim_vha_task, 5, assigned_to: business_line) end + let!(:remand_tasks_on_active_decision_reviews) do + create_list(:remand_vha_task, 5, assigned_to: business_line) + end + # Set some on hold tasks as well let!(:on_hold_sc_tasks_on_active_decision_reviews) do tasks = create_list(:supplemental_claim_vha_task, 5, assigned_to: business_line) @@ -443,13 +473,14 @@ after { FeatureToggle.disable!(:board_grant_effectuation_task) } it "All tasks associated with active decision reviews and BoardGrantEffectuationTasks are included" do - expect(subject.size).to eq 25 + expect(subject.size).to eq 30 expect(subject.map(&:id)).to match_array( (veteran_record_request_on_active_appeals + board_grant_effectuation_tasks + hlr_tasks_on_active_decision_reviews + sc_tasks_on_active_decision_reviews + - on_hold_sc_tasks_on_active_decision_reviews + on_hold_sc_tasks_on_active_decision_reviews + + remand_tasks_on_active_decision_reviews ).pluck(:id) ) end @@ -461,12 +492,13 @@ before { FeatureToggle.disable!(:board_grant_effectuation_task) } it "All tasks associated with active decision reviews are included, but not BoardGrantEffectuationTasks" do - expect(subject.size).to eq 20 + expect(subject.size).to eq 25 expect(subject.map(&:id)).to match_array( (veteran_record_request_on_active_appeals + hlr_tasks_on_active_decision_reviews + sc_tasks_on_active_decision_reviews + - on_hold_sc_tasks_on_active_decision_reviews + on_hold_sc_tasks_on_active_decision_reviews + + remand_tasks_on_active_decision_reviews ).pluck(:id) ) end From 01bf66463444b9ca02fc3a67403efb9054e06ab4 Mon Sep 17 00:00:00 2001 From: jonathanh-va <111081469+jonathanh-va@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:36:19 -0500 Subject: [PATCH 06/19] JHoang/APPEALS-42318 (#22270) * some preliminary remand & sc model updates * typo * some preliminary hlr checks? * undo seed change * decision issue and sc updates * attempt code climate fixes * another code climate fix * testing changes * useless variable.. * fix conditional in decision issue * fix undefined find_by method * attempt rspec fix * rspec fix attempt #2 * supplemental_claim_spec fix? * remove comma * code climate fix * added comment for transparency on remand creation, attempt fix for remands route * updated specs for decision_issue, hlr_spec, appeal_spec to account for remand creation type * fix lint error * fix lint error #2 --------- Co-authored-by: Jonathan Hoang --- app/models/decision_issue.rb | 33 +++++++++++++++++++------ config/routes.rb | 1 + spec/factories/supplemental_claim.rb | 8 +++++- spec/models/appeal_spec.rb | 6 +++-- spec/models/decision_issue_spec.rb | 24 ++++++++++++++++++ spec/models/higher_level_review_spec.rb | 2 ++ spec/models/supplemental_claim_spec.rb | 11 +++++++++ 7 files changed, 74 insertions(+), 11 deletions(-) diff --git a/app/models/decision_issue.rb b/app/models/decision_issue.rb index 95ef1f19422..7dff27d5f0f 100644 --- a/app/models/decision_issue.rb +++ b/app/models/decision_issue.rb @@ -250,14 +250,7 @@ def create_remand_supplemental_claim! # Checking our assumption that approx_decision_date will always be populated for Decision Issues fail "approx_decision_date is required to create a DTA Supplemental Claim" unless approx_decision_date - sc = SupplementalClaim.create!( - veteran_file_number: veteran_file_number, - decision_review_remanded: decision_review, - benefit_type: benefit_type, - legacy_opt_in_approved: decision_review.legacy_opt_in_approved, - veteran_is_not_claimant: decision_review.veteran_is_not_claimant, - receipt_date: approx_decision_date - ) + sc = determine_remand_creation_type fail AppealDTAPayeeCodeError, decision_review.id unless dta_payee_code sc.create_claimant!( @@ -273,4 +266,28 @@ def create_remand_supplemental_claim! decision_review.update_error!("DTA SC creation failed") raise end + + # Create a Remand if the decision review is an Appeal. + # HLRs will still create remands as SupplementalClaims as of (APPEALS-41559) + def determine_remand_creation_type + if decision_review_type == Appeal.name + Remand.create!( + veteran_file_number: veteran_file_number, + decision_review_remanded: decision_review, + benefit_type: benefit_type, + legacy_opt_in_approved: decision_review.legacy_opt_in_approved, + veteran_is_not_claimant: decision_review.veteran_is_not_claimant, + receipt_date: approx_decision_date + ) + else + SupplementalClaim.create!( + veteran_file_number: veteran_file_number, + decision_review_remanded: decision_review, + benefit_type: benefit_type, + legacy_opt_in_approved: decision_review.legacy_opt_in_approved, + veteran_is_not_claimant: decision_review.veteran_is_not_claimant, + receipt_date: approx_decision_date + ) + end + end end diff --git a/config/routes.rb b/config/routes.rb index c9f14e7da8b..754aef7887e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -291,6 +291,7 @@ post 'edit_ep', on: :member end match '/supplemental_claims/:claim_id/edit/:any' => 'supplemental_claims#edit', via: [:get] + get '/remands(/*path)', to: redirect('/supplemental_claims/%{path}') resources :decision_reviews, param: :business_line_slug do resources :tasks, controller: :decision_reviews, param: :task_id, only: [:show, :update] do diff --git a/spec/factories/supplemental_claim.rb b/spec/factories/supplemental_claim.rb index 05841c7f9d3..9a257e1676d 100644 --- a/spec/factories/supplemental_claim.rb +++ b/spec/factories/supplemental_claim.rb @@ -286,7 +286,13 @@ factory :remand, class: Remand do type { Remand.name } - decision_review_remanded { create(:appeal) } + decision_review_remanded do + create( + :appeal, + :with_decision_issue, + disposition: "remanded" + ) + end end end end diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb index 26bbd09cceb..c8e9077cd86 100644 --- a/spec/models/appeal_spec.rb +++ b/spec/models/appeal_spec.rb @@ -278,14 +278,15 @@ let!(:not_remanded_decision_issue) { create(:decision_issue, decision_review: appeal) } - it "creates supplemental claim, request issues, and starts processing" do + it "creates remand, request issues, and starts processing" do subject - remanded_supplemental_claims = SupplementalClaim.where(decision_review_remanded: appeal) + remanded_supplemental_claims = Remand.where(decision_review_remanded: appeal) expect(remanded_supplemental_claims.count).to eq(2) vbms_remand = remanded_supplemental_claims.find_by(benefit_type: "compensation") + expect(vbms_remand.type).to eq(Remand.name) expect(vbms_remand).to have_attributes( receipt_date: decision_date.to_date ) @@ -297,6 +298,7 @@ expect(vbms_remand.tasks).to be_empty caseflow_remand = remanded_supplemental_claims.find_by(benefit_type: "nca") + expect(caseflow_remand.type).to eq(Remand.name) expect(caseflow_remand).to have_attributes( receipt_date: decision_date.to_date ) diff --git a/spec/models/decision_issue_spec.rb b/spec/models/decision_issue_spec.rb index 3ed5a1fb89e..4013a9ded31 100644 --- a/spec/models/decision_issue_spec.rb +++ b/spec/models/decision_issue_spec.rb @@ -419,6 +419,30 @@ end end end + context "decision review type" do + context "when type is Appeal" do + let(:decision_review) { create(:appeal) } + + it "creates a Remand class" do + expect(subject.type).to eq(Remand.name) + end + end + + context "when type is HLR" do + let(:decision_review) { create(:higher_level_review) } + before do + decision_review.create_claimant!( + participant_id: "98765", + payee_code: "00", + type: "VeteranClaimant" + ) + end + + it "creates a SupplementalClaim class" do + expect(subject.type).to eq(SupplementalClaim.name) + end + end + end end end end diff --git a/spec/models/higher_level_review_spec.rb b/spec/models/higher_level_review_spec.rb index 9182f62a383..031fb9ae2c0 100644 --- a/spec/models/higher_level_review_spec.rb +++ b/spec/models/higher_level_review_spec.rb @@ -264,6 +264,7 @@ expect(supplemental_claim).to_not be_nil expect(supplemental_claim.establishment_submitted_at).to_not be_nil expect(supplemental_claim.request_issues.count).to eq(2) + expect(supplemental_claim.type).to eq(SupplementalClaim.name) first_dta_request_issue = RequestIssue.find_by( decision_review: supplemental_claim, @@ -332,6 +333,7 @@ caseflow_decision_date.to_date + vbms_offset - SupplementalClaim.processing_retry_interval_hours.hours + 1.minute ) + expect(dta_sc.type).to eq(SupplementalClaim.name) expect do subject end.to_not have_enqueued_job(DecisionReviewProcessJob) diff --git a/spec/models/supplemental_claim_spec.rb b/spec/models/supplemental_claim_spec.rb index 96360af191f..bb2f1a80bd5 100644 --- a/spec/models/supplemental_claim_spec.rb +++ b/spec/models/supplemental_claim_spec.rb @@ -120,6 +120,17 @@ end context "create_remand_issues!" do + let(:classifier) { Remand } + let(:supplemental_claim) do + classifier.new( + veteran_file_number: veteran_file_number, + receipt_date: receipt_date, + benefit_type: benefit_type, + legacy_opt_in_approved: legacy_opt_in_approved, + veteran_is_not_claimant: veteran_is_not_claimant, + decision_review_remanded: decision_review_remanded + ) + end subject { supplemental_claim.create_remand_issues! } let(:decision_review_remanded) { create(:appeal) } From 60274ac6e2b3d32035c52c731545785da88d984b Mon Sep 17 00:00:00 2001 From: Clay Sheppard Date: Thu, 8 Aug 2024 08:20:10 -0500 Subject: [PATCH 07/19] APPEALS-42458 (#22365) * disable edit issues link for remands * add banners explaining that remands are not editable also makes sure that remands are not editable on the edit issues page --- app/views/supplemental_claims/edit.html.erb | 1 + client/COPY.json | 4 ++-- .../app/intake/pages/addIssues/addIssues.jsx | 5 +++++ .../app/intakeEdit/components/EditButtons.jsx | 5 ++++- client/app/intakeEdit/reducers/index.js | 2 ++ client/app/nonComp/components/Disposition.jsx | 19 ++++++++++++++-- .../intake/supplemental_claim/edit_spec.rb | 20 +++++++++++++++++ spec/feature/non_comp/dispositions_spec.rb | 22 +++++++++++++++++++ 8 files changed, 73 insertions(+), 5 deletions(-) diff --git a/app/views/supplemental_claims/edit.html.erb b/app/views/supplemental_claims/edit.html.erb index bf8d611f463..833453bad1a 100644 --- a/app/views/supplemental_claims/edit.html.erb +++ b/app/views/supplemental_claims/edit.html.erb @@ -7,6 +7,7 @@ userCanEditIntakeIssues: current_user.can_edit_intake_issues?, userCanRequestIssueUpdates: current_user.can_request_for_issue_updates?, userIsVhaAdmin: current_user.vha_business_line_admin_user?, + isRemand: supplemental_claim.is_a?(Remand), dropdownUrls: dropdown_urls, applicationUrls: application_urls, feedbackUrl: feedback_url, diff --git a/client/COPY.json b/client/COPY.json index 5735eec2290..4f0b460e5eb 100644 --- a/client/COPY.json +++ b/client/COPY.json @@ -1,4 +1,3 @@ - { "CASE_SEARCH_HOME_PAGE_HEADING": "Veteran Case Search", "CASE_SEARCH_INPUT_PLACEHOLDER": "Enter a file number, SSN, or AMA docket number", @@ -1516,5 +1515,6 @@ } }, "VHA_BANNER_DISPOSITIONS_CANNOT_BE_UPDATED_NON_ADMIN": "Requests for issue modifications have been submitted for this case. Dispositions cannot be made until a VHA admin completes review of the requested changes.", - "VHA_BANNER_DISPOSITIONS_CANNOT_BE_UPDATED_ADMIN": "Requests for issue modifications have been submitted for this case. Dispositions cannot be made until a VHA admin completes review of the requested changes. Click the \"Edit issues\" button above to review the issue modification requests." + "VHA_BANNER_DISPOSITIONS_CANNOT_BE_UPDATED_ADMIN": "Requests for issue modifications have been submitted for this case. Dispositions cannot be made until a VHA admin completes review of the requested changes. Click the \"Edit issues\" button above to review the issue modification requests.", + "REMANDS_NOT_EDITABLE": "Remands can not be edited." } diff --git a/client/app/intake/pages/addIssues/addIssues.jsx b/client/app/intake/pages/addIssues/addIssues.jsx index 39e7d267d44..3ac7d848f5e 100644 --- a/client/app/intake/pages/addIssues/addIssues.jsx +++ b/client/app/intake/pages/addIssues/addIssues.jsx @@ -328,6 +328,7 @@ class AddIssuesPage extends React.Component { userIsVhaAdmin, userCanSplitAppeal, userCanRequestIssueUpdates, + isRemand, isLegacy, pendingIssueModificationRequests, } = this.props; @@ -423,6 +424,7 @@ class AddIssuesPage extends React.Component { const showRequestIssueUpdateOptions = editPage && userCanRequestIssueUpdates && !originalIssuesHaveNoDecisionDate() && + !isRemand && intakeData.benefitType === 'vha'; const disableIssueActions = editPage && @@ -854,6 +856,8 @@ class AddIssuesPage extends React.Component { {editPage && this.establishmentCredits()} + {editPage && isRemand ? : null} + {!_.isEmpty(issuesPendingWithdrawal) && ( @@ -957,6 +961,7 @@ export const EditAddIssuesPage = connect( userIsVhaAdmin: state.userIsVhaAdmin, userCanSplitAppeal: state.userCanSplitAppeal, userCanRequestIssueUpdates: state.userCanRequestIssueUpdates, + isRemand: state.isRemand, isLegacy: state.isLegacy, }), (dispatch) => diff --git a/client/app/intakeEdit/components/EditButtons.jsx b/client/app/intakeEdit/components/EditButtons.jsx index a624f79f56b..f97d0c5cac6 100644 --- a/client/app/intakeEdit/components/EditButtons.jsx +++ b/client/app/intakeEdit/components/EditButtons.jsx @@ -164,6 +164,7 @@ class SaveButtonUnconnected extends React.Component { benefitType, pendingIssueModificationRequests, originalPendingIssueModificationRequests, + isRemand, openIssueModificationRequests } = this.props; @@ -190,7 +191,7 @@ class SaveButtonUnconnected extends React.Component { const saveDisabled = (_.isEqual(addedIssues, originalIssues) && _.isEqual(pendingIssueModificationRequests, originalPendingIssueModificationRequests)) || invalidVeteran || - !withdrawDateValid || hasPendingAdditionRequests; + !withdrawDateValid || hasPendingAdditionRequests || isRemand; let saveButtonText; @@ -301,6 +302,7 @@ SaveButtonUnconnected.propTypes = { specialtyCaseTeamDistribution: PropTypes.bool, pendingIssueModificationRequests: PropTypes.array, originalPendingIssueModificationRequests: PropTypes.array, + isRemand: PropTypes.bool, openIssueModificationRequests: PropTypes.array, state: PropTypes.shape({ addedIssues: PropTypes.array @@ -325,6 +327,7 @@ const SaveButton = connect( specialtyCaseTeamDistribution: state.featureToggles.specialtyCaseTeamDistribution, pendingIssueModificationRequests: state.pendingIssueModificationRequests, openIssueModificationRequests: getOpenPendingIssueModificationRequests(state), + isRemand: state.isRemand, originalPendingIssueModificationRequests: state.originalPendingIssueModificationRequests, state }), diff --git a/client/app/intakeEdit/reducers/index.js b/client/app/intakeEdit/reducers/index.js index 2da15ad303d..d8efbe63be8 100644 --- a/client/app/intakeEdit/reducers/index.js +++ b/client/app/intakeEdit/reducers/index.js @@ -17,6 +17,7 @@ export const mapDataToInitialState = function(props = {}) { userIsVhaAdmin, userCanSplitAppeal, userCanRequestIssueUpdates, + isRemand, userFullName, userCssId, isLegacy, @@ -47,6 +48,7 @@ export const mapDataToInitialState = function(props = {}) { userIsVhaAdmin, userCanSplitAppeal, userCanRequestIssueUpdates, + isRemand, userCssId, userFullName, isLegacy, diff --git a/client/app/nonComp/components/Disposition.jsx b/client/app/nonComp/components/Disposition.jsx index 4bb4e015ff2..6ad77dbdbd9 100644 --- a/client/app/nonComp/components/Disposition.jsx +++ b/client/app/nonComp/components/Disposition.jsx @@ -177,6 +177,8 @@ class NonCompDispositions extends React.PureComponent { } let editIssuesLink = null; + const editIssuesDisabled = task.type === 'Remand'; + const editIssuesButtonType = editIssuesDisabled ? 'disabled' : 'secondary'; const displayPOAComponent = task.business_line === 'vha'; const displayRequestIssueModification = (!displayPOAComponent || isBusinessLineAdmin); @@ -194,9 +196,17 @@ class NonCompDispositions extends React.PureComponent { ; editIssuesLink = (displayRequestIssueModification) ? - Edit Issues + + Edit Issues + : - Request issue modification + + Request issue modification + ; } @@ -237,6 +247,11 @@ class NonCompDispositions extends React.PureComponent { {isBusinessLineAdmin && decisionHasPendingRequestIssues ? null :
{decisionHeaderText}
} + {editIssuesDisabled ? +
+ + +
: null} {decisionHasPendingRequestIssues ?
diff --git a/spec/feature/intake/supplemental_claim/edit_spec.rb b/spec/feature/intake/supplemental_claim/edit_spec.rb index 3f2df22e705..1aa698d7434 100644 --- a/spec/feature/intake/supplemental_claim/edit_spec.rb +++ b/spec/feature/intake/supplemental_claim/edit_spec.rb @@ -930,5 +930,25 @@ def click_cancel(visit_page) safe_click "#decision-date" expect(page).to have_button("Add this issue", disabled: true) end + + context "with a remand" do + let(:remand) { create(:remand_vha_task, assigned_at: 1.minute.ago) } + + before do + remand.appeal.establish! + end + + let(:edit_url) do + "/supplemental_claims/#{remand.appeal.uuid}/edit" + end + + it "should not allow editing" do + visit edit_url + + expect(page).to have_content(COPY::REMANDS_NOT_EDITABLE) + expect(page).not_to have_css(".cf-select__control") + expect(page).to have_button("Establish", disabled: true) + end + end end end diff --git a/spec/feature/non_comp/dispositions_spec.rb b/spec/feature/non_comp/dispositions_spec.rb index 36fb413bc45..7d270821654 100644 --- a/spec/feature/non_comp/dispositions_spec.rb +++ b/spec/feature/non_comp/dispositions_spec.rb @@ -502,6 +502,28 @@ def find_disabled_disposition(disposition, description = nil) end end end + + context "viewing a remand" do + let(:in_progress_remand_task) do + create(:remand_vha_task, assigned_at: 1.minute.ago) + end + + let(:dispositions_url) { "#{business_line_url}/tasks/#{in_progress_remand_task.id}" } + + it "should disable the request issue modification button" do + visit dispositions_url + + expect(page).to have_css(".usa-button-disabled", text: "Request issue modification") + expect(page).to have_content(COPY::REMANDS_NOT_EDITABLE) + end + + it "should disable the edit issues button" do + User.authenticate!(user: admin_user) + visit dispositions_url + + expect(page).to have_css(".usa-button-disabled", text: "Edit Issues") + end + end end def enable_feature_flag_and_redirect_to_disposition From c2d73f55526d6549c414ef1ba2a486fd7b89395c Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:54:50 -0400 Subject: [PATCH 08/19] TYLERB/APPEALS-44922: Update Generate task report page UI to include Remand in conditions (#22356) * Initial commit. Changed the DecisionReviewType condition filter to a searchable dropdown and added the remands option to it as well. Updated the report page filter parsing to work with the searchable dropdown instead of checkboxes for the decision review type condition filter. * Updated the ReportPage jest test and the report page feature test to work with the decision review type filter as a searchable dropdown instead of checkboxes. --- .../Conditions/DecisionReviewType.jsx | 69 ++++++++----------- client/app/nonComp/pages/ReportPage.jsx | 4 +- .../test/app/nonComp/pages/ReportPage.test.js | 30 ++++---- spec/feature/non_comp/report_page_spec.rb | 12 +--- 4 files changed, 50 insertions(+), 65 deletions(-) diff --git a/client/app/nonComp/components/ReportPage/Conditions/DecisionReviewType.jsx b/client/app/nonComp/components/ReportPage/Conditions/DecisionReviewType.jsx index 0cf4879d68c..85ba8461da3 100644 --- a/client/app/nonComp/components/ReportPage/Conditions/DecisionReviewType.jsx +++ b/client/app/nonComp/components/ReportPage/Conditions/DecisionReviewType.jsx @@ -1,69 +1,58 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useFormContext } from 'react-hook-form'; -import Checkbox from 'app/components/Checkbox'; +import { Controller, useFormContext } from 'react-hook-form'; import * as yup from 'yup'; import { get } from 'lodash'; +import SearchableDropdown from 'app/components/SearchableDropdown'; import { AT_LEAST_ONE_OPTION } from 'constants/REPORT_PAGE_VALIDATION_ERRORS'; -const CHECKBOX_OPTIONS = [ +const DROPDOWN_OPTIONS = [ { label: 'Higher-Level Reviews', - name: 'HigherLevelReview' + value: 'HigherLevelReview' }, { label: 'Supplemental Claims', - name: 'SupplementalClaim' + value: 'SupplementalClaim' + }, + { + label: 'Remands', + value: 'Remand' } ]; export const decisionReviewTypeSchema = yup.object({ - HigherLevelReview: yup.boolean(), - SupplementalClaim: yup.boolean(), -}).test('at-least-one-true', AT_LEAST_ONE_OPTION, (obj) => { - const atLeastOneTrue = Object.values(obj).some((value) => value === true); - - if (!atLeastOneTrue) { - return false; - } - - return true; + decisionReviewTypes: yup.array().min(1, AT_LEAST_ONE_OPTION) }); -export const DecisionReviewType = ({ field, name, register }) => { +export const DecisionReviewType = ({ control, field, name }) => { const { errors } = useFormContext(); - const hasFormErrors = get(errors, name); - - const classNames = hasFormErrors ? - 'decisionReviewTypeContainer decisionReviewTypeContainerError' : - 'decisionReviewTypeContainer'; - - const errorMessage = get(errors, name)?.options?.message; + const nameDecisionReviewTypes = `${name}.options.decisionReviewTypes`; return ( -
- {hasFormErrors ? -
{errorMessage}
: - null - } -
- {CHECKBOX_OPTIONS.map((checkbox) => ( - + ( + - ))} -
+ )} + />
); }; DecisionReviewType.propTypes = { + control: PropTypes.object, field: PropTypes.object, name: PropTypes.string, - register: PropTypes.func }; diff --git a/client/app/nonComp/pages/ReportPage.jsx b/client/app/nonComp/pages/ReportPage.jsx index 3e3a7ba89a8..84417cc4335 100644 --- a/client/app/nonComp/pages/ReportPage.jsx +++ b/client/app/nonComp/pages/ReportPage.jsx @@ -247,14 +247,12 @@ const ReportPage = ({ history }) => { let formattedOptions; switch (condition) { - case 'decisionReviewType': - formattedOptions = Object.keys(options).filter((key) => options[key]); - break; // Multi select conditions case 'personnel': case 'facility': case 'issueDisposition': case 'issueType': + case 'decisionReviewType': formattedOptions = Object.values(options)[0].map((item) => item.value); break; // Else it is probably already an object, so it just pass the existing options diff --git a/client/test/app/nonComp/pages/ReportPage.test.js b/client/test/app/nonComp/pages/ReportPage.test.js index 502d9832392..2df071c4a73 100644 --- a/client/test/app/nonComp/pages/ReportPage.test.js +++ b/client/test/app/nonComp/pages/ReportPage.test.js @@ -195,28 +195,34 @@ describe('ReportPage', () => { it('shows the correct checkbox fields', async () => { await navigateToConditionInput('Decision Review Type'); + const dropdown = screen.getByLabelText('Decision Review Type'); + await selectEvent.select(dropdown, ['Higher-Level Reviews']); expect(screen.getByText('Higher-Level Reviews')).toBeInTheDocument(); + + await selectEvent.select(dropdown, ['Supplemental Claims']); expect(screen.getByText('Supplemental Claims')).toBeInTheDocument(); + + await selectEvent.select(dropdown, ['Remands']); + expect(screen.getByText('Higher-Level Reviews')).toBeInTheDocument(); + expect(screen.getByText('Supplemental Claims')).toBeInTheDocument(); + expect(screen.getByText('Remands')).toBeInTheDocument(); }); - it('clicking the checkbox should toggle the checked status', async () => { + it('renders an error if no selection is made', async () => { await navigateToConditionInput('Decision Review Type'); - const checkbox = screen.getByLabelText('Higher-Level Reviews'); - - await userEvent.click(checkbox); - expect(checkbox.checked).toEqual(true); + const generateTaskReport = screen.getByRole('button', { name: 'Generate task report' }); - await userEvent.click(checkbox); - expect(checkbox.checked).toEqual(false); + expect(generateTaskReport).not.toHaveClass('usa-button-disabled'); - }); + // Wait for the validation text to appear before making assertions + await fireEvent.click(generateTaskReport); + await waitFor(() => { + const validationText = screen.getByText('Please select at least one option'); - it('should render an error if no checkbox is checked', async () => { - await navigateToConditionInput('Decision Review Type'); - expect(screen.getByText('Higher-Level Reviews')).toBeInTheDocument(); - await checkForValidationText('Please select at least one option'); + expect(validationText).toBeInTheDocument(); + }); }); }); diff --git a/spec/feature/non_comp/report_page_spec.rb b/spec/feature/non_comp/report_page_spec.rb index 6cf2c458097..24169074898 100644 --- a/spec/feature/non_comp/report_page_spec.rb +++ b/spec/feature/non_comp/report_page_spec.rb @@ -56,19 +56,11 @@ expect(page).to have_button("Generate task report", disabled: false) click_button "Generate task report" - # This might happen too fast for capybara - # expect(page).to have_button("Generate task report", disabled: true) - # expect(page).to have_content("Generating CSV...") - # Check the csv to make sure it returns the filter row, the column header row, and all 15 event rows csv_file = change_history_csv_file number_of_rows = CSV.read(csv_file).length expect(number_of_rows).to eq(17) - # CSV.foreach(csv_file) do |row| - # puts "Row: #{row}" - # end - # Add in some specific event filters now fill_in_specific_event_filters(["Added issue", "Completed disposition"]) @@ -306,7 +298,7 @@ def add_personnel_condition_with_values(values) def add_decision_review_condition_with_values(values) add_condition("Decision Review Type") - fill_in_decision_review_type(values) + fill_in_multi_select_condition(values, "Decision Review Type", ".decision-review-types") end def add_days_waiting_with_values(time_range, num_days, end_days = nil) @@ -321,7 +313,7 @@ def add_issue_disposition_with_values(values) def add_issue_type_with_values(values) add_condition("Issue Type") - fill_in_multi_select_condition(values, "Issue Type", "issue-types") + fill_in_multi_select_condition(values, "Issue Type", ".issue-types") end def clear_filters From da11e1cd817dd62f16255999a98bdd8ba3760cef Mon Sep 17 00:00:00 2001 From: Sean Craig <110493538+seancva@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:05:55 -0500 Subject: [PATCH 09/19] SeanC/APPEALS-42317 | Migrate existing Supplemental Claim Remands to Remand records via script (#22359) * initial verison of update script * remanded migration file * updated sc spec with type * schema * fixed a few issues in the migration * small fix to migration file * moved the type column check into a seperate it block * fixed wrong column in migration file * created a new migration for creating the view * updated the backfill migration file --------- Co-authored-by: Brandon Lee Dorner --- ...ackfill_supplemental_claims_type_column.rb | 20 +++++++++++++++++++ .../20240805154526_create_remands_view.rb | 20 +++++++++++++++++++ db/schema.rb | 2 +- spec/models/supplemental_claim_spec.rb | 16 +++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb create mode 100644 db/migrate/20240805154526_create_remands_view.rb diff --git a/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb b/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb new file mode 100644 index 00000000000..75fdeaade80 --- /dev/null +++ b/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb @@ -0,0 +1,20 @@ +class BackfillSupplementalClaimsTypeColumn < Caseflow::Migration + disable_ddl_transaction! + + def up + SupplementalClaim.in_batches do |batch| + batch.update_all( + <<-SQL + type = CASE + WHEN decision_review_remanded_id IS NOT NULL AND decision_review_remanded_type = 'Appeal' THEN 'Remand' + ELSE 'SupplementalClaim' + END + SQL + ) + end + end + + def down + SupplementalClaim.in_batches.update_all type: 'SupplementalClaim' + end +end diff --git a/db/migrate/20240805154526_create_remands_view.rb b/db/migrate/20240805154526_create_remands_view.rb new file mode 100644 index 00000000000..b01cc5c93f4 --- /dev/null +++ b/db/migrate/20240805154526_create_remands_view.rb @@ -0,0 +1,20 @@ +class CreateRemandsView < Caseflow::Migration + def up + safety_assured do + execute <<-SQL + CREATE VIEW remands AS + SELECT * + FROM supplemental_claims + WHERE type = 'Remand'; + SQL + end + end + + def down + safety_assured do + execute <<-SQL + DROP VIEW remands; + SQL + end + end +end diff --git a/db/schema.rb b/db/schema.rb index dccc1be67f0..bb0c89c0c40 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_07_16_143816) do +ActiveRecord::Schema.define(version: 2024_08_05_154526) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/models/supplemental_claim_spec.rb b/spec/models/supplemental_claim_spec.rb index bb2f1a80bd5..702e7d57197 100644 --- a/spec/models/supplemental_claim_spec.rb +++ b/spec/models/supplemental_claim_spec.rb @@ -25,6 +25,17 @@ ) end + let(:remand) do + Remand.new( + veteran_file_number: veteran_file_number, + receipt_date: receipt_date, + benefit_type: benefit_type, + legacy_opt_in_approved: legacy_opt_in_approved, + veteran_is_not_claimant: veteran_is_not_claimant, + decision_review_remanded: decision_review_remanded + ) + end + let!(:intake) do create(:intake, user: current_user, detail: supplemental_claim, veteran_file_number: veteran_file_number) end @@ -44,6 +55,11 @@ let(:legacy_opt_in_approved) { false } let(:receipt_date) { 1.day.ago } + it "sets the type column correctly" do + expect(supplemental_claim.type).to eq(SupplementalClaim.name) + expect(remand.type).to eq(Remand.name) + end + it "is valid" do is_expected.to be true end From c32a83db161cc3ff4f83a5cc28f2a085d1894e65 Mon Sep 17 00:00:00 2001 From: Prajwal Amatya <122557351+pamatyatake2@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:15:56 -0600 Subject: [PATCH 10/19] =?UTF-8?q?adding=20logic=20to=20bring=20in=20issue-?= =?UTF-8?q?modification=20request=20and=20updating=20the=20=E2=80=A6=20(#2?= =?UTF-8?q?2238)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adding logic to bring in issue-modification request and updating the csv logic * refactor logic to get pending status and null to request addition * pending status filter work fixing * adding withdrawal date and adding logic to display request issue components * getting edit of request rows and fixing cancelled data * fixing linter and fixing the query to make test happy * fixing specs failures * removing byebug * removing frontend changes, adding test, fixing the query to replace higherlevel review,adding new field in serializer * making code climate happy by doing some minor refactor * fixing the serializer spec failure * After merging APPEALS-45335, filter for Requested issues were broken, so refactored it and removed the unnecessary methood that i created * fixing code climate and spec was failing because of hard coded date * refactor the edit of request logic to take from version, refactored business line query to remove coalesce and fix test * fixing codeclimate, remove unnecessary field * refactor to make first comment appear first in edit of request * fixing linter issue, spec and refactor to fix the bug * fixing pending status filter issue and adding test for business line * fixing broken spec failure * adding user facilty * fixing the lead and lag function to prevent entry of pending status multiple times * fixing an issue in which in progress event was not being made * adding decision reason that was missed and fixing the event logic one more time * fixing the multiple issue modification request after decision has been made * refactor inprogress logic for approved data * fixing spec failure * fixing the first version logic to be more simpler and fixing name and adding spec * fixing more inprogess event * removing the disabled complexity and taking out the boolean condition to a separate method --- app/models/organizations/business_line.rb | 262 ++++++++++++---- .../change_history_event_serializer.rb | 18 ++ .../change_history_filter_parser.rb | 16 +- .../change_history_reporter.rb | 7 +- .../claim_history_event.rb | 291 +++++++++++++++++- .../claim_history_service.rb | 50 ++- .../decision_reviews_controller_spec.rb | 49 ++- spec/factories/issue_modification_request.rb | 40 ++- spec/models/business_line_spec.rb | 110 ++++++- .../change_history_event_serializer_spec.rb | 62 +++- .../change_history_reporter_spec.rb | 121 +++++++- .../claim_history_event_spec.rb | 228 +++++++++++++- .../claim_history_service_spec.rb | 124 +++++++- 13 files changed, 1243 insertions(+), 135 deletions(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 5b43066bce2..9afed5d3d3f 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -222,8 +222,20 @@ def change_history_rows AND tasks.assigned_to_id = '#{parent.id.to_i}' GROUP BY versions.item_id, versions.item_type - ) - SELECT tasks.id AS task_id, tasks.status AS task_status, request_issues.id AS request_issue_id, + ), imr_version_agg AS (SELECT + versions.item_id, + versions.item_type, + ARRAY_AGG(versions.object_changes ORDER BY versions.id) AS object_changes_array + FROM + versions + INNER JOIN issue_modification_requests ON issue_modification_requests.id = versions.item_id + WHERE versions.item_type = 'IssueModificationRequest' + GROUP BY + versions.item_id, versions.item_type) + SELECT tasks.id AS task_id, + check_imr_current_status.is_assigned_present, + tasks.status AS task_status, + request_issues.id AS request_issue_id, request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description, request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id, request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at, @@ -246,7 +258,36 @@ def change_history_rows NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '), NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '), bgs_attorneys.name - ) AS claimant_name + ) AS claimant_name, + imr.id AS issue_modification_request_id, + imr.nonrating_issue_category AS requested_issue_type, + imr.nonrating_issue_description As requested_issue_description, + imr.remove_original_issue, + imr.request_reason AS modification_request_reason, + imr.decision_date AS requested_decision_date, + imr.request_type AS request_type, + imr.status AS issue_modification_request_status, + imr.decision_reason AS decision_reason, + imr.decider_id decider_id, + imr.requestor_id as requestor_id, + imr.decided_at AS decided_at, + imr.created_at AS issue_modification_request_created_at, + imr.updated_at AS issue_modification_request_updated_at, + imr.edited_at AS issue_modification_request_edited_at, + imr.withdrawal_date AS issue_modification_request_withdrawal_date, + imr.decision_review_id AS decision_review_id, + imr.decision_review_type AS decision_review_type, + requestor.full_name AS requestor, + requestor.station_id AS requestor_station_id, + requestor.css_id AS requestor_css_id, + decider.full_name AS decider, + decider.station_id AS decider_station_id, + decider.css_id AS decider_css_id, + itv.object_changes_array AS imr_versions, + lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, + lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, + itv.object_changes_array[1] as first_static_version, + MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type AND request_issues.decision_review_id = tasks.appeal_id @@ -254,6 +295,17 @@ def change_history_rows AND tasks.appeal_id = higher_level_reviews.id INNER JOIN intakes ON tasks.appeal_type = intakes.detail_type AND intakes.detail_id = tasks.appeal_id + LEFT JOIN LATERAL ( + SELECT * FROM issue_modification_requests imr + WHERE imr.decision_review_id = tasks.appeal_id + AND imr.decision_review_type = 'HigherLevelReview' + AND imr.request_issue_id = request_issues.id + UNION ALL + SELECT * FROM issue_modification_requests imr_add + where imr_add.decision_review_id = tasks.appeal_id + AND imr_add.decision_review_type = 'HigherLevelReview' + AND imr_add.request_type = 'addition' + ) imr on true LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type AND request_issues_updates.review_id = tasks.appeal_id LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id @@ -270,62 +322,132 @@ def change_history_rows LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id + LEFT JOIN users requestor ON imr.requestor_id = requestor.id + LEFT JOIN users decider ON imr.decider_id = decider.id + LEFT JOIN imr_version_agg itv ON itv.item_type = 'IssueModificationRequest' AND itv.item_id = imr.id + LEFT JOIN LATERAL ( + SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM issue_modification_requests imr + WHERE imr.decision_review_id = request_issues.decision_review_id + AND imr.decision_review_type = 'HigherLevelReview' + AND imr.status = 'assigned' + ) THEN true + ELSE false + END AS is_assigned_present + ) check_imr_current_status on true WHERE tasks.type = 'DecisionReviewTask' AND tasks.assigned_to_type = 'Organization' AND tasks.assigned_to_id = '#{parent.id.to_i}' #{sanitized_filters} - UNION ALL - SELECT tasks.id AS task_id, tasks.status AS task_status, request_issues.id AS request_issue_id, - request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description, - request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id, - request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at, - intakes.completed_at AS intake_completed_at, update_users.full_name AS update_user_name, tasks.created_at AS task_created_at, - intake_users.full_name AS intake_user_name, update_users.station_id AS update_user_station_id, tasks.closed_at AS task_closed_at, - intake_users.station_id AS intake_user_station_id, decision_issues.created_at AS decision_created_at, - COALESCE(decision_users.station_id, decision_users_completed_by.station_id) AS decision_user_station_id, - COALESCE(decision_users.full_name, decision_users_completed_by.full_name) AS decision_user_name, - COALESCE(decision_users.css_id, decision_users_completed_by.css_id) AS decision_user_css_id, - intake_users.css_id AS intake_user_css_id, update_users.css_id AS update_user_css_id, - request_issues_updates.before_request_issue_ids, request_issues_updates.after_request_issue_ids, - request_issues_updates.withdrawn_request_issue_ids, request_issues_updates.edited_request_issue_ids, - decision_issues.caseflow_decision_date, request_issues.decision_date_added_at, - tasks.appeal_type, tasks.appeal_id, request_issues.nonrating_issue_category, request_issues.nonrating_issue_description, - request_issues.decision_date, decision_issues.disposition, tasks.assigned_at, request_issues.unidentified_issue_text, - request_decision_issues.decision_issue_id, request_issues.closed_at AS request_issue_closed_at, - tv.object_changes_array AS task_versions, (CURRENT_TIMESTAMP::date - tasks.assigned_at::date) AS days_waiting, - COALESCE(intakes.veteran_file_number, supplemental_claims.veteran_file_number) AS veteran_file_number, - COALESCE( - NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '), - NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '), - bgs_attorneys.name - ) AS claimant_name - FROM tasks - INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type - AND request_issues.decision_review_id = tasks.appeal_id - INNER JOIN supplemental_claims ON tasks.appeal_type = 'SupplementalClaim' - AND tasks.appeal_id = supplemental_claims.id - LEFT JOIN intakes ON tasks.appeal_type = intakes.detail_type - AND intakes.detail_id = tasks.appeal_id - LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type - AND request_issues_updates.review_id = tasks.appeal_id - LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id - LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id - AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id - LEFT JOIN claimants ON claimants.decision_review_id = tasks.appeal_id - AND claimants.decision_review_type = tasks.appeal_type - LEFT JOIN versions_agg tv ON tv.item_type = 'Task' AND tv.item_id = tasks.id - LEFT JOIN people ON claimants.participant_id = people.participant_id - LEFT JOIN bgs_attorneys ON claimants.participant_id = bgs_attorneys.participant_id - LEFT JOIN unrecognized_appellants ON claimants.id = unrecognized_appellants.claimant_id - LEFT JOIN unrecognized_party_details ON unrecognized_appellants.unrecognized_party_detail_id = unrecognized_party_details.id - LEFT JOIN users intake_users ON intakes.user_id = intake_users.id - LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id - LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int - LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id - WHERE tasks.type = 'DecisionReviewTask' - AND tasks.assigned_to_type = 'Organization' - AND tasks.assigned_to_id = '#{parent.id.to_i}' - #{sanitized_filters} + UNION ALL + SELECT tasks.id AS task_id, check_imr_current_status.is_assigned_present, tasks.status AS task_status, request_issues.id AS request_issue_id, + request_issues_updates.created_at AS request_issue_update_time, decision_issues.description AS decision_description, + request_issues.benefit_type AS request_issue_benefit_type, request_issues_updates.id AS request_issue_update_id, + request_issues.created_at AS request_issue_created_at, request_decision_issues.created_at AS request_decision_created_at, + intakes.completed_at AS intake_completed_at, update_users.full_name AS update_user_name, tasks.created_at AS task_created_at, + intake_users.full_name AS intake_user_name, update_users.station_id AS update_user_station_id, tasks.closed_at AS task_closed_at, + intake_users.station_id AS intake_user_station_id, decision_issues.created_at AS decision_created_at, + COALESCE(decision_users.station_id, decision_users_completed_by.station_id) AS decision_user_station_id, + COALESCE(decision_users.full_name, decision_users_completed_by.full_name) AS decision_user_name, + COALESCE(decision_users.css_id, decision_users_completed_by.css_id) AS decision_user_css_id, + intake_users.css_id AS intake_user_css_id, update_users.css_id AS update_user_css_id, + request_issues_updates.before_request_issue_ids, request_issues_updates.after_request_issue_ids, + request_issues_updates.withdrawn_request_issue_ids, request_issues_updates.edited_request_issue_ids, + decision_issues.caseflow_decision_date, request_issues.decision_date_added_at, + tasks.appeal_type, tasks.appeal_id, request_issues.nonrating_issue_category, request_issues.nonrating_issue_description, + request_issues.decision_date, decision_issues.disposition, tasks.assigned_at, request_issues.unidentified_issue_text, + request_decision_issues.decision_issue_id, request_issues.closed_at AS request_issue_closed_at, + tv.object_changes_array AS task_versions, (CURRENT_TIMESTAMP::date - tasks.assigned_at::date) AS days_waiting, + COALESCE(intakes.veteran_file_number, supplemental_claims.veteran_file_number) AS veteran_file_number, + COALESCE( + NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '), + NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '), + bgs_attorneys.name + ) AS claimant_name, + imr.id AS issue_modification_request_id, + imr.nonrating_issue_category AS requested_issue_type, + imr.nonrating_issue_description As requested_issue_description, + imr.remove_original_issue, + imr.request_reason AS modification_request_reason, + imr.decision_date AS requested_decision_date, + imr.request_type AS request_type, + imr.status AS issue_modification_request_status, + imr.decision_reason AS decision_reason, + imr.decider_id AS decider_id, + imr.requestor_id AS requestor_id, + imr.decided_at AS decided_at, + imr.created_at AS issue_modification_request_created_at, + imr.updated_at AS issue_modification_request_updated_at, + imr.edited_at AS issue_modification_request_edited_at, + imr.withdrawal_date AS issue_modification_request_withdrawal_date, + imr.decision_review_id AS decision_review_id, + imr.decision_review_type AS decision_review_type, + requestor.full_name AS requestor, + requestor.station_id AS requestor_station_id, + requestor.css_id AS requestor_css_id, + decider.full_name AS decider, + decider.station_id AS decider_station_id, + decider.css_id AS decider_css_id, + itv.object_changes_array AS imr_versions, + lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, + lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, + itv.object_changes_array[1] as first_static_version, + MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date + FROM tasks + INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type + AND request_issues.decision_review_id = tasks.appeal_id + INNER JOIN supplemental_claims ON tasks.appeal_type = 'SupplementalClaim' + AND tasks.appeal_id = supplemental_claims.id + LEFT JOIN intakes ON tasks.appeal_type = intakes.detail_type + AND intakes.detail_id = tasks.appeal_id + LEFT JOIN LATERAL ( + SELECT * FROM issue_modification_requests imr + WHERE imr.decision_review_id = tasks.appeal_id + AND imr.decision_review_type = 'SupplementalClaim' + AND imr.request_issue_id = request_issues.id + UNION ALL + SELECT *FROM issue_modification_requests imr_add + where imr_add.decision_review_id = tasks.appeal_id + AND imr_add.decision_review_type = 'SupplementalClaim' + AND imr_add.request_type = 'addition' + ) imr on true + LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type + AND request_issues_updates.review_id = tasks.appeal_id + LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id + LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id + AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id + LEFT JOIN claimants ON claimants.decision_review_id = tasks.appeal_id + AND claimants.decision_review_type = tasks.appeal_type + LEFT JOIN versions_agg tv ON tv.item_type = 'Task' AND tv.item_id = tasks.id + LEFT JOIN people ON claimants.participant_id = people.participant_id + LEFT JOIN bgs_attorneys ON claimants.participant_id = bgs_attorneys.participant_id + LEFT JOIN unrecognized_appellants ON claimants.id = unrecognized_appellants.claimant_id + LEFT JOIN unrecognized_party_details ON unrecognized_appellants.unrecognized_party_detail_id = unrecognized_party_details.id + LEFT JOIN users intake_users ON intakes.user_id = intake_users.id + LEFT JOIN users update_users ON request_issues_updates.user_id = update_users.id + LEFT JOIN users decision_users ON decision_users.id = tv.version_closed_by_id::int + LEFT JOIN users decision_users_completed_by ON decision_users_completed_by.id = tasks.completed_by_id + LEFT JOIN users requestor ON imr.requestor_id = requestor.id + LEFT JOIN users decider ON imr.decider_id = decider.id + LEFT JOIN imr_version_agg itv ON itv.item_type = 'IssueModificationRequest' AND itv.item_id = imr.id + LEFT JOIN LATERAL ( + SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM issue_modification_requests imr + WHERE imr.decision_review_id = request_issues.decision_review_id + AND imr.decision_review_type = 'SupplementalClaim' + AND imr.status = 'assigned' + ) THEN true + ELSE false + END AS is_assigned_present + ) check_imr_current_status on true + WHERE tasks.type = 'DecisionReviewTask' + AND tasks.assigned_to_type = 'Organization' + AND tasks.assigned_to_id = '#{parent.id.to_i}' + #{sanitized_filters} SQL ActiveRecord::Base.transaction do @@ -357,12 +479,40 @@ def change_history_sql_filter_array def task_status_filter if query_params[:task_status].present? - " AND #{where_clause_from_array(Task, :status, query_params[:task_status]).to_sql}" + task_specific_status_filter else " AND tasks.status IN ('assigned', 'in_progress', 'on_hold', 'completed', 'cancelled') " end end + def task_specific_status_filter + if query_params[:task_status].include?("pending") + task_status_pending_filter + else + task_status_without_pending_filter + end + end + + def task_status_pending_filter + <<-SQL + AND ( + (imr.id IS NOT NULL AND imr.status = 'assigned') + OR #{where_clause_from_array(Task, :status, query_params[:task_status].uniq).to_sql} + ) + SQL + end + + def task_status_without_pending_filter + <<-SQL + AND NOT EXISTS( + SELECT decision_review_id FROM issue_modification_requests WHERE + issue_modification_requests.status = 'assigned' + AND issue_modification_requests.decision_review_id = tasks.appeal_id + AND tasks.appeal_type = issue_modification_requests.decision_review_type) + AND #{where_clause_from_array(Task, :status, query_params[:task_status].uniq).to_sql} + SQL + end + def claim_type_filter if query_params[:claim_type].present? " AND #{where_clause_from_array(Task, :appeal_type, query_params[:claim_type]).to_sql}" diff --git a/app/services/claim_change_history/change_history_event_serializer.rb b/app/services/claim_change_history/change_history_event_serializer.rb index 5b8bca631ce..a8398468a39 100644 --- a/app/services/claim_change_history/change_history_event_serializer.rb +++ b/app/services/claim_change_history/change_history_event_serializer.rb @@ -24,4 +24,22 @@ class ChangeHistoryEventSerializer withdrawalRequestDate: object.withdrawal_request_date } end + + attribute :modificationRequestDetails do |object| + { + requestType: object.request_type, + benefitType: object.benefit_type, + newIssueType: object.new_issue_type, + newIssueDescription: object.new_issue_description, + newDecisionDate: object.new_decision_date, + modificationRequestReason: object.modification_request_reason, + issueModificationRequestWithdrawalDate: object.issue_modification_request_withdrawal_date, + removeOriginalIssue: object.remove_original_issue, + issueModificationRequestStatus: object.issue_modification_request_status, + requestor: object.requestor, + decider: object.decider, + decidedAtDate: object.decided_at_date, + decisionReason: object.decision_reason + } + end end diff --git a/app/services/claim_change_history/change_history_filter_parser.rb b/app/services/claim_change_history/change_history_filter_parser.rb index 2e423e43e13..4fbcd6b2826 100644 --- a/app/services/claim_change_history/change_history_filter_parser.rb +++ b/app/services/claim_change_history/change_history_filter_parser.rb @@ -25,6 +25,7 @@ def parse_filters private + # rubocop:disable Metrics/MethodLength def events_filter_helper event_mapping = { "added_decision_date" => :added_decision_date, @@ -34,21 +35,32 @@ def events_filter_helper "claim_closed" => [:completed, :cancelled], "claim_status_incomplete" => :incomplete, "claim_status_inprogress" => :in_progress, + "claim_status_pending" => :pending, "completed_disposition" => :completed_disposition, "removed_issue" => :removed_issue, "withdrew_issue" => :withdrew_issue, - "claim_cancelled" => :cancelled + "claim_cancelled" => :cancelled, + "requested_issue_modification" => :modification, + "requested_issue_addition" => :addition, + "requested_issue_removal" => :removal, + "requested_issue_withdrawal" => :withdrawal, + "approval_of_request" => :request_approved, + "rejection_of_request" => :request_denied, + "cancellation_of_request" => :request_cancelled, + "edit_of_request" => :request_edited } filter_params[:events]&.values&.map { |event_type| event_mapping[event_type] }&.flatten end + # rubocop:enable Metrics/MethodLength def task_status_filter_helper status_mapping = { "incomplete" => "on_hold", "in_progress" => %w[assigned in_progress], "completed" => "completed", - "cancelled" => "cancelled" + "cancelled" => "cancelled", + "pending" => "pending" } filter_params[:statuses]&.values&.map { |task_status| status_mapping[task_status] }&.flatten diff --git a/app/services/claim_change_history/change_history_reporter.rb b/app/services/claim_change_history/change_history_reporter.rb index 68cf043c19c..8025b63d60e 100644 --- a/app/services/claim_change_history/change_history_reporter.rb +++ b/app/services/claim_change_history/change_history_reporter.rb @@ -18,7 +18,12 @@ class ChangeHistoryReporter Edit\ Action Issue\ Type Issue\ Description - Prior\ Decision\ Date + Decision\ Date + New\ Issue\ Type + New\ Issue\ Description + New\ Decision\ Date + Request\ Reason + Reason\ for\ Rejection Disposition Disposition\ Description Disposition\ Date diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb index 0cae36dc208..3c60a227293 100644 --- a/app/services/claim_change_history/claim_history_event.rb +++ b/app/services/claim_change_history/claim_history_event.rb @@ -15,7 +15,10 @@ class ClaimHistoryEvent :benefit_type, :issue_type, :issue_description, :decision_date, :disposition, :decision_description, :withdrawal_request_date, :task_status, :disposition_date, :intake_completed_date, :event_user_name, - :event_user_css_id + :event_user_css_id, :new_issue_type, :new_issue_description, :new_decision_date, + :modification_request_reason, :request_type, :decision_reason, :decided_at_date, + :issue_modification_request_withdrawal_date, :requestor, + :decider, :remove_original_issue, :issue_modification_request_status EVENT_TYPES = [ :completed_disposition, @@ -28,7 +31,16 @@ class ClaimHistoryEvent :in_progress, :completed, :incomplete, - :cancelled + :cancelled, + :pending, + :modification, + :addition, + :withdrawal, + :removal, + :request_approved, + :request_denied, + :request_cancelled, + :request_edited ].freeze ISSUE_EVENTS = [ @@ -47,10 +59,29 @@ class ClaimHistoryEvent :added_decision_date ].freeze - STATUS_EVENTS = [:in_progress, :incomplete, :completed, :claim_creation, :cancelled].freeze + STATUS_EVENTS = [ + :completed, + :claim_creation, + :cancelled, + :in_progress, + :incomplete, + :pending + ].freeze + + REQUEST_ISSUE_MODIFICATION_EVENTS = [ + :modification, + :addition, + :withdrawal, + :removal, + :request_approved, + :request_denied, + :request_cancelled, + :request_edited + ].freeze REQUEST_ISSUE_TIME_WINDOW = 15 STATUS_EVENT_TIME_WINDOW = 2 + ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW = 60 class << self def from_change_data(event_type, change_data) @@ -73,11 +104,127 @@ def create_claim_creation_event(change_data) from_change_data(:claim_creation, change_data.merge(intake_event_hash(change_data))) end + def create_issue_modification_request_event(change_data) + issue_modification_events = [] + request_type = change_data["request_type"] + event_hash = request_issue_modification_event_hash(change_data) + + if change_data["first_static_version"].present? + first_version = parse_versions("{#{change_data['first_static_version']}}") + event_hash.merge!(update_event_hash_data_from_version(first_version[0], 0)) + end + + if request_type == "addition" + change_data = issue_attributes_for_request_type_addition(change_data) + end + + issue_modification_events.push from_change_data(request_type.to_sym, change_data.merge(event_hash)) + end + + def create_edited_request_issue_events(change_data) + edited_events = [] + imr_versions = parse_versions(change_data["imr_versions"]) + + if imr_versions.present? + *rest_of_versions, last_version = imr_versions + if last_version["status"].present? + edited_events.push(*create_last_version_events(change_data, last_version)) + else + rest_of_versions.push(last_version) + end + edited_events.push(*create_event_from_rest_of_versions(change_data, rest_of_versions)) + else + create_pending_status_events(change_data, change_data["issue_modification_request_updated_at"]) + end + edited_events + end + + def create_event_from_rest_of_versions(change_data, edited_versions) + edit_of_request_events = [] + event_type = :request_edited + event_date_hash = {} + + edited_versions.map do |version| + event_date_hash = { + "event_date" => version["updated_at"][1], + "event_user_name" => change_data["requestor"], + "user_facility" => change_data["requestor_station_id"] + } + + event_date_hash.merge!(update_event_hash_data_from_version(version, 1)) + edit_of_request_events.push(*from_change_data(event_type, change_data.merge(event_date_hash))) + end + edit_of_request_events + end + + def create_last_version_events(change_data, last_version) + edited_events = [] + + last_version["status"].map.with_index do |status, index| + if status == "assigned" + edited_events.push(*create_pending_status_events(change_data, last_version["updated_at"][index])) + else + edited_events.push(*create_request_issue_decision_events( + change_data, last_version["updated_at"][index], status + )) + end + end + edited_events + end + + def create_request_issue_decision_events(change_data, event_date, event) + events = [] + event_user = change_data["decider"] || change_data["requestor"] + + decision_event_hash = pending_system_hash + .merge("event_date" => event_date, + "event_user_name" => event_user, + "user_facility" => decider_user_facility(change_data)) + + change_data = issue_attributes_for_request_type_addition(change_data) if change_data["request_type"] == "addition" + + request_event_type = "request_#{event}" + events.push from_change_data(request_event_type.to_sym, change_data.merge(decision_event_hash)) + + events.push create_in_progress_status_event(change_data) + events + end + + def create_in_progress_status_event(change_data) + in_progress_system_hash_events = pending_system_hash + .merge("event_date" => (change_data["decided_at"] || + change_data["issue_modification_request_updated_at"])) + + if should_create_imr_in_progress_status_event?(change_data) + from_change_data(:in_progress, change_data.merge(in_progress_system_hash_events)) + end + end + + def should_create_imr_in_progress_status_event?(change_data) + (imr_last_decided_row?(change_data) && + (!imr_decided_in_same_transaction?(change_data) || change_data["previous_imr_decided_at"].nil?)) || + (change_data["previous_imr_created_at"].nil? || + !imr_created_in_same_transaction?(change_data) && + %w[cancelled denied approved].include?(change_data["issue_modification_request_status"])) + end + + def create_pending_status_events(change_data, event_date) + pending_system_hash_events = pending_system_hash + .merge("event_date" => event_date) + + imr_created_in_same_transaction = imr_created_in_same_transaction?(change_data) + # if two imr's are of different transaction and if decision has already been made then we + # want to put pending status since it went back to pending status before it was approved/cancelled or denied. + if change_data["previous_imr_created_at"].nil? || + !imr_created_in_same_transaction + from_change_data(:pending, change_data.merge(pending_system_hash_events)) + end + end + # rubocop:disable Metrics/MethodLength def create_status_events(change_data) status_events = [] - versions = parse_versions(change_data) - + versions = parse_versions(change_data["task_versions"]) hookless_cancelled_events = handle_hookless_cancelled_status_events(versions, change_data) status_events.push(*hookless_cancelled_events) @@ -112,15 +259,12 @@ def create_status_events(change_data) end # rubocop:enable Metrics/MethodLength - def parse_versions(change_data) - versions = change_data["task_versions"] + def parse_versions(versions) if versions # Quite a bit faster but less safe. Should probably be fine since it's coming from the database # rubocop:disable Security/YAMLLoad versions[1..-2].split(",").map { |yaml| YAML.load(yaml.gsub(/^"|"$/, "")) } - # versions[1..-2].split(",").map { |yaml| YAML.safe_load(yaml.gsub(/^"|"$/, ""), [Time]) } # rubocop:enable Security/YAMLLoad - end end @@ -144,10 +288,43 @@ def create_issue_events(change_data) issue_events end + def issue_attributes_for_request_type_addition(change_data) + # addition should not have issue_type that is pre-existing + issue_data = { + "nonrating_issue_category" => nil, + "nonrating_issue_description" => nil, + "decision_date" => nil + } + + change_data.merge(issue_data) + end + + def imr_created_in_same_transaction?(change_data) + timestamp_within_seconds?(change_data["issue_modification_request_created_at"], + change_data["previous_imr_created_at"] || + change_data["issue_modification_request_created_at"], + ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW) + end + + def imr_last_decided_row?(change_data) + change_data["imr_last_decided_date"] == (change_data["decided_at"] || + change_data["issue_modification_request_updated_at"]) + end + + def imr_decided_in_same_transaction?(change_data) + timestamp_within_seconds?(change_data["decided_at"], + change_data["previous_imr_decided_at"], + ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW) + end + def extract_issue_ids_from_change_data(change_data, key) (change_data[key] || "").scan(/\d+/).map(&:to_i) end + def decider_user_facility(change_data) + change_data["decider_station_id"] || change_data["requestor_station_id"] + end + def process_issue_ids(request_issue_ids, event_type, change_data) created_events = [] @@ -221,10 +398,27 @@ def task_status_to_event_type(task_status) "assigned" => :in_progress, "on_hold" => :incomplete, "completed" => :completed, - "cancelled" => :cancelled + "cancelled" => :cancelled, + "pending" => :pending }[task_status] end + def update_event_hash_data_from_version(version, index) + version_database_field_mapping = { + "nonrating_issue_category" => "requested_issue_type", + "nonrating_issue_description" => "requested_issue_description", + "remove_original_issue" => "remove_original_issue", + "request_reason" => "modification_request_reason", + "decision_date" => "requested_decision_date", + "decision_reason" => "decision_reason", + "withdrawal_date" => "issue_modification_request_withdrawal_date" + } + + version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data| + data[db_key] = version[version_key][index] unless version[version_key].nil? + end + end + def event_from_version(changes, index, change_data) # If there is no task status change in the set of papertrail changes, ignore the object if changes["status"] @@ -301,6 +495,22 @@ def retrieve_issue_update_data(change_data) end end + def request_issue_modification_event_hash(change_data) + { + "event_date" => change_data["issue_modification_request_created_at"], + "event_user_name" => change_data["decider"] || change_data["requestor"], + "user_facility" => change_data["decider_station_id"] || change_data["requestor_station_id"], + "event_user_css_id" => change_data["decider_css_id"] || change_data["requestor_css_id"] + } + end + + def pending_system_hash + { + "event_user_name" => "System", + "event_type" => "in_progress" + } + end + def timestamp_within_seconds?(first_date, second_date, time_in_seconds) return false unless first_date && second_date @@ -356,7 +566,7 @@ def to_csv_array [ veteran_file_number, claimant_name, task_url, readable_task_status, days_waiting, readable_claim_type, readable_facility_name, readable_user_name, readable_event_date, - readable_event_type, issue_or_status_information, disposition_information + readable_event_type, issue_or_status_information, issue_modification_request_information, disposition_information ] end @@ -371,7 +581,8 @@ def readable_task_status "in_progress" => "in progress", "on_hold" => "incomplete", "completed" => "completed", - "cancelled" => "cancelled" + "cancelled" => "cancelled", + "pending" => "pending" }[task_status] end @@ -398,6 +609,10 @@ def readable_decision_date format_date_string(decision_date) end + def readable_new_decision_date + format_date_string(new_decision_date) + end + def readable_disposition_date format_date_string(disposition_date) end @@ -408,10 +623,12 @@ def readable_facility_name [Constants::BGS_FACILITY_CODES[user_facility], " (", user_facility, ")"].join end + # rubocop:disable Metrics/MethodLength def readable_event_type { in_progress: "Claim status - In progress", incomplete: "Claim status - Incomplete", + pending: "Claim status - Pending", completed: "Claim closed", claim_creation: "Claim created", completed_disposition: "Completed disposition", @@ -420,9 +637,18 @@ def readable_event_type withdrew_issue: "Withdrew issue", removed_issue: "Removed issue", added_decision_date: "Added decision date", - cancelled: "Claim closed" + cancelled: "Claim closed", + addition: "Requested issue addition", + removal: "Requested issue removal", + modification: "Requested issue modification", + withdrawal: "Requested issue withdrawal", + request_approved: "Approval of request - issue #{request_type}", + request_denied: "Rejection of request - issue #{request_type}", + request_cancelled: "Cancellation of request", + request_edited: "Edit of request - issue #{request_type}" }[event_type] end + # rubocop:enable Metrics/MethodLength def issue_event? ISSUE_EVENTS.include?(event_type) @@ -440,6 +666,10 @@ def status_event? STATUS_EVENTS.include?(event_type) end + def event_has_modification_request? + REQUEST_ISSUE_MODIFICATION_EVENTS.include?(event_type) + end + private def set_attributes_from_change_history_data(new_event_type, change_data) @@ -451,11 +681,13 @@ def set_attributes_from_change_history_data(new_event_type, change_data) parse_task_attributes(change_data) parse_issue_attributes(change_data) parse_disposition_attributes(change_data) + parse_request_issue_modification_attributes(change_data) end + # :reek:FeatureEnvy def parse_task_attributes(change_data) @task_id = change_data["task_id"] - @task_status = change_data["task_status"] + @task_status = change_data["is_assigned_present"] ? "pending" : change_data["task_status"] @claim_type = change_data["appeal_type"] @assigned_at = change_data["assigned_at"] @days_waiting = change_data["days_waiting"] @@ -467,7 +699,7 @@ def parse_intake_attributes(change_data) end def parse_issue_attributes(change_data) - if issue_event? + if issue_event? || event_has_modification_request? @issue_type = change_data["nonrating_issue_category"] @issue_description = change_data["nonrating_issue_description"] || change_data["unidentified_issue_text"] @decision_date = change_data["decision_date"] @@ -491,6 +723,22 @@ def parse_event_attributes(change_data) @event_user_css_id = change_data["event_user_css_id"] end + def parse_request_issue_modification_attributes(change_data) + if event_has_modification_request? + @request_type = change_data["request_type"] + @new_issue_type = change_data["requested_issue_type"] + @new_issue_description = change_data["requested_issue_description"] + @new_decision_date = change_data["requested_decision_date"] + @modification_request_reason = change_data["modification_request_reason"] + @decision_reason = change_data["decision_reason"] + @decided_at_date = change_data["decided_at"] + @issue_modification_request_withdrawal_date = change_data["issue_modification_request_withdrawal_date"] + @remove_original_issue = change_data["remove_original_issue"] + @requestor = change_data["requestor"] + @decider = change_data["decider"] + end + end + ############ CSV and Serializer Helpers ############ def abbreviated_user_name(name_string) @@ -499,11 +747,19 @@ def abbreviated_user_name(name_string) end def issue_information - if issue_event? + if issue_event? || event_has_modification_request? [issue_type, issue_description, readable_decision_date] end end + def issue_modification_request_information + if event_has_modification_request? + [new_issue_type, new_issue_description, readable_new_decision_date, modification_request_reason, decision_reason] + else + [nil, nil, nil, nil, nil] + end + end + def disposition_information if disposition_event? [disposition, decision_description, readable_disposition_date] @@ -524,7 +780,8 @@ def status_description incomplete: "Claim cannot be processed until decision date is entered.", completed: "Claim closed.", claim_creation: "Claim created.", - cancelled: "Claim closed." + cancelled: "Claim cancelled.", + pending: "Claim cannot be processed until VHA admin reviews pending requests." }[event_type] end diff --git a/app/services/claim_change_history/claim_history_service.rb b/app/services/claim_change_history/claim_history_service.rb index c778ae4f01b..5402fa0c53d 100644 --- a/app/services/claim_change_history/claim_history_service.rb +++ b/app/services/claim_change_history/claim_history_service.rb @@ -4,7 +4,9 @@ class ClaimHistoryService attr_reader :business_line, :processed_task_ids, :processed_request_issue_ids, :processed_request_issue_update_ids, - :processed_decision_issue_ids, :events, :filters + :processed_decision_issue_ids, :processed_issue_modification_request_ids, + :processed_issue_modification_task_ids, + :events, :filters attr_writer :filters TIMING_RANGES = %w[ @@ -23,23 +25,23 @@ def initialize(business_line = VhaBusinessLine.singleton, filters = {}) @processed_request_issue_update_ids = Set.new @processed_decision_issue_ids = Set.new @processed_request_issue_ids = Set.new + @processed_issue_modification_request_ids = Set.new + @processed_issue_modification_task_ids = Set.new @events = [] end def build_events # Reset the instance attributes from the last time build_events was ran reset_processing_attributes - all_data = business_line.change_history_rows(@filters) - all_data.entries.each do |change_data| process_request_issue_update_events(change_data) process_request_issue_events(change_data) process_decision_issue_and_task_events(change_data) + process_request_issue_modification_events(change_data) # Don't process task events outside of decision issues unless there are no decision issues process_task_events(change_data) unless change_data["task_status"] == "completed" end - # Compact and sort in place to reduce garbage collection @events.compact! @events.sort_by! do |event| @@ -64,6 +66,8 @@ def reset_processing_attributes @processed_request_issue_update_ids.clear @processed_decision_issue_ids.clear @processed_request_issue_ids.clear + @processed_issue_modification_request_ids.clear + @processed_issue_modification_task_ids.clear @events.clear end @@ -98,7 +102,9 @@ def matches_filter(new_events) def process_event_filter(new_events) return new_events if @filters[:events].blank? - new_events.select { |event| event && ensure_array(@filters[:events]).include?(event.event_type) } + new_events.select do |event| + event && ensure_array(@filters[:events]).include?(event.event_type) + end end def process_issue_type_filter(new_events) @@ -205,7 +211,6 @@ def process_request_issue_update_events(change_data) def process_task_events(change_data) task_id = change_data["task_id"] - if task_id && !@processed_task_ids.include?(task_id) @processed_task_ids.add(task_id) save_events(ClaimHistoryEvent.create_claim_creation_event(change_data)) @@ -235,6 +240,39 @@ def process_decision_issue_and_task_events(change_data) end end + def process_request_issue_modification_events(change_data) + issue_modification_request_id = change_data["issue_modification_request_id"] + + return unless issue_modification_request_id + + is_processed = @processed_issue_modification_request_ids.include?(issue_modification_request_id) + + if issue_modification_request_id && !is_processed + @processed_issue_modification_request_ids.add(issue_modification_request_id) + save_events(ClaimHistoryEvent.create_issue_modification_request_event(change_data)) + save_events(ClaimHistoryEvent.create_edited_request_issue_events(change_data)) + end + # if imr version doesn't have status it means, imr has been made but no action yet taken from admin + process_pending_status_event(change_data) if !is_processed + end + + def process_pending_status_event(change_data) + task_id = change_data["task_id"] + + # processed_issue_modification_task_id stores all the task id that has already + # been processed so that it prevents the duplicate entry of pending event based on change_data. + if task_id && + !@processed_issue_modification_task_ids.include?(task_id) && + change_data["issue_modification_request_status"] == "assigned" + @processed_issue_modification_task_ids.add(task_id) + + save_events( + ClaimHistoryEvent.create_pending_status_events(change_data, + change_data["issue_modification_request_created_at"]) + ) + end + end + def ensure_array(variable) variable.is_a?(Array) ? variable : [variable] end diff --git a/spec/controllers/decision_reviews_controller_spec.rb b/spec/controllers/decision_reviews_controller_spec.rb index e16465f0362..c3e9b283636 100644 --- a/spec/controllers/decision_reviews_controller_spec.rb +++ b/spec/controllers/decision_reviews_controller_spec.rb @@ -821,11 +821,44 @@ end let!(:task_event) do - create(:higher_level_review_vha_task_with_decision) + create(:issue_modification_request, + :with_higher_level_review_with_decision, + :edit_of_request, + :update_decider, + :with_request_issue, + nonrating_issue_category: "Medical and Dental Care Reimbursement") end + let!(:issue_modification_request_modification) do + create(:issue_modification_request, + :with_request_issue, + request_type: "modification", + decision_review: task_event.decision_review) + end + + let!(:issue_modification_request_withdrawal) do + create(:issue_modification_request, + :with_request_issue, + :withdrawal, + decision_review: task_event.decision_review) + end + + let!(:issue_modification_request_removal) do + create(:issue_modification_request, + :with_request_issue, + request_type: "removal", + decision_review: task_event.decision_review) + end + let!(:issue_modification_request_cancel) do + create(:issue_modification_request, + :cancel_of_request, + decision_review: task_event.decision_review) + end + + let(:task_id) { task_event.decision_review.tasks.ids[0] } + it "should return task details" do - get :history, params: { task_id: task_event.id, decision_review_business_line_slug: vha_org.url }, + get :history, params: { task_id: task_id, decision_review_business_line_slug: vha_org.url }, format: :json expect(response.status).to eq 200 @@ -833,11 +866,19 @@ res = JSON.parse(response.body) expected_events = [ - { "taskID" => task_event.id, "eventType" => "added_issue", "claimType" => "Higher-Level Review", + { "taskID" => task_id, "eventType" => "added_issue", "claimType" => "Higher-Level Review", "readableEventType" => "Added issue" }, { "eventType" => "claim_creation", "readableEventType" => "Claim created" }, { "eventType" => "in_progress", "readableEventType" => "Claim status - In progress" }, - { "eventType" => "completed_disposition", "readableEventType" => "Completed disposition" } + { "eventType" => "completed_disposition", "readableEventType" => "Completed disposition" }, + { "eventType" => "addition", "readableEventType" => "Requested issue addition" }, + { "eventType" => "pending", "readableEventType" => "Claim status - Pending" }, + { "eventType" => "request_approved", "readableEventType" => "Approval of request - issue addition" }, + { "eventType" => "request_edited", "readableEventType" => "Edit of request - issue addition" }, + { "eventType" => "modification", "readableEventType" => "Requested issue modification" }, + { "eventType" => "removal", "readableEventType" => "Requested issue removal" }, + { "eventType" => "request_cancelled", "readableEventType" => "Cancellation of request" }, + { "eventType" => "withdrawal", "readableEventType" => "Requested issue withdrawal" } ] expected_events.each do |expected_attributes| diff --git a/spec/factories/issue_modification_request.rb b/spec/factories/issue_modification_request.rb index 17ea37cbafc..8f528b53f5c 100644 --- a/spec/factories/issue_modification_request.rb +++ b/spec/factories/issue_modification_request.rb @@ -6,7 +6,7 @@ request_reason { Faker::Lorem.sentence } benefit_type { "vha" } - nonrating_issue_category {} + nonrating_issue_category { Constants::ISSUE_CATEGORIES["vha"].sample } status { "assigned" } decision_date { Time.zone.today - rand(0..29) } @@ -15,14 +15,36 @@ remove_original_issue { false } requestor_id { create(:user).id } + trait :withdrawal do + request_type { "withdrawal" } + withdrawal_date { Time.zone.today - rand(0..29) } + end + trait :update_decider do after(:create) do |imr, evaluator| - imr.status = evaluator.status || "approved" + imr.status = evaluator.status == "assigned" ? "approved" : evaluator.status imr.decider_id = create(:user).id imr.save! end end + trait :edit_of_request do + after(:create) do |imr, evaluator| + imr.request_reason = "I edited this request." + imr.nonrating_issue_category = evaluator.nonrating_issue_category || + Constants::ISSUE_CATEGORIES[evaluator.benefit_type].sample + imr.status = evaluator.status + imr.save! + end + end + + trait :cancel_of_request do + after(:create) do |imr| + imr.status = "cancelled" + imr.save! + end + end + trait :with_request_issue do request_issue do create(:request_issue, @@ -47,6 +69,7 @@ trait :with_supplemental_claim do decision_review do create(:supplemental_claim, + :with_intake, :with_vha_issue, :update_assigned_at, :processed, @@ -57,8 +80,21 @@ trait :with_higher_level_review do decision_review do create(:higher_level_review, + :with_intake, + :with_vha_issue, + :update_assigned_at, + :processed, + claimant_type: :veteran_claimant) + end + end + + trait :with_higher_level_review_with_decision do + decision_review do + create(:higher_level_review, + :with_intake, :with_vha_issue, :update_assigned_at, + :with_decision, :processed, claimant_type: :veteran_claimant) end diff --git a/spec/models/business_line_spec.rb b/spec/models/business_line_spec.rb index f9e382d4742..e180d980eca 100644 --- a/spec/models/business_line_spec.rb +++ b/spec/models/business_line_spec.rb @@ -486,6 +486,19 @@ benefit_type: "vha", claimant_type: :dependent_claimant)) end + let!(:hlr_task_with_imr) do + create(:issue_modification_request, + :with_higher_level_review, + :edit_of_request, + nonrating_issue_category: "Medical and Dental Care Reimbursement") + end + + let!(:sc_task_with_imr) do + create(:issue_modification_request, + :with_supplemental_claim, + :edit_of_request, + nonrating_issue_category: "Medical and Dental Care Reimbursement") + end let(:decision_issue) { create(:decision_issue, disposition: "denied", benefit_type: hlr_task.appeal.benefit_type) } let(:intake_user) { create(:user, full_name: "Alexander Dewitt", css_id: "ALEXVHA", station_id: "103") } let(:decision_user) { create(:user, full_name: "Gaius Baelsar", css_id: "GAIUSVHA", station_id: "104") } @@ -586,6 +599,29 @@ "days_waiting" => (Time.zone.today - Date.parse(sc_task.assigned_at.iso8601)).to_i ) end + let(:imr_hlr_expectation) do + a_hash_including( + "requested_issue_type" => "Medical and Dental Care Reimbursement", + "requested_issue_description" => nil, + "remove_original_issue" => false, + "modification_request_reason" => "I edited this request.", + "request_type" => "addition", + "issue_modification_request_status" => "assigned", + "decision_review_type" => "HigherLevelReview" + ) + end + let(:imr_sc_expectation) do + a_hash_including( + "requested_issue_type" => "Medical and Dental Care Reimbursement", + "requested_issue_description" => nil, + "remove_original_issue" => false, + "modification_request_reason" => "I edited this request.", + "request_type" => "addition", + "issue_modification_request_status" => "assigned", + "decision_reason" => nil, + "decision_review_type" => "SupplementalClaim" + ) + end let(:all_expectations) do [ @@ -641,7 +677,7 @@ context "without filters" do it "should return all rows" do - expect(subject.count).to eq 5 + expect(subject.count).to eq 7 expect(subject.entries).to include(*all_expectations) end end @@ -676,7 +712,7 @@ let(:change_history_filters) { { claim_type: "SupplementalClaim" } } it "should only return rows for the filtered claim type" do - expect(subject.entries.count).to eq(1) + expect(subject.entries.count).to eq(2) expect(subject.entries).to include(sc_task_1_ri_1_expectation) end end @@ -685,7 +721,7 @@ let(:change_history_filters) { { claim_type: "HigherLevelReview" } } it "should only return rows for the filtered claim type" do - expect(subject.entries.count).to eq(4) + expect(subject.entries.count).to eq(5) expect(subject.entries).to include(*(all_expectations - [sc_task_1_ri_1_expectation])) end end @@ -703,6 +739,32 @@ end end + context "with task status filter pending" do + let(:change_history_filters) { { task_status: ["pending"] } } + + it "should only return rows for the filtered status types" do + expect(subject.entries.count).to eq(2) + expect(subject.entries).to include( + imr_hlr_expectation, + imr_sc_expectation + ) + end + end + + context "with task status filter pending and completed" do + let(:change_history_filters) { { task_status: %w[pending completed] } } + + it "should only return rows for the filtered status types" do + expect(subject.entries.count).to eq(4) + expect(subject.entries).to include( + hlr_task_1_ri_1_expectation, + hlr_task_1_ri_2_expectation, + imr_hlr_expectation, + imr_sc_expectation + ) + end + end + context "with dispositions filter" do context "with multiple disposition filters" do let(:change_history_filters) { { dispositions: %w[Granted denied] } } @@ -729,7 +791,7 @@ let(:change_history_filters) { { dispositions: ["Blank"] } } it "should return rows that do not have a disposition" do - expect(subject.entries.count).to eq(3) + expect(subject.entries.count).to eq(5) expect(subject.entries).to include( hlr_task_2_ri_1_expectation, hlr_task_2_ri_2_expectation, @@ -741,7 +803,7 @@ let(:change_history_filters) { { dispositions: %w[denied Blank] } } it "should return rows that match denied or have no disposition" do - expect(subject.entries.count).to eq(4) + expect(subject.entries.count).to eq(6) expect(subject.entries).to include( hlr_task_1_ri_2_expectation, hlr_task_2_ri_1_expectation, @@ -751,6 +813,14 @@ end end end + + context "with a single disposition filter and task status pending" do + let(:change_history_filters) { { dispositions: ["denied"], task_status: ["pending"] } } + + it "should only not return any row since pending task cannot have disposition" do + expect(subject.entries.count).to eq(0) + end + end end context "with issue types filter" do @@ -758,7 +828,7 @@ let(:change_history_filters) { { issue_types: ["Beneficiary Travel", "CHAMPVA"] } } it "should only return rows for the filtered issue type values" do - expect(subject.entries.count).to eq(2) + expect(subject.entries.count).to eq(3) expect(subject.entries).to include( hlr_task_1_ri_2_expectation, sc_task_1_ri_1_expectation @@ -770,13 +840,22 @@ let(:change_history_filters) { { issue_types: ["Caregiver | Other"] } } it "should only return rows for the filtered issue type values" do - expect(subject.entries.count).to eq(2) + expect(subject.entries.count).to eq(3) expect(subject.entries).to include( hlr_task_1_ri_1_expectation, hlr_task_2_ri_1_expectation ) end end + + context "with a single issue type filter and pending task status" do + let(:change_history_filters) { { issue_types: ["Caregiver | Other"], task_status: ["pending"] } } + + it "should only return rows for the filtered issue type values" do + expect(subject.entries.count).to eq(1) + expect(subject.entries).to include(imr_hlr_expectation) + end + end end context "with days waiting filter" do @@ -784,7 +863,7 @@ let(:change_history_filters) { { days_waiting: { number_of_days: 6, operator: "<" } } } it "should only return rows that are under the filtered days waiting value" do - expect(subject.entries.count).to eq(2) + expect(subject.entries.count).to eq(4) expect(subject.entries).to include( hlr_task_2_ri_1_expectation, hlr_task_2_ri_2_expectation @@ -848,7 +927,7 @@ let(:change_history_filters) { { facilities: ["101"] } } it "only return rows where either an intake, decisions, or updates user matches the station id" do - expect(subject.entries.count).to eq(3) + expect(subject.entries.count).to eq(5) expect(subject.entries).to include( hlr_task_1_ri_1_expectation, hlr_task_1_ri_2_expectation, @@ -906,10 +985,21 @@ end it "should only return rows that match both filters" do - expect(subject.entries.count).to eq(1) + expect(subject.entries.count).to eq(2) expect(subject.entries).to include(sc_task_1_ri_1_expectation) end end + + context "multiple issue types and claim type and task status pending" do + let(:change_history_filters) do + { issue_types: ["Beneficiary Travel", "CHAMPVA"], claim_type: "SupplementalClaim", task_status: ["pending"] } + end + + it "should only return rows that match both filters" do + expect(subject.entries.count).to eq(1) + expect(subject.entries).to include(imr_sc_expectation) + end + end end end diff --git a/spec/services/claim_change_history/change_history_event_serializer_spec.rb b/spec/services/claim_change_history/change_history_event_serializer_spec.rb index 540cc563f2d..474ea72db40 100644 --- a/spec/services/claim_change_history/change_history_event_serializer_spec.rb +++ b/spec/services/claim_change_history/change_history_event_serializer_spec.rb @@ -43,23 +43,39 @@ type: :change_history_event, attributes: { claimType: "Higher-Level Review", + readableEventType: "Claim created", claimantName: events[0].claimant_name, - details: - { - benefitType: "vha", - decisionDate: nil, - decisionDescription: nil, - disposition: nil, - dispositionDate: nil, - issueDescription: nil, - issueType: nil, - withdrawalRequestDate: nil - }, + eventUser: "L. Roth", eventDate: events[0].event_date, eventType: :claim_creation, - eventUser: "L. Roth", - readableEventType: "Claim created", - taskID: vha_task.id + taskID: vha_task.id, + details: + { + benefitType: "vha", + decisionDate: nil, + decisionDescription: nil, + disposition: nil, + dispositionDate: nil, + issueDescription: nil, + issueType: nil, + withdrawalRequestDate: nil + }, + modificationRequestDetails: + { + benefitType: "vha", + requestType: nil, + issueModificationRequestWithdrawalDate: nil, + modificationRequestReason: nil, + newDecisionDate: nil, + newIssueDescription: nil, + newIssueType: nil, + removeOriginalIssue: nil, + issueModificationRequestStatus: nil, + requestor: nil, + decider: nil, + decidedAtDate: nil, + decisionReason: nil + } } }, { @@ -67,6 +83,7 @@ type: :change_history_event, attributes: { claimType: "Higher-Level Review", + readableEventType: "Added issue", claimantName: events[1].claimant_name, details: { @@ -79,10 +96,25 @@ issueType: "Other", withdrawalRequestDate: nil }, + modificationRequestDetails: + { + benefitType: "vha", + requestType: nil, + issueModificationRequestWithdrawalDate: nil, + modificationRequestReason: nil, + newDecisionDate: nil, + newIssueDescription: nil, + newIssueType: nil, + removeOriginalIssue: nil, + issueModificationRequestStatus: nil, + requestor: nil, + decider: nil, + decidedAtDate: nil, + decisionReason: nil + }, eventDate: events[1].event_date, eventType: :added_issue, eventUser: "L. Roth", - readableEventType: "Added issue", taskID: vha_task.id } } diff --git a/spec/services/claim_change_history/change_history_reporter_spec.rb b/spec/services/claim_change_history/change_history_reporter_spec.rb index c5dcc4bbbbd..8a196ee025f 100644 --- a/spec/services/claim_change_history/change_history_reporter_spec.rb +++ b/spec/services/claim_change_history/change_history_reporter_spec.rb @@ -145,6 +145,18 @@ new_event.instance_variable_set(:@task_status, "cancelled") new_event end + let(:pending_issue_event) do + new_event = removed_issue_event.clone + new_event.instance_variable_set(:@event_type, :pending) + new_event.instance_variable_set(:@task_status, "assigned") + new_event + end + let(:issue_modification_request_event) do + new_event = pending_issue_event.clone + new_event.instance_variable_set(:@event_type, :modification) + new_event.instance_variable_set(:@task_status, "assigned") + new_event + end let(:events) do [ @@ -158,7 +170,9 @@ completed_disposition_event, added_decision_date_event, removed_issue_event, - withdrew_issue_event + withdrew_issue_event, + pending_issue_event, + issue_modification_request_event ] end @@ -177,6 +191,11 @@ "CHAMPVA", "CHAMPVA issue description", added_issue_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, nil ] end @@ -195,6 +214,11 @@ "Claim created", nil, "Claim created.", + nil, + nil, + nil, + nil, + nil, nil ] end @@ -213,6 +237,11 @@ "Claim status - In progress", nil, "Claim can be processed.", + nil, + nil, + nil, + nil, + nil, nil ] end @@ -230,7 +259,12 @@ cancelled_status_event.readable_event_date, "Claim closed", nil, - "Claim closed.", + "Claim cancelled.", + nil, + nil, + nil, + nil, + nil, nil ] end @@ -248,6 +282,11 @@ "Claim closed", nil, "Claim closed.", + nil, + nil, + nil, + nil, + nil, nil ] end @@ -265,6 +304,11 @@ "Claim status - Incomplete", nil, "Claim cannot be processed until decision date is entered.", + nil, + nil, + nil, + nil, + nil, nil ] end @@ -283,6 +327,11 @@ "CHAMPVA", "CHAMPVA issue description", added_issue_without_decision_date_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, nil ] end @@ -301,6 +350,11 @@ "CHAMPVA", "CHAMPVA issue description", completed_disposition_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, "Granted", "Decision for CHAMPVA issue", completed_disposition_event.readable_disposition_date @@ -321,6 +375,11 @@ "CHAMPVA", "CHAMPVA issue description", added_decision_date_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, nil ] end @@ -339,6 +398,11 @@ "CHAMPVA", "CHAMPVA issue description", removed_issue_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, nil ] end @@ -357,12 +421,63 @@ "CHAMPVA", "CHAMPVA issue description", withdrew_issue_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, + nil + ] + end + let(:pending_issue_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/900", + "in progress", + "20", + "Higher-Level Review", + "Austin AAC (200)", + "E. Thompson", + added_issue_event.readable_event_date, + "Claim status - Pending", + nil, + "Claim cannot be processed until VHA admin reviews pending requests.", + nil, + nil, + nil, + nil, + nil, + nil + ] + end + let(:modification_issue_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/900", + "in progress", + "20", + "Higher-Level Review", + "Austin AAC (200)", + "E. Thompson", + added_issue_event.readable_event_date, + "Requested issue modification", + "CHAMPVA", + "CHAMPVA issue description", + added_issue_event.readable_decision_date, + nil, + nil, + nil, + nil, + nil, nil ] end it "returns a csv string with the column headers, filters, and event rows" do rows = CSV.parse(subject) + expect(rows.count).to eq(2 + events.length) expect(rows[0]).to eq([]) expect(rows[1]).to eq(column_headers) @@ -377,6 +492,8 @@ expect(rows[10]).to eq(added_decision_date_event_row) expect(rows[11]).to eq(removed_issue_event_row) expect(rows[12]).to eq(withdrew_issue_event_row) + expect(rows[13]).to eq(pending_issue_event_row) + expect(rows[14]).to eq(modification_issue_event_row) end end end diff --git a/spec/services/claim_change_history/claim_history_event_spec.rb b/spec/services/claim_change_history/claim_history_event_spec.rb index 1ee915d9c08..96177a92483 100644 --- a/spec/services/claim_change_history/claim_history_event_spec.rb +++ b/spec/services/claim_change_history/claim_history_event_spec.rb @@ -48,7 +48,35 @@ "event_date" => change_data_event_date, "task_versions" => version_changes, "days_waiting" => 25, - "task_closed_at" => "2023-10-19 22:47:16.233187" + "task_closed_at" => "2023-10-19 22:47:16.233187", + "issue_modification_request_id" => 61, + "requested_issue_type" => "Caregiver | Tier Level", + "requested_issue_description" => "test", + "remove_original_issue" => nil, + "modification_request_reason" => "Testing", + "requested_decision_date" => "2024-07-07", + "request_type" => request_type, + "issue_modification_request_status" => "cancelled", + "decision_reason" => nil, + "decider_id" => nil, + "requestor_id" => "2000006012", + "decided_at" => nil, + "issue_modification_request_created_at" => issue_modification_request_created_at, + "issue_modification_request_updated_at" => "2024-07-22 15:58:42.908194", + "issue_modification_request_edited_at" => nil, + "issue_modification_request_withdrawal_date" => nil, + "decision_review_id" => 65, + "decision_review_type" => "HigherLevelReview", + "requestor" => "Monte Mann", + "requestor_station_id" => "741", + "requestor_css_id" => "ACBAUERVVHAH", + "decider" => nil, + "decider_station_id" => nil, + "decider_css_id" => nil, + "imr_versions" => imr_versions, + "previous_imr_created_at" => nil, + "updater_user_name" => "Monte Mann", + "is_assigned_present" => is_assigned_present } end @@ -62,8 +90,18 @@ let(:change_data_decision_date) { "2023-05-31" } let(:change_data_decision_date_added_at) { Time.zone.parse("2023-10-19 22:48:25.281657") } let(:version_changes) { nil } + let(:imr_versions) { nil } + let(:request_type) { :addition } let(:request_issue_update_time) { Time.zone.parse("2023-10-19 22:47:16.233187") } let(:request_issue_created_at) { Time.zone.parse("2023-10-19 22:45:43.108934") } + let(:issue_modification_request_created_at) { Time.zone.parse("2023-10-20 22:47:16.233187") } + let(:imr_last_decided_date) { Time.zone.parse("2023-10-21 22:47:16.233187") } + let(:previous_imr_created_at) { Time.zone.parse("2023-10-19 22:47:16.233187") } + + let(:decision_reason) { nil } + let(:decider_id) { nil } + let(:decided_at) { nil } + let(:is_assigned_present) { false } let(:event_attribute_data) do { assigned_at: Time.zone.parse("2023-10-19 22:47:16.222148"), @@ -116,6 +154,19 @@ } end + let(:issue_modification_request_attribute_data) do + { + request_type: request_type, + new_issue_type: "Caregiver | Tier Level", + new_issue_description: "test", + new_decision_date: "2024-07-07", + modification_request_reason: "Testing", + decision_reason: nil, + decided_at_date: nil, + issue_modification_request_withdrawal_date: nil + } + end + let(:intake_event_data) do { event_date: change_data["intake_completed_at"], @@ -134,6 +185,54 @@ } end + let(:pending_attribute_data) do + { + event_type: :pending, + claimant_name: "Bob Smithboehm", + event_user_name: "System" + } + end + let(:in_progress_attribute_data) do + { + event_type: :in_progress, + claimant_name: "Bob Smithboehm", + event_user_name: "System" + } + end + let(:modification_attribute_data) do + { + event_type: :modification, + request_type: :modification, + new_issue_type: "Caregiver | Tier Level", + new_issue_description: "test", + new_decision_date: "2024-07-07", + modification_request_reason: "Testing" + } + end + let(:issue_modification_response_attribute) do + { + event_type: :request_approved, + request_type: :addition, + new_issue_type: "Caregiver | Tier Level", + new_issue_description: "test", + new_decision_date: "2024-07-07", + modification_request_reason: "Testing" + } + end + + let(:issue_modification_edited_attribute) do + { + event_type: :request_edited, + request_type: :addition, + issue_type: "Clothing Allowance", + new_issue_type: "Caregiver | Tier Level", + new_issue_description: "modifiedvalue", + new_decision_date: "2024-07-07", + modification_request_reason: "Addition is the only request issue-modifiedvalue Z", + event_user_name: "Monte Mann" + } + end + describe "class methods" do describe ".from_change_data" do subject { described_class.from_change_data(event_type, change_data) } @@ -143,7 +242,6 @@ it "should create an instance and not raise an error" do claim_history_event = subject - expect_attributes(claim_history_event, status_event_attribute_data) end end @@ -164,6 +262,25 @@ expect { subject }.to raise_error(InvalidEventType) end end + + context "when the event type is request addition" do + let(:event_type) { :addition } + + it "should create an instance with issue request addition" do + claim_history_event = subject + + expect_attributes(claim_history_event, issue_modification_request_attribute_data) + end + end + + context "when the event type is request modification" do + let(:event_type) { :modification } + let(:request_type) { :modification } + it "should create an instance with issue request addition" do + claim_history_event = subject + expect_attributes(claim_history_event, issue_modification_request_attribute_data) + end + end end describe ".create_completed_disposition_event" do @@ -631,6 +748,93 @@ end end + describe ".create_pending_status_events" do + let(:event_date) { change_data["issue_modification_request_created_at"] } + let(:is_assigned_present) { true } + + subject { described_class.create_pending_status_events(change_data, event_date) } + + it "should return one pending event" do + expect_attributes(subject, pending_attribute_data) + end + + context "it should return pending event if issue modification requests are made in two different days" do + before do + change_data["previous_imr_created_at"] = previous_imr_created_at + end + + it "should return one pending event" do + expect_attributes(subject, pending_attribute_data) + end + end + + context "it should not pending event if issue modification requests are made in same transaction" do + let(:previous_imr_created_at) { issue_modification_request_created_at } + + before do + change_data["previous_imr_created_at"] = previous_imr_created_at + change_data["issue_modification_request_created_at"] = issue_modification_request_created_at + end + + it "should return one pending event" do + expect(subject).to be_nil + end + end + end + + describe ".create_issue_modification_request_event" do + let(:request_type) { :modification } + + subject { described_class.create_issue_modification_request_event(change_data) } + + it "should return single event with request_type passed as the request_type" do + verify_attributes_and_count(subject, 1, modification_attribute_data) + end + end + + describe ".create_edited_request_issue_events" do + let(:imr_versions) do + "{\"---\n" \ + "nonrating_issue_description:\n" \ + "- First value\n" \ + "- modifiedvalue\n" \ + "request_reason:\n" \ + "- Addition is the only request issue\n" \ + "- Addition is the only request issue-modifiedvalue Z\n" \ + "edited_at:\n" \ + "- \n" \ + "- 2024-07-19 22:39:14.207143000 Z\n" \ + "updated_at:\n" \ + "- 2024-07-19 22:39:14.207143000 Z\n" \ + "- 2024-07-19 22:39:14.207143000 Z\n" \ + "\",---\n" \ + "status:\n" \ + "- assigned\n" \ + "- approved\n" \ + "nonrating_issue_description:\n" \ + "- modifiedvalue \n" \ + "- approved by me\n" \ + "updated_at:\n" \ + "- 2024-07-19 22:45:43.148742000 Z\n" \ + "- 2024-07-19 22:47:16.222311778 Z\n" \ + "\"}" + end + before do + change_data["decided_at"] = imr_last_decided_date + change_data["imr_last_decided_date"] = imr_last_decided_date + end + subject { described_class.create_edited_request_issue_events(change_data) } + + it "returns request_edited event type with multiple events" do + expect(subject.count).to be(4) + + expect_attributes(subject[0], pending_attribute_data) + expect_attributes(subject[1], issue_modification_response_attribute) + expect_attributes(subject[3], issue_modification_edited_attribute) + expect_attributes(subject[2], in_progress_attribute_data) + end + end + describe "helper class methods" do describe ".retrieve_issue_data" do before do @@ -782,7 +986,9 @@ event_instance.readable_task_status, event_instance.days_waiting, event_instance.readable_claim_type, event_instance.readable_facility_name, event_instance.readable_user_name, event_instance.readable_event_date, event_instance.readable_event_type, - event_instance.send(:issue_or_status_information), event_instance.send(:disposition_information) + event_instance.send(:issue_or_status_information), + event_instance.send(:issue_modification_request_information), + event_instance.send(:disposition_information) ] end @@ -912,7 +1118,16 @@ added_issue: "Added issue", withdrew_issue: "Withdrew issue", removed_issue: "Removed issue", - added_decision_date: "Added decision date" + added_decision_date: "Added decision date", + cancelled: "Claim closed", + addition: "Requested issue addition", + removal: "Requested issue removal", + modification: "Requested issue modification", + withdrawal: "Requested issue withdrawal", + request_approved: "Approval of request - issue addition", + request_denied: "Rejection of request - issue addition", + request_cancelled: "Cancellation of request", + request_edited: "Edit of request - issue addition" } event_types.each do |event_type, readable_name| @@ -1131,4 +1346,9 @@ def expect_attributes(object_instance, attribute_value_pairs) expect(object_instance.send(attribute)).to eq(expected_value) end end + + def verify_attributes_and_count(subject, num_of_records, attribute) + expect(subject.count).to eq(num_of_records) + expect_attributes(subject[0], attribute) + end end diff --git a/spec/services/claim_change_history/claim_history_service_spec.rb b/spec/services/claim_change_history/claim_history_service_spec.rb index fe244f251b5..71713e3cf15 100644 --- a/spec/services/claim_change_history/claim_history_service_spec.rb +++ b/spec/services/claim_change_history/claim_history_service_spec.rb @@ -13,6 +13,14 @@ benefit_type: "vha", claimant_type: :dependent_claimant)) end + let!(:hlr_task_with_imr) do + create(:issue_modification_request, + :with_higher_level_review, + :edit_of_request, + :update_decider, + nonrating_issue_category: "Medical and Dental Care Reimbursement") + end + let!(:extra_hlr_request_issue) do create(:request_issue, nonrating_issue_category: "Camp Lejune Family Member", @@ -88,6 +96,18 @@ ] end + let(:expected_imr_event_types) do + [ + :claim_creation, + :added_issue, + :pending, + :addition, + :request_edited, + :request_approved, + :in_progress + ] + end + before do # Remove the versions to setup specific versions hlr_task.versions.each(&:delete) @@ -146,8 +166,8 @@ expect(events).to eq(service_instance.events) # Expect to get back all the combined event types - all_event_types = expected_hlr_event_types + expected_sc_event_types - expect(events.count).to eq(14) + all_event_types = expected_hlr_event_types + expected_sc_event_types + expected_imr_event_types + expect(events.count).to eq(21) expect(events.map(&:event_type)).to contain_exactly(*all_event_types) # Verify the issue data is correct for the completed_dispostion events @@ -165,9 +185,12 @@ expect(disposition_events.map(&:disposition_date)).to contain_exactly(*disposition_dates) # Verify the issue data is correct for all the add issue events - added_issue_types = [*disposition_issue_types, "CHAMPVA", "Beneficiary Travel"] - added_issue_descriptions = [*disposition_issue_descriptions, "Withdrew CHAMPVA", "VHA issue description "] - added_issue_user_names = ["Lauren Roth", "Lauren Roth", "Lauren Roth", "Eleanor Reynolds"] + added_issue_types = [*disposition_issue_types, "CHAMPVA", "Beneficiary Travel", "Caregiver | Other"] + added_issue_descriptions = [*disposition_issue_descriptions, + "Withdrew CHAMPVA", + "VHA issue description ", + "VHA - Caregiver "] + added_issue_user_names = ["Lauren Roth", "Lauren Roth", "Lauren Roth", "Eleanor Reynolds", "Lauren Roth"] add_issue_events = events.select do |event| event.event_type == :added_issue || event.event_type == :added_issue_without_decision_date end @@ -237,7 +260,10 @@ it "should only return events for tasks that match the claim type filter" do subject - expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_hlr_event_types) + expect(service_instance.events.map(&:event_type)).to contain_exactly( + *expected_hlr_event_types, + *expected_imr_event_types + ) end context "with no filter matches" do @@ -255,7 +281,10 @@ it "should only return events for the tasks that match the task status filter" do subject - expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_sc_event_types) + expect(service_instance.events.map(&:event_type)).to contain_exactly( + *expected_sc_event_types, + *expected_imr_event_types + ) end context "with no filter matches" do @@ -296,7 +325,7 @@ it "should return events without a disposition" do subject - expect(service_instance.events.count).to eq(14) + expect(service_instance.events.count).to eq(21) end end end @@ -308,7 +337,9 @@ subject expected_event_types = [ :added_issue, - :completed_disposition + :completed_disposition, + :added_issue, + :request_edited ] expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_event_types) end @@ -331,7 +362,9 @@ :added_issue, :completed_disposition, :added_issue, - :withdrew_issue + :withdrew_issue, + :added_issue, + :request_edited ] expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_event_types) end @@ -347,6 +380,7 @@ :added_issue, :added_issue, :added_issue, + :added_issue, :completed_disposition, :completed_disposition ] @@ -380,7 +414,8 @@ subject expect(service_instance.events.map(&:event_type)).to contain_exactly( *expected_hlr_event_types, - *expected_sc_event_types + *expected_sc_event_types, + *expected_imr_event_types ) end end @@ -408,7 +443,8 @@ ] expect(service_instance.events.map(&:event_type)).to contain_exactly( *filtered_hlr_event_types, - *expected_sc_event_types + *expected_sc_event_types, + *expected_imr_event_types ) end end @@ -432,7 +468,8 @@ subject expect(service_instance.events.map(&:event_type)).to contain_exactly( *(expected_hlr_event_types - [:claim_creation]), - *expected_sc_event_types + *expected_sc_event_types, + *expected_imr_event_types ) end @@ -542,7 +579,8 @@ it "should return all events" do subject expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_hlr_event_types, - *expected_sc_event_types) + *expected_sc_event_types, + *expected_imr_event_types) end end end @@ -553,7 +591,8 @@ it "should only return events for tasks that match the days waiting filter" do subject - expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_hlr_event_types) + expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_hlr_event_types, + *expected_imr_event_types) end end @@ -666,11 +705,64 @@ subject expected_event_types = [ :completed, - :in_progress + :in_progress, + :request_approved ] expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_event_types) end end + + context "with issue modification request task id" do + let(:filters) { { task_id: hlr_task_with_imr.decision_review.tasks.ids[0] } } + it "should only return the filtered events for the specific task ids" do + subject + expect(service_instance.events.map(&:event_type)).to contain_exactly(*expected_imr_event_types) + end + end + + context "with multiple filters for task id and event" do + let(:filters) do + { task_id: hlr_task_with_imr.decision_review.tasks.ids[0], events: [:added_issue, :claim_creation] } + end + + it "should only return the filtered events for the specific task ids" do + subject + expect(service_instance.events.map(&:event_type)).to contain_exactly(:added_issue, :claim_creation) + end + end + + context "with multiple filters for task id and event" do + let(:filters) do + { task_id: hlr_task_with_imr.decision_review.tasks.ids[0], events: [:request_edited] } + end + + it "should only return the filtered events for the specific task ids" do + subject + expect(service_instance.events.map(&:event_type)).to contain_exactly(:request_edited) + end + end + + context "with multiple filters for task id and event" do + let(:filters) do + { task_id: hlr_task_with_imr.decision_review.tasks.ids[0], events: [:request_approved] } + end + + it "should only return the filtered events for the specific task ids" do + subject + expect(service_instance.events.map(&:event_type)).to contain_exactly(:request_approved) + end + end + + context "with multiple filters for task id and event" do + let(:filters) do + { task_id: hlr_task_with_imr.decision_review.tasks.ids[0], events: [:pending] } + end + + it "should only return the filtered events for the specific task ids" do + subject + expect(service_instance.events.map(&:event_type)).to contain_exactly(:pending) + end + end end end end From 3f727d4657df0267ac2127183c2eee88d7ce9234 Mon Sep 17 00:00:00 2001 From: jonathanh-va <111081469+jonathanh-va@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:14:49 -0500 Subject: [PATCH 11/19] J hoang/appeals 44940 (#22428) * initial * some factory and spec updates * change history query update and spec fixes * code climate/lint fix * change @claim_type identifier * fix claim_history_event_spec.rb * test coverage for change history with Remand claim type * spec lint fixes * refactor claim_type filtering to account for remand subtype * business_line_spec fix * decision_reviews_controller_spec update for remand claim type * lint fixes for decision_reviews_controller_spec.rb * comment updates in business_line.rb * renamed mock data in change_history_reporter_spec * trigger GA * refactor claim type filter for less conditional handling * business line spec fix --------- Co-authored-by: Jonathan Hoang --- app/models/organizations/business_line.rb | 23 +- .../claim_history_event.rb | 5 +- .../decision_reviews_controller_spec.rb | 23 ++ spec/factories/supplemental_claim.rb | 1 - spec/models/business_line_spec.rb | 86 +++++- .../change_history_reporter_spec.rb | 279 +++++++++++++++++- .../claim_history_event_spec.rb | 11 +- 7 files changed, 404 insertions(+), 24 deletions(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index c4b65345b50..9fd82d71054 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -282,7 +282,7 @@ def change_history_rows NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '), NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '), bgs_attorneys.name - ) AS claimant_name + ) AS claimant_name, 'HigherLevelReview' AS type_classifier FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type AND request_issues.decision_review_id = tasks.appeal_id @@ -334,7 +334,7 @@ def change_history_rows NULLIF(CONCAT(unrecognized_party_details.name, ' ', unrecognized_party_details.last_name), ' '), NULLIF(CONCAT(people.first_name, ' ', people.last_name), ' '), bgs_attorneys.name - ) AS claimant_name + ) AS claimant_name, supplemental_claims.type AS type_classifier FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type AND request_issues.decision_review_id = tasks.appeal_id @@ -362,6 +362,7 @@ def change_history_rows AND tasks.assigned_to_type = 'Organization' AND tasks.assigned_to_id = '#{parent.id.to_i}' #{sanitized_filters} + #{sc_type_filter} SQL ActiveRecord::Base.transaction do @@ -401,9 +402,23 @@ def task_status_filter def claim_type_filter if query_params[:claim_type].present? - " AND #{where_clause_from_array(Task, :appeal_type, query_params[:claim_type]).to_sql}" + temp_claim_types = query_params[:claim_type].dup + + if query_params[:claim_type].include?(Remand.name) + temp_claim_types.push "SupplementalClaim" + end + + " AND #{where_clause_from_array(Task, :appeal_type, temp_claim_types).to_sql} " else - " AND tasks.appeal_type IN ('HigherLevelReview', 'SupplementalClaim') " + " AND tasks.appeal_type IN ('HigherLevelReview', 'SupplementalClaim' ) " + end + end + + def sc_type_filter + if query_params[:claim_type].present? + if query_params[:claim_type].include?(Remand.name) || query_params[:claim_type].include?(SupplementalClaim.name) + " AND #{where_clause_from_array(SupplementalClaim, :type, query_params[:claim_type]).to_sql} " + end end end diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb index 0cae36dc208..010273a561b 100644 --- a/app/services/claim_change_history/claim_history_event.rb +++ b/app/services/claim_change_history/claim_history_event.rb @@ -378,7 +378,8 @@ def readable_task_status def readable_claim_type { "HigherLevelReview" => "Higher-Level Review", - "SupplementalClaim" => "Supplemental Claim" + "SupplementalClaim" => "Supplemental Claim", + "Remand" => "Remand" }[claim_type] end @@ -456,7 +457,7 @@ def set_attributes_from_change_history_data(new_event_type, change_data) def parse_task_attributes(change_data) @task_id = change_data["task_id"] @task_status = change_data["task_status"] - @claim_type = change_data["appeal_type"] + @claim_type = change_data["type_classifier"] @assigned_at = change_data["assigned_at"] @days_waiting = change_data["days_waiting"] end diff --git a/spec/controllers/decision_reviews_controller_spec.rb b/spec/controllers/decision_reviews_controller_spec.rb index 41bdf0ad618..f23372cf786 100644 --- a/spec/controllers/decision_reviews_controller_spec.rb +++ b/spec/controllers/decision_reviews_controller_spec.rb @@ -883,6 +883,10 @@ create(:higher_level_review_vha_task_with_decision) end + let!(:remand_task_event) do + create(:remand_vha_task) + end + it "should return task details" do get :history, params: { task_id: task_event.id, decision_review_business_line_slug: vha_org.url }, format: :json @@ -905,6 +909,25 @@ ) end end + + it "should return remand task details" do + get :history, params: { task_id: remand_task_event.id, decision_review_business_line_slug: vha_org.url }, + format: :json + + expect(response.status).to eq 200 + + res = JSON.parse(response.body) + + expected_events = [ + { "taskID" => remand_task_event.id, "eventType" => "added_issue", "claimType" => "Remand" } + ] + + expected_events.each do |expected_attributes| + expect(res).to include( + a_hash_including("attributes" => a_hash_including(expected_attributes)) + ) + end + end end end diff --git a/spec/factories/supplemental_claim.rb b/spec/factories/supplemental_claim.rb index 9a257e1676d..2ed4c7ef5a2 100644 --- a/spec/factories/supplemental_claim.rb +++ b/spec/factories/supplemental_claim.rb @@ -7,7 +7,6 @@ benefit_type { "compensation" } uuid { SecureRandom.uuid } veteran_is_not_claimant { true } - type { SupplementalClaim.name } transient do number_of_claimants { nil } diff --git a/spec/models/business_line_spec.rb b/spec/models/business_line_spec.rb index a567c6bc04f..1684d7228db 100644 --- a/spec/models/business_line_spec.rb +++ b/spec/models/business_line_spec.rb @@ -518,6 +518,13 @@ benefit_type: "vha", claimant_type: :dependent_claimant)) end + let!(:remand_task) do + create(:remand_vha_task, + appeal: create(:remand, + benefit_type: "vha", + claimant_type: :dependent_claimant)) + end + let(:decision_issue) { create(:decision_issue, disposition: "denied", benefit_type: hlr_task.appeal.benefit_type) } let(:intake_user) { create(:user, full_name: "Alexander Dewitt", css_id: "ALEXVHA", station_id: "103") } let(:decision_user) { create(:user, full_name: "Gaius Baelsar", css_id: "GAIUSVHA", station_id: "104") } @@ -618,6 +625,25 @@ "days_waiting" => (Time.zone.today - Date.parse(sc_task.assigned_at.iso8601)).to_i ) end + let(:remand_task_1_ri_1_expectation) do + a_hash_including( + "nonrating_issue_category" => "Clothing Allowance", + "nonrating_issue_description" => "This is a Clothing Allowance issue", + "task_id" => remand_task.id, + "veteran_file_number" => remand_task.appeal.veteran_file_number, + "intake_user_name" => nil, + "intake_user_css_id" => nil, + "intake_user_station_id" => nil, + "disposition" => nil, + "decision_user_name" => nil, + "decision_user_css_id" => nil, + "decision_user_station_id" => nil, + "claimant_name" => remand_task.appeal.claimant.name, + "task_status" => remand_task.status, + "request_issue_benefit_type" => "vha", + "days_waiting" => (Time.zone.today - Date.parse(remand_task.assigned_at.iso8601)).to_i + ) + end let(:all_expectations) do [ @@ -625,7 +651,8 @@ hlr_task_1_ri_2_expectation, hlr_task_2_ri_1_expectation, hlr_task_2_ri_2_expectation, - sc_task_1_ri_1_expectation + sc_task_1_ri_1_expectation, + remand_task_1_ri_1_expectation ] end @@ -638,8 +665,16 @@ nonrating_issue_category: "Camp Lejune Family Member", nonrating_issue_description: "This is a Camp Lejune issue", benefit_type: "vha") + remand_issue = create(:request_issue, + nonrating_issue_category: "Clothing Allowance", + nonrating_issue_description: "This is a Clothing Allowance issue", + benefit_type: "vha", + decision_review: remand_task.appeal) hlr_task.appeal.request_issues << issue hlr_task2.appeal.request_issues << issue2 + remand_task.appeal.request_issues << remand_issue + remand_task.save + remand_task.reload # Add a different intake user to the second hlr task for data differences second_intake = hlr_task2.appeal.intake @@ -673,21 +708,22 @@ context "without filters" do it "should return all rows" do - expect(subject.count).to eq 5 + expect(subject.count).to eq 6 expect(subject.entries).to include(*all_expectations) end end context "with task_id filter" do context "with multiple task ids" do - let(:change_history_filters) { { task_id: [hlr_task.id, sc_task.id] } } + let(:change_history_filters) { { task_id: [hlr_task.id, sc_task.id, remand_task.id] } } it "should return rows for all matching ids" do - expect(subject.entries.count).to eq(3) + expect(subject.entries.count).to eq(4) expect(subject.entries).to include( hlr_task_1_ri_1_expectation, hlr_task_1_ri_2_expectation, - sc_task_1_ri_1_expectation + sc_task_1_ri_1_expectation, + remand_task_1_ri_1_expectation ) end end @@ -718,7 +754,18 @@ it "should only return rows for the filtered claim type" do expect(subject.entries.count).to eq(4) - expect(subject.entries).to include(*(all_expectations - [sc_task_1_ri_1_expectation])) + expect(subject.entries).to include( + *(all_expectations - [sc_task_1_ri_1_expectation] - [remand_task_1_ri_1_expectation]) + ) + end + end + + context "Remand claim filter" do + let(:change_history_filters) { { claim_type: ["Remand"] } } + + it "should only return rows for the filtered claim type" do + expect(subject.entries.count).to eq(1) + expect(subject.entries).to include(remand_task_1_ri_1_expectation) end end end @@ -761,11 +808,12 @@ let(:change_history_filters) { { dispositions: ["Blank"] } } it "should return rows that do not have a disposition" do - expect(subject.entries.count).to eq(3) + expect(subject.entries.count).to eq(4) expect(subject.entries).to include( hlr_task_2_ri_1_expectation, hlr_task_2_ri_2_expectation, - sc_task_1_ri_1_expectation + sc_task_1_ri_1_expectation, + remand_task_1_ri_1_expectation ) end @@ -773,12 +821,13 @@ let(:change_history_filters) { { dispositions: %w[denied Blank] } } it "should return rows that match denied or have no disposition" do - expect(subject.entries.count).to eq(4) + expect(subject.entries.count).to eq(5) expect(subject.entries).to include( hlr_task_1_ri_2_expectation, hlr_task_2_ri_1_expectation, hlr_task_2_ri_2_expectation, - sc_task_1_ri_1_expectation + sc_task_1_ri_1_expectation, + remand_task_1_ri_1_expectation ) end end @@ -828,9 +877,10 @@ let(:change_history_filters) { { days_waiting: { number_of_days: 11, operator: ">" } } } it "should only return rows that are over the filtered days waiting value" do - expect(subject.entries.count).to eq(1) + expect(subject.entries.count).to eq(2) expect(subject.entries).to include( - sc_task_1_ri_1_expectation + sc_task_1_ri_1_expectation, + remand_task_1_ri_1_expectation ) end end @@ -852,7 +902,9 @@ it "should only return rows that are between the number of days and end of days" do expect(subject.entries.count).to eq(4) - expect(subject.entries).to include(*(all_expectations - [sc_task_1_ri_1_expectation])) + expect(subject.entries).to include( + *(all_expectations - [sc_task_1_ri_1_expectation] - [remand_task_1_ri_1_expectation]) + ) end end end @@ -872,7 +924,9 @@ it "only return rows where either an intake, decisions, or updates user matches the station id" do expect(subject.entries.count).to eq(4) - expect(subject.entries).to include(*(all_expectations - [sc_task_1_ri_1_expectation])) + expect(subject.entries).to include( + *(all_expectations - [sc_task_1_ri_1_expectation] - [remand_task_1_ri_1_expectation]) + ) end end @@ -905,7 +959,9 @@ it "only return rows where either an intake, decisions, or updates user matches the css_ids" do expect(subject.entries.count).to eq(4) - expect(subject.entries).to include(*(all_expectations - [sc_task_1_ri_1_expectation])) + expect(subject.entries).to include( + *(all_expectations - [sc_task_1_ri_1_expectation] - [remand_task_1_ri_1_expectation]) + ) end end diff --git a/spec/services/claim_change_history/change_history_reporter_spec.rb b/spec/services/claim_change_history/change_history_reporter_spec.rb index c5dcc4bbbbd..a18a9782b70 100644 --- a/spec/services/claim_change_history/change_history_reporter_spec.rb +++ b/spec/services/claim_change_history/change_history_reporter_spec.rb @@ -146,6 +146,83 @@ new_event end + let(:remand_claim_creation_event) do + new_event = added_issue_event.clone + new_event.instance_variable_set(:@event_type, :claim_creation) + new_event.instance_variable_set(:@event_user_name, "System") + new_event.instance_variable_set(:@user_facility, nil) + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_in_progress_status_event) do + new_event = claim_creation_event.clone + new_event.instance_variable_set(:@event_type, :in_progress) + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_cancelled_status_event) do + new_event = in_progress_status_event.clone + new_event.instance_variable_set(:@event_type, :cancelled) + new_event.instance_variable_set(:@task_status, "cancelled") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_completed_status_event) do + new_event = in_progress_status_event.clone + new_event.instance_variable_set(:@event_type, :completed) + new_event.instance_variable_set(:@task_status, "completed") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_incomplete_status_event) do + new_event = in_progress_status_event.clone + new_event.instance_variable_set(:@event_type, :incomplete) + new_event.instance_variable_set(:@task_status, "on_hold") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_added_issue_without_decision_date_event) do + new_event = added_issue_event.clone + new_event.instance_variable_set(:@event_type, :added_issue_without_decision_date) + new_event.instance_variable_set(:@task_status, "on_hold") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event.instance_variable_set(:@decision_date, nil) + new_event + end + let(:remand_completed_disposition_event) do + new_event = added_issue_event.clone + new_event.instance_variable_set(:@event_type, :completed_disposition) + new_event.instance_variable_set(:@task_status, "completed") + new_event.instance_variable_set(:@disposition, "Granted") + new_event.instance_variable_set(:@decision_description, "Decision for CHAMPVA issue") + new_event.instance_variable_set(:@disposition_date, 4.days.ago.iso8601) + new_event.instance_variable_set(:@user_facility, "200") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_added_decision_date_event) do + new_event = completed_disposition_event.clone + new_event.instance_variable_set(:@event_type, :added_decision_date) + new_event.instance_variable_set(:@task_status, "assigned") + new_event.instance_variable_set(:@task_id, 900) + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_removed_issue_event) do + new_event = added_decision_date_event.clone + new_event.instance_variable_set(:@event_type, :removed_issue) + new_event.instance_variable_set(:@task_status, "cancelled") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:remand_withdrew_issue_event) do + new_event = removed_issue_event.clone + new_event.instance_variable_set(:@event_type, :withdrew_issue) + new_event.instance_variable_set(:@task_status, "cancelled") + new_event.instance_variable_set(:@claim_type, "Remand") + new_event + end + let(:events) do [ added_issue_event, @@ -158,7 +235,17 @@ completed_disposition_event, added_decision_date_event, removed_issue_event, - withdrew_issue_event + withdrew_issue_event, + remand_claim_creation_event, + remand_in_progress_status_event, + remand_cancelled_status_event, + remand_completed_status_event, + remand_incomplete_status_event, + remand_added_issue_without_decision_date_event, + remand_completed_disposition_event, + remand_added_decision_date_event, + remand_removed_issue_event, + remand_withdrew_issue_event ] end @@ -361,6 +448,186 @@ ] end + let(:remand_claim_creation_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "in progress", + "20", + "Remand", + "", + "System", + remand_claim_creation_event.readable_event_date, + "Claim created", + nil, + "Claim created.", + nil + ] + end + + let(:remand_in_progress_status_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "in progress", + "20", + "Remand", + "", + "System", + remand_in_progress_status_event.readable_event_date, + "Claim status - In progress", + nil, + "Claim can be processed.", + nil + ] + end + + let(:remand_cancelled_status_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "cancelled", + "20", + "Remand", + "", + "System", + remand_cancelled_status_event.readable_event_date, + "Claim closed", + nil, + "Claim closed.", + nil + ] + end + let(:remand_completed_status_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "completed", + "20", + "Remand", + "", + "System", + remand_completed_status_event.readable_event_date, + "Claim closed", + nil, + "Claim closed.", + nil + ] + end + let(:remand_incomplete_status_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "incomplete", + "20", + "Remand", + "", + "System", + remand_incomplete_status_event.readable_event_date, + "Claim status - Incomplete", + nil, + "Claim cannot be processed until decision date is entered.", + nil + ] + end + let(:remand_added_issue_without_decision_date_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "incomplete", + "20", + "Remand", + "VACO (101)", + "E. Thompson", + remand_added_issue_without_decision_date_event.readable_event_date, + "Added issue - No decision date", + "CHAMPVA", + "CHAMPVA issue description", + remand_added_issue_without_decision_date_event.readable_decision_date, + nil + ] + end + let(:remand_completed_disposition_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/999", + "completed", + "20", + "Remand", + "Austin AAC (200)", + "E. Thompson", + remand_completed_disposition_event.readable_event_date, + "Completed disposition", + "CHAMPVA", + "CHAMPVA issue description", + remand_completed_disposition_event.readable_decision_date, + "Granted", + "Decision for CHAMPVA issue", + remand_completed_disposition_event.readable_disposition_date + ] + end + let(:remand_added_decision_date_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/900", + "in progress", + "20", + "Remand", + "Austin AAC (200)", + "E. Thompson", + remand_added_decision_date_event.readable_event_date, + "Added decision date", + "CHAMPVA", + "CHAMPVA issue description", + remand_added_decision_date_event.readable_decision_date, + nil + ] + end + let(:remand_removed_issue_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/900", + "cancelled", + "20", + "Remand", + "Austin AAC (200)", + "E. Thompson", + remand_removed_issue_event.readable_event_date, + "Removed issue", + "CHAMPVA", + "CHAMPVA issue description", + remand_removed_issue_event.readable_decision_date, + nil + ] + end + let(:remand_withdrew_issue_event_row) do + [ + "242080004", + "Mason Rodriguez", + "/decision_reviews/vha/tasks/900", + "cancelled", + "20", + "Remand", + "Austin AAC (200)", + "E. Thompson", + remand_withdrew_issue_event.readable_event_date, + "Withdrew issue", + "CHAMPVA", + "CHAMPVA issue description", + remand_withdrew_issue_event.readable_decision_date, + nil + ] + end + it "returns a csv string with the column headers, filters, and event rows" do rows = CSV.parse(subject) expect(rows.count).to eq(2 + events.length) @@ -377,6 +644,16 @@ expect(rows[10]).to eq(added_decision_date_event_row) expect(rows[11]).to eq(removed_issue_event_row) expect(rows[12]).to eq(withdrew_issue_event_row) + expect(rows[13]).to eq(remand_claim_creation_event_row) + expect(rows[14]).to eq(remand_in_progress_status_event_row) + expect(rows[15]).to eq(remand_cancelled_status_event_row) + expect(rows[16]).to eq(remand_completed_status_event_row) + expect(rows[17]).to eq(remand_incomplete_status_event_row) + expect(rows[18]).to eq(remand_added_issue_without_decision_date_event_row) + expect(rows[19]).to eq(remand_completed_disposition_event_row) + expect(rows[20]).to eq(remand_added_decision_date_event_row) + expect(rows[21]).to eq(remand_removed_issue_event_row) + expect(rows[22]).to eq(remand_withdrew_issue_event_row) end end end diff --git a/spec/services/claim_change_history/claim_history_event_spec.rb b/spec/services/claim_change_history/claim_history_event_spec.rb index 1ee915d9c08..8b4aeeabdb8 100644 --- a/spec/services/claim_change_history/claim_history_event_spec.rb +++ b/spec/services/claim_change_history/claim_history_event_spec.rb @@ -48,7 +48,8 @@ "event_date" => change_data_event_date, "task_versions" => version_changes, "days_waiting" => 25, - "task_closed_at" => "2023-10-19 22:47:16.233187" + "task_closed_at" => "2023-10-19 22:47:16.233187", + "type_classifier" => change_data_claim_type } end @@ -851,6 +852,14 @@ expect(subject).to eq("Supplemental Claim") end end + + context "when the claim type is Remand" do + let(:change_data_claim_type) { "Remand" } + + it "readable claim type of Remand" do + expect(subject).to eq("Remand") + end + end end describe ".readable_user_name" do From 99bd221dc9f5a92741477b0f280479f35f846da9 Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:47:32 -0400 Subject: [PATCH 12/19] Initial commit for the brakeman sql injection warning fixes. (#22598) --- app/models/organizations/business_line.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 9fd82d71054..02a881a48af 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -239,6 +239,7 @@ def change_history_rows # Generate all of the filter queries to be used in both the HLR and SC block sql = Arel.sql(change_history_sql_filter_array.join(" ")) sanitized_filters = ActiveRecord::Base.sanitize_sql_array([sql]) + sc_type_clauses = ActiveRecord::Base.sanitize_sql_array([sc_type_filter]) change_history_sql_block = <<-SQL WITH versions_agg AS NOT MATERIALIZED ( @@ -362,7 +363,7 @@ def change_history_rows AND tasks.assigned_to_type = 'Organization' AND tasks.assigned_to_id = '#{parent.id.to_i}' #{sanitized_filters} - #{sc_type_filter} + #{sc_type_clauses} SQL ActiveRecord::Base.transaction do @@ -418,7 +419,11 @@ def sc_type_filter if query_params[:claim_type].present? if query_params[:claim_type].include?(Remand.name) || query_params[:claim_type].include?(SupplementalClaim.name) " AND #{where_clause_from_array(SupplementalClaim, :type, query_params[:claim_type]).to_sql} " + else + "" end + else + "" end end From 4daaba299e46637dd65c5d9ef905e7f1de6bfa35 Mon Sep 17 00:00:00 2001 From: almorbah <149511814+almorbah@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:53:51 -0500 Subject: [PATCH 13/19] Al/APPEALS-45334 Individual change history table which includes Reassign Cases to CAMO events (#22483) * added logic for claim history events table * fixed linting errors * fixing test failures * added change Pr feedback * PR feedback round 2 * changes from round 2 of PR feedback * Added logic event data versions and tests to cover more edgecases * changed RequestedIssueFragment * Updated the individual claim history spec file for test failures. Updated an attribute in the IndividualClaimHistory page that was using the wrong getter. Fixed a code climate linting error. * Removing unnecessary clear filter * Combined some of the individual claim history tests into one test so it will run more quickly. * fixing the withdrawal date versioning issue for different level changes * fixing the spec failure and linter --------- Co-authored-by: Brandon Lee Dorner Co-authored-by: = Co-authored-by: Prajwal Amatya --- app/models/organizations/business_line.rb | 5 +- .../change_history_event_serializer.rb | 7 +- .../claim_history_event.rb | 86 +- .../IndividualClaimHistoryTable.jsx | 249 ++++- .../ClaimHistoryPage.test.js.snap | 266 +++--- spec/factories/issue_modification_request.rb | 1 + .../non_comp/individual_claim_history_spec.rb | 864 +++++++++++++++++- spec/models/business_line_spec.rb | 10 +- .../change_history_event_serializer_spec.rb | 57 +- 9 files changed, 1341 insertions(+), 204 deletions(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 9afed5d3d3f..96bfe862bce 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -225,6 +225,7 @@ def change_history_rows ), imr_version_agg AS (SELECT versions.item_id, versions.item_type, + ARRAY_AGG(versions.object ORDER BY versions.id) AS object_array, ARRAY_AGG(versions.object_changes ORDER BY versions.id) AS object_changes_array FROM versions @@ -286,7 +287,7 @@ def change_history_rows itv.object_changes_array AS imr_versions, lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, - itv.object_changes_array[1] as first_static_version, + itv.object_array as previous_state_array, MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type @@ -393,7 +394,7 @@ def change_history_rows itv.object_changes_array AS imr_versions, lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, - itv.object_changes_array[1] as first_static_version, + itv.object_array as previous_state_array, MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type diff --git a/app/services/claim_change_history/change_history_event_serializer.rb b/app/services/claim_change_history/change_history_event_serializer.rb index a8398468a39..5bd16ea05e9 100644 --- a/app/services/claim_change_history/change_history_event_serializer.rb +++ b/app/services/claim_change_history/change_history_event_serializer.rb @@ -39,7 +39,12 @@ class ChangeHistoryEventSerializer requestor: object.requestor, decider: object.decider, decidedAtDate: object.decided_at_date, - decisionReason: object.decision_reason + decisionReason: object.decision_reason, + previousIssueType: object.previous_issue_type, + previousIssueDescription: object.previous_issue_description, + previousDecisionDate: object.previous_decision_date, + previousModificationRequestReason: object.previous_modification_request_reason, + previousWithdrawalDate: object.previous_withdrawal_date } end end diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb index 3c60a227293..02440c4983e 100644 --- a/app/services/claim_change_history/claim_history_event.rb +++ b/app/services/claim_change_history/claim_history_event.rb @@ -18,7 +18,9 @@ class ClaimHistoryEvent :event_user_css_id, :new_issue_type, :new_issue_description, :new_decision_date, :modification_request_reason, :request_type, :decision_reason, :decided_at_date, :issue_modification_request_withdrawal_date, :requestor, - :decider, :remove_original_issue, :issue_modification_request_status + :decider, :remove_original_issue, :issue_modification_request_status, + :previous_issue_type, :previous_issue_description, :previous_decision_date, + :previous_modification_request_reason, :previous_withdrawal_date EVENT_TYPES = [ :completed_disposition, @@ -109,9 +111,9 @@ def create_issue_modification_request_event(change_data) request_type = change_data["request_type"] event_hash = request_issue_modification_event_hash(change_data) - if change_data["first_static_version"].present? - first_version = parse_versions("{#{change_data['first_static_version']}}") - event_hash.merge!(update_event_hash_data_from_version(first_version[0], 0)) + if change_data["previous_state_array"].present? + first_version = parse_versions(change_data["previous_state_array"])[0] + event_hash.merge!(update_event_hash_data_from_version_object(first_version)) end if request_type == "addition" @@ -124,39 +126,56 @@ def create_issue_modification_request_event(change_data) def create_edited_request_issue_events(change_data) edited_events = [] imr_versions = parse_versions(change_data["imr_versions"]) + previous_version = parse_versions(change_data["previous_state_array"]) if imr_versions.present? *rest_of_versions, last_version = imr_versions + if last_version["status"].present? edited_events.push(*create_last_version_events(change_data, last_version)) else rest_of_versions.push(last_version) end - edited_events.push(*create_event_from_rest_of_versions(change_data, rest_of_versions)) + edited_events.push(*create_event_from_rest_of_versions(change_data, rest_of_versions, previous_version)) else create_pending_status_events(change_data, change_data["issue_modification_request_updated_at"]) end edited_events end - def create_event_from_rest_of_versions(change_data, edited_versions) + def create_event_from_rest_of_versions(change_data, edited_versions, previous_version) edit_of_request_events = [] event_type = :request_edited event_date_hash = {} - - edited_versions.map do |version| + edited_versions.map.with_index do |version, index| event_date_hash = { "event_date" => version["updated_at"][1], "event_user_name" => change_data["requestor"], "user_facility" => change_data["requestor_station_id"] } + # this create_event_from_version_object updated the previous version fields in change data + # that is being used in the front end to show the original records. + if !previous_version[index].nil? + event_date_hash.merge!(create_event_from_version_object(previous_version[index])) + # this update_event_hash_data_from_version_object updates the change_data values with previous or + # unedited data. since change_data has the final version of the data that was updated. + # this is necessary to preserve the history that is displayed in the frontend. + event_date_hash.merge!(update_event_hash_data_from_version_object(previous_version[index])) + end + event_date_hash.merge!(update_event_hash_data_from_version(version, 1)) edit_of_request_events.push(*from_change_data(event_type, change_data.merge(event_date_hash))) end edit_of_request_events end + def create_event_from_version_object(version) + previous_version_database_field.each_with_object({}) do |(db_key, version_key), data| + data[db_key] = version[version_key] unless version[version_key].nil? + end + end + def create_last_version_events(change_data, last_version) edited_events = [] @@ -404,21 +423,17 @@ def task_status_to_event_type(task_status) end def update_event_hash_data_from_version(version, index) - version_database_field_mapping = { - "nonrating_issue_category" => "requested_issue_type", - "nonrating_issue_description" => "requested_issue_description", - "remove_original_issue" => "remove_original_issue", - "request_reason" => "modification_request_reason", - "decision_date" => "requested_decision_date", - "decision_reason" => "decision_reason", - "withdrawal_date" => "issue_modification_request_withdrawal_date" - } - version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data| data[db_key] = version[version_key][index] unless version[version_key].nil? end end + def update_event_hash_data_from_version_object(version) + version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data| + data[db_key] = version[version_key] unless version[version_key].nil? + end + end + def event_from_version(changes, index, change_data) # If there is no task status change in the set of papertrail changes, ignore the object if changes["status"] @@ -463,6 +478,28 @@ def update_event_hash(change_data) } end + def version_database_field_mapping + { + "nonrating_issue_category" => "requested_issue_type", + "nonrating_issue_description" => "requested_issue_description", + "remove_original_issue" => "remove_original_issue", + "request_reason" => "modification_request_reason", + "decision_date" => "requested_decision_date", + "decision_reason" => "decision_reason", + "withdrawal_date" => "issue_modification_request_withdrawal_date" + } + end + + def previous_version_database_field + { + "previous_issue_type" => "nonrating_issue_category", + "previous_issue_description" => "nonrating_issue_description", + "previous_decision_date" => "decision_date", + "previous_modification_request_reason" => "request_reason", + "previous_withdrawal_date" => "withdrawal_date" + } + end + def add_issue_update_event_hash(change_data) # Check the current request issue updates time to see if the issue update is in the correct row # If it is, then do the normal update_event_hash information @@ -734,11 +771,24 @@ def parse_request_issue_modification_attributes(change_data) @decided_at_date = change_data["decided_at"] @issue_modification_request_withdrawal_date = change_data["issue_modification_request_withdrawal_date"] @remove_original_issue = change_data["remove_original_issue"] + @issue_modification_request_status = change_data["issue_modification_request_status"] @requestor = change_data["requestor"] @decider = change_data["decider"] + parse_previous_issue_modification_attributes(change_data) end end + def parse_previous_issue_modification_attributes(change_data) + @previous_issue_type = change_data["previous_issue_type"] || change_data["requested_issue_type"] + @previous_decision_date = change_data["previous_decision_date"] || change_data["requested_decision_date"] + @previous_modification_request_reason = change_data["previous_modification_request_reason"] || + change_data["modification_request_reason"] + @previous_issue_description = change_data["previous_issue_description"] || + change_data["requested_issue_description"] + @previous_withdrawal_date = change_data["previous_withdrawal_date"] || + change_data["issue_modification_request_withdrawal_date"] + end + ############ CSV and Serializer Helpers ############ def abbreviated_user_name(name_string) diff --git a/client/app/nonComp/components/IndividualClaimHistoryTable.jsx b/client/app/nonComp/components/IndividualClaimHistoryTable.jsx index a42bb20078b..78b766a8e4d 100644 --- a/client/app/nonComp/components/IndividualClaimHistoryTable.jsx +++ b/client/app/nonComp/components/IndividualClaimHistoryTable.jsx @@ -1,8 +1,12 @@ -import React from 'react'; +/* eslint-disable max-lines */ +import React, { useState } from 'react'; import QueueTable from '../../queue/QueueTable'; import BENEFIT_TYPES from 'constants/BENEFIT_TYPES'; import { formatDateStr } from 'app/util/DateUtil'; import PropTypes from 'prop-types'; +import StringUtil from 'app/util/StringUtil'; + +const { capitalizeFirst } = StringUtil; const IndividualClaimHistoryTable = (props) => { @@ -34,25 +38,153 @@ const IndividualClaimHistoryTable = (props) => { return Claim can be processed.; }; + const ClaimPendingFragment = () => { + return Claim cannot be processed until VHA admin reviews pending requests.; + }; + const ClaimIncompleteFragment = () => { return Claim cannot be processed until decision date is entered.; }; + const benefitType = (details) => { + return <> + Benefit type: {BENEFIT_TYPES[details.benefitType]}
+ ; + }; + const ClaimClosedFragment = (details) => { - return - Claim closed.
+ const fragment = details.eventType === 'cancelled' ? <> + Claim cancelled. + : <> + Claim closed.
Claim decision date: {formatDecisionDate(details.dispositionDate)} -
; + ; + + return ( +
+ {fragment} +
+ ); }; const AddedIssueFragment = (details) => { return - Benefit type: {BENEFIT_TYPES[details.benefitType]}
+ { benefitType(details) } Issue type: {details.issueType}
Issue description: {details.issueDescription}
; }; + const RequestedIssueFragment = (details) => { + return + { benefitType(details) } + Issue type: {details.newIssueType}
+ Issue description: {details.newIssueDescription}
+ Decision date: {formatDecisionDate(details.newDecisionDate)}
+ {capitalizeFirst(details.requestType)} request reason: {details.modificationRequestReason}
+
; + }; + + const WithdrawalRequestedIssueFragment = (details) => { + return <> + + Withdrawal request date: {formatDecisionDate(details.issueModificationRequestWithdrawalDate)}
+ ; + }; + + const formatLabel = (baseLabel, prefix) => { + if (prefix) { + return `${prefix} ${baseLabel.toLowerCase()}`; + } + + return baseLabel; + }; + + const previousModificationFragment = (details, prefix) => { + return + {formatLabel('Issue type:', prefix)} {details.previousIssueType}
+ {formatLabel('Issue description:', prefix)} {details.previousIssueDescription}
+ {formatLabel('Decision date:', prefix)} {formatDecisionDate(details.previousDecisionDate)}
+ {capitalizeFirst(details.requestType)} request reason: {details.previousModificationRequestReason}
+
; + }; + + const RequestedIssueModificationFragment = (details) => { + return + { benefitType(details) } + Current issue type: {details.issueType}
+ Current issue description: {details.issueDescription}
+ Current decision date: {formatDecisionDate(details.decisionDate)}
+ { previousModificationFragment(details, 'New') } +
; + }; + + const RemoveOriginalIssueFragment = (details) => { + return + Remove original issue: {details.removeOriginalIssue ? 'Yes' : 'No' }
+
; + }; + + const requestDecision = (details) => { + return + Request decision: {details.issueModificationRequestStatus === 'denied' ? 'Rejected' : 'Approved'}
+
; + }; + + const RequestedIssueDecisionFragment = (details) => { + return + {requestDecision(details)} + { details.issueModificationRequestStatus === 'approved' && details.requestType === 'modification' ? + : null + } + { details.issueModificationRequestStatus === 'denied' ? + + Reason for rejection: {details.decisionReason}
+
: null + } + Request originated by: {details.requestor}
+
; + }; + + const modificationRequestReason = (details) => { + return + New {details.requestType} request reason: {details.modificationRequestReason}
+
; + }; + + const EditOfRequestIssueModification = (details) => { + let component = null; + + switch (details.requestType) { + case 'modification': + case 'addition': + component = + New issue type: {details.newIssueType}
+ New issue description: {details.newIssueDescription}
+ New decision date: {formatDecisionDate(details.newDecisionDate)}
+ {modificationRequestReason(details)} +
; + break; + case 'removal': + component = + {modificationRequestReason(details)} + ; + break; + case 'withdrawal': + component = + {modificationRequestReason(details)} + New withdrawal request date: {formatDecisionDate(details.issueModificationRequestWithdrawalDate)}
+
; + break; + default: + return null; + } + + return + {component} + ; + }; + const AddedIssueWithDateFragment = (details) => { return @@ -93,12 +225,74 @@ const IndividualClaimHistoryTable = (props) => { ; }; - const DetailsFragment = (row) => { + const WithdrawnRequestedIssueModificationFragment = (details) => { + return + + Withdrawal request date: {formatDecisionDate(details.previousWithdrawalDate)}
+
; + }; + + const PreviousFragmentWithBenefitType = (details) => { + return + { benefitType(details) } + { previousModificationFragment(details) } + ; + }; + + const OriginalRequestedIssueModificationFragment = (details) => { let component = null; - const { readableEventType, details } = row; + switch (details.requestType) { + case 'modification': + component = ; + break; + case 'addition': + case 'removal': + component = ; + break; + case 'withdrawal': + component = ; + break; + default: + return null; + } + + return ( +
+ {component} +
+ ); + }; + + const OriginalDetailsFragments = (row) => { + const { details, modificationRequestDetails } = row; + const requestModificationDetails = { ...details, ...modificationRequestDetails }; - const detailsExtended = { ...details, eventDate: row.eventDate }; + const [isOpen, setIsOpen] = useState(false); + + const toggle = () => { + setIsOpen(!isOpen); + }; + + return ( + + ); + }; + + const DetailsFragment = (row) => { + + let component = null; + const { readableEventType, details, modificationRequestDetails } = row; + const detailsExtended = { ...details, eventDate: row.eventDate, eventType: row.eventType }; + const requestIssueModificationDetails = { ...modificationRequestDetails, ...detailsExtended }; switch (readableEventType) { case 'Claim created': @@ -110,6 +304,9 @@ const IndividualClaimHistoryTable = (props) => { case 'Completed disposition': component = ; break; + case 'Claim status - Pending': + component = ; + break; case 'Claim status - In progress': component = ; break; @@ -131,13 +328,42 @@ const IndividualClaimHistoryTable = (props) => { case 'Removed issue': component = ; break; + case 'Cancellation of request': + component = ; + break; + case 'Requested issue modification': + component = ; + break; + case 'Requested issue addition': + case 'Requested issue removal': + component = ; + break; + case 'Requested issue withdrawal': + component = ; + break; + case `Edit of request - issue ${requestIssueModificationDetails.requestType}`: + component = ; + break; + case `Rejection of request - issue ${requestIssueModificationDetails.requestType}`: + case `Approval of request - issue ${requestIssueModificationDetails.requestType}`: + component = ; + break; default: return null; } - return

- {component} -

; + const chunk = [ + 'request_approved', + 'request_edited', + 'request_denied' + ]; + + return ( +
+

{component}

+ { chunk.includes(requestIssueModificationDetails.eventType) ? : null } +
+ ); }; const dateSort = (row) => { @@ -210,3 +436,4 @@ IndividualClaimHistoryTable.propTypes = { }; export default IndividualClaimHistoryTable; +/* eslint-enable max-lines */ diff --git a/client/test/app/nonComp/pages/__snapshots__/ClaimHistoryPage.test.js.snap b/client/test/app/nonComp/pages/__snapshots__/ClaimHistoryPage.test.js.snap index da3dc91ccc1..2c211e5097e 100644 --- a/client/test/app/nonComp/pages/__snapshots__/ClaimHistoryPage.test.js.snap +++ b/client/test/app/nonComp/pages/__snapshots__/ClaimHistoryPage.test.js.snap @@ -388,32 +388,34 @@ exports[`ClaimHistoryPage renders correctly for Admin user renders correctly 1`] role="gridcell" tabindex="-1" > -

- - Benefit type: - - Veterans Health Administration -
- - Issue type: - - Other -
- - Issue description: - - issue added after removing unidentified issues -
- - Decision date: - - 09/12/2023 -
- - Removed issue date: - - 02/23/2024 -

+
+

+ + Benefit type: + + Veterans Health Administration +
+ + Issue type: + + Other +
+ + Issue description: + + issue added after removing unidentified issues +
+ + Decision date: + + 09/12/2023 +
+ + Removed issue date: + + 02/23/2024 +

+
-

- - Benefit type: - - Veterans Health Administration -
- - Issue type: - - Beneficiary Travel -
- - Issue description: - - go fly -
- - Decision date: - - 02/23/2024 -
-

+
+

+ + Benefit type: + + Veterans Health Administration +
+ + Issue type: + + Beneficiary Travel +
+ + Issue description: + + go fly +
+ + Decision date: + + 02/23/2024 +
+

+
-

- - Benefit type: - - Veterans Health Administration -
- - Issue type: - -
- - Issue description: - - unidentified issue description -
- - Decision date: - - No decision date -
- - Removed issue date: - - 12/12/2023 -

+
+

+ + Benefit type: + + Veterans Health Administration +
+ + Issue type: + +
+ + Issue description: + + unidentified issue description +
+ + Decision date: + + No decision date +
+ + Removed issue date: + + 12/12/2023 +

+
-

- - Benefit type: - - Veterans Health Administration -
- - Issue type: - - Other -
- - Issue description: - - issue added after removing unidentified issues -
- - Decision date: - - 09/12/2023 -
-

+
+

+ + Benefit type: + + Veterans Health Administration +
+ + Issue type: + + Other +
+ + Issue description: + + issue added after removing unidentified issues +
+ + Decision date: + + 09/12/2023 +
+

+
-

- Claim can be processed. -

+
+

+ Claim can be processed. +

+
-

- - Benefit type: - - Veterans Health Administration -
- - Issue type: - -
- - Issue description: - - unidentified issue description -
- - Decision date: - - No decision date -
-

+
+

+ + Benefit type: + + Veterans Health Administration +
+ + Issue type: + +
+ + Issue description: + + unidentified issue description +
+ + Decision date: + + No decision date +
+

+
-

- Claim cannot be processed until decision date is entered. -

+
+

+ Claim cannot be processed until decision date is entered. +

+
-

- Claim created. -

+
+

+ Claim created. +

+
diff --git a/spec/factories/issue_modification_request.rb b/spec/factories/issue_modification_request.rb index 8f528b53f5c..48fb4d8cb12 100644 --- a/spec/factories/issue_modification_request.rb +++ b/spec/factories/issue_modification_request.rb @@ -6,6 +6,7 @@ request_reason { Faker::Lorem.sentence } benefit_type { "vha" } + nonrating_issue_description { Faker::Lorem.sentence } nonrating_issue_category { Constants::ISSUE_CATEGORIES["vha"].sample } status { "assigned" } diff --git a/spec/feature/non_comp/individual_claim_history_spec.rb b/spec/feature/non_comp/individual_claim_history_spec.rb index f7d5a141752..95cf20e8ca2 100644 --- a/spec/feature/non_comp/individual_claim_history_spec.rb +++ b/spec/feature/non_comp/individual_claim_history_spec.rb @@ -10,27 +10,869 @@ let(:non_comp_org) { VhaBusinessLine.singleton } let(:user) { create(:default_user, css_id: "REPORT USER", full_name: "Report User") } let(:veteran) { create(:veteran) } - let!(:task) { create(:higher_level_review_vha_task_with_decision) } - let(:task_history_url) { "/decision_reviews/vha/tasks/#{task.id}/history" } - let(:events) { ClaimHistoryService.new(non_comp_org, task_id: task.id).build_events } before do User.stub = user non_comp_org.add_user(user) OrganizationsUser.make_user_admin(user, non_comp_org) - visit task_history_url end - scenario "display the claim history table" do - number_of_events = events.length + def click_filter_option(filter_text) + sort = find("[aria-label='Filter by Activity']") + sort.click - expect(page).to have_text("Viewing 1-#{number_of_events} of #{number_of_events} total") + dropdown_filter = page.find(class: "cf-dropdown-filter") + dropdown_filter.find("label", text: filter_text, match: :prefer_exact).click + end + + def clear_filter_option(filter_text) + sort = find("[aria-label='Filter by Activity. Filtering by #{filter_text}']") + sort.click + + clear_button_filter = page.find(class: "cf-clear-filter-button-wrapper") + clear_button_filter.click + end + + describe "Check for event hitsoty activity dynamic labels" do + let!(:task_event) do + create(:issue_modification_request, + :with_higher_level_review_with_decision, + nonrating_issue_category: "Medical and Dental Care Reimbursement") + end + + # Edited then approved + let!(:issue_modification_request_modification_edit) do + request = create(:issue_modification_request, + :with_request_issue, + :edit_of_request, + request_type: "modification", + decision_review: task_event.decision_review) + request.update(status: "approved") + request + end + + let!(:issue_modification_request_withdrawal_edit) do + request = create(:issue_modification_request, + :with_request_issue, + :edit_of_request, + :withdrawal, + decision_review: task_event.decision_review) + request.update(status: "approved") + request + end + + let!(:issue_modification_request_addition_edit) do + request = create(:issue_modification_request, + :edit_of_request, + decision_review: task_event.decision_review) + request.update(status: "approved") + request + end + + let!(:issue_modification_request_removal_edit) do + request = create(:issue_modification_request, + :with_request_issue, + :edit_of_request, + request_type: "removal", + decision_review: task_event.decision_review) + request.update(status: "approved") + request + end + + # cancelled + let!(:cancelled_issue_modification_request_modification) do + create(:issue_modification_request, + :with_request_issue, + :cancel_of_request, + request_type: "modification", + decision_review: task_event.decision_review) + end + + let!(:cancelled_issue_modification_request_withdrawal) do + create(:issue_modification_request, + :with_request_issue, + :cancel_of_request, + :withdrawal, + decision_review: task_event.decision_review) + end + + let!(:cancelled_issue_modification_request_addition) do + create(:issue_modification_request, + :cancel_of_request, + decision_review: task_event.decision_review) + end + + let!(:cancelled_issue_modification_request_removal) do + create(:issue_modification_request, + :with_request_issue, + :cancel_of_request, + request_type: "removal", + decision_review: task_event.decision_review) + end + + # Rejected aka Denied + let!(:denied_issue_modification_request_modification) do + request = create(:issue_modification_request, + :with_request_issue, + request_type: "modification", + decision_review: task_event.decision_review) + + request.update( + status: "denied", + decision_reason: "Decision for denial" + ) + request + end + + let!(:denied_issue_modification_request_withdrawal) do + request = create(:issue_modification_request, + :with_request_issue, + :withdrawal, + decision_review: task_event.decision_review) + + request.update( + status: "denied", + decision_reason: "Decision for denial" + ) + request + end + + let!(:denied_issue_modification_request_addition) do + request = create(:issue_modification_request, + decision_review: task_event.decision_review) + + request.update( + status: "denied", + decision_reason: "Decision for denial" + ) + request + end + + let!(:denied_issue_modification_request_removal) do + request = create(:issue_modification_request, + :with_request_issue, + request_type: "removal", + decision_review: task_event.decision_review) + + request.update( + status: "denied", + decision_reason: "Decision for denial" + ) + request + end + + let!(:claim_closed) do + create(:higher_level_review_vha_task_with_decision) + end + + let(:task_id) { task_event.decision_review.tasks.ids[0] } + + let(:task_history_url) { "/decision_reviews/vha/tasks/#{task_id}/history" } + let(:events) { ClaimHistoryService.new(non_comp_org, task_id: task_id).build_events } + + before do + visit task_history_url + end + + let(:event_types) { events.map(&:readable_event_type).uniq.sort! } + + context "Testing all Event Types" do + it "Filtering each event and verifiying row attributes" do + step "display the claim history table count" do + expect(page).to have_text("Viewing 1-15 of #{events.length} total") + + click_filter_option("Approval of request - issue addition (1)") + expect(event_types.include?("Approval of request - issue addition")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Approval of request - issue addition") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Request originated by:") + + expect(table_row).to have_content("View original request") + table_row.first("a", text: "View original request").click + + expect(table_row).to have_content("Hide original request") + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Addition request reason:") + clear_filter_option("Approval of request - issue addition") + end + + step "Checking Approval of request - issue modification" do + click_filter_option("Approval of request - issue modification (1)") + expect(event_types.include?("Approval of request - issue modification")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Approval of request - issue modification") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Remove original issue:") + expect(table_row).to have_content("Request originated by") + expect(table_row).to have_content("View original request") + + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Current issue type:") + expect(table_row).to have_content("Current issue description:") + expect(table_row).to have_content("Current decision date:") + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("Modification request reason:") + clear_filter_option("Approval of request - issue modification") + end + + step "Checking Approval of request - issue removal" do + click_filter_option("Approval of request - issue removal (1)") + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + expect(event_types.include?("Approval of request - issue removal")).to be_truthy + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Approval of request - issue removal") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Request originated by") + expect(table_row).to have_content("View original request") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Removal request reason:") + clear_filter_option("Approval of request - issue removal") + end + + step "Checking Approval of request - issue withdrawal" do + click_filter_option("Approval of request - issue withdrawal (1)") + expect(event_types.include?("Approval of request - issue withdrawal")).to be_truthy + + table = page.find("tbody") + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Approval of request - issue withdrawal") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Request originated by") + expect(table_row).to have_content("View original request") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Withdrawal request reason:") + expect(table_row).to have_content("Withdrawal request date:") + clear_filter_option("Approval of request - issue withdrawal") + end + + step "Checking Cancellation of request" do + click_filter_option("Cancellation of request") + expect(event_types.include?("Cancellation of request")).to be_truthy + table = page.find("tbody") + + expect(table).to have_selector("tr", count: 4) + clear_filter_option("Cancellation of request") + end + + step "Checking Edit of request - issue addition" do + click_filter_option("Edit of request - issue addition (1)") + expect(event_types.include?("Edit of request - issue addition")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first('tr[id^="table-row"]') + + expect(table_row).to have_content("Edit of request - issue addition") + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("New addition request reason:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Addition request reason:") + clear_filter_option("Edit of request - issue addition") + end + + step "Checking Edit of request - issue modification" do + click_filter_option("Edit of request - issue modification (1)") + expect(event_types.include?("Edit of request - issue modification")).to be_truthy + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Edit of request - issue modification") + + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("New modification request reason:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Current issue type:") + expect(table_row).to have_content("Current issue description:") + expect(table_row).to have_content("Current decision date:") + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("Modification request reason:") + + clear_filter_option("Edit of request - issue modification") + end + + step "Checking Edit of request - issue removal" do + click_filter_option("Edit of request - issue removal (4)") + expect(event_types.include?("Edit of request - issue removal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 4) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Edit of request - issue removal") + expect(table_row).to have_content("New removal request reason:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Removal request reason:") + clear_filter_option("Edit of request - issue removal") + end + + step "Checking Edit of request - issue withdrawal" do + click_filter_option("Edit of request - issue withdrawal (4)") + expect(event_types.include?("Edit of request - issue withdrawal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 4) + + table_row = table.first("tr") + expect(table_row).to have_content("Edit of request - issue withdrawal") + expect(table_row).to have_content("New withdrawal request reason:") + expect(table_row).to have_content("New withdrawal request date:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Withdrawal request reason:") + expect(table_row).to have_content("Withdrawal request date:") + clear_filter_option("Edit of request - issue withdrawal") + end + + step "Checking Rejection of request - issue addition" do + click_filter_option("Rejection of request - issue addition (1)") + expect(event_types.include?("Rejection of request - issue addition")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Reason for rejection:") + expect(table_row).to have_content("Request originated by:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Addition request reason:") + + clear_filter_option("Rejection of request - issue addition") + end + + step "Checking Rejection of request - issue modification" do + click_filter_option("Rejection of request - issue modification (1)") + expect(event_types.include?("Rejection of request - issue modification")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Reason for rejection:") + expect(table_row).to have_content("Request originated by:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Current issue type:") + expect(table_row).to have_content("Current issue description:") + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("Modification request reason:") + + clear_filter_option("Rejection of request - issue modification") + end + + step "Checking Rejection of request - issue removal" do + click_filter_option("Rejection of request - issue removal (1)") + expect(event_types.include?("Rejection of request - issue removal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Reason for rejection:") + expect(table_row).to have_content("Request originated by:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Decision date:") + + clear_filter_option("Rejection of request - issue removal") + end + + step "Checking Rejection of request - issue withdrawal" do + click_filter_option("Rejection of request - issue withdrawal (1)") + expect(event_types.include?("Rejection of request - issue withdrawal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Request decision:") + expect(table_row).to have_content("Reason for rejection:") + expect(table_row).to have_content("Request originated by:") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + + expect(table_row).to have_content("Hide original request") + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Withdrawal request reason:") + expect(table_row).to have_content("Withdrawal request date:") + + clear_filter_option("Rejection of request - issue withdrawal") + end + + step "Checking Added issue" do + click_filter_option("Added issue") + expect(event_types.include?("Added issue")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 10) + + table_row = table.first("tr") + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + + clear_filter_option("Added issue") + end + + step "checking Requested issue addition" do + click_filter_option("Requested issue addition (4)") + expect(event_types.include?("Requested issue addition")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 4) + + table_row = table.first("tr") + expect(table_row).to have_content("Requested issue addition") + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Addition request reason:") + + clear_filter_option("Requested issue addition") + end + + step "Checking Requested issue modification" do + click_filter_option("Requested issue modification (3)") + expect(event_types.include?("Requested issue modification")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 3) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Requested issue modification") + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Current issue type:") + expect(table_row).to have_content("Current issue description:") + expect(table_row).to have_content("Current decision date:") + expect(table_row).to have_content("New issue type:") + expect(table_row).to have_content("New issue description:") + expect(table_row).to have_content("New decision date:") + expect(table_row).to have_content("Modification request reason:") + + clear_filter_option("Requested issue modification") + end + + step "Checking Requested issue removal" do + click_filter_option("Requested issue removal (3)") + expect(event_types.include?("Requested issue removal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 3) + + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Requested issue removal") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Removal request reason:") + + clear_filter_option("Requested issue removal") + end + + step "Checking Requested issue withdrawal" do + click_filter_option("Requested issue withdrawal (3)") + expect(event_types.include?("Requested issue withdrawal")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 3) + + table_row = table.first("tr") + expect(table_row).to have_content("Requested issue withdrawal") + + expect(table_row).to have_content("Benefit type:") + expect(table_row).to have_content("Issue type:") + expect(table_row).to have_content("Issue description:") + expect(table_row).to have_content("Decision date:") + expect(table_row).to have_content("Withdrawal request reason:") + expect(table_row).to have_content("Withdrawal request date:") + + clear_filter_option("Requested issue withdrawal") + end + + step "Checking Claim status - Pending" do + click_filter_option("Claim status - Pending (1)") + expect(event_types.include?("Claim status - Pending")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Claim status - Pending") + expect(table_row).to have_content("Claim cannot be processed until VHA admin reviews pending requests.") + + clear_filter_option("Claim status - Pending") + end + + step "Checking Claim created" do + click_filter_option("Claim created (1)") + expect(event_types.include?("Claim created")).to be_truthy + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Claim created") + expect(table_row).to have_content("Claim created.") + end + end + end + + context "should do expected details to show claim close for a claim close" do + before { visit "/decision_reviews/vha/tasks/#{claim_closed.id}/history" } + + it "Claim closed" do + click_filter_option("Claim closed (1)") + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + + table_row = table.first("tr") + expect(table_row).to have_content("Claim closed") + expect(table_row).to have_content("Claim decision date:") + end + end + end + + describe "check for dynamic data coming in" do + let!(:task_event_two) do + create(:issue_modification_request, + :with_higher_level_review_with_decision, + nonrating_issue_category: "Medical and Dental Care Reimbursement") + end + let(:task_event_two_id) { task_event_two.decision_review.tasks.ids[0] } + let!(:approved_modification_edit) do + request = create(:issue_modification_request, + :with_request_issue, + :edit_of_request, + request_type: "modification", + decision_review: task_event_two.decision_review) + request.update(status: "approved") + request + end + + let!(:cancelled_issue_modification_request_modification) do + create(:issue_modification_request, + :with_request_issue, + :cancel_of_request, + request_type: "modification", + decision_review: task_event_two.decision_review) + end + + let!(:denied_issue_modification_request_modification) do + request = create(:issue_modification_request, + :with_request_issue, + request_type: "modification", + decision_review: task_event_two.decision_review) + + request.update( + status: "denied", + decision_reason: "Decision for approval" + ) + request + end + + let(:events) { ClaimHistoryService.new(non_comp_org, task_id: task_event_two_id).build_events } + + before do + visit "/decision_reviews/vha/tasks/#{task_event_two_id}/history" + end + + context "Check for data output" do + it "check for the correct data for Edited Request Modification" do + click_filter_option("Edit of request - issue modification (1)") + + original_modification_request = events.detect { |e| e.event_type == :request_edited } + + new_decision_date = original_modification_request.new_decision_date + request_issue_decision_date = Date.parse(original_modification_request.decision_date) + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Edit of request - issue modification") + expect(table_row).to have_content("New issue type: #{approved_modification_edit.nonrating_issue_category}") + expect(table_row).to have_content( + "New issue description: #{approved_modification_edit.nonrating_issue_description}" + ) + expect(table_row).to have_content( + "New decision date: #{approved_modification_edit.decision_date.strftime('%m/%d/%Y')}" + ) + expect(table_row).to have_content( + "New modification request reason: #{approved_modification_edit.request_reason}" + ) + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type: Veterans Health Administration") + expect(table_row).to have_content("Current issue type: #{original_modification_request.issue_type}") + expect(table_row).to have_content( + "Current issue description: #{original_modification_request.issue_description}" + ) + expect(table_row).to have_content( + "Current decision date: #{request_issue_decision_date.strftime('%m/%d/%Y')}" + ) + expect(table_row).to have_content("New issue type: #{original_modification_request.new_issue_type}") + expect(table_row).to have_content( + "New issue description: #{original_modification_request.new_issue_description}" + ) + expect(table_row).to have_content("New decision date: #{new_decision_date.strftime('%m/%d/%Y')}") + expect(table_row).to have_content( + "Modification request reason: #{original_modification_request.previous_modification_request_reason}" + ) + + clear_filter_option("Edit of request - issue modification") + + step "check for the correct data for Approval of request - issue modification" do + click_filter_option("Approval of request - issue modification (1)") + + original_modification_request = events.reverse.find { |e| e.event_type == :request_approved } + new_decision_date = Date.parse(original_modification_request.new_decision_date) + request_issue_decision_date = Date.parse(original_modification_request.decision_date) + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Approval of request - issue modification") + + expect(table_row).to have_content("Request decision: #{ + original_modification_request.issue_modification_request_status == 'denied' ? 'Rejected' : 'Approved' + }") + + expect(table_row).to have_content("Remove original issue: #{ + original_modification_request.remove_original_issue ? 'Yes' : 'No' + }") + expect(table_row).to have_content("Request originated by: #{original_modification_request.requestor}") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type: Veterans Health Administration") + expect(table_row).to have_content("Current issue type: #{original_modification_request.issue_type}") + expect(table_row).to have_content( + "Current issue description: #{original_modification_request.issue_description}" + ) + expect(table_row).to have_content( + "Current decision date: #{request_issue_decision_date.strftime('%m/%d/%Y')}" + ) + expect(table_row).to have_content("New issue type: #{original_modification_request.new_issue_type}") + expect(table_row).to have_content( + "New issue description: #{original_modification_request.new_issue_description}" + ) + expect(table_row).to have_content("New decision date: #{new_decision_date.strftime('%m/%d/%Y')}") + expect(table_row).to have_content( + "Modification request reason: #{original_modification_request.modification_request_reason}" + ) + + clear_filter_option("Approval of request - issue modification") + end + + step "check for the correct data for denied request modification" do + click_filter_option("Rejection of request - issue modification (1)") + + original_modification_request = events.detect { |e| e.event_type == :request_denied } + new_decision_date = Date.parse(original_modification_request.new_decision_date) + request_issue_decision_date = Date.parse(original_modification_request.decision_date) + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Rejection of request - issue modification") + + expect(table_row).to have_content("Request decision: #{ + original_modification_request.issue_modification_request_status == 'denied' ? 'Rejected' : 'Approved' + }") + expect(table_row).to have_content("Request originated by: #{original_modification_request.requestor}") + + expect(table_row).to have_content("View original request") + first("a", text: "View original request").click + expect(table_row).to have_content("Hide original request") + + expect(table_row).to have_content("Benefit type: Veterans Health Administration") + expect(table_row).to have_content("Current issue type: #{original_modification_request.issue_type}") + expect(table_row).to have_content( + "Current issue description: #{original_modification_request.issue_description}" + ) + expect(table_row).to have_content( + "Current decision date: #{request_issue_decision_date.strftime('%m/%d/%Y')}" + ) + expect(table_row).to have_content("New issue type: #{original_modification_request.new_issue_type}") + expect(table_row).to have_content( + "New issue description: #{original_modification_request.new_issue_description}" + ) + expect(table_row).to have_content("New decision date: #{new_decision_date.strftime('%m/%d/%Y')}") + expect(table_row).to have_content( + "Modification request reason: #{original_modification_request.modification_request_reason}" + ) + + clear_filter_option("Rejection of request - issue modification") + end + + step "check for the correct data for Cancellation of request" do + click_filter_option("Cancellation of request (1)") + + original_modification_request = events.detect { |e| e.event_type == :request_cancelled } + + new_decision_date = Date.parse(original_modification_request.new_decision_date) + request_issue_decision_date = Date.parse(original_modification_request.decision_date) + + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) + table_row = table.first('tr[id^="table-row"]') + expect(table_row).to have_content("Cancellation of request") + + expect(table_row).to have_content("Benefit type: Veterans Health Administration") + expect(table_row).to have_content( + "Current issue type: #{original_modification_request.issue_type}" + ) + expect(table_row).to have_content( + "Current issue description: #{original_modification_request.issue_description}" + ) + expect(table_row).to have_content( + "Current decision date: #{request_issue_decision_date.strftime('%m/%d/%Y')}" + ) + expect(table_row).to have_content("New issue type: #{original_modification_request.new_issue_type}") + expect(table_row).to have_content( + "New issue description: #{original_modification_request.new_issue_description}" + ) + expect(table_row).to have_content("New decision date: #{new_decision_date.strftime('%m/%d/%Y')}") + expect(table_row).to have_content( + "Modification request reason: #{original_modification_request.modification_request_reason}" + ) + end + end + end + end + + describe "check for cancelled claim" do + let!(:cancelled_claim) do + create(:supplemental_claim, :with_vha_issue, :with_update_users) + end + + before do + cancelled_claim.establish! + cancelled_claim.request_issues.update(closed_status: "withdrawn", closed_at: Time.zone.now) + cancelled_claim.tasks.update(status: "cancelled") + cancelled_claim.reload + + visit "/decision_reviews/vha/tasks/#{cancelled_claim.tasks[0].id}/history" + end + + context "should do expected details to show claim closed when cancelled" do + it "Claim Cancelled" do + click_filter_option("Claim closed (1)") - table = page.find("tbody") - expect(table).to have_selector("tr", count: number_of_events) + table = page.find("tbody") + expect(table).to have_selector("tr", count: 1) - events.each do |event| - expect(page).to have_text(event.readable_event_type) + table_row = table.first("tr") + expect(table_row).to have_content("Claim closed") + expect(table_row).to have_content("Claim cancelled.") + end end end end diff --git a/spec/models/business_line_spec.rb b/spec/models/business_line_spec.rb index e180d980eca..61596fd1e6b 100644 --- a/spec/models/business_line_spec.rb +++ b/spec/models/business_line_spec.rb @@ -490,14 +490,16 @@ create(:issue_modification_request, :with_higher_level_review, :edit_of_request, - nonrating_issue_category: "Medical and Dental Care Reimbursement") + nonrating_issue_category: "Medical and Dental Care Reimbursement", + nonrating_issue_description: "Reimbursement note description") end let!(:sc_task_with_imr) do create(:issue_modification_request, :with_supplemental_claim, :edit_of_request, - nonrating_issue_category: "Medical and Dental Care Reimbursement") + nonrating_issue_category: "Medical and Dental Care Reimbursement", + nonrating_issue_description: "Reimbursement note description") end let(:decision_issue) { create(:decision_issue, disposition: "denied", benefit_type: hlr_task.appeal.benefit_type) } let(:intake_user) { create(:user, full_name: "Alexander Dewitt", css_id: "ALEXVHA", station_id: "103") } @@ -602,7 +604,7 @@ let(:imr_hlr_expectation) do a_hash_including( "requested_issue_type" => "Medical and Dental Care Reimbursement", - "requested_issue_description" => nil, + "requested_issue_description" => "Reimbursement note description", "remove_original_issue" => false, "modification_request_reason" => "I edited this request.", "request_type" => "addition", @@ -613,7 +615,7 @@ let(:imr_sc_expectation) do a_hash_including( "requested_issue_type" => "Medical and Dental Care Reimbursement", - "requested_issue_description" => nil, + "requested_issue_description" => "Reimbursement note description", "remove_original_issue" => false, "modification_request_reason" => "I edited this request.", "request_type" => "addition", diff --git a/spec/services/claim_change_history/change_history_event_serializer_spec.rb b/spec/services/claim_change_history/change_history_event_serializer_spec.rb index 474ea72db40..1cf20284e7c 100644 --- a/spec/services/claim_change_history/change_history_event_serializer_spec.rb +++ b/spec/services/claim_change_history/change_history_event_serializer_spec.rb @@ -36,6 +36,29 @@ ClaimHistoryService.new(vha_org, task_id: vha_task.id).build_events end + let(:modificationRequestDetailsObject) do + { + benefitType: "vha", + requestType: nil, + issueModificationRequestWithdrawalDate: nil, + modificationRequestReason: nil, + newDecisionDate: nil, + newIssueDescription: nil, + newIssueType: nil, + previousDecisionDate: nil, + previousIssueDescription: nil, + previousIssueType: nil, + previousModificationRequestReason: nil, + previousWithdrawalDate: nil, + removeOriginalIssue: nil, + issueModificationRequestStatus: nil, + requestor: nil, + decider: nil, + decidedAtDate: nil, + decisionReason: nil + } + end + let(:serialized_hash_array) do [ { @@ -60,22 +83,7 @@ issueType: nil, withdrawalRequestDate: nil }, - modificationRequestDetails: - { - benefitType: "vha", - requestType: nil, - issueModificationRequestWithdrawalDate: nil, - modificationRequestReason: nil, - newDecisionDate: nil, - newIssueDescription: nil, - newIssueType: nil, - removeOriginalIssue: nil, - issueModificationRequestStatus: nil, - requestor: nil, - decider: nil, - decidedAtDate: nil, - decisionReason: nil - } + modificationRequestDetails: modificationRequestDetailsObject } }, { @@ -96,22 +104,7 @@ issueType: "Other", withdrawalRequestDate: nil }, - modificationRequestDetails: - { - benefitType: "vha", - requestType: nil, - issueModificationRequestWithdrawalDate: nil, - modificationRequestReason: nil, - newDecisionDate: nil, - newIssueDescription: nil, - newIssueType: nil, - removeOriginalIssue: nil, - issueModificationRequestStatus: nil, - requestor: nil, - decider: nil, - decidedAtDate: nil, - decisionReason: nil - }, + modificationRequestDetails: modificationRequestDetailsObject, eventDate: events[1].event_date, eventType: :added_issue, eventUser: "L. Roth", From 509be31867e9b5d46716093f1057ec2e729b854a Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:22:36 -0400 Subject: [PATCH 14/19] Updated the business line reporter class to properly display Remands as a seperate type than SupplementalClaims. Also added additional testing logic for the business line reporter for hlr and scs since it was only covering appeals. (#22664) Co-authored-by: Robert Travis Pierce --- app/services/business_line_reporter.rb | 2 +- spec/services/business_line_reporter_spec.rb | 23 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/services/business_line_reporter.rb b/app/services/business_line_reporter.rb index 1ac8490b042..92637539d58 100644 --- a/app/services/business_line_reporter.rb +++ b/app/services/business_line_reporter.rb @@ -26,7 +26,7 @@ def as_csv csv << [ business_line.name, task.appeal_id, - task.appeal_type, + task.appeal.class.review_title, task.appeal.claimant&.name, task.appeal.request_issues.size, task.appeal.decision_issues.size, diff --git a/spec/services/business_line_reporter_spec.rb b/spec/services/business_line_reporter_spec.rb index ffbf5bf373e..1a5604e3501 100644 --- a/spec/services/business_line_reporter_spec.rb +++ b/spec/services/business_line_reporter_spec.rb @@ -14,17 +14,35 @@ let(:third_appeal) { create(:appeal, :with_post_intake_tasks) } let(:third_ama_task) { create(:ama_task, appeal: third_appeal, assigned_to: business_line) } + let(:remand) { create(:remand, benefit_type: business_line.url, claimant_type: :veteran_claimant) } + let(:remand_task) do + DecisionReviewTask.create!(appeal: remand, assigned_at: Time.zone.now, assigned_to: business_line) + end + + let(:hlr) { create(:higher_level_review, benefit_type: business_line.url, claimant_type: :veteran_claimant) } + let(:hlr_task) { DecisionReviewTask.create!(appeal: hlr, assigned_at: Time.zone.now, assigned_to: business_line) } + + let(:supplemental_claim) do + create(:supplemental_claim, benefit_type: business_line.url, claimant_type: :veteran_claimant) + end + let(:sc_task) do + DecisionReviewTask.create!(appeal: supplemental_claim, assigned_at: Time.zone.now, assigned_to: business_line) + end + before do Timecop.freeze(Time.utc(2020, 1, 1, 19, 0, 0)) first_ama_task.completed! second_ama_task.completed! + remand_task.completed! + hlr_task.completed! + sc_task.completed! end describe "#tasks" do subject { BusinessLineReporter.new(business_line).tasks } it "returns the completed tasks" do - expect(subject).to include(first_ama_task, second_ama_task) + expect(subject).to include(first_ama_task, second_ama_task, remand_task, hlr_task, sc_task) end it "does not return an open task" do @@ -41,6 +59,9 @@ def expected_csv business_line,appeal_id,appeal_type,claimant_name,request_issues_count,decision_issues_count,veteran_file_number,intake_user_id,task_type,task_id,tasks_url,task_assigned_to,created_at,closed_at #{business_line.name},#{first_appeal.id},Appeal,#{first_appeal.claimant.name},0,0,#{first_appeal.veteran.file_number},,Task,#{first_ama_task.id},https://appeals.cf.ds.va.gov#{business_line.tasks_url}/tasks/#{first_ama_task.id},#{first_ama_task.assigned_to.name},2020-01-01,2020-01-01 #{business_line.name},#{second_appeal.id},Appeal,#{second_appeal.claimant.name},0,0,#{second_appeal.veteran.file_number},,Task,#{second_ama_task.id},https://appeals.cf.ds.va.gov#{business_line.tasks_url}/tasks/#{second_ama_task.id},#{second_ama_task.assigned_to.name},2020-01-01,2020-01-01 + #{business_line.name},#{remand.id},Remand,#{remand.claimant.name},0,0,#{remand.veteran.file_number},,DecisionReviewTask,#{remand_task.id},https://appeals.cf.ds.va.gov#{business_line.tasks_url}/tasks/#{remand_task.id},#{remand_task.assigned_to.name},2020-01-01,2020-01-01 + #{business_line.name},#{hlr.id},Higher-Level Review,#{hlr.claimant.name},0,0,#{hlr.veteran.file_number},,DecisionReviewTask,#{hlr_task.id},https://appeals.cf.ds.va.gov#{business_line.tasks_url}/tasks/#{hlr_task.id},#{hlr_task.assigned_to.name},2020-01-01,2020-01-01 + #{business_line.name},#{supplemental_claim.id},Supplemental Claim,#{supplemental_claim.claimant.name},0,0,#{supplemental_claim.veteran.file_number},,DecisionReviewTask,#{sc_task.id},https://appeals.cf.ds.va.gov#{business_line.tasks_url}/tasks/#{sc_task.id},#{sc_task.assigned_to.name},2020-01-01,2020-01-01 EOF end # rubocop:enable Metrics/AbcSize From 21bf071e4ad3ddfe1061706481b38447f02c73b3 Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:48:52 -0400 Subject: [PATCH 15/19] TYLERB/APPEALS-57367: change-history-updates (#22479) * Initial commit with new tests cases for issue modification requests for change history. * Added two cte queries to the business line change history query to properly work with a lead for the issue modification requests. Added new methods into the claim history event to work with this new lead and modified the existing issue modification in progress and pending event generation methods to better reflect the state of a claim that is going through the issue modficiation process. * Cleaned up unused code and todos. * Updated the claim history event create_imr_in_progress_status_event? with even more logic and edge cases. * Fixed a possible error when with a greater than operator could be used on a null value. * Fixed a few rspec tests. * Renamed a method and fixed a few code climate issues. * Fixed a bug in event status generation if there is a task with versions but no versions that change status. * Updated tests to work with new logic and remove a todo statement. * Fixed code complexity warnings. * Updated serializer spec to work with the bug fix. * Adjusted the pending method to prevent possible race conditions. * Fixed a bug where the incorrect decision event user would be shown for an issue modification request creation if the request has been decided when it should be the requestor as the event user. * Added the correct css ids to a few IMR change history events so that the filtering would work correctly. Also added some additional sql to properly filter based on css id and station id for requestor and decider joins. * Updated the IMR business line logic so that the decided at fields would always account for cancelled status since it uses updated at instead of decided at to determine when the IMR was closed. Fixed a bit of logic in the claim history event IMR in progress event generation to work another edge case. * Added in previous IMR edit versions for displaying it to the user. * Added a test for the change history service class to verify that request edit events have the correct attributes from the previous edits. * Fixed a typo for one of the table aliases in the business line query. * Fixed another typo where the facilities filter was using css id instead of station id from a copy/paste error. * adding changes for previous state array and some test modification * adding test for previous data * Fixed some outstanding test failures. * Fixed a linting error. Removed some duplicated code that happened when merging in the feature branch. Updated a factorybot method that was causing tests to fail. * Fixed a few code climate warnings. * Removed some puts statements in the individual claim history feature test. --------- Co-authored-by: Prajwal Amatya --- app/models/organizations/business_line.rb | 124 +++--- .../claim_history_event.rb | 254 +++++++++---- .../claim_history_service.rb | 4 +- spec/factories/appeal.rb | 2 +- spec/factories/higher_level_review.rb | 2 +- .../non_comp/individual_claim_history_spec.rb | 32 +- spec/models/business_line_spec.rb | 4 +- .../change_history_event_serializer_spec.rb | 24 ++ .../claim_history_event_spec.rb | 162 ++++++-- .../claim_history_service_spec.rb | 358 +++++++++++++++++- 10 files changed, 812 insertions(+), 154 deletions(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 96bfe862bce..f05d5461516 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -123,6 +123,15 @@ class QueryBuilder } }.freeze + USER_TABLE_ALIASES = [ + :intake_users, + :update_users, + :decision_users, + :decision_users_completed_by, + :requestor, + :decider + ].freeze + def initialize(query_type: :in_progress, parent: business_line, query_params: {}) @query_type = query_type @parent = parent @@ -232,7 +241,34 @@ def change_history_rows INNER JOIN issue_modification_requests ON issue_modification_requests.id = versions.item_id WHERE versions.item_type = 'IssueModificationRequest' GROUP BY - versions.item_id, versions.item_type) + versions.item_id, versions.item_type + ), imr_distinct AS ( + SELECT DISTINCT ON (imr_cte.id) + imr_cte.id, + imr_cte.decided_at, + imr_cte.created_at, + imr_cte.decision_review_type, + imr_cte.decision_review_id, + imr_cte.status, + imr_cte.updated_at + FROM issue_modification_requests imr_cte + ), imr_lead_decided AS ( + SELECT id, + decision_review_id, + LEAD( + CASE + WHEN status = 'cancelled' THEN updated_at + ELSE decided_at + END, + 1, + '9999-12-31 23:59:59' -- Fake value to indicate out of bounds + ) OVER (PARTITION BY decision_review_id, decision_review_type ORDER BY decided_at, created_at DESC) AS next_decided_or_cancelled_at + FROM imr_distinct + ), imr_lead_created AS ( + SELECT id, + LEAD(created_at, 1, '9999-12-31 23:59:59') OVER (PARTITION BY decision_review_id, decision_review_type ORDER BY created_at ASC) AS next_created_at + FROM imr_distinct + ) SELECT tasks.id AS task_id, check_imr_current_status.is_assigned_present, tasks.status AS task_status, @@ -271,7 +307,7 @@ def change_history_rows imr.decision_reason AS decision_reason, imr.decider_id decider_id, imr.requestor_id as requestor_id, - imr.decided_at AS decided_at, + CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END AS decided_at, imr.created_at AS issue_modification_request_created_at, imr.updated_at AS issue_modification_request_updated_at, imr.edited_at AS issue_modification_request_edited_at, @@ -285,10 +321,11 @@ def change_history_rows decider.station_id AS decider_station_id, decider.css_id AS decider_css_id, itv.object_changes_array AS imr_versions, - lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, - lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, + LAG(imr.created_at, 1) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) AS previous_imr_created_at, + LAG(CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) AS previous_imr_decided_at, itv.object_array as previous_state_array, - MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date + imr_lead_decided.next_decided_or_cancelled_at, + imr_lead_created.next_created_at FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type AND request_issues.decision_review_id = tasks.appeal_id @@ -296,19 +333,20 @@ def change_history_rows AND tasks.appeal_id = higher_level_reviews.id INNER JOIN intakes ON tasks.appeal_type = intakes.detail_type AND intakes.detail_id = tasks.appeal_id + LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type + AND request_issues_updates.review_id = tasks.appeal_id LEFT JOIN LATERAL ( - SELECT * FROM issue_modification_requests imr + SELECT * + FROM issue_modification_requests imr WHERE imr.decision_review_id = tasks.appeal_id AND imr.decision_review_type = 'HigherLevelReview' - AND imr.request_issue_id = request_issues.id - UNION ALL - SELECT * FROM issue_modification_requests imr_add - where imr_add.decision_review_id = tasks.appeal_id - AND imr_add.decision_review_type = 'HigherLevelReview' - AND imr_add.request_type = 'addition' - ) imr on true - LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type - AND request_issues_updates.review_id = tasks.appeal_id + AND ( + imr.request_issue_id = request_issues.id + OR imr.request_type = 'addition' + ) + ) imr ON true + LEFT JOIN imr_lead_decided ON imr_lead_decided.id = imr.id + LEFT JOIN imr_lead_created ON imr_lead_created.id = imr.id LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id @@ -378,7 +416,7 @@ def change_history_rows imr.decision_reason AS decision_reason, imr.decider_id AS decider_id, imr.requestor_id AS requestor_id, - imr.decided_at AS decided_at, + CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END AS decided_at, imr.created_at AS issue_modification_request_created_at, imr.updated_at AS issue_modification_request_updated_at, imr.edited_at AS issue_modification_request_edited_at, @@ -392,10 +430,11 @@ def change_history_rows decider.station_id AS decider_station_id, decider.css_id AS decider_css_id, itv.object_changes_array AS imr_versions, - lag(imr.created_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) as previous_imr_created_at, - lag(imr.decided_at, 1) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.decided_at) as previous_imr_decided_at, + LAG(imr.created_at, 1) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.created_at) AS previous_imr_created_at, + LAG(CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) OVER (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY CASE WHEN imr.status = 'cancelled' THEN imr.updated_at ELSE imr.decided_at END) AS previous_imr_decided_at, itv.object_array as previous_state_array, - MAX(imr.decided_at) over (PARTITION BY tasks.id, imr.decision_review_id, imr.decision_review_type ORDER BY imr.updated_at desc) as imr_last_decided_date + imr_lead_decided.next_decided_or_cancelled_at, + imr_lead_created.next_created_at FROM tasks INNER JOIN request_issues ON request_issues.decision_review_type = tasks.appeal_type AND request_issues.decision_review_id = tasks.appeal_id @@ -403,19 +442,20 @@ def change_history_rows AND tasks.appeal_id = supplemental_claims.id LEFT JOIN intakes ON tasks.appeal_type = intakes.detail_type AND intakes.detail_id = tasks.appeal_id + LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type + AND request_issues_updates.review_id = tasks.appeal_id LEFT JOIN LATERAL ( - SELECT * FROM issue_modification_requests imr + SELECT * + FROM issue_modification_requests imr WHERE imr.decision_review_id = tasks.appeal_id AND imr.decision_review_type = 'SupplementalClaim' - AND imr.request_issue_id = request_issues.id - UNION ALL - SELECT *FROM issue_modification_requests imr_add - where imr_add.decision_review_id = tasks.appeal_id - AND imr_add.decision_review_type = 'SupplementalClaim' - AND imr_add.request_type = 'addition' - ) imr on true - LEFT JOIN request_issues_updates ON request_issues_updates.review_type = tasks.appeal_type - AND request_issues_updates.review_id = tasks.appeal_id + AND ( + imr.request_issue_id = request_issues.id + OR imr.request_type = 'addition' + ) + ) imr ON true + LEFT JOIN imr_lead_decided ON imr_lead_decided.id = imr.id + LEFT JOIN imr_lead_created ON imr_lead_created.id = imr.id LEFT JOIN request_decision_issues ON request_decision_issues.request_issue_id = request_issues.id LEFT JOIN decision_issues ON decision_issues.decision_review_id = tasks.appeal_id AND decision_issues.decision_review_type = tasks.appeal_type AND decision_issues.id = request_decision_issues.decision_issue_id @@ -571,19 +611,16 @@ def days_waiting_filter end end - # rubocop:disable Metrics/AbcSize def station_id_filter if query_params[:facilities].present? + conditions = USER_TABLE_ALIASES.map do |alias_name| + User.arel_table.alias(alias_name)[:station_id].in(query_params[:facilities]).to_sql + end + <<-SQL AND ( - #{User.arel_table.alias(:intake_users)[:station_id].in(query_params[:facilities]).to_sql} - OR - #{User.arel_table.alias(:update_users)[:station_id].in(query_params[:facilities]).to_sql} - OR - #{User.arel_table.alias(:decision_users)[:station_id].in(query_params[:facilities]).to_sql} - OR - #{User.arel_table.alias(:decision_users_completed_by)[:station_id].in(query_params[:facilities]).to_sql} + #{conditions.join(' OR ')} ) SQL end @@ -591,21 +628,18 @@ def station_id_filter def user_css_id_filter if query_params[:personnel].present? + conditions = USER_TABLE_ALIASES.map do |alias_name| + User.arel_table.alias(alias_name)[:css_id].in(query_params[:personnel]).to_sql + end + <<-SQL AND ( - #{User.arel_table.alias(:intake_users)[:css_id].in(query_params[:personnel]).to_sql} - OR - #{User.arel_table.alias(:update_users)[:css_id].in(query_params[:personnel]).to_sql} - OR - #{User.arel_table.alias(:decision_users)[:css_id].in(query_params[:personnel]).to_sql} - OR - #{User.arel_table.alias(:decision_users_completed_by)[:css_id].in(query_params[:personnel]).to_sql} + #{conditions.join(' OR ')} ) SQL end end - # rubocop:enable Metrics/AbcSize #################### End of Change history filter helpers ######################## diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb index 02440c4983e..06482dc5803 100644 --- a/app/services/claim_change_history/claim_history_event.rb +++ b/app/services/claim_change_history/claim_history_event.rb @@ -84,6 +84,8 @@ class ClaimHistoryEvent REQUEST_ISSUE_TIME_WINDOW = 15 STATUS_EVENT_TIME_WINDOW = 2 ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW = 60 + # Used to signal when the database lead function is out of bounds + OUT_OF_BOUNDS_LEAD_TIME = Time.utc(9999, 12, 31, 23, 59, 59) class << self def from_change_data(event_type, change_data) @@ -138,7 +140,7 @@ def create_edited_request_issue_events(change_data) end edited_events.push(*create_event_from_rest_of_versions(change_data, rest_of_versions, previous_version)) else - create_pending_status_events(change_data, change_data["issue_modification_request_updated_at"]) + create_pending_status_event(change_data, change_data["issue_modification_request_updated_at"]) end edited_events end @@ -148,12 +150,8 @@ def create_event_from_rest_of_versions(change_data, edited_versions, previous_ve event_type = :request_edited event_date_hash = {} edited_versions.map.with_index do |version, index| - event_date_hash = { - "event_date" => version["updated_at"][1], - "event_user_name" => change_data["requestor"], - "user_facility" => change_data["requestor_station_id"] - } - + event_date_hash = request_issue_modification_event_hash(change_data) + .merge("event_date" => version["updated_at"][1]) # this create_event_from_version_object updated the previous version fields in change data # that is being used in the front end to show the original records. if !previous_version[index].nil? @@ -170,18 +168,12 @@ def create_event_from_rest_of_versions(change_data, edited_versions, previous_ve edit_of_request_events end - def create_event_from_version_object(version) - previous_version_database_field.each_with_object({}) do |(db_key, version_key), data| - data[db_key] = version[version_key] unless version[version_key].nil? - end - end - def create_last_version_events(change_data, last_version) edited_events = [] last_version["status"].map.with_index do |status, index| if status == "assigned" - edited_events.push(*create_pending_status_events(change_data, last_version["updated_at"][index])) + edited_events.push(*create_pending_status_event(change_data, last_version["updated_at"][index])) else edited_events.push(*create_request_issue_decision_events( change_data, last_version["updated_at"][index], status @@ -198,44 +190,147 @@ def create_request_issue_decision_events(change_data, event_date, event) decision_event_hash = pending_system_hash .merge("event_date" => event_date, "event_user_name" => event_user, - "user_facility" => decider_user_facility(change_data)) + "user_facility" => change_data["decider_station_id"] || change_data["requestor_station_id"], + "event_user_css_id" => change_data["decider_css_id"] || change_data["requestor_css_id"]) change_data = issue_attributes_for_request_type_addition(change_data) if change_data["request_type"] == "addition" request_event_type = "request_#{event}" events.push from_change_data(request_event_type.to_sym, change_data.merge(decision_event_hash)) - events.push create_in_progress_status_event(change_data) + events.push create_imr_in_progress_status_event(change_data) events end - def create_in_progress_status_event(change_data) + def create_imr_in_progress_status_event(change_data) in_progress_system_hash_events = pending_system_hash .merge("event_date" => (change_data["decided_at"] || change_data["issue_modification_request_updated_at"])) - if should_create_imr_in_progress_status_event?(change_data) + # If the imr is not decided, then always skip in progress creation + if imr_decided_or_cancelled?(change_data) && create_imr_in_progress_status_event?(change_data) from_change_data(:in_progress, change_data.merge(in_progress_system_hash_events)) end end - def should_create_imr_in_progress_status_event?(change_data) - (imr_last_decided_row?(change_data) && - (!imr_decided_in_same_transaction?(change_data) || change_data["previous_imr_decided_at"].nil?)) || - (change_data["previous_imr_created_at"].nil? || - !imr_created_in_same_transaction?(change_data) && - %w[cancelled denied approved].include?(change_data["issue_modification_request_status"])) + def create_imr_in_progress_status_event?(change_data) + # If the next imr is decided already in the same transaction then defer creation and it's not in reverse order + return false if next_imr_decided_or_cancelled_in_same_transaction?(change_data) && + !imr_reverse_order?(change_data) + + if do_not_defer_in_progress_creation?(change_data) + # If it's in reverse order and the creation of the next imr is after the current decision time then generate + # an event since the next imr will start a new pending/in progress loop + # Or + # If the next created by was after the decided_at then, this was an in progress transition so create one + # Or + # If it's the last IMR and the next imr was decided or cancelled in the same transaction then go ahead + # and generate an in progress event since the ordering is odd due to the decided at in the same transaction + true + elsif next_imr_decided_is_out_of_bounds?(change_data) + # If it's the end of the lead rows, then this is the last decided row + # If the next created at is in the same transaction, then defer event creation, otherwise create an in progress + # Or + # If the next imr was created at the same time that the current imr is decided, then defer + create_in_progress_event_for_last_decided_by_imr?(change_data) + elsif defer_in_progress_creation?(change_data) + # If the next imr was in the same transaction and it's also decided, then defer event creation to it. + # Or + # If the next imr was created in the same transaction as the next decided, then defer to the next imr + # Or + # If the next imr was created at the same time that the current imr is decided, then defer + # since it should never leave the current pending loop in that case + false + else + # If nothing else matches and the next one is also decided then go ahead and generate an in progress event + # This may occasionally result in a false positive but it should be right most of the time + change_data["next_decided_or_cancelled_at"].present? + end + end + + def do_not_defer_in_progress_creation?(change_data) + (imr_reverse_order?(change_data) && next_imr_created_by_after_current_decided_at?(change_data)) || + (change_data["next_decided_or_cancelled_at"].nil? && + next_imr_created_by_after_current_decided_at?(change_data)) || + (last_imr?(change_data) && next_imr_decided_or_cancelled_in_same_transaction?(change_data)) + end + + def defer_in_progress_creation?(change_data) + (next_imr_created_in_same_transaction?(change_data) && change_data["next_decided_or_cancelled_at"]) || + next_imr_created_at_and_decided_at_in_same_transaction?(change_data) || + next_imr_created_in_same_transaction_as_decided_at?(change_data) + end + + def imr_decided_or_cancelled?(change_data) + %w[cancelled denied approved].include?(change_data["issue_modification_request_status"]) + end + + def next_imr_decided_or_cancelled_in_same_transaction?(change_data) + timestamp_within_seconds?(change_data["decided_at"], change_data["next_decided_or_cancelled_at"], 2) + end + + def next_imr_created_in_same_transaction?(change_data) + timestamp_within_seconds?(change_data["issue_modification_request_created_at"], + change_data["next_created_at"], + 2) + end + + def next_imr_created_in_same_transaction_as_decided_at?(change_data) + timestamp_within_seconds?(change_data["next_created_at"], + change_data["decided_at"], + 2) + end + + def next_imr_created_by_after_current_decided_at?(change_data) + change_data["next_created_at"] && + change_data["decided_at"] && + !last_imr?(change_data) && + (change_data["next_created_at"].change(usec: 0) > change_data["decided_at"].change(usec: 0)) + end + + def next_imr_created_at_and_decided_at_in_same_transaction?(change_data) + timestamp_within_seconds?(change_data["next_decided_or_cancelled_at"], + change_data["next_created_at"], + 2) + end + + def imr_reverse_order?(change_data) + change_data["previous_imr_decided_at"].nil? || change_data["decided_at"].nil? || + (change_data["previous_imr_decided_at"] > change_data["decided_at"]) + end + + def next_imr_decided_is_out_of_bounds?(change_data) + change_data["next_decided_or_cancelled_at"] == OUT_OF_BOUNDS_LEAD_TIME + end + + def last_imr?(change_data) + change_data["next_created_at"] == OUT_OF_BOUNDS_LEAD_TIME end - def create_pending_status_events(change_data, event_date) + def create_in_progress_event_for_last_decided_by_imr?(change_data) + if next_imr_created_in_same_transaction?(change_data) || + next_imr_created_in_same_transaction_as_decided_at?(change_data) + false + else + true + end + end + + def create_pending_status_event(change_data, event_date) pending_system_hash_events = pending_system_hash .merge("event_date" => event_date) - imr_created_in_same_transaction = imr_created_in_same_transaction?(change_data) - # if two imr's are of different transaction and if decision has already been made then we - # want to put pending status since it went back to pending status before it was approved/cancelled or denied. - if change_data["previous_imr_created_at"].nil? || - !imr_created_in_same_transaction + if change_data["previous_imr_created_at"].nil? + # If this is the first IMR then it will always generate a pending event. + from_change_data(:pending, change_data.merge(pending_system_hash_events)) + elsif timestamp_within_seconds?(change_data["previous_imr_decided_at"], + change_data["issue_modification_request_created_at"], + STATUS_EVENT_TIME_WINDOW) + # If this IMR was created at the same time as the previous decided at then skip pending event creation. + nil + elsif !previous_imr_created_in_same_transaction?(change_data) + # if two imr's are of different transaction and if decision has already been made then we + # want to put pending status since it went back to pending status before it was approved/cancelled or denied. from_change_data(:pending, change_data.merge(pending_system_hash_events)) end end @@ -265,19 +360,29 @@ def create_status_events(change_data) rest_of_versions.map do |version| status_events.push event_from_version(version, 1, change_data) end + + # If there are no events, then it had versions but none that altered status so create one from current status + status_events.compact! + if status_events.empty? + status_events.push create_status_event_from_current_status(change_data) + end elsif hookless_cancelled_events.empty? # No versions so make an event with the current status - # There is a chance that a task has no intake either through data setup or through a remanded SC - event_date = change_data["intake_completed_at"] || change_data["task_created_at"] - status_events.push from_change_data(task_status_to_event_type(change_data["task_status"]), - change_data.merge("event_date" => event_date, - "event_user_name" => "System")) + status_events.push create_status_event_from_current_status(change_data) end status_events end # rubocop:enable Metrics/MethodLength + def create_status_event_from_current_status(change_data) + # There is a chance that a task has no intake either through data setup or through a remanded SC + from_change_data(task_status_to_event_type(change_data["task_status"]), + change_data.merge("event_date" => change_data["intake_completed_at"] || + change_data["task_created_at"], + "event_user_name" => "System")) + end + def parse_versions(versions) if versions # Quite a bit faster but less safe. Should probably be fine since it's coming from the database @@ -318,24 +423,13 @@ def issue_attributes_for_request_type_addition(change_data) change_data.merge(issue_data) end - def imr_created_in_same_transaction?(change_data) + def previous_imr_created_in_same_transaction?(change_data) timestamp_within_seconds?(change_data["issue_modification_request_created_at"], change_data["previous_imr_created_at"] || change_data["issue_modification_request_created_at"], ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW) end - def imr_last_decided_row?(change_data) - change_data["imr_last_decided_date"] == (change_data["decided_at"] || - change_data["issue_modification_request_updated_at"]) - end - - def imr_decided_in_same_transaction?(change_data) - timestamp_within_seconds?(change_data["decided_at"], - change_data["previous_imr_decided_at"], - ISSUE_MODIFICATION_REQUEST_CREATION_WINDOW) - end - def extract_issue_ids_from_change_data(change_data, key) (change_data[key] || "").scan(/\d+/).map(&:to_i) end @@ -430,7 +524,13 @@ def update_event_hash_data_from_version(version, index) def update_event_hash_data_from_version_object(version) version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data| - data[db_key] = version[version_key] unless version[version_key].nil? + data[db_key] = version[version_key] + end + end + + def create_event_from_version_object(version) + previous_version_database_field_mapping.each_with_object({}) do |(version_key, db_key), data| + data[db_key] = version[version_key] end end @@ -490,13 +590,13 @@ def version_database_field_mapping } end - def previous_version_database_field + def previous_version_database_field_mapping { - "previous_issue_type" => "nonrating_issue_category", - "previous_issue_description" => "nonrating_issue_description", - "previous_decision_date" => "decision_date", - "previous_modification_request_reason" => "request_reason", - "previous_withdrawal_date" => "withdrawal_date" + "nonrating_issue_category" => "previous_issue_type", + "nonrating_issue_description" => "previous_issue_description", + "decision_date" => "previous_decision_date", + "request_reason" => "previous_modification_request_reason", + "withdrawal_date" => "previous_withdrawal_date" } end @@ -535,9 +635,9 @@ def retrieve_issue_update_data(change_data) def request_issue_modification_event_hash(change_data) { "event_date" => change_data["issue_modification_request_created_at"], - "event_user_name" => change_data["decider"] || change_data["requestor"], - "user_facility" => change_data["decider_station_id"] || change_data["requestor_station_id"], - "event_user_css_id" => change_data["decider_css_id"] || change_data["requestor_css_id"] + "event_user_name" => change_data["requestor"], + "user_facility" => change_data["requestor_station_id"], + "event_user_css_id" => change_data["requestor_css_id"] } end @@ -721,15 +821,18 @@ def set_attributes_from_change_history_data(new_event_type, change_data) parse_request_issue_modification_attributes(change_data) end - # :reek:FeatureEnvy def parse_task_attributes(change_data) @task_id = change_data["task_id"] - @task_status = change_data["is_assigned_present"] ? "pending" : change_data["task_status"] + @task_status = derive_task_status(change_data) @claim_type = change_data["appeal_type"] @assigned_at = change_data["assigned_at"] @days_waiting = change_data["days_waiting"] end + def derive_task_status(change_data) + change_data["is_assigned_present"] ? "pending" : change_data["task_status"] + end + def parse_intake_attributes(change_data) @intake_completed_date = change_data["intake_completed_at"] @veteran_file_number = change_data["veteran_file_number"] @@ -779,14 +882,31 @@ def parse_request_issue_modification_attributes(change_data) end def parse_previous_issue_modification_attributes(change_data) - @previous_issue_type = change_data["previous_issue_type"] || change_data["requested_issue_type"] - @previous_decision_date = change_data["previous_decision_date"] || change_data["requested_decision_date"] - @previous_modification_request_reason = change_data["previous_modification_request_reason"] || - change_data["modification_request_reason"] - @previous_issue_description = change_data["previous_issue_description"] || - change_data["requested_issue_description"] - @previous_withdrawal_date = change_data["previous_withdrawal_date"] || - change_data["issue_modification_request_withdrawal_date"] + @previous_issue_type = derive_previous_issue_type(change_data) + @previous_decision_date = derive_previous_decision_date(change_data) + @previous_modification_request_reason = derive_previous_modification_request_reason(change_data) + @previous_issue_description = derive_previous_issue_description(change_data) + @previous_withdrawal_date = derive_previous_withdrawal_date(change_data) + end + + def derive_previous_issue_type(change_data) + change_data["previous_issue_type"] || change_data["requested_issue_type"] + end + + def derive_previous_decision_date(change_data) + change_data["previous_decision_date"] || change_data["requested_decision_date"] + end + + def derive_previous_issue_description(change_data) + change_data["previous_issue_description"] || change_data["requested_issue_description"] + end + + def derive_previous_modification_request_reason(change_data) + change_data["previous_modification_request_reason"] || change_data["modification_request_reason"] + end + + def derive_previous_withdrawal_date(change_data) + change_data["previous_withdrawal_date"] || change_data["issue_modification_request_withdrawal_date"] end ############ CSV and Serializer Helpers ############ diff --git a/app/services/claim_change_history/claim_history_service.rb b/app/services/claim_change_history/claim_history_service.rb index 5402fa0c53d..5ed8065f002 100644 --- a/app/services/claim_change_history/claim_history_service.rb +++ b/app/services/claim_change_history/claim_history_service.rb @@ -267,8 +267,8 @@ def process_pending_status_event(change_data) @processed_issue_modification_task_ids.add(task_id) save_events( - ClaimHistoryEvent.create_pending_status_events(change_data, - change_data["issue_modification_request_created_at"]) + ClaimHistoryEvent.create_pending_status_event(change_data, + change_data["issue_modification_request_created_at"]) ) end end diff --git a/spec/factories/appeal.rb b/spec/factories/appeal.rb index c73c2424981..e26ccc43b7c 100644 --- a/spec/factories/appeal.rb +++ b/spec/factories/appeal.rb @@ -686,7 +686,7 @@ create(:request_issue, benefit_type: "vha", nonrating_issue_category: "Caregiver | Other", - nonrating_issue_description: "VHA - Caregiver ", + nonrating_issue_description: "VHA - Caregiver", decision_review: appeal, decision_date: 1.month.ago) appeal.reload diff --git a/spec/factories/higher_level_review.rb b/spec/factories/higher_level_review.rb index 612fdafeab7..8880a175a42 100644 --- a/spec/factories/higher_level_review.rb +++ b/spec/factories/higher_level_review.rb @@ -167,7 +167,7 @@ create(:request_issue, benefit_type: "vha", nonrating_issue_category: "Caregiver | Other", - nonrating_issue_description: "VHA - Caregiver ", + nonrating_issue_description: "VHA - Caregiver", decision_review: higher_level_review, decision_date: 1.month.ago) diff --git a/spec/feature/non_comp/individual_claim_history_spec.rb b/spec/feature/non_comp/individual_claim_history_spec.rb index 95cf20e8ca2..a5d01b3b835 100644 --- a/spec/feature/non_comp/individual_claim_history_spec.rb +++ b/spec/feature/non_comp/individual_claim_history_spec.rb @@ -642,14 +642,14 @@ def clear_filter_option(filter_text) nonrating_issue_category: "Medical and Dental Care Reimbursement") end let(:task_event_two_id) { task_event_two.decision_review.tasks.ids[0] } + let!(:approved_modification_edit) do - request = create(:issue_modification_request, - :with_request_issue, - :edit_of_request, - request_type: "modification", - decision_review: task_event_two.decision_review) - request.update(status: "approved") - request + create(:issue_modification_request, + request_type: "modification", + decision_review: task_event_two.decision_review, + request_issue: task_event_two.decision_review.request_issues.first, + request_reason: "Initial request reason", + decision_date: 2.days.ago) end let!(:cancelled_issue_modification_request_modification) do @@ -676,11 +676,26 @@ def clear_filter_option(filter_text) let(:events) { ClaimHistoryService.new(non_comp_org, task_id: task_event_two_id).build_events } before do - visit "/decision_reviews/vha/tasks/#{task_event_two_id}/history" + Timecop.freeze(Time.zone.now) + # approved_modification_edit + Timecop.travel(2.minutes.from_now) + + # Edit the request to create a request edit event + approved_modification_edit.update!(request_reason: "I edited this request.", + nonrating_issue_category: "CHAMPVA", + nonrating_issue_description: "Newly edited issue description") + + Timecop.travel(2.minutes.from_now) + approved_modification_edit.update!(status: "approved") + end + + after do + Timecop.return end context "Check for data output" do it "check for the correct data for Edited Request Modification" do + visit "/decision_reviews/vha/tasks/#{task_event_two_id}/history" click_filter_option("Edit of request - issue modification (1)") original_modification_request = events.detect { |e| e.event_type == :request_edited } @@ -719,6 +734,7 @@ def clear_filter_option(filter_text) expect(table_row).to have_content( "New issue description: #{original_modification_request.new_issue_description}" ) + expect(table_row).to have_content("New decision date: #{new_decision_date.strftime('%m/%d/%Y')}") expect(table_row).to have_content( "Modification request reason: #{original_modification_request.previous_modification_request_reason}" diff --git a/spec/models/business_line_spec.rb b/spec/models/business_line_spec.rb index 61596fd1e6b..b12aac33b71 100644 --- a/spec/models/business_line_spec.rb +++ b/spec/models/business_line_spec.rb @@ -509,7 +509,7 @@ let(:hlr_task_1_ri_1_expectation) do a_hash_including( "nonrating_issue_category" => "Caregiver | Other", - "nonrating_issue_description" => "VHA - Caregiver ", + "nonrating_issue_description" => "VHA - Caregiver", "task_id" => hlr_task.id, "veteran_file_number" => hlr_task.appeal.veteran_file_number, "intake_user_name" => hlr_task.appeal.intake.user.full_name, @@ -547,7 +547,7 @@ let(:hlr_task_2_ri_1_expectation) do a_hash_including( "nonrating_issue_category" => "Caregiver | Other", - "nonrating_issue_description" => "VHA - Caregiver ", + "nonrating_issue_description" => "VHA - Caregiver", "task_id" => hlr_task2.id, "veteran_file_number" => hlr_task2.appeal.veteran_file_number, "intake_user_name" => intake_user.full_name, diff --git a/spec/services/claim_change_history/change_history_event_serializer_spec.rb b/spec/services/claim_change_history/change_history_event_serializer_spec.rb index 1cf20284e7c..5829c4c02de 100644 --- a/spec/services/claim_change_history/change_history_event_serializer_spec.rb +++ b/spec/services/claim_change_history/change_history_event_serializer_spec.rb @@ -110,6 +110,30 @@ eventUser: "L. Roth", taskID: vha_task.id } + }, + { + id: expected_uuid, + type: :change_history_event, + attributes: { + eventType: :in_progress, + eventUser: "System", + claimType: "Higher-Level Review", + readableEventType: "Claim status - In progress", + claimantName: events[2].claimant_name, + details: { + benefitType: "vha", + issueType: nil, + issueDescription: nil, + decisionDate: nil, + disposition: nil, + decisionDescription: nil, + dispositionDate: nil, + withdrawalRequestDate: nil + }, + modificationRequestDetails: modificationRequestDetailsObject, + eventDate: events[2].event_date, + taskID: vha_task.id + } } ] end diff --git a/spec/services/claim_change_history/claim_history_event_spec.rb b/spec/services/claim_change_history/claim_history_event_spec.rb index 96177a92483..90cc3e484c5 100644 --- a/spec/services/claim_change_history/claim_history_event_spec.rb +++ b/spec/services/claim_change_history/claim_history_event_spec.rb @@ -76,7 +76,8 @@ "imr_versions" => imr_versions, "previous_imr_created_at" => nil, "updater_user_name" => "Monte Mann", - "is_assigned_present" => is_assigned_present + "is_assigned_present" => is_assigned_present, + "previous_state_array" => previous_state_array } end @@ -95,13 +96,15 @@ let(:request_issue_update_time) { Time.zone.parse("2023-10-19 22:47:16.233187") } let(:request_issue_created_at) { Time.zone.parse("2023-10-19 22:45:43.108934") } let(:issue_modification_request_created_at) { Time.zone.parse("2023-10-20 22:47:16.233187") } - let(:imr_last_decided_date) { Time.zone.parse("2023-10-21 22:47:16.233187") } let(:previous_imr_created_at) { Time.zone.parse("2023-10-19 22:47:16.233187") } + let(:out_of_bounds_time) { Time.utc(9999, 12, 31, 23, 59, 59) } let(:decision_reason) { nil } let(:decider_id) { nil } let(:decided_at) { nil } let(:is_assigned_present) { false } + let(:previous_state_array) {} + let(:event_attribute_data) do { assigned_at: Time.zone.parse("2023-10-19 22:47:16.222148"), @@ -199,20 +202,29 @@ event_user_name: "System" } end + let(:modification_request_decision_date) { Time.zone.parse("2024-07-28").to_date } let(:modification_attribute_data) do { event_type: :modification, request_type: :modification, - new_issue_type: "Caregiver | Tier Level", - new_issue_description: "test", - new_decision_date: "2024-07-07", - modification_request_reason: "Testing" + benefit_type: "vha", + issue_type: "Clothing Allowance", + issue_description: "Clothing allowance no decision date", + decision_date: "2023-05-31", + new_issue_type: "Caregiver | Eligibility", + new_issue_description: "Rejection of withrwaslasas", + new_decision_date: modification_request_decision_date, + modification_request_reason: "Please withdwasdadsadadadad", + previous_issue_type: "Caregiver | Eligibility", + previous_issue_description: "Rejection of withrwaslasas", + previous_decision_date: modification_request_decision_date, + previous_modification_request_reason: "Please withdwasdadsadadadad" } end let(:issue_modification_response_attribute) do { event_type: :request_approved, - request_type: :addition, + request_type: request_type, new_issue_type: "Caregiver | Tier Level", new_issue_description: "test", new_decision_date: "2024-07-07", @@ -223,13 +235,18 @@ let(:issue_modification_edited_attribute) do { event_type: :request_edited, - request_type: :addition, + request_type: :withdrawal, issue_type: "Clothing Allowance", - new_issue_type: "Caregiver | Tier Level", + issue_description: "Clothing allowance no decision date", + new_issue_type: "Caregiver | Eligibility", new_issue_description: "modifiedvalue", - new_decision_date: "2024-07-07", + new_decision_date: modification_request_decision_date, modification_request_reason: "Addition is the only request issue-modifiedvalue Z", - event_user_name: "Monte Mann" + event_user_name: "Monte Mann", + previous_issue_type: "Caregiver | Eligibility", + previous_issue_description: "Rejection of withrwaslasas", + previous_decision_date: modification_request_decision_date, + previous_modification_request_reason: "Please withdwasdadsadadadad" } end @@ -748,11 +765,11 @@ end end - describe ".create_pending_status_events" do + describe ".create_pending_status_event" do let(:event_date) { change_data["issue_modification_request_created_at"] } let(:is_assigned_present) { true } - subject { described_class.create_pending_status_events(change_data, event_date) } + subject { described_class.create_pending_status_event(change_data, event_date) } it "should return one pending event" do expect_attributes(subject, pending_attribute_data) @@ -783,16 +800,64 @@ end describe ".create_issue_modification_request_event" do - let(:request_type) { :modification } - - subject { described_class.create_issue_modification_request_event(change_data) } - - it "should return single event with request_type passed as the request_type" do - verify_attributes_and_count(subject, 1, modification_attribute_data) + context "when request type is modification" do + let(:request_type) { :modification } + let(:previous_state_array) do + "{\"---\n" \ + "id: 150\n" \ + "status: assigned\n" \ + "requestor_id: 2000006012\n" \ + "nonrating_issue_category: Caregiver | Eligibility\n" \ + "decision_date: 2024-07-28\n" \ + "nonrating_issue_description: 'Rejection of withrwaslasas'\n" \ + "request_reason: Please withdwasdadsadadadad\n" \ + "withdrawal_date: 2024-08-04 04:00:00.000000000 Z\n" \ + "edited_at: \n" \ + "request_issue_id: 251\n" \ + "request_type: modification\n" \ + "benefit_type: vha\n" \ + "created_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "decided_at: \n" \ + "decider_id: \n" \ + "decision_reason: \n" \ + "decision_review_id: 31\n" \ + "decision_review_type: SupplementalClaim\n" \ + "remove_original_issue: false\n" \ + "updated_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "\",---\n" \ + "id: 150\n" \ + "status: assigned\n" \ + "requestor_id: 2000006012\n" \ + "nonrating_issue_category: Caregiver | Eligibility\n" \ + "decision_date: 2024-07-28\n" \ + "nonrating_issue_description: 'Rejection of withrwaslasas'\n" \ + "request_reason: Please Withdraw this one since its no longer valid.\n" \ + "withdrawal_date: 2024-08-04 04:00:00.000000000 Z\n" \ + "edited_at: 2024-08-26 17:23:28.055850000 Z\n" \ + "request_issue_id: 251\n" \ + "request_type: modification\n" \ + "benefit_type: vha\n" \ + "created_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "decided_at: \n" \ + "decider_id: \n" \ + "decision_reason: \n" \ + "decision_review_id: 31\n" \ + "decision_review_type: SupplementalClaim\n" \ + "remove_original_issue: false\n" \ + "updated_at: 2024-08-26 17:23:28.072803000 Z\n" \ + "\"}" + end + + subject { described_class.create_issue_modification_request_event(change_data) } + + it "should return single event with request_type passed as the request_type" do + verify_attributes_and_count(subject, 1, modification_attribute_data) + end end end describe ".create_edited_request_issue_events" do + let(:request_type) { :withdrawal } let(:imr_versions) do "{\"---\n" \ "nonrating_issue_description:\n" \ @@ -819,15 +884,64 @@ "- 2024-07-19 22:47:16.222311778 Z\n" \ "\"}" end + let(:previous_state_array) do + "{\"---\n" \ + "id: 150\n" \ + "status: assigned\n" \ + "requestor_id: 2000006012\n" \ + "nonrating_issue_category: Caregiver | Eligibility\n" \ + "decision_date: 2024-07-28\n" \ + "nonrating_issue_description: 'Rejection of withrwaslasas'\n" \ + "request_reason: Please withdwasdadsadadadad\n" \ + "withdrawal_date: 2024-08-04 04:00:00.000000000 Z\n" \ + "edited_at: \n" \ + "request_issue_id: 251\n" \ + "request_type: withdrawal\n" \ + "benefit_type: vha\n" \ + "created_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "decided_at: \n" \ + "decider_id: \n" \ + "decision_reason: \n" \ + "decision_review_id: 31\n" \ + "decision_review_type: SupplementalClaim\n" \ + "remove_original_issue: false\n" \ + "updated_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "\",---\n" \ + "id: 150\n" \ + "status: assigned\n" \ + "requestor_id: 2000006012\n" \ + "nonrating_issue_category: Caregiver | Eligibility\n" \ + "decision_date: 2024-07-28\n" \ + "nonrating_issue_description: 'Rejection of withrwaslasas'\n" \ + "request_reason: Please Withdraw this one since its no longer valid.\n" \ + "withdrawal_date: 2024-08-04 04:00:00.000000000 Z\n" \ + "edited_at: 2024-08-26 17:23:28.055850000 Z\n" \ + "request_issue_id: 251\n" \ + "request_type: withdrawal\n" \ + "benefit_type: vha\n" \ + "created_at: 2024-08-26 17:22:53.454663000 Z\n" \ + "decided_at: \n" \ + "decider_id: \n" \ + "decision_reason: \n" \ + "decision_review_id: 31\n" \ + "decision_review_type: SupplementalClaim\n" \ + "remove_original_issue: false\n" \ + "updated_at: 2024-08-26 17:23:28.072803000 Z\n" \ + "\"}" + end + before do - change_data["decided_at"] = imr_last_decided_date - change_data["imr_last_decided_date"] = imr_last_decided_date + change_data["decided_at"] = Time.zone.parse("2023-10-21 22:47:16.233187") + change_data["next_decided_or_cancelled_at"] = out_of_bounds_time + change_data["next_created_at"] = out_of_bounds_time + # The event is generated via the versions so it needs to match the status instead of cancelled. + # It's not important for generation, but it's a more correct data state for a claim with a single approved IMR + change_data["issue_modification_request_status"] = "approved" end subject { described_class.create_edited_request_issue_events(change_data) } it "returns request_edited event type with multiple events" do expect(subject.count).to be(4) - expect_attributes(subject[0], pending_attribute_data) expect_attributes(subject[1], issue_modification_response_attribute) expect_attributes(subject[3], issue_modification_edited_attribute) @@ -1347,8 +1461,8 @@ def expect_attributes(object_instance, attribute_value_pairs) end end - def verify_attributes_and_count(subject, num_of_records, attribute) + def verify_attributes_and_count(subject, num_of_records, attributes) expect(subject.count).to eq(num_of_records) - expect_attributes(subject[0], attribute) + expect_attributes(subject[0], attributes) end end diff --git a/spec/services/claim_change_history/claim_history_service_spec.rb b/spec/services/claim_change_history/claim_history_service_spec.rb index 71713e3cf15..c26c6f4d2e9 100644 --- a/spec/services/claim_change_history/claim_history_service_spec.rb +++ b/spec/services/claim_change_history/claim_history_service_spec.rb @@ -88,6 +88,8 @@ ] end + let(:total_event_count) { 22 } + let(:expected_sc_event_types) do [ :added_issue, @@ -100,6 +102,7 @@ [ :claim_creation, :added_issue, + :in_progress, :pending, :addition, :request_edited, @@ -167,13 +170,13 @@ # Expect to get back all the combined event types all_event_types = expected_hlr_event_types + expected_sc_event_types + expected_imr_event_types - expect(events.count).to eq(21) + expect(events.count).to eq(total_event_count) expect(events.map(&:event_type)).to contain_exactly(*all_event_types) # Verify the issue data is correct for the completed_dispostion events disposition_events = events.select { |event| event.event_type == :completed_disposition } disposition_issue_types = ["Caregiver | Other", "Camp Lejune Family Member"] - disposition_issue_descriptions = ["VHA - Caregiver ", "Camp Lejune description"] + disposition_issue_descriptions = ["VHA - Caregiver", "Camp Lejune description"] disposition_user_names = ["Gaius Baelsar", "Gaius Baelsar"] disposition_values = %w[Granted denied] disposition_dates = [5.days.ago.to_date.to_s] * 2 @@ -189,7 +192,7 @@ added_issue_descriptions = [*disposition_issue_descriptions, "Withdrew CHAMPVA", "VHA issue description ", - "VHA - Caregiver "] + "VHA - Caregiver"] added_issue_user_names = ["Lauren Roth", "Lauren Roth", "Lauren Roth", "Eleanor Reynolds", "Lauren Roth"] add_issue_events = events.select do |event| event.event_type == :added_issue || event.event_type == :added_issue_without_decision_date @@ -227,6 +230,353 @@ end end + context "issue modification edge cases" do + let!(:sc_task_with_imrs) do + create(:supplemental_claim_vha_task, + appeal: create(:supplemental_claim, + :with_vha_issue, + :with_intake, + benefit_type: "vha", + claimant_type: :veteran_claimant)) + end + + let(:request_issue) { sc_task_with_imrs.appeal.request_issues.first } + let(:supplemental_claim) { sc_task_with_imrs.appeal } + + let(:starting_imr_events) do + [:claim_creation, :added_issue, :in_progress, :removal, :pending, :addition] + end + + let!(:issue_modification_addition) do + create(:issue_modification_request, + request_type: "addition", + decision_review: supplemental_claim, + requestor: vha_user, + nonrating_issue_category: "CHAMPVA", + nonrating_issue_description: "Starting issue description", + decision_date: 5.days.ago) + end + + let(:issue_modification_modify) do + create(:issue_modification_request, + request_type: "modification", + decision_review: supplemental_claim, + requestor: vha_user, + request_issue: supplemental_claim.request_issues.first) + end + + # Only generate the events for this task to keep it focused on the issue modification request events + let!(:filters) { { task_id: [sc_task_with_imrs.id] } } + + let(:vha_admin) { create(:user, full_name: "VHA ADMIN", css_id: "VHAADMIN") } + let(:vha_user) { create(:user, full_name: "VHA USER", css_id: "VHAUSER") } + + before do + OrganizationsUser.make_user_admin(vha_admin, VhaBusinessLine.singleton) + VhaBusinessLine.singleton.add_user(vha_user) + Timecop.freeze(Time.zone.now) + end + + after do + Timecop.return + end + + def create_last_addition_and_verify_events(original_events, current_events) + new_events = current_events.dup + Timecop.travel(2.minutes.from_now) + addition = create(:issue_modification_request, request_type: "addition", decision_review: supplemental_claim) + + events = service_instance.build_events + new_events.push(:addition, :pending) + expect(events.map(&:event_type)).to contain_exactly(*original_events + new_events) + + # Approve the newest addition to make sure the in progress and approval events are correct + Timecop.travel(2.minutes.from_now) + addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason2") + + events = service_instance.build_events + new_events.push(:in_progress, :request_approved) + expect(events.map(&:event_type)).to contain_exactly(*original_events + new_events) + end + + it "should correctly generate temporary in progress and pending events for a single imr event" do + events = subject + one_imr_events = *starting_imr_events - [:removal] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events) + + # Make an edit to the addition and make sure the events are correct + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(edited_at: Time.zone.now, nonrating_issue_category: "CHAMPVA") + + # Rebuild events + service_instance.build_events + new_events = [:request_edited] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Approve the addition and make sure the events are correct + # NOTE: This only does the issue modification events and does not create a request issue update + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason") + + # Rebuild events + service_instance.build_events + new_events.push(:in_progress, :request_approved) + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(one_imr_events, new_events) + end + + it "should correctly generate events for an imr that is cancelled while another is added" do + events = subject + one_imr_events = *starting_imr_events - [:removal] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events) + + # Cancel the addition IMR at the same time as creating a new issue modification request to + # modify the existing request issue on the supplemental claim + Timecop.travel(2.minutes.from_now) + ActiveRecord::Base.transaction do + issue_modification_addition.update!(status: "cancelled") + issue_modification_modify + end + + # Rebuild events + service_instance.build_events + new_events = [:modification, :request_cancelled] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Approve the modification to verify that it create a new in progress event and a denied event + Timecop.travel(2.minutes.from_now) + issue_modification_modify.update!(decider: vha_admin, status: :denied, decision_reason: "Better reason") + + # Rebuild events + service_instance.build_events + new_events.push(:in_progress, :request_denied) + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(one_imr_events, new_events) + end + + it "should correctly track the previous version data for multiple IMR edits" do + events = subject + one_imr_events = *starting_imr_events - [:removal] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events) + + # Edit several fields to create a new version of the IMR + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(nonrating_issue_category: "Other", + nonrating_issue_description: "Edited description 1", + edited_at: Time.zone.now) + + # Rebuild events + service_instance.build_events + new_events = [:request_edited] + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Edit several fields to create a new version of the IMR + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(nonrating_issue_description: "Edited description 2", + edited_at: Time.zone.now) + + # Rebuild events + service_instance.build_events + new_events.push(:request_edited) + expect(events.map(&:event_type)).to contain_exactly(*one_imr_events + new_events) + + # Verify that each of the edited events has the information from the previous version + edited_events = events.select { |event| event.event_type == :request_edited } + + first_edit = edited_events.first + second_edit = edited_events.last + + expect(first_edit).to have_attributes( + new_issue_description: "Edited description 1", + new_issue_type: "Other", + previous_issue_description: "Starting issue description", + previous_issue_type: "CHAMPVA" + ) + + expect(second_edit).to have_attributes( + new_issue_description: "Edited description 2", + new_issue_type: "Other", + previous_issue_description: "Edited description 1", + previous_issue_type: "Other" + ) + end + + context "starting with two imrs" do + let!(:issue_modification_removal) do + create(:issue_modification_request, + request_type: "removal", + request_issue: request_issue, + decision_review: supplemental_claim) + end + + it "should correctly generate temporary in progress events for two imrs created at the same time" do + events = subject + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events) + + # Deny the removal and make sure the events are correct + Timecop.travel(2.minutes.from_now) + issue_modification_removal.update!(decider: vha_admin, status: :denied, decision_reason: "Just cause") + + # Rebuild events + service_instance.build_events + new_events = [:request_denied] + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Approve the addition and make sure the events are correct + # NOTE: This only does the issue modification events and does not create a request issue update + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason") + + # Rebuild events + service_instance.build_events + new_events.push(:in_progress, :request_approved) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(starting_imr_events, new_events) + end + + it "should correctly generate temporary in progress events for two imrs decided at the same time" do + events = subject + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events) + + # Deny the removal and approve the addition and make sure the events are correct + Timecop.travel(2.minutes.from_now) + ActiveRecord::Base.transaction do + issue_modification_removal.update!(decider: vha_admin, status: :denied, decision_reason: "Just cause") + issue_modification_addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason") + end + + # Rebuild events + service_instance.build_events + new_events = [:request_denied, :request_approved, :in_progress] + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + Timecop.travel(2.minutes.from_now) + addition2 = create(:issue_modification_request, request_type: "addition", decision_review: supplemental_claim) + + service_instance.build_events + new_events.push(:addition, :pending) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Approve the newest addition to make sure the in progress and approval events are correct + Timecop.travel(2.minutes.from_now) + addition2.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason2") + + service_instance.build_events + new_events.push(:in_progress, :request_approved) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(starting_imr_events, new_events) + end + + it "should correctly generate temporary in progress events for two imrs with one cancelled in reverse order" do + events = subject + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events) + + # Approve the addition and make sure the events are correct + # NOTE: This only does the issue modification events and does not create a request issue update + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason") + + # Rebuild events + service_instance.build_events + new_events = [:request_approved] + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Cancel the removal and make sure the events are correct + Timecop.travel(2.minutes.from_now) + issue_modification_removal.update!(decider: vha_admin, status: :cancelled, decision_reason: "Just cause") + + # Rebuild events + service_instance.build_events + new_events.push(:request_cancelled, :in_progress) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(starting_imr_events, new_events) + end + + it "when an imr is cancelled at the same time and another is created" do + events = subject + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events) + + # Approve the addition and make sure the events are correct + # NOTE: This only does the issue modification events and does not create a request issue update + Timecop.travel(2.minutes.from_now) + issue_modification_addition.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason") + + # Rebuild events + service_instance.build_events + new_events = [:request_approved] + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Cancel the removal and add a new approval at the same time + Timecop.travel(2.minutes.from_now) + addition2 = nil + ActiveRecord::Base.transaction do + issue_modification_removal.update!(decider: vha_admin, status: :cancelled, decision_reason: "Just cause") + addition2 = create(:issue_modification_request, + request_type: "addition", + decision_review: supplemental_claim) + end + + # Rebuild events + service_instance.build_events + new_events.push(:request_cancelled, :addition) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Approve the newest addition to make sure the in progress and approval events are correct + Timecop.travel(2.minutes.from_now) + addition2.update!(decider: vha_admin, status: :approved, decision_reason: "Better reason2") + + service_instance.build_events + new_events.push(:in_progress, :request_approved) + expect(events.map(&:event_type)).to contain_exactly(*starting_imr_events + new_events) + + # Create another addition IMR to verify that the event sequence works through one more iteration + create_last_addition_and_verify_events(starting_imr_events, new_events) + end + end + + context "with multiple text edit in for a withdrawal event" do + let!(:issue_modification_withdrawal) do + create(:issue_modification_request, + :withdrawal, + request_issue: request_issue, + decision_review: supplemental_claim, + request_reason: "first comment in the array", + nonrating_issue_description: "first nonrating description") + end + + let!(:issue_modification_edit_of_request_first) do + issue_modification_withdrawal.nonrating_issue_description = "this is first update" + issue_modification_withdrawal.updated_at = Time.zone.today + issue_modification_withdrawal.save! + end + + let!(:issue_modification_edit_of_request_second) do + issue_modification_withdrawal.nonrating_issue_description = "this is Second update" + issue_modification_withdrawal.withdrawal_date = Time.zone.today - 12.days + issue_modification_withdrawal.updated_at = Time.zone.today + issue_modification_withdrawal.save! + end + + it "should have two request of edit event and a withdrawal event" do + events = service_instance.build_events + starting_event_without_removal = *starting_imr_events - [:removal] + new_events = [:withdrawal, :request_edited, :request_edited] + expect(events.map(&:event_type)).to contain_exactly(*starting_event_without_removal + new_events) + end + end + end + context "with filters" do context "with task_id filter" do let(:filters) { { task_id: sc_task.id } } @@ -325,7 +675,7 @@ it "should return events without a disposition" do subject - expect(service_instance.events.count).to eq(21) + expect(service_instance.events.count).to eq(total_event_count) end end end From 2cd6e150223f5d69fa6827925254f332e960b8dd Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Thu, 12 Sep 2024 07:58:24 -0400 Subject: [PATCH 16/19] Fixed a bug where the frontend would error due to calling .toLowerCase() on a null or undefined value which could happen sometimes if the api filter options are in an incorrect state. (#22796) Co-authored-by: Robert Travis Pierce --- client/app/components/TableFilter.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/app/components/TableFilter.jsx b/client/app/components/TableFilter.jsx index bccd70ce974..248903e4fb4 100644 --- a/client/app/components/TableFilter.jsx +++ b/client/app/components/TableFilter.jsx @@ -75,7 +75,11 @@ class TableFilter extends React.PureComponent { }); // Case insensitive ordering for the filter options - return _.orderBy(filterOptionsFromApi, [(option) => option.displayText.toLowerCase()], ['asc']); + return _.orderBy( + filterOptionsFromApi.filter((option) => option.displayText), + [(option) => option.displayText.toLowerCase()], + ['asc'] + ); } const columnValues = tableDataByRow.map((obj) => { From 28cc1739e2d8a887b08f6b1001f43774a087a929 Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:41:56 -0400 Subject: [PATCH 17/19] Updated the remand migration files to work with the rails 6 upgrade and the changes to Caseflow::Migration. (#22825) --- ...and_index_to_supplemental_claims_for_remand_inheritance.rb | 4 +++- ...20240723152034_backfill_supplemental_claims_type_column.rb | 2 +- db/migrate/20240805154526_create_remands_view.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb b/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb index da38b8cd52a..f5e08799f29 100644 --- a/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb +++ b/db/migrate/20240716143816_add_type_column_and_index_to_supplemental_claims_for_remand_inheritance.rb @@ -1,4 +1,6 @@ -class AddTypeColumnAndIndexToSupplementalClaimsForRemandInheritance < Caseflow::Migration +class AddTypeColumnAndIndexToSupplementalClaimsForRemandInheritance < ActiveRecord::Migration[6.1] + include Caseflow::Migrations::AddIndexConcurrently + def change safety_assured do add_column :supplemental_claims, :type, :string, default: "SupplementalClaim", null: false, comment: "The class name for the single table inheritance type of Supplemental Claim for example Remand" diff --git a/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb b/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb index 75fdeaade80..c72349eab79 100644 --- a/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb +++ b/db/migrate/20240723152034_backfill_supplemental_claims_type_column.rb @@ -1,4 +1,4 @@ -class BackfillSupplementalClaimsTypeColumn < Caseflow::Migration +class BackfillSupplementalClaimsTypeColumn < ActiveRecord::Migration[6.1] disable_ddl_transaction! def up diff --git a/db/migrate/20240805154526_create_remands_view.rb b/db/migrate/20240805154526_create_remands_view.rb index b01cc5c93f4..80922efa246 100644 --- a/db/migrate/20240805154526_create_remands_view.rb +++ b/db/migrate/20240805154526_create_remands_view.rb @@ -1,4 +1,4 @@ -class CreateRemandsView < Caseflow::Migration +class CreateRemandsView < ActiveRecord::Migration[6.1] def up safety_assured do execute <<-SQL From ffb747c620be8b42b904db43c67a6069e22458d5 Mon Sep 17 00:00:00 2001 From: Sean Craig <110493538+seancva@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:10:24 -0500 Subject: [PATCH 18/19] added fix for failing test in decision_reviews_controller_spec (#22829) --- spec/controllers/decision_reviews_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/decision_reviews_controller_spec.rb b/spec/controllers/decision_reviews_controller_spec.rb index f23372cf786..059d22d7dac 100644 --- a/spec/controllers/decision_reviews_controller_spec.rb +++ b/spec/controllers/decision_reviews_controller_spec.rb @@ -79,7 +79,7 @@ expect(response.status).to eq 200 expect(response.headers["Content-Type"]).to include "text/csv" expect(response.body).to start_with("business_line") - expect(response.body.match?(task.appeal_type)).to eq true + expect(response.body.match?(task.appeal.class.review_title)).to eq true end end end From 93779344ec848bbbf78c31eadc9fb3374ba61667 Mon Sep 17 00:00:00 2001 From: Tyler Broyles <109369527+TylerBroyles@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:47:11 -0400 Subject: [PATCH 19/19] TYLERB/APPEALS-58656: Change History CSV and Version Parsing fix (#22874) * Fixed a modification request claim history in progress event generation bug that could occur when 3-4 pending requests were all created at the same time and then later cancelled at the same time. Also fixed an issue count bug used when building the issue type filter due to additional duplicate rows being inserted into issue type count query due to the left join to the issue modification requests table. * Altered the version parsing for change history to work with commas in the version strings since that was causing parsing errors. Altered the business line query to use '|||' as a delimeter instead of comma and also changed the array_agg functions to string agg to avoid the '{}' array type casting that was happening between postgres and rails. Updated tests to work with this new parsing. * Updated vha seed data to have associations to intake data so those seeded claims can correctly generate change history. --- app/models/organizations/business_line.rb | 10 ++--- .../claim_history_event.rb | 21 +++++---- db/seeds/veterans_health_administration.rb | 2 + .../claim_history_event_spec.rb | 44 +++++++++---------- 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/models/organizations/business_line.rb b/app/models/organizations/business_line.rb index 2558ee60a74..23628e730c7 100644 --- a/app/models/organizations/business_line.rb +++ b/app/models/organizations/business_line.rb @@ -219,9 +219,9 @@ def issue_type_count nonrating_issue_count = ActiveRecord::Base.connection.execute <<-SQL WITH task_review_issues AS ( #{hlr_query.to_sql} - UNION ALL + UNION #{sc_query.to_sql} - UNION ALL + UNION #{appeals_query.to_sql} ) SELECT issue_category, COUNT(1) AS nonrating_issue_count @@ -255,7 +255,7 @@ def change_history_rows SELECT versions.item_id, versions.item_type, - ARRAY_AGG(versions.object_changes ORDER BY versions.id) AS object_changes_array, + STRING_AGG(versions.object_changes, '|||' ORDER BY versions.id) AS object_changes_array, MAX(CASE WHEN versions.object_changes LIKE '%closed_at:%' THEN versions.whodunnit ELSE NULL @@ -271,8 +271,8 @@ def change_history_rows ), imr_version_agg AS (SELECT versions.item_id, versions.item_type, - ARRAY_AGG(versions.object ORDER BY versions.id) AS object_array, - ARRAY_AGG(versions.object_changes ORDER BY versions.id) AS object_changes_array + STRING_AGG(versions.object, '|||' ORDER BY versions.id) AS object_array, + STRING_AGG(versions.object_changes, '|||' ORDER BY versions.id) AS object_changes_array FROM versions INNER JOIN issue_modification_requests ON issue_modification_requests.id = versions.item_id diff --git a/app/services/claim_change_history/claim_history_event.rb b/app/services/claim_change_history/claim_history_event.rb index b43747d6538..e19ac3ed289 100644 --- a/app/services/claim_change_history/claim_history_event.rb +++ b/app/services/claim_change_history/claim_history_event.rb @@ -214,9 +214,9 @@ def create_imr_in_progress_status_event(change_data) end def create_imr_in_progress_status_event?(change_data) - # If the next imr is decided already in the same transaction then defer creation and it's not in reverse order - return false if next_imr_decided_or_cancelled_in_same_transaction?(change_data) && - !imr_reverse_order?(change_data) + # If the next imr is already decided in the same transaction, it's not in reverse order, and it's + # not the last imr then defer creation + return false if early_deferral?(change_data) if do_not_defer_in_progress_creation?(change_data) # If it's in reverse order and the creation of the next imr is after the current decision time then generate @@ -316,6 +316,11 @@ def create_in_progress_event_for_last_decided_by_imr?(change_data) end end + def early_deferral?(change_data) + next_imr_decided_or_cancelled_in_same_transaction?(change_data) && + !imr_reverse_order?(change_data) && !last_imr?(change_data) + end + def create_pending_status_event(change_data, event_date) pending_system_hash_events = pending_system_hash .merge("event_date" => event_date) @@ -384,12 +389,10 @@ def create_status_event_from_current_status(change_data) end def parse_versions(versions) - if versions - # Quite a bit faster but less safe. Should probably be fine since it's coming from the database - # rubocop:disable Security/YAMLLoad - versions[1..-2].split(",").map { |yaml| YAML.load(yaml.gsub(/^"|"$/, "")) } - # rubocop:enable Security/YAMLLoad - end + # Quite a bit faster but less safe. Should probably be fine since it's coming from the database + # rubocop:disable Security/YAMLLoad + versions&.split("|||")&.map { |yaml| YAML.load(yaml.gsub(/^"|"$/, "")) } + # rubocop:enable Security/YAMLLoad end def create_issue_events(change_data) diff --git a/db/seeds/veterans_health_administration.rb b/db/seeds/veterans_health_administration.rb index f99c4192da9..c3161f82d72 100644 --- a/db/seeds/veterans_health_administration.rb +++ b/db/seeds/veterans_health_administration.rb @@ -63,6 +63,7 @@ def create_supplemental_claims def create_hlr_with_claimant(benefit_type, claimant_type) hlr = create( :higher_level_review, + :with_intake, :with_request_issue, :processed, benefit_type: benefit_type, @@ -75,6 +76,7 @@ def create_hlr_with_claimant(benefit_type, claimant_type) def create_sc_with_claimant(benefit_type, claimant_type) sc = create( :supplemental_claim, + :with_intake, :with_request_issue, :processed, benefit_type: benefit_type, diff --git a/spec/services/claim_change_history/claim_history_event_spec.rb b/spec/services/claim_change_history/claim_history_event_spec.rb index fc80bdcc48b..9a0a672018d 100644 --- a/spec/services/claim_change_history/claim_history_event_spec.rb +++ b/spec/services/claim_change_history/claim_history_event_spec.rb @@ -347,7 +347,7 @@ context "if the task status was assigned -> completed" do let(:version_changes) do - "{\"---\n" \ + "\"---\n" \ "closed_at:\n" \ "- \n" \ "- 2023-11-08 19:22:47.244142348 Z\n" \ @@ -357,7 +357,7 @@ "updated_at:\n" \ "- 2023-11-08 19:22:47.227634704 Z\n" \ "- 2023-11-09 19:22:47.244304624 Z\n" \ - "\"}" + "\"" end it "should create an in progress event and a completed status event" do @@ -374,7 +374,7 @@ context "if the task status was assigned -> cancelled" do let(:version_changes) do - "{\"---\n" \ + "\"---\n" \ "closed_at:\n" \ "- \n" \ "- 2023-11-09 23:16:28.446266110 Z\n" \ @@ -384,7 +384,7 @@ "updated_at:\n" \ "- 2023-11-09 23:16:15.724150103 Z\n" \ "- 2023-11-11 23:16:28.446399290 Z\n" \ - "\"}" + "\"" end it "should generate an in progress and a cancelled status event" do @@ -401,7 +401,7 @@ context "if the task status was assigned -> on_hold -> assigned -> completed" do let(:version_changes) do - "{\"---\n" \ + "\"---\n" \ "status:\n" \ "- assigned\n" \ "- on_hold\n" \ @@ -411,7 +411,7 @@ "updated_at:\n" \ "- 2023-10-19 22:39:14.207143000 Z\n" \ "- 2023-10-19 22:45:43.148742110 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "status:\n" \ "- on_hold\n" \ "- assigned\n" \ @@ -421,7 +421,7 @@ "updated_at:\n" \ "- 2023-10-19 22:45:43.148742000 Z\n" \ "- 2023-10-19 22:47:16.222311778 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "status:\n" \ "- assigned\n" \ "- completed\n" \ @@ -431,7 +431,7 @@ "updated_at:\n" \ "- 2023-10-19 22:47:16.222311000 Z\n" \ "- 2023-10-19 22:48:25.324023984 Z\n" \ - "\"}" + "\"" end it "should generate four status events" do @@ -458,7 +458,7 @@ context "if the task has no decision date and the task status was immediately set to on hold during intake" do let(:version_changes) do - "{\"---\n" \ + "\"---\n" \ "status:\n" \ "- assigned\n" \ "- on_hold\n" \ @@ -468,7 +468,7 @@ "updated_at:\n" \ "- 2023-10-19 22:39:14.207143000 Z\n" \ "- 2023-10-19 22:39:14.207143000 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "status:\n" \ "- on_hold\n" \ "- assigned\n" \ @@ -478,7 +478,7 @@ "updated_at:\n" \ "- 2023-10-19 22:45:43.148742000 Z\n" \ "- 2023-10-19 22:47:16.222311778 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "status:\n" \ "- assigned\n" \ "- completed\n" \ @@ -488,7 +488,7 @@ "updated_at:\n" \ "- 2023-10-19 22:47:16.222311000 Z\n" \ "- 2023-10-19 22:48:25.324023984 Z\n" \ - "\"}" + "\"" end it "should create an on_hold event, an in progress event, and a completed event" do @@ -523,7 +523,7 @@ context "if the task versions are from a hookless papertrail cancelled task" do let(:version_changes) do - "{\"--- {}\n\",\"--- {}\n\"}" + "\"--- {}\n\"|||\"--- {}\n\"" end it "should create an assigned and a cancelled task status event" do @@ -804,7 +804,7 @@ context "when request type is modification" do let(:request_type) { :modification } let(:previous_state_array) do - "{\"---\n" \ + "\"---\n" \ "id: 150\n" \ "status: assigned\n" \ "requestor_id: 2000006012\n" \ @@ -825,7 +825,7 @@ "decision_review_type: SupplementalClaim\n" \ "remove_original_issue: false\n" \ "updated_at: 2024-08-26 17:22:53.454663000 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "id: 150\n" \ "status: assigned\n" \ "requestor_id: 2000006012\n" \ @@ -846,7 +846,7 @@ "decision_review_type: SupplementalClaim\n" \ "remove_original_issue: false\n" \ "updated_at: 2024-08-26 17:23:28.072803000 Z\n" \ - "\"}" + "\"" end subject { described_class.create_issue_modification_request_event(change_data) } @@ -860,7 +860,7 @@ describe ".create_edited_request_issue_events" do let(:request_type) { :withdrawal } let(:imr_versions) do - "{\"---\n" \ + "\"---\n" \ "nonrating_issue_description:\n" \ "- First value\n" \ "- modifiedvalue\n" \ @@ -873,7 +873,7 @@ "updated_at:\n" \ "- 2024-07-19 22:39:14.207143000 Z\n" \ "- 2024-07-19 22:39:14.207143000 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "status:\n" \ "- assigned\n" \ "- approved\n" \ @@ -883,10 +883,10 @@ "updated_at:\n" \ "- 2024-07-19 22:45:43.148742000 Z\n" \ "- 2024-07-19 22:47:16.222311778 Z\n" \ - "\"}" + "\"" end let(:previous_state_array) do - "{\"---\n" \ + "\"---\n" \ "id: 150\n" \ "status: assigned\n" \ "requestor_id: 2000006012\n" \ @@ -907,7 +907,7 @@ "decision_review_type: SupplementalClaim\n" \ "remove_original_issue: false\n" \ "updated_at: 2024-08-26 17:22:53.454663000 Z\n" \ - "\",---\n" \ + "\"|||---\n" \ "id: 150\n" \ "status: assigned\n" \ "requestor_id: 2000006012\n" \ @@ -928,7 +928,7 @@ "decision_review_type: SupplementalClaim\n" \ "remove_original_issue: false\n" \ "updated_at: 2024-08-26 17:23:28.072803000 Z\n" \ - "\"}" + "\"" end before do