From 99cb4c3aefd4297fd8d6ba2e4740f14772a941d2 Mon Sep 17 00:00:00 2001 From: Nader Kutub Date: Fri, 30 Aug 2024 16:58:59 -0700 Subject: [PATCH] Feature/appeals 28087 36678 phase 1 merge (#22676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * APPEALS-36688- Build out Decision Review Created API route & Controller (#20569) * added skeleton for api route * removed duplicate code * removed development envs for api and moved to creating an ApiKey * removing any changes to development.rb * removed extra auth code * removed before action --------- Co-authored-by: TuckerRose * APPEALS-38232 -Build out Decision Review Created Event Failure API route & Controller (#20601) * APPEALS-38232 - renamed controller to AC req, added decision_review_created_error endpoint, added decision_review_created_error method, and added RSpec for new method * APPEALS-38232 - add comments to RSpec * Jonathan/appeals 36684 (#20516) * initial Events migration and model creation * created model for DecisionReviewCreatedEvent * updated comment with example * added spec for DRCE model * APPEALS-36684 created event_records migration and added polymorphic associations to specific models, and added rspec for the event_record model * APPEALS-36684 - Updated RSpec tests and updated variables and got unhappy path to pass * cleaned lint * saving DRCE spec changes * fixed spec test * changed has_many to has_one * updated event model spec * added validation for ER poly associations * changed association to has_one * added new method and updated tests * added foreign key after running checks * some PR comment changes * refactored methods in EventConcern --------- Co-authored-by: Jonathan Tsang Co-authored-by: Enrilo Ugalde * added migration, scopes and specs for events (#20707) * fixed migration to update existing events table * rollbacked to fix schema * schema fixes --------- Co-authored-by: TuckerRose * Jr/APPEALS-38926 (#20714) * APPEALS-38926 - Created DecisionReviewCreatedError Service Class, added logic for handling service error, updated DecisonReviewCreatedController, and DecisionReviewCreatedController spec, with the updated service logic * APPEALS-38926- created RSpec Test for DecisionReviewCreatedError Service Class and edited the Rails.logger for the service class * APPEALS-38926 Added new info column to update transaction and added it to the RSpec test * APPEALS-38926 - added comments to the Service Class * APPEALS-38926 - Code Changes from TL Code Review, added rescues and fails * APPEALS-38926 - fixed lint * Jonathan/appeals 36689 (#20671) * created new service class * add rspec test cases * service class methods * controller action and spec * controller update * CC fixes * removed accidental line * changed to find_or_create_by * reworked error for redis lock * additional rspec for controller * fixed test * rspec fix * delete lock key afterwards * moved Event creation back into lock block --------- Co-authored-by: Jonathan Tsang * APPEALS-39663 Create CreateUserOnEvent service class and add logic to create user if needed (#20838) * added user creation class & test * removed extra lines * add comment to class * added context for args from avro --------- Co-authored-by: Jonathan Tsang * Jr/APPEALS-39664 (#20898) * APPEALS-39664 - Created updated_vacols_on_optin module class, and removed extra private * APPEALS-39664 - created UpdatedVacolsOnOptin module, RSpec file, as well as sudo code for SOC and SSOC optin check in main service class DecisionReviewCreated * APPEALS-39664 - Created RSpec Test - PASS, Updated method name. * APPEALS-39664 - Added Error Handling to Sub Service Class * APPEALS-39664 - Removed un-needed comments * APPEALS-39664 - added include for the module UpdateVacolsOnOptin inside decision_review_created service * APPEALS-39664 - Added Custom Error , and updated all .perform! to .process! * APPEALS-39664 - Updated RSpec Test to reflect changes - all pass * Updated comment for decision_review_created Service Class * Will/appeals 36691 (#20909) * Created attribute for failed claims on event and displaying failed claim * passing all failed events back to serializer * added controller tests for failed_claims and added class method for finding claims on events * renamed failed_claims to claim_errors --------- Co-authored-by: TuckerRose * JR/APPEALS-40954 Create CreateIntake service class and add logic to create Intake (#20967) * APPEALS-40954 - Added Sudo Code for CreateIntake Logic * APPEALS-40954 - added logic to CreateIntake module * APPEALS-40954 - added Create Intake spec with error handling - All pass * APPEALS-40954 - Added CreateIntake Module to DecisionReviewCreated Main Service * APPEALS-40954 - updated folder name and namespace * APPEALS-40954 - Updated RSpec to match folder * APPEALS-40954- Upated decision review created servie include to match folder * Fixed failing test due to folder structure change * Jonathan/appeals 40950 (#20965) * Added new veteran creation module * saving test changes * rspec * fixed datetime assign * renamed var --------- Co-authored-by: Jonathan Tsang * APPEALS-40950 - update var veteran to vbms_veteran (#21050) * APPEALS-40950 - update var veteran to vbms_veteran * updated private method in controller to match happy path params dcr to drc * Create CreateClaimantOnEvent service class and add logic to create claimant if needed (#21044) * created service class and basic unit tests * added conditionals for veteran claimants and will create veteran claimant now * modified create claimant to use eventing data * change to a class * creating claimant correctly * test fixes * removed old comments * fixed type for veteran_is_not_claimant * changed specs to match pulling out hash params and removed event.reference id * moved back to dot notation * updated comments * fixed create claimant issue * updated process to use bang method and returning claimant --------- Co-authored-by: TuckerRose * APPEALS-41968 Modify Issues Endpoints & Update Metric Service Logic (#21108) * moved metricsService call to top of method * modified rspec case * moved metric logging spec case higher up --------- Co-authored-by: Jonathan Tsang * Resolved merge conflicts while merging master into feature/APPEALS-28… (#21167) * Jonathan/APPEALS-41957 (#21171) * added interface + parser class * edited veteran parse methods * renamed var to "payload" * started refactor * more refactor + rspec * added EPE attr * dateTime conversions * added class comments * rubocop lint changes * fixed test case --------- Co-authored-by: Jonathan Tsang * Jr/appeals 41931 (#21192) * APPEALS-41931 - Created create_ep_establishment file and class * APPEALS-41931 - added process! method that creates epe from payload * APPEALS-41931 - added logic for EventRecord being created and error handling * APPEALS-41931 - added comments to process method * APPEALS-41931 - set up rspec test * APPEALS-41931 - removed lint * APPEALS-41931-created Rspec and Test Pass * APPEALS-41931 - cleaned lint and added error test 100% code coverage * APPEALS-41931- fixed lint * APPEALS-41931- fixed lint and fixed %100 code cov * APPEALS-41931 - cleaned up lint and warn for Service Class * APPEALS-41931 - Added CreateEpEstablishment.process! to the decision_review_created Parent Service Class * APPEALS-41931 - refactor code to implement new parser * fixed linting issues * APPEALS-41931 - Updated Comments for CreateEpEstablishment * APPEALS-41931 - moved logical date int to parser and refactored code in Class and RSepc * edits * Will/appeals 41929 (#21205) * added an error and commented out call for createclaim * added new parser logic * merge request changes * fixed rubocop issues and added checks for claim review attributes --------- Co-authored-by: TuckerRose * APPEALS-42631- Create Rspec for Parser with sample payload method, and add additional parser methods (#21294) * APPEALS-42631 - create example.json * APPEALS-42631 - implemented example_response and load_example method and works as expected * APPEALS-42631 - created RSpec for DecisionReviewCreatedParser * APPEALS-42631 - Refactored parser and Added RSpec for current praser * APPEALS-42631 - added methods to parser for intake, claimant, and claim_review and added matching rspec for new methods * APPEALS-42631 - updated code per TL Comments * APPEALS-421631- added additional comments and fixing lint * APPEALS-41934 (#21251) * initial commit * implementation & error * renamed method * rspec * saving refactor progress * finished refactoring class * added comment * minor parser/rspec updates --------- Co-authored-by: Jonathan Tsang * added RI parser methods to rspec (#21322) * added RI parser methods to rspec * updated config for Consumer --------- Co-authored-by: Jonathan Tsang * Update DecisionReviewCreated to make all calls and link all intake records (#21334) * updated decision review created to uncomment actions and updated specs * remove binding.pry * removed comments * fixed a bunch of broken tests * fixed last broken tests * fixed params for methods and specs --------- Co-authored-by: TuckerRose * APPEALS-43446- Change name of BackfillRecord polymorphic model to EventedRecord (#21382) * APPEALS-43446 - renamed columns for backfill record to evented record migrate and rollback work as intended * APPEALS-43446 replaced all backfill_record with evented_record within the models associations and updated Rspec tests * APPEALS-43446 fixed error message * Jr/ama controller refactor (#21365) * fixed test and refactored controller * saving changes * init commit passing all the way to request_issues * all pass and functions as expected * lint * lint * updated initializer to use deep_symbolize_keys * updated headers to be more dry * added missing Intake attributes * fixed failing tests * updated createIntake test * fixed veteran rspec * fixed RI test * APPEALS-43446- Change name of BackfillRecord polymorphic model to EventedRecord (#21382) * APPEALS-43446 - renamed columns for backfill record to evented record migrate and rollback work as intended * APPEALS-43446 replaced all backfill_record with evented_record within the models associations and updated Rspec tests * APPEALS-43446 fixed error message * updated intake * modified intake --------- Co-authored-by: Jonathan Tsang * redo init commit * updated imp. logic * attorney widget fix * Create end to end , happy path rspec's for Decision Created event Feature (#21395) * updated more tests and fixed user creation * added in person creation * corrected headers and fixed broken tests * added type * fixed failing spec * creating event when person is created * updated spec to account for both events being created * ignored long lines for spec file * linting fixes * fixed more linting errors/ ignored long lines --------- Co-authored-by: TuckerRose * Jonathan/appeals 43589 (#21397) * saving * saving user class error progress * error handling for user creation * updated error handling/raises * validator methods * validations * update logical date converter * changed veteran service class to use file_number * added fields to hlr * updated epe data * removed byebug * fixed typos * Edit 5: adding detail_id to Intake * EDIT 6: add claimant_participant_id to epe * Edit 8: Intake is correctly linked to veteran * Edit 7: RI additions * fixed spacing * fixed typo * saving rspec changes * rspec updates * added datetime conversion for person dob * fixed rspec * remove unused methods --------- Co-authored-by: Jonathan Tsang * feature/APPEALS-35707-29633-29632 (uat) (#21435) * 🔀 Squash merge AlecK/APPEALS-35707 - Replace `database_cleaner` with `database_cleaner-active_record` * 🔀 Squash merge jcroteau/APPEALS-29632-fix-deprecation-action-view-base-instances * 🔀 Squash merge jcroteau/APPEALS-29633-fix-deprecation-warning-active_record-result-to_hash * awillis/APPEALS-45152 (#21506) * APPEALS-45152 Updated the logic in create_claimant_on_event.rb to always generate a claimant record. Updated RSPEC. * APPEALS-45152 Cleaned up RSPEC. * APPEALS-45152 Fixed Failing RSPEC within decision_review_created_spec.rb * APPEALS-45152 Updated RSPEC within decision_review_created_spec.rb with addtional checks. * APPEALS-44319 (#21449) (#21541) * added logic for legacy issues in DRC * more legacy logic * updated rspec context lang * error cov --------- Co-authored-by: Jonathan Tsang <98970951+jtsangVA@users.noreply.github.com> Co-authored-by: Jonathan Tsang * Konstantin/APPEALS-45175 (#21517) * logical_date_converter re-written to work with yyyymmdd numbers from json payload * comments removed * comments removed2 * method rewitten * json example fixed * date in scenario_b_spec.rb replaced by valid ones, additional check for empty string parameter added to the logical_date_converter * removed filter for non rating request issue dropdown (#21480) * removed filter for non rating request issue dropdown * removed excluded types as we don't need to check this anymore * fixed broken test * added category back * no longer need test to check for missing categories since we're returning them all. --------- Co-authored-by: TuckerRose * Column added to RequestIssues table (#21578) * added migration and simple spec to test * fixed schema deleted issues * updated spec to verify data being set * error was no longer trigger from update. removed check and passed in correct variable --------- Co-authored-by: TuckerRose Co-authored-by: TuckerRose * added bgs_source to parser, controller and serializer along with specs (#21621) Co-authored-by: TuckerRose * Konstantin/appeals 45180 (#21591) * bug is fixed, condition added to DecisionReviewCreatedController * rspec test added * json message updated * comment added * Update decision_review_created_controller.rb comment updated --------- Co-authored-by: Nader Kutub * Konstantin/appeals 45149 (#21644) * RequestIssueSerializer attributes added * development_item_reference_id, same_office, legacy_opt_in_approved attributes were added * merge conflicts resolved * merge conflict resolved * fixed * all datapoints were added * empty line removed * comment removed * refactored * app/controllers/api/docs/v3/ama_issues.yaml updated with new attributes * example of request issue updated app/controllers/api/docs/v3/ama_issues.yaml * null to nil edited in example ama_issues.yaml * example ama_issues.yaml updated * example ama_issues.yaml updated2 * linters fixed * linters fixed2 * linters are fixed * APPEALS-45883 - Remove End Product Establishment records within the Event Records table in Caseflow (#21745) * APPEALS-45883 - changes to remove the creating of Event Records for epe * APPEALS-45883 - remove removed event params from method calls * APPEALS-45883 removed Event Double * APPEALS-48553 updated event_record spec * removed eventing from create claim review (#21718) * removed eventing from create claim review * checking why file is not being found for event_record * removed event var and require dependency * spec fixes * updated specs * fixed revert --------- Co-authored-by: TuckerRose Co-authored-by: Nader Kutub * Nrithner/appeals 45913 (#21716) * Convert from milliseconds to datetime * Add expection to have datetime after parsing * Fix linting issues * APPEALS - 45914 & 45915 (#21744) * Decision review parser and spec changes * remove byebug * Added db seed and testing (#21284) * Added db seed and testing * Change ApiKey seed name to ConsumerApiKey, and associated changes --------- Co-authored-by: Matt Ray Co-authored-by: Chris-Martine Co-authored-by: Nader Kutub * re-merge Cmartine/appeals 46861 (#21855) * Edit DecisionReviewCreatedController to respond with ok status if event exists and is completed, edit test * Move checking if event exists and is completed into event model, edit tests * Fix linter errors --------- Co-authored-by: Chris-Martine * Nrithner/appeals 46860 (#21856) * Set event info back to default default state for info is an empty json object * Correct linting errors * Test happy path with rspec * Refactor rspec example * Converte module to class * Remove module import --------- Co-authored-by: Nico Rithner * Jonathan/appeals 45899/45878 (#21714) * add "from event" value to Store * updated setup guide for M3 * added new badges * added jest * storybook file * additional story * bugfix * snap update * Konstantin/appeals 46175 (#21768) * draft processor method done * process_nonrating method moved to DecisionReviewCreated service * json example added, process_nonrating method updated * 4 rspec test added * useless json example removed * commented lines removed * condition added * condition added. * test updated * first condition moved out of the method * process_nonrating method call moved down * 1 comment removed, 1 added * process nonrating method rafactored, logic transferred into the parser * method refactored, rspec tests updated * rspec test slightly refactored --------- Co-authored-by: Nader Kutub * Edit Issues screen update (#21907) * Edit Issues screen update * relocated row * updated capybara test * updated capybara to check for new added text * cmartine/APPEALS-46905 (#21884) * Remove cache updating call for veteran when veteran is being created for an event * Change seeds api_key.rb to consumer_api_key.rb --------- Co-authored-by: Matt Ray <108031363+mattray-gov@users.noreply.github.com> * Update process_nonrating to process_nonrationg_issue_category Remove extra logic * Update rspec. Remove unused example * Change edited_by_css_id to return users css_id * Konstantin/appeals 48306 (#21998) * ama_eventing_enabled feature check added * toggle renamed * rspec tests added * 2 comments added * toggle renamed, logic trasferred into controller, tests updated * empty line removed --------- Co-authored-by: Matt Ray <108031363+mattray-gov@users.noreply.github.com> * 930 code updates * remove byebug * Change edited_by_css_id to return users css_id (#22062) Co-authored-by: Matt Ray <108031363+mattray-gov@users.noreply.github.com> * Nrithner/appeals 49245 (#22107) * Update process_nonrating to process_nonrationg_issue_category Remove extra logic * Update rspec. Remove unused example --------- Co-authored-by: Matt Ray <108031363+mattray-gov@users.noreply.github.com> * fix extra commas from merge * APPEALS-51223: Disable ClaimantValidator for DecisionReviewCreatedEvents (#22132) * APPEALS-51223 refactored the claimant_validator class method claimant_details_required? to consider the conditional statement * APPEALS-51223 updated Rspec to confirm claimant address_line_1 is nil and error is empty * APPEALS-51223 made changes to to claimant_validator to have all Unit test passing * APPEALS-51223 added suggested changes * Revert "fix merge conflict with AMA" This reverts commit 6775316c85e2c3d8cd94c1baae6d29d75503163d, reversing changes made to 66cf311d1ddb18d11137b173b94ecbafaa022e2b. * added comments * Delete docker-bin/build.sh, I didnt make these changes * Revert "Delete docker-bin/build.sh, I didnt make these changes" This reverts commit 03a3830c5df26e9d4830fcf1d66ea77af4703346. --------- Co-authored-by: nader k * Jonathan/appeals 51926 (#22215) * Hide UI changes behind featureFlag * fixed lint spacing warnings * code climate spec fix (#22211) * code climate spec fix * up argument count * created a parser helper for reusable methods * reset codeclimate back to original and added issue parser * modified methods to take in a hash instead of multiple params * reduced create params down to 3 params * added space inside * swapped to safe navigator for intake check * fixed spec * fixed linting issue * fixed failing spec * fixed merge conflicts with code changes for helper --------- Co-authored-by: TuckerRose Co-authored-by: TuckerRose Co-authored-by: Nader Kutub * Konstantin/appeals 52321 (#22363) * utc conversion method added in app/serializers/api/v3/issues/ama/request_issue_serializer.rb * set_contested_rating_issue_profile_date method updated in RequestIssue model, updated contested_rating_issue_profile_date in DecisionIssue model * decision issue updated, tests added * tests added, converters improve app/models/decision_issue.rb app/serializers/api/v3/issues/ama/request_issue_serializer.rb * set_contested_rating_issue_profile_date restored in request issue model * uneccessary logic removed * 3 more tests added * test added, error handling aaded in format_rating_profile_date method * Update decision_issue.rb * Update request_issue.rb * Appeals 52317 (#22321) * wip * added the parser to skeleton * removed commented code * added in parser logic * added dru_error route, error handling and params * added rspecs and fixed routing for update error to be a post * added empty spec * updated dru params to match intake json * rubocop fixes * more rubocop fixes * fixed routing to post now and updated specs --------- Co-authored-by: TuckerRose * fixed logic for AMA issues API serializer attributes (#22358) * fixed logic for added_by fields * methods for fetching removed_by user * withdrawn_by methods * methods for edited_by & instance vars * more logic for added_by methods * fixed failing tests * rspec updates * rspec for RIU addition * small changes * fix test failure * Revert "Appeals 52317 (#22321)" This reverts commit 7e3426e620f559d81b1e4aa0837785cb581c3424. * Bug Fix for APPEALS-44115 (#22538) * running ci * testing category * ci with conditional * set back to false * reverted to original spec * running ci * updated intake helpers to account for using none of these match option * ignored metrics abc size * removed from helper * removed comments * updated spec to check on each category * removed single quotes * fixed a flakey test that was failing on a duplicate key * linting fix * pushed up feature flag * lint clean up * swapped to different featureflag * fixing spec feature toggle param * added feature flags back * checking if label is there * flipped feature test to correct order --------- Co-authored-by: TuckerRose * fix for contested_issue_description test on decision review created * fix api keey spec * fix API Key seed --------- Co-authored-by: Will Love Co-authored-by: TuckerRose Co-authored-by: Enrilo Ugalde <71367882+Jruuuu@users.noreply.github.com> Co-authored-by: Jonathan Tsang <98970951+jtsangVA@users.noreply.github.com> Co-authored-by: Jonathan Tsang Co-authored-by: Enrilo Ugalde Co-authored-by: isaiahsaucedo Co-authored-by: Calvin Co-authored-by: Craig Reese Co-authored-by: Jeremy Croteau Co-authored-by: (Jeffrey) Aaron Willis <98484567+Aaron-Willis@users.noreply.github.com> Co-authored-by: Konstantin Shevtsov <166068160+KonstantinShevtsov@users.noreply.github.com> Co-authored-by: TuckerRose Co-authored-by: nicorithner-bah <148365039+nicorithner-bah@users.noreply.github.com> Co-authored-by: Kodi Shiflett Co-authored-by: Matt Ray Co-authored-by: Chris-Martine Co-authored-by: Nico Rithner Co-authored-by: Chris-Martine <135330019+Chris-Martine@users.noreply.github.com> Co-authored-by: Matt Ray <108031363+mattray-gov@users.noreply.github.com> Co-authored-by: Nader Kutub --- MAC_M1.md | 314 +++++ .../v1/decision_review_created_controller.rb | 95 ++ .../api/v3/issues/ama/veterans_controller.rb | 21 +- .../v3/issues/vacols/veterans_controller.rb | 22 +- app/controllers/swagger/v3/ama_issues.yaml | 57 +- app/helpers/parser_helper.rb | 28 + app/models/claim_review.rb | 10 +- app/models/claimant.rb | 4 + app/models/concerns/event_concern.rb | 24 + app/models/end_product.rb | 4 +- app/models/end_product_establishment.rb | 5 + .../events/decision_review_created_event.rb | 7 + app/models/events/event.rb | 25 + app/models/events/event_record.rb | 28 + app/models/higher_level_review.rb | 1 + app/models/intake.rb | 2 + app/models/legacy_issue.rb | 6 + app/models/legacy_issue_optin.rb | 6 + app/models/person.rb | 2 + app/models/request_issue.rb | 87 ++ app/models/supplemental_claim.rb | 1 + app/models/user.rb | 2 + app/models/validators/claimant_validator.rb | 5 +- app/models/veteran.rb | 2 + .../v3/issues/ama/request_issue_serializer.rb | 71 +- .../api/v3/issues/ama/vbms_ama_dto_builder.rb | 4 +- .../decision_review_created_issue_parser.rb | 112 ++ .../events/create_claimant_on_event.rb | 38 + app/services/events/create_user_on_event.rb | 23 + .../events/create_veteran_on_event.rb | 44 + .../events/decision_review_created.rb | 100 ++ .../create_claim_review.rb | 51 + .../create_ep_establishment.rb | 40 + .../decision_review_created/create_intake.rb | 37 + .../create_request_issues.rb | 103 ++ .../decision_review_created_example.json | 86 ++ .../decision_review_created_parser.rb | 304 ++++ .../update_vacols_on_optin.rb | 15 + .../events/decision_review_created_error.rb | 44 + .../events/veteran_extractor_interface.rb | 41 + app/views/higher_level_reviews/edit.html.erb | 4 +- app/views/queue/index.html.erb | 3 +- app/views/supplemental_claims/edit.html.erb | 4 +- client/app/components/badges/BadgeArea.jsx | 15 +- .../badges/IntakeBadge/IntakeBadge.jsx | 44 + .../badges/IntakeBadge/IntakeBadge.stories.js | 27 + .../badges/IntakeBadge/IntakeBadge.test.js | 33 + .../__snapshots__/IntakeBadge.test.js.snap | 163 +++ .../components/NonratingRequestIssueModal.jsx | 5 + .../app/intake/pages/addIssues/addIssues.jsx | 47 +- client/app/intakeEdit/reducers/index.js | 6 +- client/app/queue/OtherReviewsTable.jsx | 131 +- client/app/queue/utils.js | 1 + client/constants/END_PRODUCT_CODES.json | 2 + client/constants/EP_CLAIM_TYPES.json | 1256 +++++++++-------- config/environments/development.rb | 3 + config/routes.rb | 8 + db/migrate/20240116174509_create_events.rb | 12 + .../20240116211523_create_event_records.rb | 11 + ...0240123191625_add_foreign_key_to_events.rb | 5 + ...3192715_validate_foreign_keys_on_events.rb | 5 + .../20240205154329_add_info_to_events.rb | 15 + ...ename_backfill_record_to_evented_record.rb | 9 + ...ting_issue_bgs_source_to_request_issues.rb | 5 + db/schema.rb | 22 + db/seeds.rb | 1 + db/seeds/api_keys.rb | 21 + lib/caseflow/error.rb | 13 + lib/helpers/END_PRODUCT_CODES.json | 2 + ...decision_review_created_controller_spec.rb | 161 +++ .../decision_review_created_event.rb | 7 + spec/factories/events.rb | 14 + ...ision_review_created_event_failure_spec.rb | 122 ++ .../scenario_a_spec.rb | 147 ++ .../scenario_b_spec.rb | 109 ++ .../scenario_c_spec.rb | 140 ++ .../intake/edit_ep_claim_labels_spec.rb | 6 +- .../nonrating_request_issue_modal_spec.rb | 77 +- spec/feature/queue/pre_docket_spec.rb | 5 +- .../hearings/receive_notification_job_spec.rb | 7 +- .../decision_review_created_event_spec.rb | 50 + spec/models/events/event_record_spec.rb | 125 ++ spec/models/events/event_spec.rb | 46 + spec/models/request_issue_spec.rb | 12 +- .../validators/claimant_validator_spec.rb | 26 + .../v3/issues/ama/veterans_controller_spec.rb | 32 +- spec/seeds/api_keys_spec.rb | 14 + .../ama/request_issue_serializer_spec.rb | 224 ++- .../events/create_claimant_on_event_spec.rb | 180 +++ .../events/create_user_on_event_spec.rb | 26 + .../events/create_veteran_on_event_spec.rb | 88 ++ .../create_claim_review_spec.rb | 78 + .../create_ep_establishment_spec.rb | 78 + .../create_intake_spec.rb | 47 + .../create_request_issues_spec.rb | 166 +++ .../decision_review_created_parser_spec.rb | 218 +++ .../update_vacols_on_optin_spec.rb | 36 + .../decision_review_created_error_spec.rb | 46 + .../events/decision_review_created_spec.rb | 110 ++ 99 files changed, 5378 insertions(+), 763 deletions(-) create mode 100644 app/controllers/api/events/v1/decision_review_created_controller.rb create mode 100644 app/helpers/parser_helper.rb create mode 100644 app/models/concerns/event_concern.rb create mode 100644 app/models/events/decision_review_created_event.rb create mode 100644 app/models/events/event.rb create mode 100644 app/models/events/event_record.rb create mode 100644 app/services/decision_review_created_issue_parser.rb create mode 100644 app/services/events/create_claimant_on_event.rb create mode 100644 app/services/events/create_user_on_event.rb create mode 100644 app/services/events/create_veteran_on_event.rb create mode 100644 app/services/events/decision_review_created.rb create mode 100644 app/services/events/decision_review_created/create_claim_review.rb create mode 100644 app/services/events/decision_review_created/create_ep_establishment.rb create mode 100644 app/services/events/decision_review_created/create_intake.rb create mode 100644 app/services/events/decision_review_created/create_request_issues.rb create mode 100644 app/services/events/decision_review_created/decision_review_created_example.json create mode 100644 app/services/events/decision_review_created/decision_review_created_parser.rb create mode 100644 app/services/events/decision_review_created/update_vacols_on_optin.rb create mode 100644 app/services/events/decision_review_created_error.rb create mode 100644 app/services/events/veteran_extractor_interface.rb create mode 100644 client/app/components/badges/IntakeBadge/IntakeBadge.jsx create mode 100644 client/app/components/badges/IntakeBadge/IntakeBadge.stories.js create mode 100644 client/app/components/badges/IntakeBadge/IntakeBadge.test.js create mode 100644 client/app/components/badges/IntakeBadge/__snapshots__/IntakeBadge.test.js.snap create mode 100644 db/migrate/20240116174509_create_events.rb create mode 100644 db/migrate/20240116211523_create_event_records.rb create mode 100644 db/migrate/20240123191625_add_foreign_key_to_events.rb create mode 100644 db/migrate/20240123192715_validate_foreign_keys_on_events.rb create mode 100644 db/migrate/20240205154329_add_info_to_events.rb create mode 100644 db/migrate/20240411185612_rename_backfill_record_to_evented_record.rb create mode 100644 db/migrate/20240507145426_add_nonrating_issue_bgs_source_to_request_issues.rb create mode 100644 db/seeds/api_keys.rb create mode 100644 spec/controllers/api/events/v1/decision_review_created_controller_spec.rb create mode 100644 spec/factories/decision_review_created_event.rb create mode 100644 spec/factories/events.rb create mode 100644 spec/feature/events/decision_review_created/decision_review_created_event_failure_spec.rb create mode 100644 spec/feature/events/decision_review_created/scenario_a_spec.rb create mode 100644 spec/feature/events/decision_review_created/scenario_b_spec.rb create mode 100644 spec/feature/events/decision_review_created/scenario_c_spec.rb create mode 100644 spec/models/events/decision_review_created_event_spec.rb create mode 100644 spec/models/events/event_record_spec.rb create mode 100644 spec/models/events/event_spec.rb create mode 100644 spec/seeds/api_keys_spec.rb create mode 100644 spec/services/events/create_claimant_on_event_spec.rb create mode 100644 spec/services/events/create_user_on_event_spec.rb create mode 100644 spec/services/events/create_veteran_on_event_spec.rb create mode 100644 spec/services/events/decision_review_created/create_claim_review_spec.rb create mode 100644 spec/services/events/decision_review_created/create_ep_establishment_spec.rb create mode 100644 spec/services/events/decision_review_created/create_intake_spec.rb create mode 100644 spec/services/events/decision_review_created/create_request_issues_spec.rb create mode 100644 spec/services/events/decision_review_created/decision_review_created_parser_spec.rb create mode 100644 spec/services/events/decision_review_created/update_vacols_on_optin_spec.rb create mode 100644 spec/services/events/decision_review_created_error_spec.rb create mode 100644 spec/services/events/decision_review_created_spec.rb diff --git a/MAC_M1.md b/MAC_M1.md index 571b340cba5..31d15d8eca6 100644 --- a/MAC_M1.md +++ b/MAC_M1.md @@ -216,6 +216,320 @@ Note: It takes several minutes for the VACOLS VM to go through its startup and l --- # Installation Workarounds +M3 Mac Gem Installation +--- +**Follow these steps if you are getting errors when running `bundle install`** + +1. Create a new file `script.py` with the following contents: + ``` + import subprocess + import re + + #string = subprocess.check_output("gem query --local", shell=True) + #string = re.findall("(?![^\\(]*\\))[A-Za-z-_]+", string.decode("utf-8")) + + with open("./gems.txt") as file: + lines = [line.rstrip() for line in file] + + for i in lines: + print("bundle config build." + i + " --with-cflags=\"-Wno-error=incompatible-function-pointer-types\" --with-cppflags=\"-Wno-compound-token-split-by-macro\"") + output = subprocess.check_output("bundle config build." + i + " --with-cflags=\"-Wno-error=incompatible-function-pointer-types\" --with-cppflags=\"-Wno-compound-token-split-by-macro\"", shell=True) + + ``` +2. Create a new file `gems.txt` with the following contents: + ``` + aasm + actioncable + actionmailbox + actionmailer + actionpack + actiontext + actionview + activejob + activemodel + activerecord + activerecord-import + activerecord-oracle_enhanced-adapter + activestorage + activesupport + acts_as_tree + addressable + akami + amoeba + anbt-sql-formatter + ast + aws-sdk + aws-sdk-core + aws-sdk-resources + aws-sigv4 + backport + benchmark-ips + bgs + bootsnap + bourbon + brakeman + browser + builder + bullet + bummr + bundler-audit + business_time + byebug + capybara + capybara-screenshot + caseflow + choice + claide + claide-plugins + cliver + coderay + colored2 + colorize + concurrent-ruby + connect_mpi + connect_vbms + connection_pool + console_tree_renderer + cork + countries + crack + crass + d3-rails + danger + database_cleaner + date + ddtrace + debase + debase-ruby_core_source + derailed_benchmarks + diff-lcs + docile + dogstatsd-ruby + dotenv + dotenv-rails + dry-configurable + dry-container + dry-core + dry-equalizer + dry-inflector + dry-initializer + dry-logic + dry-schema + dry-types + ecma-re-validator + erubi + execjs + factory_bot + factory_bot_rails + faker + faraday + faraday-http-cache + faraday_middleware + fast_jsonapi + fasterer + ffi + foreman + formatador + fuzzy_match + get_process_mem + git + globalid + govdelivery-tms + guard + guard-compat + guard-rspec + gyoku + hana + hashdiff + heapy + holidays + httpclient + httpi + i18n + i18n_data + icalendar + ice_cube + immigrant + jaro_winkler + jmespath + jquery-rails + jshint + json + json_schemer + kaminari + kaminari-actionview + kaminari-activerecord + kaminari-core + knapsack_pro + kramdown + kramdown-parser-gfm + launchy + libv8 + listen + logstasher + loofah + lumberjack + mail + marcel + maruku + memory_profiler + meta_request + method_source + mime-types + mime-types-data + mini_mime + minitest + moment_timezone-rails + momentjs-rails + msgpack + multi_json + multipart-post + multiverse + nap + neat + nenv + net-imap + net-pop + net-protocol + net-smtp + newrelic_rpm + nio4r + no_proxy_fix + nokogiri + nori + notiffany + octokit + open4 + paper_trail + parallel + paranoia + parser + pdf-forms + pdfjs_viewer-rails + pdfkit + pg + pluck_to_hash + pry + pry-byebug + public_suffix + puma + racc + rack + rack-contrib + rack-test + rails + rails-dom-testing + rails-erd + rails-html-sanitizer + railties + rainbow + rake + rb-fsevent + rb-inotify + rb-readline + rchardet + react_on_rails + redis + redis-actionpack + redis-activesupport + redis-classy + redis-mutex + redis-namespace + redis-rack + redis-rails + redis-store + ref + regexp_parser + request_store + reverse_markdown + rexml + roo + rspec + rspec-core + rspec-expectations + rspec-github + rspec-mocks + rspec-rails + rspec-retry + rspec-support + rspec_junit_formatter + rubocop + rubocop-performance + rubocop-rails + ruby-debug-ide + ruby-graphviz + ruby-oci8 + ruby-plsql + ruby-prof + ruby-progressbar + ruby_dep + ruby_parser + rubyzip + safe_shell + safe_yaml + sass + sass-listen + sass-rails + savon + sawyer + scss_lint + selenium-webdriver + sentry-raven + sexp_processor + shellany + shoryuken + shoulda-matchers + simplecov + simplecov-html + single_cov + sixarm_ruby_unaccent + sniffybara + socksify + solargraph + sprockets + sprockets-rails + sql_tracker + statsd-instrument + stringex + strong_migrations + terminal-table + test-prof + therubyracer + thor + thread_safe + tilt + timecop + timeout + tty-tree + tzinfo + uglifier + unicode-display_width + unicode_utils + uniform_notifier + uri_template + validates_email_format_of + wasabi + webdrivers + webmock + webrick + websocket + websocket-driver + websocket-extensions + xmldsig + xmlenc + xmlmapper + xpath + yard + zeitwerk + ziptz + ``` +3. Move both files into the `caseflow` root folder +4. In your Terminal, run `python3 script.py` +5. Run `bundle install` again +6. If any gems fail to install, manually install it by running `gem install` (e.g. `gem install pg:1.1.4`) and then run `bundle install` again to check for additional failures +7. Once `bundle install` stops throwing errors, return to the last step for Script 2 (run ```./scripts/dev_env_setup_step2.sh```) + + OpenSSL --- **When installing rbenv, nodenv, or pyenv, both openssl libraries should install as dependencies. _Only follow the below instructions if you have problems with openssl@3 or openssl@1.1 not compiling_.** diff --git a/app/controllers/api/events/v1/decision_review_created_controller.rb b/app/controllers/api/events/v1/decision_review_created_controller.rb new file mode 100644 index 00000000000..31c0dcb4ff2 --- /dev/null +++ b/app/controllers/api/events/v1/decision_review_created_controller.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +class Api::Events::V1::DecisionReviewCreatedController < Api::ApplicationController + # Checks if API is disabled + before_action do + if FeatureToggle.enabled?(:disable_ama_eventing) + render json: { + errors: [ + { + status: "501", + title: "API is disabled", + detail: "This endpoint is not supported." + } + ] + }, + status: :not_implemented + end + end + + # rubocop:disable Layout/LineLength + def decision_review_created + consumer_event_id = drc_params[:event_id] + + return render json: { message: "Record already exists in Caseflow" }, status: :ok if Event.exists_and_is_completed?(consumer_event_id) + + claim_id = drc_params[:claim_id] + headers = request.headers + consumer_and_claim_ids = { consumer_event_id: consumer_event_id, reference_id: claim_id } + ::Events::DecisionReviewCreated.create!(consumer_and_claim_ids, headers, drc_params) + render json: { message: "DecisionReviewCreatedEvent successfully processed and backfilled" }, status: :created + rescue Caseflow::Error::RedisLockFailed => error + render json: { message: error.message }, status: :conflict + rescue StandardError => error + render json: { message: error.message }, status: :unprocessable_entity + end + # rubocop:enable Layout/LineLength + + def decision_review_created_error + event_id = drc_error_params[:event_id] + errored_claim_id = drc_error_params[:errored_claim_id] + error_message = drc_error_params[:error] + ::Events::DecisionReviewCreatedError.handle_service_error(event_id, errored_claim_id, error_message) + render json: { message: "Decision Review Created Error Saved in Caseflow" }, status: :created + rescue Caseflow::Error::RedisLockFailed => error + render json: { message: error.message }, status: :conflict + rescue StandardError => error + render json: { message: error.message }, status: :unprocessable_entity + end + + private + + def drc_error_params + params.permit(:event_id, :errored_claim_id, :error) + end + + # rubocop:disable Metrics/MethodLength + def drc_params + params.permit(:event_id, + :claim_id, + :css_id, + :detail_type, + :station, + intake: {}, + veteran: {}, + claimant: {}, + claim_review: {}, + end_product_establishment: {}, + request_issues: [:benefit_type, + :contested_issue_description, + :contention_reference_id, + :contested_rating_decision_reference_id, + :contested_rating_issue_profile_date, + :contested_rating_issue_reference_id, + :contested_decision_issue_id, + :decision_date, + :ineligible_due_to_id, + :ineligible_reason, + :is_unidentified, + :unidentified_issue_text, + :nonrating_issue_category, + :nonrating_issue_description, + :untimely_exemption, + :untimely_exemption_notes, + :vacols_id, + :vacols_sequence_id, + :closed_at, + :closed_status, + :contested_rating_issue_diagnostic_code, + :ramp_claim_id, + :rating_issue_associated_at, + :nonrating_issue_bgs_id, + :nonrating_issue_bgs_source]) + end + # rubocop:enable Metrics/MethodLength +end diff --git a/app/controllers/api/v3/issues/ama/veterans_controller.rb b/app/controllers/api/v3/issues/ama/veterans_controller.rb index 2c5c8caad4b..3377660e636 100644 --- a/app/controllers/api/v3/issues/ama/veterans_controller.rb +++ b/app/controllers/api/v3/issues/ama/veterans_controller.rb @@ -9,16 +9,17 @@ class Api::V3::Issues::Ama::VeteransController < Api::V3::BaseController end def show - veteran = find_veteran - page = init_page - per_page = init_per - if veteran - MetricsService.record("Retrieving AMA Request Issues for Veteran: #{veteran.participant_id}", - service: "AMA Request Issue endpoint", - name: "VeteransController.show") do - render_request_issues(Api::V3::Issues::Ama::VbmsAmaDtoBuilder.new(veteran, page, per_page).hash_response) - end - end + MetricsService.record("Retrieving AMA Request Issues for Veteran with participant ID: #{params[:participant_id]}", + service: "AMA Request Issue endpoint", + name: "VeteransController.show") do + veteran = find_veteran + page = init_page + per_page = init_per + if veteran + render_request_issues(Api::V3::Issues::Ama::VbmsAmaDtoBuilder.new(veteran, page, per_page) + .hash_response) + end + end end private diff --git a/app/controllers/api/v3/issues/vacols/veterans_controller.rb b/app/controllers/api/v3/issues/vacols/veterans_controller.rb index a9d7ee8b018..9195012b213 100644 --- a/app/controllers/api/v3/issues/vacols/veterans_controller.rb +++ b/app/controllers/api/v3/issues/vacols/veterans_controller.rb @@ -44,17 +44,21 @@ def file_number end def show - page = ActiveRecord::Base.sanitize_sql(params[:page].to_i) if params[:page] - # per_page uses the default value defined in the DtoBuilder unless a param is given, - # but it cannot exceed the upper bound - per_page = [params[:per_page].to_i, DEFAULT_UPPER_BOUND_PER_PAGE].min if params[:per_page]&.to_i&.positive? - # Disallow page(0) since page(0) == page(1) in kaminari. This is to avoid confusion. - (page.nil? || page <= 0) ? page = 1 : page ||= 1 - MetricsService.record("VACOLS: Get VACOLS Issues information for Veteran", name: "Api::V3::Issues::Vacols::VeteransController.show") do - render_vacols_issues(Api::V3::Issues::Vacols::VbmsVacolsDtoBuilder.new(@veteran, page, per_page)) - end + page = ActiveRecord::Base.sanitize_sql(params[:page].to_i) if params[:page] + # per_page uses the default value defined in the DtoBuilder unless a param is given, + # but it cannot exceed the upper bound + if params[:per_page]&.to_i&.positive? + per_page_input = params[:per_page].to_i + per_page = [per_page_input, DEFAULT_UPPER_BOUND_PER_PAGE].min + end + # Disallow page(0) since page(0) == page(1) in kaminari. This is to avoid confusion. + (page.nil? || page <= 0) ? page = 1 : page ||= 1 + + render_vacols_issues(Api::V3::Issues::Vacols::VbmsVacolsDtoBuilder.new(@veteran, page, + per_page)) + end end private diff --git a/app/controllers/swagger/v3/ama_issues.yaml b/app/controllers/swagger/v3/ama_issues.yaml index 65d8ae75fad..aa2b585eaf7 100644 --- a/app/controllers/swagger/v3/ama_issues.yaml +++ b/app/controllers/swagger/v3/ama_issues.yaml @@ -292,7 +292,20 @@ paths: caseflow_considers_eligible: true claimant_participant_id: "111111" decision_issues: - - + - + development_item_reference_id: "111" + same_office: nil + legacy_opt_in_approved: false + added_by_station_id: "101" + added_by_css_id: "BVADWISE101" + corrected_by_station_id: nil + corrected_by_css_id: nil + edited_by_station_id: nil + edited_by_css_id: nil + removed_by_css_id: "BVADWISE101" + removed_by_station_id: "101" + withdrawn_by_css_id: nil + withdrawn_by_station_id: nil '401': description: 401 Unauthorized content: @@ -362,6 +375,12 @@ components: id: type: integer description: "The unique identifier for a request issue" + added_by_css_id: + type: string + description: "It comes from the user record tied to the RequestIssuesUpdate. If there is no RequestIssuesUpdate for the issue being added, then it comes from the user record linked to intake." + added_by_station_id: + type: string + description: "It comes from the user record tied to the RequestIssuesUpdate. If there is no RequestIssuesUpdate for the issue being added, then it comes from the user record linked to intake." benefit_type: type: string description: "The Line of Business the issue is connected with." @@ -401,6 +420,12 @@ components: created_at: type: string description: "The date and time the record was created" + corrected_by_css_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has the Request Issue being corrected. Indicates css_id of that user." + corrected_by_station_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has the Request Issue being corrected. Indicates station_id of that user." decision_date: type: string description: "Either the rating issue's promulgation date, the decision issue's approx decision date or the decision date entered by the user (for nonrating and unidentified issues)." @@ -410,6 +435,15 @@ components: decision_review_type: type: string description: "Class name of the decision review that this request issue belongs to." + development_item_reference_id: + type: string + description: "When a Veteran requests an informal conference with their higher level review, a tracked item is created. This stores the ID of the of the tracked item, it is also used to indicate the success of creating the tracked item." + edited_by_css_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has the Request Issue being edited." + edited_by_station_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has the Request Issue being edited." edited_description: type: string description: "The edited description for the contested issue, optionally entered by the user." @@ -425,9 +459,15 @@ components: is_unidentified: type: boolean description: "Indicates whether a Request Issue is unidentified, meaning it wasn't found in the list of contestable issues, and is not a new nonrating issue. Contentions for unidentified issues are created on a rating End Product if processed in VBMS but without the issue description, and someone is required to edit it in Caseflow before proceeding with the decision." + legacy_opt_in_approved: + type: boolean + description: "Indicates whether a Veteran opted to withdraw their Higher Level Review 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." nonrating_issue_bgs_id: type: string description: "The ID of the nonrating request issue in BGS/CorpDB" + nonrating_issue_bgs_source: + type: string + description: "Name of Table in Corporate Database where the nonrating issue is stored. This datapoint is correlated with the nonrating_issue_bgs_id." nonrating_issue_category: type: string description: "The category selected for nonrating request issues. These vary by business line." @@ -440,6 +480,15 @@ components: ramp_claim_id: type: string description: "If a rating issue was created as a result of an issue intaken for a RAMP Review, it will be connected to the former RAMP issue by its End Product's claim ID." + removed_by_css_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has a removed Request Issue." + removed_by_station_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has a removed Request Issue. Indicates station_id of user who updated closed_stauts to 'removed'." + same_office: + type: boolean + description: "comes from Higher Level Review. Whether the Veteran wants their issues to be reviewed by the same office where they were previously reviewed. This creates a special issue on all of the contentions created on this Higher Level Review." split_issue_status: type: string description: "If a request issue is part of a split, on_hold status applies to the original request issues while active are request issues on splitted appeals." @@ -467,6 +516,12 @@ components: veteran_participant_id: type: string description: "The veteran participant ID. This should be unique in upstream systems and used in the future to reconcile duplicates." + withdrawn_by_css_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has a Withdrawn Request Issue." + withdrawn_by_station_id: + type: string + description: "Comes from the user record tied to the RequestIssuesUpdate that has a withdrawn Request Issue." caseflow_considers_decision_review_active: type: boolean description: "This value is populated by a method call in the request_issue model. Returns true if the Request Issue's decision_review is an Appeal that has open tasks OR if the End Product Establishment's synced_status is nil or anything other than CAN or CLR." diff --git a/app/helpers/parser_helper.rb b/app/helpers/parser_helper.rb new file mode 100644 index 00000000000..bcbf72d14a3 --- /dev/null +++ b/app/helpers/parser_helper.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ParserHelper + # Checking for nonrating_issue_category is "Disposition" and processing such issues. + def process_nonrating_issue_category(payload_json) + payload_json[:request_issues].each do |issue| + next unless issue[:nonrating_issue_category] == "Disposition" + + issue[:nonrating_issue_category] = "Unknown Issue Category" + end + end + + # Generic/universal methods + # rubocop:disable Rails/TimeZone + def convert_milliseconds_to_datetime(milliseconds) + milliseconds.nil? ? nil : Time.at(milliseconds.to_i / 1000).to_datetime + end + # rubocop:enable Rails/TimeZone + + # convert logical date int to date + def logical_date_converter(logical_date_int) + return nil if logical_date_int.nil? || logical_date_int.to_i.days == 0 + + base_date = Date.new(1970, 1, 1) + converted_date = base_date + logical_date_int.to_i.days + converted_date + end +end diff --git a/app/models/claim_review.rb b/app/models/claim_review.rb index 150e5977318..f25bfc609ac 100644 --- a/app/models/claim_review.rb +++ b/app/models/claim_review.rb @@ -8,7 +8,7 @@ class ClaimReview < DecisionReview has_many :end_product_establishments, as: :source has_many :messages, as: :detail - + has_one :event_record, as: :evented_record with_options if: :saving_review do validate :validate_receipt_date validate :validate_veteran @@ -223,7 +223,8 @@ def search_table_ui_hash receipt_date: receipt_date, veteran_file_number: veteran_file_number, veteran_full_name: claim_veteran&.name&.formatted(:readable_full), - caseflow_only_edit_issues_url: caseflow_only_edit_issues_url + caseflow_only_edit_issues_url: caseflow_only_edit_issues_url, + intake_from_vbms: from_decision_review_created_event? } end @@ -307,6 +308,11 @@ def cleared_nonrating_ep? processed? && cleared_end_products.any?(&:nonrating?) end + def from_decision_review_created_event? + # refer back to the associated Intake to see if both objects came from DRCE + intake&.from_decision_review_created_event? || false + end + def sct_appeal? false end diff --git a/app/models/claimant.rb b/app/models/claimant.rb index bc6b001a8a0..8f376074f89 100644 --- a/app/models/claimant.rb +++ b/app/models/claimant.rb @@ -13,6 +13,7 @@ class Claimant < CaseflowRecord has_one :unrecognized_appellant, lambda { |claimant| where(id: UnrecognizedAppellant.order(:id).find_by(claimant: claimant)&.id) }, dependent: :destroy + has_one :event_record, as: :evented_record # rubocop:disable Rails/UniqueValidationWithoutIndex validates :participant_id, @@ -86,6 +87,9 @@ def find_power_of_attorney # no-op except on BgsRelatedClaimants end + # refer back to the associated Person record to see if both objects came from DRCE + delegate :from_decision_review_created_event?, to: :person + private def bgs_address_service diff --git a/app/models/concerns/event_concern.rb b/app/models/concerns/event_concern.rb new file mode 100644 index 00000000000..2c352395121 --- /dev/null +++ b/app/models/concerns/event_concern.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# This concern is used to identify objects associated with Appeals-Consumer Events. +module EventConcern + extend ActiveSupport::Concern + + # Check if this object is associated with any Event, regardless of type + # check if this object exists in the Event Records table + def from_event? + event_record.present? + end + + # Check if this object is associated with a DecisionReviewCreatedEvent + def from_decision_review_created_event? + if from_event? + # retrieve the record and the event the record is tied to + event = event_record.event + + event.type == DecisionReviewCreatedEvent.name + else + false + end + end +end diff --git a/app/models/end_product.rb b/app/models/end_product.rb index d8f38f68c30..f54529c66f0 100644 --- a/app/models/end_product.rb +++ b/app/models/end_product.rb @@ -23,7 +23,9 @@ class EndProduct "930AMADOR" => "AMA Difference of Opinion - Rating", "930AMADONR" => "AMA Difference of Opinion - NR", "930DORPMC" => "PMC AMA Difference of Opinion - Rating", - "930DONRPMC" => "PMC AMA Difference of Opinion - NR" + "930DONRPMC" => "PMC AMA Difference of Opinion - NR", + "930ADORPMC" => "PMC AMA Difference of Opinion - Rating", + "930ADONRPMC" => "PMC AMA Difference of Opinion - NR" }.freeze RAMP_CODES = { diff --git a/app/models/end_product_establishment.rb b/app/models/end_product_establishment.rb index 08cdca2983c..cb6bf345b3c 100644 --- a/app/models/end_product_establishment.rb +++ b/app/models/end_product_establishment.rb @@ -383,6 +383,11 @@ def status_type_code end end + def from_decision_review_created_event? + # refer back to the associated Intake to see if both objects came from DRCE + source.intake.from_decision_review_created_event? + end + private def status_type diff --git a/app/models/events/decision_review_created_event.rb b/app/models/events/decision_review_created_event.rb new file mode 100644 index 00000000000..74d3752cebf --- /dev/null +++ b/app/models/events/decision_review_created_event.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# This class represents the DecisionReviewCreatedEvent info that is POSTed to Caseflow +# Represents a single "event" and is tied to "event records" that contain info regarding +# the different objects that Caseflow performs backfill creations for after VBMS Intake. +class DecisionReviewCreatedEvent < Event +end diff --git a/app/models/events/event.rb b/app/models/events/event.rb new file mode 100644 index 00000000000..993427b7e3e --- /dev/null +++ b/app/models/events/event.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# This class is the parent class for different events that Caseflow receives from appeals-consumer +class Event < CaseflowRecord + has_many :event_records + store_accessor :info, :errored_claim_id + + scope :with_errored_claim_id, -> { where.not("info -> 'errored_claim_id' IS NULL") } + + def completed? + completed_at? + end + + def self.find_errors_by_claim_id(claim_id) + with_errored_claim_id + .where("info ->> 'errored_claim_id' = ?", claim_id) + .pluck(:error) + end + + # Check if there's already a CF Event that references that Appeals-Consumer EventID and + # was successfully completed + def self.exists_and_is_completed?(consumer_event_id) + where(reference_id: consumer_event_id).where.not(completed_at: nil).exists? + end +end diff --git a/app/models/events/event_record.rb b/app/models/events/event_record.rb new file mode 100644 index 00000000000..3c21e4f5cff --- /dev/null +++ b/app/models/events/event_record.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class EventRecord < CaseflowRecord + belongs_to :event + belongs_to :evented_record, polymorphic: true + + validate :valid_evented_record + + def valid_evented_record + unless %w[ + Intake + ClaimReview + HigherLevelReview + SupplementalClaim + EndProductEstablishment + Claimant + Veteran + Person + RequestIssue + LegacyIssue + LegacyIssueOptin + User + ].include?(evented_record_type) + + errors.add(:evented_record_type, "is not a valid evented record") + end + end +end diff --git a/app/models/higher_level_review.rb b/app/models/higher_level_review.rb index 39e9d2f309c..37f4a9d13ea 100644 --- a/app/models/higher_level_review.rb +++ b/app/models/higher_level_review.rb @@ -10,6 +10,7 @@ class HigherLevelReview < ClaimReview end has_many :remand_supplemental_claims, as: :decision_review_remanded, class_name: "SupplementalClaim" + has_one :event_record, as: :evented_record attr_accessor :appeal_split_process diff --git a/app/models/intake.rb b/app/models/intake.rb index 0d96d5c78dd..97f9a039dca 100644 --- a/app/models/intake.rb +++ b/app/models/intake.rb @@ -2,10 +2,12 @@ class Intake < CaseflowRecord class FormTypeNotSupported < StandardError; end + include EventConcern belongs_to :user belongs_to :veteran belongs_to :detail, polymorphic: true + has_one :event_record, as: :evented_record COMPLETION_TIMEOUT = 5.minutes IN_PROGRESS_EXPIRES_AFTER = 1.day diff --git a/app/models/legacy_issue.rb b/app/models/legacy_issue.rb index 22006687207..3db514328ee 100644 --- a/app/models/legacy_issue.rb +++ b/app/models/legacy_issue.rb @@ -3,6 +3,12 @@ class LegacyIssue < CaseflowRecord belongs_to :request_issue has_one :legacy_issue_optin + has_one :event_record, as: :evented_record validates :request_issue, presence: true + + def from_decision_review_created_event? + # refer back to the associated Intake to see if both objects came from DRCE + request_issue&.from_decision_review_created_event? + end end diff --git a/app/models/legacy_issue_optin.rb b/app/models/legacy_issue_optin.rb index 22e623b43fc..43e0d01e9c3 100644 --- a/app/models/legacy_issue_optin.rb +++ b/app/models/legacy_issue_optin.rb @@ -3,6 +3,7 @@ class LegacyIssueOptin < CaseflowRecord belongs_to :request_issue belongs_to :legacy_issue + has_one :event_record, as: :evented_record VACOLS_DISPOSITION_CODE = "O" # oh not zero REMAND_DISPOSITION_CODES = %w[3 L].freeze @@ -109,6 +110,11 @@ def vacols_issue AppealRepository.issues(vacols_id).find { |issue| issue.vacols_sequence_id == vacols_sequence_id } end + def from_decision_review_created_event? + # refer back to the associated Intake to see if both objects came from DRCE + request_issue&.from_decision_review_created_event? + end + private def revert_open_remand_issues diff --git a/app/models/person.rb b/app/models/person.rb index d3d6c7c7277..94866057eb4 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -3,9 +3,11 @@ class Person < CaseflowRecord include AssociatedBgsRecord include BgsService + include EventConcern has_many :advance_on_docket_motions has_many :claimants, primary_key: :participant_id, foreign_key: :participant_id + has_one :event_record, as: :evented_record validates :participant_id, presence: true CACHED_BGS_ATTRIBUTES = [ diff --git a/app/models/request_issue.rb b/app/models/request_issue.rb index 286def0744a..b741f086737 100644 --- a/app/models/request_issue.rb +++ b/app/models/request_issue.rb @@ -35,6 +35,7 @@ class RequestIssue < CaseflowRecord has_one :legacy_issue_optin has_many :legacy_issues has_many :issue_modification_requests, dependent: :destroy + has_one :event_record, as: :evented_record belongs_to :correction_request_issue, class_name: "RequestIssue", foreign_key: "corrected_by_request_issue_id" belongs_to :ineligible_due_to, class_name: "RequestIssue", foreign_key: "ineligible_due_to_id" belongs_to :contested_decision_issue, class_name: "DecisionIssue" @@ -417,6 +418,87 @@ def withdrawal_date closed_at if withdrawn? end + def removed? + closed_status == "removed" + end + + # check if this RequestIssue was edited + def edited? + edited_description&.present? + end + + # check for any RequestIssuesUpdates on the associated DecisionReview + def any_updates? + decision_review.request_issues_updates.any? + end + + # Retrieve all Updates tied to the DecisionReview (Appeal, HLR/SC) + def fetch_request_issues_updates + decision_review.request_issues_updates if any_updates? + end + + def fetch_removed_by_user + if removed? + relevant_update = request_issues_updates.find do |update| + update.removed_issues.any? { |issue| issue.id == id } + end + + User.find(relevant_update&.user_id) if relevant_update + end + end + + def fetch_withdrawn_by_user + if withdrawn? + relevant_update = request_issues_updates.find do |update| + update.withdrawn_issues.any? { |issue| issue.id == id } + end + + User.find(relevant_update&.user_id) if relevant_update + end + end + + def fetch_edited_by_user + if edited? && any_updates? + # Find the most recent update where the current issue ID is in the edited_issues list + relevant_update = request_issues_updates + .select { |update| update.edited_issues.any? { |issue| issue.id == id } } + .max_by(&:updated_at) + + User.find(relevant_update&.user_id) if relevant_update + end + end + + # This retrieves the User who added the Issue as a result of a RequestIssuesUpdate and NOT during the initial Intake + def fetch_added_by_user_from_update + if any_updates? + relevant_update = request_issues_updates.find do |update| + update.added_issues.any? { |issue| issue.id == id } + end + + User.find(relevant_update&.user_id) if relevant_update + end + end + + def request_issues_updates + @request_issues_updates ||= fetch_request_issues_updates + end + + def removed_by_user + @removed_by_user ||= fetch_removed_by_user + end + + def withdrawn_by_user + @withdrawn_by_user ||= fetch_withdrawn_by_user + end + + def edited_by_user + @edited_by_user ||= fetch_edited_by_user + end + + def added_by_user + @added_by_user ||= fetch_added_by_user_from_update || decision_review&.intake&.user + end + def serialize Intake::RequestIssueSerializer.new(self).serializable_hash[:data][:attributes] end @@ -754,6 +836,11 @@ def timely_issue?(receipt_date) decision_date >= (receipt_date - Rating::ONE_YEAR_PLUS_DAYS) end + def from_decision_review_created_event? + # refer back to the associated Intake to see if both objects came from DRCE + decision_review&.from_decision_review_created_event? + end + private def create_legacy_issue! diff --git a/app/models/supplemental_claim.rb b/app/models/supplemental_claim.rb index cc33992cb6c..014dea14a0b 100644 --- a/app/models/supplemental_claim.rb +++ b/app/models/supplemental_claim.rb @@ -4,6 +4,7 @@ class SupplementalClaim < ClaimReview END_PRODUCT_MODIFIERS = %w[040 041 042 043 044 045 046 047 048 049].freeze belongs_to :decision_review_remanded, polymorphic: true + has_one :event_record, as: :evented_record scope :updated_since_for_appeals, lambda { |since| select(:decision_review_remanded_id) diff --git a/app/models/user.rb b/app/models/user.rb index c2e562a9b55..43d9eab9662 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < CaseflowRecord # rubocop:disable Metrics/ClassLength include BgsService + include EventConcern has_many :dispatch_tasks, class_name: "Dispatch::Task" has_many :document_views @@ -16,6 +17,7 @@ class User < CaseflowRecord # rubocop:disable Metrics/ClassLength has_many :decided_membership_requests, class_name: "MembershipRequest", foreign_key: :decider_id has_many :messages has_many :unrecognized_appellants, foreign_key: :created_by_id + has_one :event_record, as: :evented_record has_one :vacols_user, class_name: "CachedUser", foreign_key: :sdomainid, primary_key: :css_id has_one :vacols_staff, class_name: "VACOLS::Staff", foreign_key: :sdomainid, primary_key: :css_id diff --git a/app/models/validators/claimant_validator.rb b/app/models/validators/claimant_validator.rb index 4df78d1d6cb..a2f472180d9 100644 --- a/app/models/validators/claimant_validator.rb +++ b/app/models/validators/claimant_validator.rb @@ -48,11 +48,12 @@ def validate_participant_id decision_review.errors[:veteran_is_not_claimant] << ERRORS[:claimant_required] end + # added conditional to check if claimant is NOT from a decision_review_created_event def validate_claimant_address - if claimant.address_line_1.nil? + if claimant.address_line_1.nil? && !claimant&.from_decision_review_created_event? errors[:address] << ERRORS[:blank] decision_review.errors[:claimant] << ERRORS[:claimant_address_required] - elsif !claimant_address_lines_valid? + elsif !claimant_address_lines_valid? && !claimant&.from_decision_review_created_event? errors[:address] << ERRORS[:invalid] decision_review.errors[:claimant] << ERRORS[:claimant_address_invalid] end diff --git a/app/models/veteran.rb b/app/models/veteran.rb index a23c83dfb5b..d898c539a7b 100644 --- a/app/models/veteran.rb +++ b/app/models/veteran.rb @@ -7,10 +7,12 @@ # rubocop:disable Metrics/ClassLength class Veteran < CaseflowRecord include AssociatedBgsRecord + include EventConcern has_many :available_hearing_locations, foreign_key: :veteran_file_number, primary_key: :file_number, class_name: "AvailableHearingLocations" + has_one :event_record, as: :evented_record bgs_attr_accessor :ptcpnt_id, :sex, :address_line1, :address_line2, :address_line3, :city, :state, :country, :zip_code, diff --git a/app/serializers/api/v3/issues/ama/request_issue_serializer.rb b/app/serializers/api/v3/issues/ama/request_issue_serializer.rb index b1345f0bd80..6a931938b72 100644 --- a/app/serializers/api/v3/issues/ama/request_issue_serializer.rb +++ b/app/serializers/api/v3/issues/ama/request_issue_serializer.rb @@ -10,6 +10,7 @@ # `Api::V3::Issues::Ama::RequestIssueSerializer.new( # RequestIssue.includes(:decision_issues).page(2), include: [:decision_issues] # ).serializable_hash.to_json` +require "time" class Api::V3::Issues::Ama::RequestIssueSerializer include FastJsonapi::ObjectSerializer @@ -20,7 +21,8 @@ class Api::V3::Issues::Ama::RequestIssueSerializer :correction_type, :created_at, :decision_date, :decision_review_id, :decision_review_type, :edited_description, :end_product_establishment_id, :ineligible_due_to_id, :ineligible_reason, :is_unidentified, - :nonrating_issue_bgs_id, :nonrating_issue_category, :nonrating_issue_description, + :nonrating_issue_bgs_id, :nonrating_issue_category, + :nonrating_issue_bgs_source, :nonrating_issue_description, :notes, :ramp_claim_id, :split_issue_status, :unidentified_issue_text, :untimely_exemption, :untimely_exemption_notes, :updated_at, :vacols_id, :vacols_sequence_id, :verified_unidentified_issue, :veteran_participant_id @@ -38,6 +40,15 @@ class Api::V3::Issues::Ama::RequestIssueSerializer object&.end_product_establishment&.reference_id end + attribute :claim_errors do |object| + claim_id = object&.end_product_establishment&.reference_id + if claim_id + Event.find_errors_by_claim_id(claim_id) + else + [] + end + end + attribute :decision_issues do |object| object.decision_issues.map do |di| { @@ -52,11 +63,67 @@ class Api::V3::Issues::Ama::RequestIssueSerializer end_product_last_action_date: di.end_product_last_action_date, percent_number: di.percent_number, rating_issue_reference_id: di.rating_issue_reference_id, - rating_profile_date: di.rating_profile_date, + rating_profile_date: format_rating_profile_date(di.rating_profile_date), rating_promulgation_date: di.rating_promulgation_date, subject_text: di.subject_text, updated_at: di.updated_at } end end + + attribute :development_item_reference_id do |object| + object&.end_product_establishment&.development_item_reference_id + end + + attribute :same_office do |object| + HigherLevelReview.find_by(veteran_file_number: object&.veteran&.file_number)&.same_office + end + + attribute :legacy_opt_in_approved do |object| + object&.decision_review&.legacy_opt_in_approved + end + + attribute :added_by_station_id do |object| + object&.added_by_user&.station_id + end + + attribute :added_by_css_id do |object| + object&.added_by_user&.css_id + end + + attribute :edited_by_station_id do |object| + object&.edited_by_user&.station_id + end + + attribute :edited_by_css_id do |object| + object&.edited_by_user&.css_id + end + + attribute :removed_by_css_id do |object| + object&.removed_by_user&.css_id + end + + attribute :removed_by_station_id do |object| + object&.removed_by_user&.station_id + end + + attribute :withdrawn_by_css_id do |object| + object&.withdrawn_by_user&.css_id + end + + attribute :withdrawn_by_station_id do |object| + object&.withdrawn_by_user&.station_id + end + + def self.format_rating_profile_date(date) + return nil if date.blank? + + begin + return Time.parse(date).utc if date.is_a?(String) + rescue ArgumentError + return date.to_s + end + + date.utc + end end diff --git a/app/services/api/v3/issues/ama/vbms_ama_dto_builder.rb b/app/services/api/v3/issues/ama/vbms_ama_dto_builder.rb index da19e43676b..cdf3ff43f71 100644 --- a/app/services/api/v3/issues/ama/vbms_ama_dto_builder.rb +++ b/app/services/api/v3/issues/ama/vbms_ama_dto_builder.rb @@ -21,7 +21,7 @@ def initialize(veteran, page, per_page) def total_request_issue_count RequestIssue.where(veteran_participant_id: @veteran_participant_id) - .where(benefit_type: %w[compensation pension fiduciary]) + .where(benefit_type: %w[compensation pension]) .count end @@ -29,7 +29,7 @@ def serialized_request_issues(page = @page, per_page = @per_page) serialized_data = Api::V3::Issues::Ama::RequestIssueSerializer.new( RequestIssue.includes(:decision_issues, :decision_review) .where(veteran_participant_id: @veteran_participant_id) - .where(benefit_type: %w[compensation pension fiduciary]) + .where(benefit_type: %w[compensation pension]) .page(page).per(per_page) ).serializable_hash[:data] diff --git a/app/services/decision_review_created_issue_parser.rb b/app/services/decision_review_created_issue_parser.rb new file mode 100644 index 00000000000..09cfb3e31cd --- /dev/null +++ b/app/services/decision_review_created_issue_parser.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +class DecisionReviewCreatedIssueParser + include ParserHelper + attr_reader :issue + + def initialize(issue) + @issue = issue + end + + def ri_benefit_type + @issue.dig(:benefit_type) + end + + def ri_contested_issue_description + @issue.dig(:contested_issue_description) + end + + def ri_contention_reference_id + @issue.dig(:contention_reference_id) + end + + def ri_contested_rating_decision_reference_id + @issue.dig(:contested_rating_decision_reference_id) + end + + def ri_contested_rating_issue_profile_date + @issue.dig(:contested_rating_issue_profile_date) + end + + def ri_contested_rating_issue_reference_id + @issue.dig(:contested_rating_issue_reference_id) + end + + def ri_contested_decision_issue_id + @issue.dig(:contested_decision_issue_id) + end + + def ri_decision_date + decision_date_int = @issue.dig(:decision_date) + logical_date_converter(decision_date_int) + end + + def ri_ineligible_due_to_id + @issue.dig(:ineligible_due_to_id) + end + + def ri_ineligible_reason + @issue.dig(:ineligible_reason) + end + + def ri_is_unidentified + @issue.dig(:is_unidentified) + end + + def ri_unidentified_issue_text + @issue.dig(:unidentified_issue_text) + end + + def ri_nonrating_issue_category + @issue.dig(:nonrating_issue_category) + end + + def ri_nonrating_issue_description + @issue.dig(:nonrating_issue_description) + end + + def ri_untimely_exemption + @issue.dig(:untimely_exemption) + end + + def ri_untimely_exemption_notes + @issue.dig(:untimely_exemption_notes) + end + + def ri_vacols_id + @issue.dig(:vacols_id) + end + + def ri_vacols_sequence_id + @issue.dig(:vacols_sequence_id) + end + + def ri_closed_at + @issue.dig(:closed_at) + end + + def ri_closed_status + @issue.dig(:closed_status) + end + + def ri_contested_rating_issue_diagnostic_code + @issue.dig(:contested_rating_issue_diagnostic_code) + end + + def ri_ramp_claim_id + @issue.dig(:ramp_claim_id) + end + + def ri_rating_issue_associated_at + ri_rating_issue_associated_at_in_ms = @issue.dig(:rating_issue_associated_at) + convert_milliseconds_to_datetime(ri_rating_issue_associated_at_in_ms) + end + + def ri_nonrating_issue_bgs_id + @issue.dig(:nonrating_issue_bgs_id) + end + + def ri_nonrating_issue_bgs_source + @issue.dig(:nonrating_issue_bgs_source) + end +end diff --git a/app/services/events/create_claimant_on_event.rb b/app/services/events/create_claimant_on_event.rb new file mode 100644 index 00000000000..335dac6a65b --- /dev/null +++ b/app/services/events/create_claimant_on_event.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class Events::CreateClaimantOnEvent + class << self + def process!(event:, parser:, decision_review:) + if parser.claim_review_veteran_is_not_claimant + # We will create the Person record and add it to the People table if the record does not already exist + create_person(event, parser) unless Person.find_by(participant_id: parser.claimant_participant_id) + end + claimant = Claimant.create!( + decision_review: decision_review, + participant_id: parser.claimant_participant_id, + payee_code: parser.claimant_payee_code, + type: parser.claimant_type + ) + claimant + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedClaimantError, error.message + end + + def create_person(event, parser) + person = Person.create(date_of_birth: parser.person_date_of_birth, + email_address: parser.person_email_address, + first_name: parser.person_first_name, + last_name: parser.person_last_name, + middle_name: parser.person_middle_name, + name_suffix: parser.claimant_name_suffix, + ssn: parser.person_ssn, + participant_id: parser.claimant_participant_id) + + # We will add the Person record to the EventRecord table to show that the person record was created by the event. + # We will not add the Claimant record to the EventRecord table because the claimant record has an association with + # the claim_review (HLR, SC) record and the claim review record has an association with the intake record, which + # is stored in the EventRecord table. + EventRecord.create!(event: event, evented_record: person) + end + end +end diff --git a/app/services/events/create_user_on_event.rb b/app/services/events/create_user_on_event.rb new file mode 100644 index 00000000000..50ae8a30ddc --- /dev/null +++ b/app/services/events/create_user_on_event.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Service Class that will be utilized by Events::DecisionReviewCreated to create a new User +# when an Event is received and that specific User does not already exist in Caseflow +class Events::CreateUserOnEvent + class << self + def handle_user_creation_on_event(event:, css_id:, station_id:) + user = User.find_by(css_id: css_id) + return user if user + + create_inactive_user(event, css_id, station_id) + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedUserError, error.message + end + + def create_inactive_user(event, css_id, station_id) + user = User.create!(css_id: css_id.upcase, station_id: station_id, status: Constants.USER_STATUSES.inactive) + # create Event record indicating this is a backfilled User + EventRecord.create!(event: event, evented_record: user) + user + end + end +end diff --git a/app/services/events/create_veteran_on_event.rb b/app/services/events/create_veteran_on_event.rb new file mode 100644 index 00000000000..d1e6c580276 --- /dev/null +++ b/app/services/events/create_veteran_on_event.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Service Class that will be utilized by Events::DecisionReviewCreated to create a new Veteran +# when an Event is received and that specific Veteran does not already exist in Caseflow +class Events::CreateVeteranOnEvent + class << self + def handle_veteran_creation_on_event(event:, parser:) + if veteran_exist?(parser.veteran_file_number) + # return existing Veteran + Veteran.find_by(file_number: parser.veteran_file_number) + else + create_backfill_veteran(event, parser) + end + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedVeteranError, error.message + end + + def veteran_exist?(veteran_file_number) + Veteran.where(file_number: veteran_file_number).exists? + end + + private + + def create_backfill_veteran(event, parser) + # Create Veteran without calling BGS + vet = Veteran.create!( + file_number: parser.veteran_file_number, + ssn: parser.veteran_ssn, + first_name: parser.veteran_first_name, + last_name: parser.veteran_last_name, + middle_name: parser.veteran_middle_name, + participant_id: parser.veteran_participant_id, + bgs_last_synced_at: parser.veteran_bgs_last_synced_at, + name_suffix: parser.veteran_name_suffix, + date_of_death: parser.veteran_date_of_death + ) + + # create EventRecord indicating this is a backfilled Veteran + EventRecord.create!(event: event, evented_record: vet) + + vet + end + end +end diff --git a/app/services/events/decision_review_created.rb b/app/services/events/decision_review_created.rb new file mode 100644 index 00000000000..d5f9c665391 --- /dev/null +++ b/app/services/events/decision_review_created.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class Events::DecisionReviewCreated + include RedisMutex::Macro + include Events::DecisionReviewCreated::UpdateVacolsOnOptin + # Default options for RedisMutex#with_lock + # :block => 1 # Specify in seconds how long you want to wait for the lock to be released. + # # Specify 0 if you need non-blocking sematics and return false immediately. (default: 1) + # :sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given. + # # It is NOT recommended to go below 0.01. (default: 0.1) + # :expire => 10 # Specify in seconds when the lock should be considered stale when something went wrong + # # with the one who held the lock and failed to unlock. (default: 10) + + class << self + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Lint/UselessAssignment + def create!(params, headers, payload) + consumer_event_id = params[:consumer_event_id] + reference_id = params[:reference_id] + return if Event.exists_and_is_completed?(consumer_event_id) + + redis = Redis.new(url: Rails.application.secrets.redis_url_cache) + + # exit out if Key is already in Redis Cache + if redis.exists("RedisMutex:EndProductEstablishment:#{reference_id}") + fail Caseflow::Error::RedisLockFailed, + message: "Key RedisMutex:EndProductEstablishment:#{reference_id} is already in the Redis Cache" + end + + RedisMutex.with_lock("EndProductEstablishment:#{reference_id}", block: 60, expire: 100) do + # key => "EndProductEstablishment:reference_id" aka "claim ID" + # Use the consumer_event_id to retrieve/create the Event object + event = find_or_create_event(consumer_event_id) + + ActiveRecord::Base.transaction do + # Initialize the Parser object that will be passed around as an argument + parser = Events::DecisionReviewCreated::DecisionReviewCreatedParser.new(headers, payload) + + # NOTE: createdByStation == station_id, createdByUsername == css_id + user = Events::CreateUserOnEvent.handle_user_creation_on_event(event: event, css_id: parser.css_id, + station_id: parser.station_id) + + # Create the Veteran. PII Info is stored in the headers + vet = Events::CreateVeteranOnEvent.handle_veteran_creation_on_event(event: event, parser: parser) + + # Note Create Claim Review, parsed schema info passed through claim_review and intake + decision_review = Events::DecisionReviewCreated::CreateClaimReview.process!(parser: parser) + + # NOTE: Create the Claimant, parsed schema info passed through vbms_claimant + Events::CreateClaimantOnEvent.process!(event: event, parser: parser, + decision_review: decision_review) + + # NOTE: event, user, and veteran need to be before this call. + Events::DecisionReviewCreated::CreateIntake.process!( + { event: event, user: user, veteran: vet, + parser: parser, decision_review: decision_review } + ) + + # NOTE: end_product_establishment & station_id is coming from the payload + # claim_review can either be a higher_level_revew or supplemental_claim + epe = Events::DecisionReviewCreated::CreateEpEstablishment.process!(parser: parser, + claim_review: decision_review, + user: user) + + # NOTE: 'epe' arg is the obj created as a result of the CreateEpEstablishment service class + Events::DecisionReviewCreated::CreateRequestIssues.process!( + { event: event, parser: parser, epe: epe, + decision_review: decision_review } + ) + + # NOTE: decision_review arg can either be a HLR or SC object. process! will only run if + # decision_review.legacy_opt_in_approved is true + Events::DecisionReviewCreated::UpdateVacolsOnOptin.process!(decision_review: decision_review) + + # Update the Event after all backfills have completed + event.update!(completed_at: Time.now.in_time_zone, error: nil, info: {}) + end + end + rescue Caseflow::Error::RedisLockFailed => error + Rails.logger.error("Key RedisMutex:EndProductEstablishment:#{reference_id} is already in the Redis Cache") + event = Event.find_by(reference_id: consumer_event_id) + event&.update!(error: error.message) + raise error + rescue RedisMutex::LockError => error + Rails.logger.error("Failed to acquire lock for Claim ID: #{reference_id}! This Event is being"\ + " processed. Please try again later.") + rescue StandardError => error + Rails.logger.error("#{error.class} : #{error.message}") + event = Event.find_by(reference_id: consumer_event_id) + event&.update!(error: "#{error.class} : #{error.message}", info: { "failed_claim_id" => reference_id }) + raise error + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Lint/UselessAssignment + + # Check if there's already a CF Event that references that Appeals-Consumer EventID + # We will update the existing Event instead of creating a new one + def find_or_create_event(consumer_event_id) + DecisionReviewCreatedEvent.find_or_create_by(reference_id: consumer_event_id) + end + end +end diff --git a/app/services/events/decision_review_created/create_claim_review.rb b/app/services/events/decision_review_created/create_claim_review.rb new file mode 100644 index 00000000000..65ea7b48256 --- /dev/null +++ b/app/services/events/decision_review_created/create_claim_review.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Events::DecisionReviewCreated::CreateClaimReview + class << self + def process!(parser:) + if parser.detail_type == "HigherLevelReview" + high_level_review = create_high_level_review(parser) + high_level_review + else + supplemental_claim = create_supplemental_claim(parser) + supplemental_claim + end + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedCreateClaimReviewError, error.message + end + + private + + def create_high_level_review(parser) + HigherLevelReview.create( + benefit_type: parser.claim_review_benefit_type, + filed_by_va_gov: parser.claim_review_filed_by_va_gov, + legacy_opt_in_approved: parser.claim_review_legacy_opt_in_approved, + receipt_date: parser.claim_review_receipt_date, + veteran_is_not_claimant: parser.claim_review_veteran_is_not_claimant, + establishment_attempted_at: parser.claim_review_establishment_attempted_at, + establishment_last_submitted_at: parser.claim_review_establishment_last_submitted_at, + establishment_processed_at: parser.claim_review_establishment_processed_at, + establishment_submitted_at: parser.claim_review_establishment_submitted_at, + veteran_file_number: parser.veteran_file_number, + informal_conference: parser.claim_review_informal_conference, + same_office: parser.claim_review_same_office + ) + end + + def create_supplemental_claim(parser) + SupplementalClaim.create( + benefit_type: parser.claim_review_benefit_type, + filed_by_va_gov: parser.claim_review_filed_by_va_gov, + legacy_opt_in_approved: parser.claim_review_legacy_opt_in_approved, + receipt_date: parser.claim_review_receipt_date, + veteran_is_not_claimant: parser.claim_review_veteran_is_not_claimant, + establishment_attempted_at: parser.claim_review_establishment_attempted_at, + establishment_last_submitted_at: parser.claim_review_establishment_last_submitted_at, + establishment_processed_at: parser.claim_review_establishment_processed_at, + establishment_submitted_at: parser.claim_review_establishment_submitted_at, + veteran_file_number: parser.veteran_file_number + ) + end + end +end diff --git a/app/services/events/decision_review_created/create_ep_establishment.rb b/app/services/events/decision_review_created/create_ep_establishment.rb new file mode 100644 index 00000000000..8326a49a1a3 --- /dev/null +++ b/app/services/events/decision_review_created/create_ep_establishment.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# This is the Sub Service Class that holds the process! of starting the +# creation of an End Product Establishment from an event. +class Events::DecisionReviewCreated::CreateEpEstablishment + class << self + # This starts the creation of End Product Establishment from an event. + # This is a sub service class that returns the End Product Establishment + # that was created from the event. Arguments claim_review, user and event + # are referring to the backfill objects being created from other sub service + # class. claim_review can be either a supplemental claim or higher level review + # rubocop:disable Metrics/MethodLength + def process!(parser:, claim_review:, user:) + end_product_establishment = EndProductEstablishment.create!( + payee_code: parser.epe_payee_code, + source: claim_review, + veteran_file_number: claim_review.veteran_file_number, + benefit_type_code: parser.epe_benefit_type_code, + claim_date: parser.epe_claim_date, + code: parser.epe_code, + committed_at: parser.epe_committed_at, + development_item_reference_id: parser.epe_development_item_reference_id, + established_at: parser.epe_established_at, + last_synced_at: parser.epe_last_synced_at, + limited_poa_access: parser.epe_limited_poa_access, + limited_poa_code: parser.epe_limited_poa_code, + modifier: parser.epe_modifier, + reference_id: parser.epe_reference_id, + station: parser.station_id, + synced_status: parser.epe_synced_status, + user_id: user.id, + claimant_participant_id: parser.claimant_participant_id + ) + end_product_establishment + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedEpEstablishmentError, error.message + end + # rubocop:enable Metrics/MethodLength + end +end diff --git a/app/services/events/decision_review_created/create_intake.rb b/app/services/events/decision_review_created/create_intake.rb new file mode 100644 index 00000000000..e7496768674 --- /dev/null +++ b/app/services/events/decision_review_created/create_intake.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# This class starts the intial Intake creation and backfills the EventRecord +# when a Decision Review Created Event is triggered +class Events::DecisionReviewCreated::CreateIntake + # This starts the process of the Intake creation and EventRecord backfill by passing in the event, user, and veteran + # that was created in the DecisionReviewCreated Service. + + class << self + def process!(params) + event = params[:event] + user = params[:user] + veteran = params[:veteran] + parser = params[:parser] + decision_review = params[:decision_review] + # create Intake + intake = Intake.create!(veteran_file_number: veteran.file_number, + user: user, + started_at: parser.intake_started_at, + completion_started_at: parser.intake_completion_started_at, + completed_at: parser.intake_completed_at, + completion_status: parser.intake_completion_status, + type: parser.intake_type, + detail_type: parser.intake_detail_type, + detail_id: decision_review.id, + veteran: veteran) + # create EventRecord + EventRecord.create!(event: event, evented_record: intake) + + intake + + # Error Handling + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedIntakeError, error.message + end + end +end diff --git a/app/services/events/decision_review_created/create_request_issues.rb b/app/services/events/decision_review_created/create_request_issues.rb new file mode 100644 index 00000000000..427677ff511 --- /dev/null +++ b/app/services/events/decision_review_created/create_request_issues.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +# Service Class that will be utilized by Events::DecisionReviewCreated to create Request Issues +# when an Event is received using the data sent from VBMS +class Events::DecisionReviewCreated::CreateRequestIssues + class << self + def process!(params) + create_request_issue_backfill(params) + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreatedRequestIssuesError, error.message + end + + private + + # iterate through the array of issues and create backfill object from each one + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def create_request_issue_backfill(params) + event = params[:event] + epe = params[:epe] + parser = params[:parser] + decision_review = params[:decision_review] + request_issues = parser.request_issues + newly_created_issues = [] + + request_issues&.each do |issue| + # create backfill RI object using extracted values + parser_issues = DecisionReviewCreatedIssueParser.new(issue) + ri = RequestIssue.create!( + benefit_type: parser_issues.ri_benefit_type, + contested_issue_description: parser_issues.ri_contested_issue_description, + contention_reference_id: parser_issues.ri_contention_reference_id, + contested_rating_decision_reference_id: parser_issues.ri_contested_rating_decision_reference_id, + contested_rating_issue_profile_date: parser_issues.ri_contested_rating_issue_profile_date, + contested_rating_issue_reference_id: parser_issues.ri_contested_rating_issue_reference_id, + contested_decision_issue_id: parser_issues.ri_contested_decision_issue_id, + decision_date: parser_issues.ri_decision_date, + ineligible_due_to_id: parser_issues.ri_ineligible_due_to_id, + ineligible_reason: parser_issues.ri_ineligible_reason, + is_unidentified: parser_issues.ri_is_unidentified, + unidentified_issue_text: parser_issues.ri_unidentified_issue_text, + nonrating_issue_category: parser_issues.ri_nonrating_issue_category, + nonrating_issue_description: parser_issues.ri_nonrating_issue_description, + untimely_exemption: parser_issues.ri_untimely_exemption, + untimely_exemption_notes: parser_issues.ri_untimely_exemption_notes, + vacols_id: parser_issues.ri_vacols_id, + vacols_sequence_id: parser_issues.ri_vacols_sequence_id, + closed_at: parser_issues.ri_closed_at, + closed_status: parser_issues.ri_closed_status, + contested_rating_issue_diagnostic_code: parser_issues.ri_contested_rating_issue_diagnostic_code, + ramp_claim_id: parser_issues.ri_ramp_claim_id, + rating_issue_associated_at: parser_issues.ri_rating_issue_associated_at, + nonrating_issue_bgs_id: parser_issues.ri_nonrating_issue_bgs_id, + nonrating_issue_bgs_source: parser_issues.ri_nonrating_issue_bgs_source, + end_product_establishment_id: epe.id, + veteran_participant_id: parser.veteran_participant_id, + decision_review: decision_review + ) + create_event_record(event, ri) + newly_created_issues.push(ri) + + # LegacyIssue + if vacols_ids_exist?(ri) + legacy_issue = create_legacy_issue_backfill(event, ri) + + # LegacyIssueOptin + if optin?(decision_review) && ri.ineligible_reason.blank? + create_legacy_optin_backfill(event, ri, legacy_issue) + end + end + end + newly_created_issues + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + + def create_event_record(event, issue) + EventRecord.create!(event: event, evented_record: issue) + end + + # Legacy issue checks + def vacols_ids_exist?(request_issue) + request_issue.vacols_id.present? && request_issue.vacols_sequence_id.present? + end + + def optin?(decision_review) + decision_review.legacy_opt_in_approved? + end + + def create_legacy_issue_backfill(event, request_issue) + li = LegacyIssue.create!(request_issue_id: request_issue.id, + vacols_id: request_issue.vacols_id, + vacols_sequence_id: request_issue.vacols_sequence_id) + create_event_record(event, li) + + li + end + + def create_legacy_optin_backfill(event, request_issue, legacy_issue) + optin = LegacyIssueOptin.create!(request_issue_id: request_issue.id, + legacy_issue: legacy_issue) + create_event_record(event, optin) + end + end +end diff --git a/app/services/events/decision_review_created/decision_review_created_example.json b/app/services/events/decision_review_created/decision_review_created_example.json new file mode 100644 index 00000000000..b1e9092a472 --- /dev/null +++ b/app/services/events/decision_review_created/decision_review_created_example.json @@ -0,0 +1,86 @@ +{ + "css_id": "BVADWISE", + "detail_type": "HigherLevelReview", + "station": "101", + "claim_id": "123566", + "event_id": "1", + "intake": { + "started_at": 1702067143435, + "completion_started_at": 1702067145000, + "completed_at": 1702067145000, + "completion_status": "success", + "type": "HigherLevelReviewIntake", + "detail_type": "HigherLevelReview" + }, + "veteran": { + "participant_id": "1826209", + "bgs_last_synced_at": 1708533584000, + "name_suffix": null, + "date_of_death": null + }, + "claimant": { + "payee_code": "00", + "type": "VeteranClaimant", + "participant_id": "1826209", + "name_suffix": null + }, + "claim_review": { + "benefit_type": "compensation", + "filed_by_va_gov": false, + "legacy_opt_in_approved": false, + "receipt_date": 19568, + "veteran_is_not_claimant": false, + "establishment_attempted_at": 1702067145000, + "establishment_last_submitted_at": 1702067145000, + "establishment_processed_at": 1702067145000, + "establishment_submitted_at": 1702067145000, + "informal_conference": false, + "same_office": false + }, + "end_product_establishment": { + "benefit_type_code": "1", + "claim_date": 19568, + "code": "030HLRNR", + "modifier": "030", + "payee_code": "00", + "reference_id": "337534", + "limited_poa_access": null, + "limited_poa_code": null, + "committed_at": 1702067145000, + "established_at": 1702067145000, + "last_synced_at": 1702067145000, + "synced_status": "RW", + "development_item_reference_id": null + }, + "request_issues": [ + { + "benefit_type": "compensation", + "contested_issue_description": "some contested description", + "contention_reference_id": 7905752, + "contested_rating_decision_reference_id": null, + "contested_rating_issue_profile_date": null, + "contested_rating_issue_reference_id": null, + "contested_decision_issue_id": null, + "decision_date": 19568, + "ineligible_due_to_id": null, + "ineligible_reason": null, + "is_unidentified": false, + "unidentified_issue_text": null, + "nonrating_issue_category": "Accrued Benefits", + "nonrating_issue_description": "The user entered description if the issue is a nonrating issue", + "untimely_exemption": null, + "untimely_exemption_notes": null, + "vacols_id": null, + "vacols_sequence_id": null, + "closed_at": null, + "closed_status": null, + "contested_rating_issue_diagnostic_code": null, + "ramp_claim_id": null, + "rating_issue_associated_at": null, + "nonrating_issue_bgs_id": "13", + "nonrating_issue_bgs_source": "Test Source" + } + ] +} + + diff --git a/app/services/events/decision_review_created/decision_review_created_parser.rb b/app/services/events/decision_review_created/decision_review_created_parser.rb new file mode 100644 index 00000000000..8bf110ab982 --- /dev/null +++ b/app/services/events/decision_review_created/decision_review_created_parser.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +# Parser Class that will be used to extract out datapoints from headers & payload for use with +# DecisionReviewCreated and it's service Classes +class Events::DecisionReviewCreated::DecisionReviewCreatedParser + include Events::VeteranExtractorInterface + include ParserHelper + + attr_reader :headers, :payload + + class << self + # This method reads the drc_example.json file for our load_example method + def example_response + File.read(Rails.root.join("app", + "services", + "events", + "decision_review_created", + "decision_review_created_example.json")) + end + + # This method creates a new instance of DecisionReviewCreatedParser in order to + # mimic the parsing of a payload recieved by appeals-consumer + # arguments being passed in are the sample_header and example_response + def load_example + sample_header = { + "X-VA-Vet-SSN" => "123456789", + "X-VA-File-Number" => "77799777", + "X-VA-Vet-First-Name" => "John", + "X-VA-Vet-Last-Name" => "Smith", + "X-VA-Vet-Middle-Name" => "Alexander" + } + new(sample_header, JSON.parse(example_response)) + end + end + + def initialize(headers, payload_json) + process_nonrating_issue_category(payload_json) if payload_json[:request_issues].present? + @payload = payload_json.to_h.deep_symbolize_keys + @headers = headers + @veteran = @payload.dig(:veteran) + end + + def css_id + @payload.dig(:css_id) + end + + def detail_type + @payload.dig(:detail_type) + end + + def station_id + @payload.dig(:station) + end + + def event_id + @payload.dig(:event_id) + end + + def claim_id + @payload.dig(:claim_id) + end + + # Intake attributes + def intake + @payload.dig(:intake) + end + + def intake_started_at + intake_started_at_milliseconds = @payload.dig(:intake, :started_at) + convert_milliseconds_to_datetime(intake_started_at_milliseconds) + end + + def intake_completion_started_at + intake_completetion_start_at_milliseconds = @payload.dig(:intake, :completion_started_at) + convert_milliseconds_to_datetime(intake_completetion_start_at_milliseconds) + end + + def intake_completed_at + intake_completed_at_milliseconds = @payload.dig(:intake, :completed_at) + convert_milliseconds_to_datetime(intake_completed_at_milliseconds) + end + + def intake_completion_status + @payload.dig(:intake, :completion_status) + end + + def intake_type + @payload.dig(:intake, :type) + end + + def intake_detail_type + @payload.dig(:intake, :detail_type) + end + + # Veteran attributes + def veteran + @payload.dig(:veteran) + end + + def veteran_file_number + @veteran_file_number ||= @headers["X-VA-File-Number"].presence + end + + def veteran_ssn + @veteran_ssn ||= @headers["X-VA-Vet-SSN"].presence + end + + def veteran_first_name + @headers["X-VA-Vet-First-Name"] + end + + def veteran_last_name + @headers["X-VA-Vet-Last-Name"] + end + + def veteran_middle_name + @headers["X-VA-Vet-Middle-Name"].presence + end + + def person_date_of_birth + dob = @headers["X-VA-Claimant-DOB"].presence + convert_milliseconds_to_datetime(dob) + end + + def person_email_address + @headers["X-VA-Claimant-Email"].presence + end + + def person_first_name + @headers["X-VA-Claimant-First-Name"].presence + end + + def person_last_name + @headers["X-VA-Claimant-Last-Name"].presence + end + + def person_middle_name + @headers["X-VA-Claimant-Middle-Name"].presence + end + + def person_ssn + @headers["X-VA-Claimant-SSN"].presence + end + + def veteran_participant_id + @payload.dig(:veteran, :participant_id) + end + + def veteran_bgs_last_synced_at + bgs_last_synced_at_milliseconds = @payload.dig(:veteran, :bgs_last_synced_at) + convert_milliseconds_to_datetime(bgs_last_synced_at_milliseconds) + end + + def veteran_name_suffix + @payload.dig(:veteran, :name_suffix) + end + + def veteran_date_of_death + date_of_death = @payload.dig(:veteran, :date_of_death) + logical_date_converter(date_of_death) + end + + # Claimant attributes + def claimant + @payload.dig(:claimant) + end + + def claimant_payee_code + @payload.dig(:claimant, :payee_code) + end + + def claimant_type + @payload.dig(:claimant, :type) + end + + def claimant_participant_id + @payload.dig(:claimant, :participant_id) + end + + def claimant_name_suffix + @payload.dig(:claimant, :name_suffix) + end + + # ClaimReview attributes + def claim_review + @payload.dig(:claim_review) + end + + def claim_review_benefit_type + @payload.dig(:claim_review, :benefit_type) + end + + def claim_review_filed_by_va_gov + @payload.dig(:claim_review, :filed_by_va_gov) + end + + def claim_review_legacy_opt_in_approved + @payload.dig(:claim_review, :legacy_opt_in_approved) + end + + def claim_review_receipt_date + receipt_date_logical_int_date = @payload.dig(:claim_review, :receipt_date) + logical_date_converter(receipt_date_logical_int_date) + end + + def claim_review_veteran_is_not_claimant + @payload.dig(:claim_review, :veteran_is_not_claimant) + end + + def claim_review_establishment_attempted_at + establishment_attempted_at_in_milliseconds = @payload.dig(:claim_review, :establishment_attempted_at) + convert_milliseconds_to_datetime(establishment_attempted_at_in_milliseconds) + end + + def claim_review_establishment_last_submitted_at + establishment_last_submitted_at_in_milliseconds = @payload.dig(:claim_review, :establishment_last_submitted_at) + convert_milliseconds_to_datetime(establishment_last_submitted_at_in_milliseconds) + end + + def claim_review_establishment_processed_at + establishment_processed_at_in_milliseconds = @payload.dig(:claim_review, :establishment_processed_at) + convert_milliseconds_to_datetime(establishment_processed_at_in_milliseconds) + end + + def claim_review_establishment_submitted_at + establishment_submitted_at_in_milliseconds = @payload.dig(:claim_review, :establishment_submitted_at) + convert_milliseconds_to_datetime(establishment_submitted_at_in_milliseconds) + end + + def claim_review_informal_conference + @payload.dig(:claim_review, :informal_conference) + end + + def claim_review_same_office + @payload.dig(:claim_review, :same_office) + end + + # EndProductEstablishment attr + def epe + @payload.dig(:end_product_establishment) + end + + def epe_benefit_type_code + @payload.dig(:end_product_establishment, :benefit_type_code) + end + + def epe_claim_date + logical_date_int = @payload.dig(:end_product_establishment, :claim_date) + logical_date_converter(logical_date_int) + end + + def epe_code + @payload.dig(:end_product_establishment, :code) + end + + def epe_modifier + @payload.dig(:end_product_establishment, :modifier) + end + + def epe_payee_code + @payload.dig(:end_product_establishment, :payee_code) + end + + def epe_reference_id + @payload.dig(:end_product_establishment, :reference_id) + end + + def epe_limited_poa_access + @payload.dig(:end_product_establishment, :limited_poa_access) + end + + def epe_limited_poa_code + @payload.dig(:end_product_establishment, :limited_poa_code) + end + + def epe_committed_at + committed_at_milliseconds = @payload.dig(:end_product_establishment, :committed_at) + convert_milliseconds_to_datetime(committed_at_milliseconds) + end + + def epe_established_at + established_at_milliseconds = @payload.dig(:end_product_establishment, :established_at) + convert_milliseconds_to_datetime(established_at_milliseconds) + end + + def epe_last_synced_at + last_synced_at_milliseconds = @payload.dig(:end_product_establishment, :last_synced_at) + convert_milliseconds_to_datetime(last_synced_at_milliseconds) + end + + def epe_synced_status + @payload.dig(:end_product_establishment, :synced_status) + end + + def epe_development_item_reference_id + @payload.dig(:end_product_establishment, :development_item_reference_id) + end + + # RequestIssues attr + # return the array of RI objects + def request_issues + @payload.dig(:request_issues) + end +end diff --git a/app/services/events/decision_review_created/update_vacols_on_optin.rb b/app/services/events/decision_review_created/update_vacols_on_optin.rb new file mode 100644 index 00000000000..1096e3aa4f1 --- /dev/null +++ b/app/services/events/decision_review_created/update_vacols_on_optin.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Events::DecisionReviewCreated::UpdateVacolsOnOptin + # Updates the Caseflow and VACOLS DB when Legacy Issues Optin to AMA + # the decision review argument being passed in can either be a Higher Level Review or a Supplemental Claim + # the decision review hash must have a benefit type. + def self.process!(decision_review:) + if decision_review.legacy_opt_in_approved + LegacyOptinManager.new(decision_review: decision_review).process! + end + # Catch the error and raise + rescue StandardError => error + raise Caseflow::Error::DecisionReviewCreateVacolsOnOptinError, error.message + end +end diff --git a/app/services/events/decision_review_created_error.rb b/app/services/events/decision_review_created_error.rb new file mode 100644 index 00000000000..709e8a88f01 --- /dev/null +++ b/app/services/events/decision_review_created_error.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# DecisionReviewCreatedError Service. This handles the service error payload from the appeals-consumer. +# Payload requires event_id, errored_claim_id, and the error_message within the request +# This service also uses the RedisMutex.with_lock to make sure parallel transactions related to the claim_id +# does not make database changes at the same time. +class Events::DecisionReviewCreatedError + # Using macro-style definition. The locking scope will be TheClass#method and only one method can run at any + # given time. + include RedisMutex::Macro + + # Default options for RedisMutex#with_lock + # :block => 1 # Specify in seconds how long you want to wait for the lock to be released. + # # Specify 0 if you need non-blocking sematics and return false immediately. (default: 1) + # :sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given. + # # It is NOT recommended to go below 0.01. (default: 0.1) + # :expire => 10 # Specify in seconds when the lock should be considered stale when something went wrong + # # with the one who held the lock and failed to unlock. (default: 10) + class << self + def handle_service_error(consumer_event_id, errored_claim_id, error_message) + # check if consumer_event_id Event.reference_id exist if not Create DecisionReviewCreated Event + event = DecisionReviewCreatedEvent.find_or_create_by(reference_id: consumer_event_id) + + redis = Redis.new(url: Rails.application.secrets.redis_url_cache) + + if redis.exists("RedisMutex:EndProductEstablishment:#{errored_claim_id}") + fail Caseflow::Error::RedisLockFailed, message: "Key RedisMutex:EndProductEstablishment:#{errored_claim_id} + is already in the Redis Cache" + end + + RedisMutex.with_lock("EndProductEstablishment:#{errored_claim_id}", block: 60, expire: 100) do + ActiveRecord::Base.transaction do + event&.update!(error: error_message, info: { "errored_claim_id" => errored_claim_id }) + end + end + rescue RedisMutex::LockError => error + Rails.logger.error("LockError occurred: #{error.message}") + rescue StandardError => error + Rails.logger.error(error.message) + event&.update!(error: error.message) + raise error + end + end +end diff --git a/app/services/events/veteran_extractor_interface.rb b/app/services/events/veteran_extractor_interface.rb new file mode 100644 index 00000000000..76e0e594af1 --- /dev/null +++ b/app/services/events/veteran_extractor_interface.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Interface for use with Events::DecisionReviewCreated::DecisionReviewCreatedParser +# to extract Veteran information +module Events::VeteranExtractorInterface + def veteran_file_number + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_ssn + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_first_name + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_last_name + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_middle_name + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_participant_id + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_bgs_last_synced_at + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_name_suffix + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def veteran_date_of_death + fail NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end +end diff --git a/app/views/higher_level_reviews/edit.html.erb b/app/views/higher_level_reviews/edit.html.erb index f05f84aae0a..367cbc686d3 100644 --- a/app/views/higher_level_reviews/edit.html.erb +++ b/app/views/higher_level_reviews/edit.html.erb @@ -13,10 +13,12 @@ buildDate: build_date, serverIntake: higher_level_review.ui_hash, claimId: url_claim_id, + intakeFromVbms: higher_level_review.from_decision_review_created_event?, featureToggles: { useAmaActivationDate: FeatureToggle.enabled?(:use_ama_activation_date, user: current_user), correctClaimReviews: FeatureToggle.enabled?(:correct_claim_reviews, user: current_user), - covidTimelinessExemption: FeatureToggle.enabled?(:covid_timeliness_exemption, user: current_user) + covidTimelinessExemption: FeatureToggle.enabled?(:covid_timeliness_exemption, user: current_user), + disableAmaEventing: FeatureToggle.enabled?(:disable_ama_eventing, user: current_user) } }) %> <% end %> diff --git a/app/views/queue/index.html.erb b/app/views/queue/index.html.erb index 60ded4506a7..8832fea20d7 100644 --- a/app/views/queue/index.html.erb +++ b/app/views/queue/index.html.erb @@ -63,7 +63,8 @@ additional_remand_reasons: FeatureToggle.enabled?(:additional_remand_reasons, user: current_user), acd_cases_tied_to_judges_no_longer_with_board: FeatureToggle.enabled?(:acd_cases_tied_to_judges_no_longer_with_board, user: current_user), admin_case_distribution: FeatureToggle.enabled?(:admin_case_distribution, user: current_user), - acd_exclude_from_affinity: FeatureToggle.enabled?(:acd_exclude_from_affinity, user: current_user) + acd_exclude_from_affinity: FeatureToggle.enabled?(:acd_exclude_from_affinity, user: current_user), + disable_ama_eventing: FeatureToggle.enabled?(:disable_ama_eventing, user: current_user) } }) %> <% end %> diff --git a/app/views/supplemental_claims/edit.html.erb b/app/views/supplemental_claims/edit.html.erb index bf8d611f463..5419b2d10ab 100644 --- a/app/views/supplemental_claims/edit.html.erb +++ b/app/views/supplemental_claims/edit.html.erb @@ -13,10 +13,12 @@ buildDate: build_date, serverIntake: supplemental_claim.ui_hash, claimId: url_claim_id, + intakeFromVbms: supplemental_claim.from_decision_review_created_event?, featureToggles: { useAmaActivationDate: FeatureToggle.enabled?(:use_ama_activation_date, user: current_user), correctClaimReviews: FeatureToggle.enabled?(:correct_claim_reviews, user: current_user), - covidTimelinessExemption: FeatureToggle.enabled?(:covid_timeliness_exemption, user: current_user) + covidTimelinessExemption: FeatureToggle.enabled?(:covid_timeliness_exemption, user: current_user), + disableAmaEventing: FeatureToggle.enabled?(:disable_ama_eventing, user: current_user) } }) %> <% end %> diff --git a/client/app/components/badges/BadgeArea.jsx b/client/app/components/badges/BadgeArea.jsx index 66c0c23d6c6..ec0777d1abc 100644 --- a/client/app/components/badges/BadgeArea.jsx +++ b/client/app/components/badges/BadgeArea.jsx @@ -8,6 +8,7 @@ import OvertimeBadge from './OvertimeBadge/OvertimeBadge'; import QueueFnodBadge from './FnodBadge/QueueFnodBadge'; import MstBadge from './MstBadge/MstBadge'; import PactBadge from './PactBadge/PactBadge'; +import IntakeBadge from './IntakeBadge/IntakeBadge'; import { mostRecentHeldHearingForAppeal } from 'app/queue/utils'; /** @@ -15,11 +16,13 @@ import { mostRecentHeldHearingForAppeal } from 'app/queue/utils'; * Each badge should individually handle whether or not they should be displayed. * This component can accept either an Appeal object or a Task object. An appeal object should be passed in places where * we are strictly showing an appeal (in case details or case search). A Task object should be passed in places we do - * have a task rather than an appeal (in queue task lists) + * have a task rather than an appeal (in queue task lists). A review obbject should be passed in places we have a + * ClaimReview rather than an appeal (e.g. case search OtherReviewsTable). * The default is for badges to be displayed listed vertically. Pass isHorizontal to display them horizontally * e.g., * * + * ** * These badges were created in the queue application, CASEFLOW-432 adds the FnodBadge to the hearings application * To do that the QueueFnodBadge was added, this is a container component which provides the queue state to the @@ -27,7 +30,7 @@ import { mostRecentHeldHearingForAppeal } from 'app/queue/utils'; **/ class BadgeArea extends React.PureComponent { render = () => { - const { appeal, isHorizontal, task } = this.props; + const { appeal, isHorizontal, task, review } = this.props; let badges; @@ -42,7 +45,7 @@ class BadgeArea extends React.PureComponent { ; - } else { + } else if (task) { badges = ; + } else { + // review (ClaimReviews) + badges = + + ; } const badgeAreaStyling = css({ @@ -66,6 +74,7 @@ class BadgeArea extends React.PureComponent { BadgeArea.propTypes = { appeal: PropTypes.object, task: PropTypes.object, + review: PropTypes.object, isHorizontal: PropTypes.bool }; diff --git a/client/app/components/badges/IntakeBadge/IntakeBadge.jsx b/client/app/components/badges/IntakeBadge/IntakeBadge.jsx new file mode 100644 index 00000000000..b6142d8f021 --- /dev/null +++ b/client/app/components/badges/IntakeBadge/IntakeBadge.jsx @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types'; +import * as React from 'react'; + +import Badge from '../Badge'; +import { COLORS } from 'app/constants/AppConstants'; + +/** + * Component to display where the Intake originated from (VBMS or Caseflow) + */ + +const IntakeBadge = (props) => { + const { review } = props; + let tooltipText = ''; + + if (review.intakeFromVbms) { + tooltipText = 'Case was intaken through VBMS'; + + return ; + } + tooltipText = 'Case was intaken through Caseflow'; + + return ; + +}; + +IntakeBadge.propTypes = { + review: PropTypes.object, +}; + +export default IntakeBadge; diff --git a/client/app/components/badges/IntakeBadge/IntakeBadge.stories.js b/client/app/components/badges/IntakeBadge/IntakeBadge.stories.js new file mode 100644 index 00000000000..cec49996fb1 --- /dev/null +++ b/client/app/components/badges/IntakeBadge/IntakeBadge.stories.js @@ -0,0 +1,27 @@ +import React from 'react'; + +import IntakeBadgeComponent from './IntakeBadge'; + +export default { + title: 'Commons/Components/Badges/Intake Badge', + component: IntakeBadgeComponent, + parameters: { + layout: 'centered', + }, + args: { + review: { + intakeFromVbms: false, + } + } +}; + +const Template = (args) => ; + +export const IntakeBadgeCF = Template.bind({}); + +export const IntakeBadgeVBMS = Template.bind({}); +IntakeBadgeVBMS.args = { + review: { + intakeFromVbms: true, + } +}; diff --git a/client/app/components/badges/IntakeBadge/IntakeBadge.test.js b/client/app/components/badges/IntakeBadge/IntakeBadge.test.js new file mode 100644 index 00000000000..09a88ff4301 --- /dev/null +++ b/client/app/components/badges/IntakeBadge/IntakeBadge.test.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import thunk from 'redux-thunk'; +import { Provider } from 'react-redux'; +import { createStore, applyMiddleware } from 'redux'; + +import rootReducer from 'app/queue/reducers'; +import IntakeBadge from './IntakeBadge'; + +describe('IntakeBadge', () => { + const defaultReview = { + intakeFromVbms: true, + }; + + const getStore = () => createStore(rootReducer, applyMiddleware(thunk)); + + const setupIntakeBadge = (store) => { + return mount( + + + + ); + }; + + it('renders correctly', () => { + const store = getStore(); + const component = setupIntakeBadge(store); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/client/app/components/badges/IntakeBadge/__snapshots__/IntakeBadge.test.js.snap b/client/app/components/badges/IntakeBadge/__snapshots__/IntakeBadge.test.js.snap new file mode 100644 index 00000000000..d6996ec765a --- /dev/null +++ b/client/app/components/badges/IntakeBadge/__snapshots__/IntakeBadge.test.js.snap @@ -0,0 +1,163 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IntakeBadge renders correctly 1`] = ` + + + +
+ + + VBMS + + + +