diff --git a/Makefile.example b/Makefile.example index af35e88cb32..09225fb21f1 100644 --- a/Makefile.example +++ b/Makefile.example @@ -253,6 +253,9 @@ reset-dbs: ## Resets Caseflow and ETL database schemas seed-vbms-ext-claim: ## Seed only vbms_ext_claim bundle exec rake db:seed:vbms_ext_claim +seed-ama-intake: ## Seeds scenarios for the AMA Intake API + bundle exec rake db:seed:ama_intake + seed-dbs: ## Seed all databases bundle exec rake local:vacols:seed bundle exec rake spec:setup_vacols diff --git a/app/controllers/api/docs/v3/ama_issues.yaml b/app/controllers/api/docs/v3/ama_issues.yaml new file mode 100644 index 00000000000..9b0823c3a5f --- /dev/null +++ b/app/controllers/api/docs/v3/ama_issues.yaml @@ -0,0 +1,533 @@ +openapi: 3.0.3 +info: + title: AMA Request Issues + description: >+ + The AMA Request Issues API allows you to interact with a veteran’s AMA Request Issues. This API provides all AMA related Request Issues and related Decision Issues per Veteran particpant ID. + + + For more information on the AMA Request Issue process, including the different types of AMA Request Issues, please see [this informative page on VA.gov](https://www.va.gov/decision-reviews/). + + + The AMA Request Issues API supports the Appeals Modernization Act (AMA) process only. + + ## Technical Summary + + The AMA Request Issues API interacts with VA's internal system, called Caseflow, that manages all benefit appeals. + + * The AMA Request Issues API accepts the veteran participant identification is supplied in the URL. + + * The AMA Request Issue request will be validated to ensure the Veteran information matches VA records and that the appeal information sent is valid. + + * Detailed error messages will be returned to help your application provide meaningful information to the Veteran attempting to make the AMA Request Issue request. + + * The Request Issues are paginated in the response. The page paramenter must be a integer greater than or equal to 1. 0 and negative numbers are defaulted to page 1. + + ## Authorization + + API requests are authorized through a token, provided in an HTTP header. + Example _curl_ (assuming you're running Caseflow locally, and the `api_v3` feature toggle has been enabled): + + ``` + curl -v -H 'Authorization: Token {{YourApiKeyHere}}'\ + 'http://localhost:3000/api/v3/issues/ama/find_by_veteran/{{VeteranParticipantId}}?page=2' + + ``` + + version: 3.0.0 +servers: + - url: '{{CaseflowInstance}}/api/v3/ama_issues' +paths: + /api/v3/issues/ama/find_by_veteran/{veteran_participant_id}: + get: + tags: + - Request Issues + parameters: + - name: veteran_participant_id + in: path + required: true + description: Veteran Participant ID + schema: + $ref: "#/components/schemas/VeteranParticipantId" + - in: query + name: page + schema: + type: integer + description: The page of request issues for the supplied veteran. Positive greater than 0. 0 or negative numbers are defaulted to 1. + summary: Retrieve all AMA request issues and associated decision issues from Veteran. + description: >+ + Will return a paginated response of request issues and associated decision issue for the Veteran. + responses: + '200': + description: 200 OK + content: + application/vnd.api+json: + examples: + veteran_returned_1_page: + value: + page: 1 + total_number_of_pages: 1 + total_request_issues_for_vet: 2 + max_request_issues_per_page: 2 + veteran_participant_id: "123456789" + legacy_appeals_present: true + request_issues: + - id: 1 + benefit_type: compensation + closed_at: nil + closed_status: nil + contention_reference_id: 1 + contested_decision_issue_id: 453 + contested_issue_description: "I am rating decision issue 0" + contested_rating_decision_reference_id: nil + contested_rating_issue_diagnostic_code: "9999" + contested_rating_issue_profile_date: nil + contested_rating_issue_reference_id: nil + corrected_by_request_issue_id: 1 + correction_type: nil + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_date: Thu, 21 Sep 2023 + decision_review_id: 2237 + decision_review_type: "Appeal" + edited_description: nil + end_product_establishment_id: 1 + ineligible_due_to_id: 1 + ineligible_reason: nil + is_unidentified: false + nonrating_issue_bgs_id: nil + nonrating_issue_category: nil + nonrating_issue_description: nil + notes: nil + ramp_claim_id: nil + split_issue_status: nil + unidentified_issue_text: nil + untimely_exemption: false + untimely_exemption_notes: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + vacols_id: nil + vacols_sequence_id: 1234 + verified_unidentified_issue: false + veteran_participant_id: "574727684" + caseflow_considers_decision_review_active: true + caseflow_considers_issue_active: true + caseflow_considers_title_of_active_review: "some title" + caseflow_considers_eligible: true + claimant_participant_id: "123456789" + decision_issues: + - id: 457 + caseflow_decision_date: Tue, 26 Sep 2023 + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_text: nil + deleted_at: nil + description: "The decision: I am rating decision issue 0 has been vacated." + diagnostic_code: nil + disposition: "vacated" + end_product_last_action_date: nil + percent_number: nil + rating_issue_reference_id: nil + rating_profile_date: nil + rating_promulgation_date: nil + subject_text: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + - id: 2 + benefit_type: compensation + closed_at: nil + closed_status: nil + contention_reference_id: 1 + contested_decision_issue_id: 453 + contested_issue_description: "I am rating decision issue 0" + contested_rating_decision_reference_id: nil + contested_rating_issue_diagnostic_code: "9999" + contested_rating_issue_profile_date: nil + contested_rating_issue_reference_id: nil + corrected_by_request_issue_id: 1 + correction_type: nil + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_date: Thu, 21 Sep 2023 + decision_review_id: 2237 + decision_review_type: "Appeal" + edited_description: nil + end_product_establishment_id: 1 + ineligible_due_to_id: 1 + ineligible_reason: nil + is_unidentified: false + nonrating_issue_bgs_id: nil + nonrating_issue_category: nil + nonrating_issue_description: nil + notes: nil + ramp_claim_id: nil + split_issue_status: nil + unidentified_issue_text: nil + untimely_exemption: false + untimely_exemption_notes: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + vacols_id: nil + vacols_sequence_id: 1234 + verified_unidentified_issue: false + veteran_participant_id: "574727684" + caseflow_considers_decision_review_active: true + caseflow_considers_issue_active: true + caseflow_considers_title_of_active_review: "some title" + caseflow_considers_eligible: true + claimant_participant_id: "123456789" + decision_issues: + - + veteran_returned_2_pages: + value: + page: 2 + total_number_of_pages: 2 + total_request_issues_for_vet: 3 + max_request_issues_per_page: 2 + veteran_participant_id: "123456789" + legacy_appeals_present: true, + request_issues: + - id: 1 + benefit_type: compensation + closed_at: nil + closed_status: nil + contention_reference_id: 1 + contested_decision_issue_id: 453 + contested_issue_description: "I am rating decision issue 0" + contested_rating_decision_reference_id: nil + contested_rating_issue_diagnostic_code: "9999" + contested_rating_issue_profile_date: nil + contested_rating_issue_reference_id: nil + corrected_by_request_issue_id: 1 + correction_type: nil + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_date: Thu, 21 Sep 2023 + decision_review_id: 2237 + decision_review_type: "Appeal" + edited_description: nil + end_product_establishment_id: 1 + ineligible_due_to_id: 1 + ineligible_reason: nil + is_unidentified: false + nonrating_issue_bgs_id: nil + nonrating_issue_category: nil + nonrating_issue_description: nil + notes: nil + ramp_claim_id: nil + split_issue_status: nil + unidentified_issue_text: nil + untimely_exemption: false + untimely_exemption_notes: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + vacols_id: nil + vacols_sequence_id: 1234 + verified_unidentified_issue: false + veteran_participant_id: "574727684" + caseflow_considers_decision_review_active: true + caseflow_considers_issue_active: true + caseflow_considers_title_of_active_review: "some title" + caseflow_considers_eligible: true + claimant_participant_id: "111111" + decision_issues: + - id: 457 + caseflow_decision_date: Tue, 26 Sep 2023 + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_text: nil + deleted_at: nil + description: "The decision: I am rating decision issue 0 has been vacated." + diagnostic_code: nil + disposition: "vacated" + end_product_last_action_date: nil + percent_number: nil + rating_issue_reference_id: nil + rating_profile_date: nil + rating_promulgation_date: nil + subject_text: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + veteran_returned_no_legacy: + value: + data: + page: 1 + total_number_of_pages: 2 + total_request_issues_for_vet: 3 + max_request_issues_per_page: 2 + veteran_participant_id: 123456789 + legacy_appeals_present: false, + request_issues: + - id: 1 + benefit_type: compensation + closed_at: nil + closed_status: nil + contention_reference_id: 1 + contested_decision_issue_id: 453 + contested_issue_description: "I am rating decision issue 0" + contested_rating_decision_reference_id: nil + contested_rating_issue_diagnostic_code: "9999" + contested_rating_issue_profile_date: nil + contested_rating_issue_reference_id: nil + corrected_by_request_issue_id: 1 + correction_type: nil + created_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + decision_date: Thu, 21 Sep 2023 + decision_review_id: 2237 + decision_review_type: "Appeal" + edited_description: nil + end_product_establishment_id: 1 + ineligible_due_to_id: 1 + ineligible_reason: nil + is_unidentified: false + nonrating_issue_bgs_id: nil + nonrating_issue_category: nil + nonrating_issue_description: nil + notes: nil + ramp_claim_id: nil + split_issue_status: nil + unidentified_issue_text: nil + untimely_exemption: false + untimely_exemption_notes: nil + updated_at: Tue, 26 Sep 2023 16:54:17 UTC +00:00 + vacols_id: nil + vacols_sequence_id: 1234 + verified_unidentified_issue: false + veteran_participant_id: "574727684" + caseflow_considers_status_active: true + caseflow_considers_issue_active: true + caseflow_considers_title_of_active_review: "some title" + caseflow_considers_eligible: true + claimant_participant_id: "111111" + decision_issues: + - + '401': + description: 401 Unauthorized + content: + application/vnd.api+json: + examples: + invalid_api_key: + value: + status: unauthorized + missing_api_key: + value: + status: unauthorized + '404': + description: 404 Veteran not found + content: + application/vnd.api+json: + examples: + not_implemented: + value: + errors: + - status: 404 + code: "veteran_not_found" + detail: "No Veteran found for the given identifier." + '500': + description: 500 Unexpected Error + content: + application/vnd.api+json: + examples: + not_implemented: + value: + errors: + - status: 500 + code: "Unknown error occured" + detail: "Message: There was a server error. Use the error uuid to submit a support ticket: {id}" + '501': + description: 501 Not Implemented + content: + application/vnd.api+json: + examples: + not_implemented: + value: + - + status: 501 + title: "Not Implemented" + detail: "This endpoint is not yet supported." +components: + schemas: + VeteranParticipantId: + type: string + Errors: + type: array + items: + type: object + properties: + status: + type: string + code: + type: string + title: + type: string + RequestIssue: + description: An AMA request issue linked with a veteran. + type: object + properties: + data: + type: object + properties: + id: + type: integer + description: "The unique identifier for a request issue" + benefit_type: + type: string + description: "The Line of Business the issue is connected with." + closed_at: + type: string + description: "Timestamp when the request issue was closed. The reason it was closed is in closed_status." + closed_status: + type: string + description: "Indicates whether the request issue is closed, for example if it was removed from a Decision Review, the associated End Product got canceled, the Decision Review was withdrawn." + contention_reference_id: + type: integer + description: "The ID of the contention created on the End Product for this request issue. This is populated after the contention is created in VBMS." + contested_decision_issue_id: + type: integer + description: "The ID of the decision issue that this request issue contests. A Request issue will contest either a rating issue or a decision issue." + contested_issue_description: + type: string + description: "Description of the contested rating or decision issue. Will be either a rating issue's decision text or a decision issue's description." + contested_rating_decision_reference_id: + type: string + description: "The BGS id for contested rating decisions. These may not have corresponding contested_rating_issue_reference_id values." + contested_rating_issue_diagnostic_code: + type: string + description: "If the contested issue is a rating issue, this is the rating issue's diagnostic code. Will be nil if this request issue contests a decision issue." + contested_rating_issue_profile_date: + type: string + description: "If the contested issue is a rating issue, this is the rating issue's profile date. Will be nil if this request issue contests a decision issue." + contested_rating_issue_reference_id: + type: string + description: "If the contested issue is a rating issue, this is the rating issue's reference id. Will be nil if this request issue contests a decision issue." + corrected_by_request_issue_id: + type: integer + description: "If this request issue has been corrected, the ID of the new correction request issue. This is needed for EP 930." + correction_type: + type: string + description: "EP 930 correction type. Allowed values: control, local_quality_error, national_quality_error where 'control' is a regular correction, 'local_quality_error' was found after the fact by a local quality review team, and 'national_quality_error' was similarly found by a national quality review team. This is needed for EP 930." + created_at: + type: string + description: "The date and time the record was created" + 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)." + decision_review_id: + type: integer + description: "ID of the decision review that this request issue belongs to" + decision_review_type: + type: string + description: "Class name of the decision review that this request issue belongs to." + edited_description: + type: string + description: "The edited description for the contested issue, optionally entered by the user." + end_product_establishment_id: + type: integer + description: "Associated appeal EP claim_id (if C&P => higher level review or supp claim)" + ineligible_due_to_id: + type: integer + description: "If a request issue is ineligible due to another request issue, for example that issue is already being actively reviewed, then the ID of the other request issue is stored here." + ineligible_reason: + type: string + description: "The reason for a Request Issue being ineligible. If a Request Issue has an ineligible_reason, it is still captured, but it will not get a contention in VBMS or a decision." + 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." + nonrating_issue_bgs_id: + type: string + description: "The ID of the nonrating request issue in BGS/CorpDB" + nonrating_issue_category: + type: string + description: "The category selected for nonrating request issues. These vary by business line." + nonrating_issue_description: + type: string + description: "The user entered description if the issue is a nonrating issue." + notes: + type: string + description: "Notes added by the Claims Assistant when adding request issues. This may be used to capture handwritten notes on the form, or other comments the CA wants to capture." + 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." + 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." + unidentified_issue_text: + type: string + description: "User entered description if the request issue is neither a rating or a nonrating issue." + untimely_exemption: + type: boolean + description: "If the contested issue's decision date was more than a year before the receipt date, it is considered untimely (unless it is a Supplemental Claim). However, an exemption to the timeliness can be requested. If so, it is indicated here." + untimely_exemption_notes: + type: string + description: "Notes related to the untimeliness exemption requested." + updated_at: + type: string + description: "The date and time the record was updated" + vacols_id: + type: string + description: "The vacols_id of the legacy appeal that had an issue found to match the request issue." + vacols_sequence_id: + type: integer + description: "The vacols_sequence_id, for the specific issue on the legacy appeal which the Claims Assistant determined to match the request issue on the Decision Review. A combination of the vacols_id (for the legacy appeal), and vacols_sequence_id (for which issue on the legacy appeal), is required to identify the issue being opted-in." + verified_unidentified_issue: + type: boolean + description: "A verified unidentified issue allows an issue whose rating data is missing to be intaken as a regular rating issue. In order to be marked as verified, a VSR needs to confirm that they were able to find the record of the decision for the issue." + 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." + 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." + caseflow_considers_issue_active: + type: boolean + description: "This value is populated by a method call in the request_issue model. Is true when the issue is eligible, not closed, no split issue status or the split issue status is in_progress." + caseflow_considers_title_of_active_review: + type: string + description: "This value is populated by a method call in the request_issue model. If the Request Issue is a duplicate of a request issue already being reviewed, the decision_review type will be listed here" + caseflow_considers_eligible: + type: boolean + description: "This value is populated by a method call in the request_issue model. If the Request Issue has a value for ineligible_reason, this value will be false" + claimant_participant_id: + type: string + description: "Participant ID of claimant." + DecisionIssue: + description: An AMA decision issue linked to a AMA request issue. + type: object + properties: + data: + type: object + properties: + id: + type: integer + description: "The ID of the record" + caseflow_decision_date: + type: string + description: "This is a decision date for decision issues where decisions are entered in Caseflow, such as for appeals or for decision reviews with a business line that is not processed in VBMS." + created_at: + type: string + description: "The date and time the record was created" + decision_text: + type: string + description: "If decision resulted in a change to a rating, the rating issue's decision text." + deleted_at: + type: string + description: "Time/date a soft delete occurred on the decision issue." + description: + type: string + description: "Optional description that the user can input for decisions made in Caseflow." + diagnostic_code: + type: string + description: "If a decision resulted in a rating, this is the rating issue's diagnostic code." + disposition: + type: string + description: "The disposition for a decision issue. Dispositions made in Caseflow and dispositions made in VBMS can have different values." + end_product_last_action_date: + type: string + description: "After an end product gets synced with a status of CLR (cleared), the end product's last_action_date is saved on any decision issues that are created as a result. This is used as a proxy for decision date for non-rating issues that are processed in VBMS because they don't have a rating profile date, and the exact decision date is not available." + percent_number: + type: string + description: "percent_number from RatingIssue (prcntNo from Rating Profile)." + rating_issue_reference_id: + type: string + description: "Identifies the specific issue on the rating that resulted from the decision issue (a rating issue can be connected to multiple contentions)." + rating_profile_date: + type: string + description: "The profile date of the rating that a decision issue resulted in (if applicable). The profile_date is used as an identifier for the rating, and is the date that most closely maps to what the Veteran writes down as the decision date." + rating_promulgation_date: + type: string + description: "The promulgation date of the rating that a decision issue resulted in (if applicable). It is used for calculating whether a decision issue is within the timeliness window to be appealed or get a higher level review." + subject_text: + type: string + description: "subject_text from RatingIssue (subjctTxt from Rating Profile)." + updated_at: + type: string + description: "The date and time the record was updated" diff --git a/app/controllers/api/docs/v3/docs_controller.rb b/app/controllers/api/docs/v3/docs_controller.rb index 8ccd53beb19..ec8dd6bdd3c 100644 --- a/app/controllers/api/docs/v3/docs_controller.rb +++ b/app/controllers/api/docs/v3/docs_controller.rb @@ -5,4 +5,14 @@ def decision_reviews swagger = YAML.safe_load(File.read("app/controllers/api/docs/v3/decision_reviews.yaml")) render json: swagger end + + def ama_issues + swagger = YAML.safe_load(File.read("app/controllers/api/docs/v3/ama_issues.yaml")) + render json: swagger + end + + def vacols_issues + swagger = YAML.safe_load(File.read("app/controllers/api/docs/v3/vacols_issues.yaml")) + render json: swagger + end end diff --git a/app/controllers/api/docs/v3/vacols_issues.yaml b/app/controllers/api/docs/v3/vacols_issues.yaml new file mode 100644 index 00000000000..2faa9b56753 --- /dev/null +++ b/app/controllers/api/docs/v3/vacols_issues.yaml @@ -0,0 +1,268 @@ +openapi: 3.0.3 +info: + title: VACOLS Issues + description: >+ + The VACOLS Issues API allows you to interact with a veteran’s VACOLS Issues. This API provides all VACOLS Issues per Veteran File Number. + + + For more information on the Legacy Appeal process, including Legacy Issues, please see [this informative page on VA.gov](https://www.va.gov/decision-reviews/legacy-appeals/). + + + + ## Technical Summary + + The VACOLS Issues API interacts with VA's internal system, called Caseflow, that manages all benefit appeals. + + * The VACOLS API accepts the veteran file number identification that is supplied in the request header. + + * The VACOLS Issue request will be validated to ensure the Veteran information matches VA records and that the appeal information sent is valid. + + * Detailed error messages will be returned to help your application provide meaningful information to the Veteran attempting to make the VACOLS Issue request. + + * The VACOLS Issues are paginated in the response. The page paramenter must be a integer greater than or equal to 1. 0 and negative numbers are defaulted to page 1. + + ## Authorization + + API requests are authorized through a token, provided in an HTTP header. + Example _curl_ (assuming you're running Caseflow locally, and the `api_v3` feature toggle has been enabled): + + ``` + curl -v -H 'Authorization: Token {{YourApiKeyHere}}'\ + 'http://localhost:3000/api/v3/issues/vacols/find_by_veteran/?page=2' + + ``` + + version: 3.0.0 +servers: + - url: '{{CaseflowInstance}}/api/v3/issues/vacols' +paths: + /api/v3/issues/vacols/find_by_veteran: + get: + tags: + - VACOLS Issues + parameters: + - in: query + name: page + schema: + type: integer + description: The page of request issues for the supplied veteran. Positive greater than 0. 0 or negative numbers are defaulted to 1. + summary: Retrieve all VACOLS issues from Veteran. + description: >+ + Will return a paginated response of VACOLS Issues for the Veteran. + responses: + '200': + description: 200 OK + content: + application/vnd.api+json: + examples: + veteran_returned_1_page: + value: + page: 1 + total_number_of_pages: 1 + total_vacols_issues_for_vet: 6 + max_vacols_issues_per_page: 10 + veteran_participant_id: '1826209' + veteran_file_number: '872958715' + vacols_issues: + - id: '2760964' + notice_of_disagreement_date: '2019-03-09T00:00:00.000Z' + legacy_appeal_status: Remand + legacy_appeal_soc_date: '2019-12-25T00:00:00.000Z' + legacy_appeal_ssoc_dates: + - '2023-01-31T00:00:00.000Z' + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: '2760964' + vacols_sequence_id: 1 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: Service connection, pancreatitis + disposition: remanded + close_date: '2023-06-16T00:00:00.000Z' + note: Itaque adipisci aut ullam voluptas recusandae possimus facilis. + - id: LEGACYID + notice_of_disagreement_date: '2022-09-18T00:00:00.000Z' + legacy_appeal_status: Advance + legacy_appeal_soc_date: '2023-03-18T00:00:00.000Z' + legacy_appeal_ssoc_dates: [] + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: LEGACYID + vacols_sequence_id: 1 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: New and material evidence to reopen claim for service connection, ankylosing spondylitis + disposition: null + close_date: null + note: null + - id: LEGACYID + notice_of_disagreement_date: '2022-09-18T00:00:00.000Z' + legacy_appeal_status: Advance + legacy_appeal_soc_date: '2023-03-18T00:00:00.000Z' + legacy_appeal_ssoc_dates: [] + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: LEGACYID + vacols_sequence_id: 2 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: New and material evidence to reopen claim for service connection, spinal fusion + disposition: null + close_date: null + note: null + - id: LEGACYID + notice_of_disagreement_date: '2022-09-18T00:00:00.000Z' + legacy_appeal_status: Advance + legacy_appeal_soc_date: '2023-03-18T00:00:00.000Z' + legacy_appeal_ssoc_dates: [] + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: LEGACYID + vacols_sequence_id: 3 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: New and material evidence to reopen claim for service connection, degenerative arthritis of the spine + disposition: null + close_date: null + note: null + - id: LEGACYID + notice_of_disagreement_date: '2022-09-18T00:00:00.000Z' + legacy_appeal_status: Advance + legacy_appeal_soc_date: '2023-03-18T00:00:00.000Z' + legacy_appeal_ssoc_dates: [] + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: LEGACYID + vacols_sequence_id: 4 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: New and material evidence to reopen claim for service connection, intervertebral disc syndrome + disposition: null + close_date: null + note: null + - id: LEGACYID + notice_of_disagreement_date: '2022-09-18T00:00:00.000Z' + legacy_appeal_status: Advance + legacy_appeal_soc_date: '2023-03-18T00:00:00.000Z' + legacy_appeal_ssoc_dates: [] + legacy_appeal_eligible_for_opt_in: false + legacy_appeal_eligible_for_soc_opt_in_with_exemption: true + vacols_id: LEGACYID + vacols_sequence_id: 5 + eligible_for_soc_opt_in: true + eligible_for_soc_opt_in_with_exemption: true + description: New and material evidence to reopen claim for service connection, ankylosis of hip + disposition: null + close_date: null + note: null + '401': + description: 401 Unauthorized + content: + application/vnd.api+json: + examples: + invalid_api_key: + value: + status: unauthorized + missing_api_key: + value: + status: unauthorized + '404': + description: 404 Veteran not found + content: + application/vnd.api+json: + examples: + not_implemented: + value: + errors: + - status: 404 + code: "veteran_not_found" + detail: "No Veteran found for the given identifier." + '500': + description: 500 Unexpected Error + content: + application/vnd.api+json: + examples: + not_implemented: + value: + errors: + - status: 500 + code: "Unknown error occured" + detail: "Message: There was a server error. Use the error uuid to submit a support ticket: {id}" + '501': + description: 501 Not Implemented + content: + application/vnd.api+json: + examples: + not_implemented: + value: + - + status: 501 + title: "Not Implemented" + detail: "This endpoint is not yet supported." +components: + schemas: + VeteranParticipantId: + type: string + Errors: + type: array + items: + type: object + properties: + status: + type: string + code: + type: string + title: + type: string + VacolsIssue: + description:Issues that are attached to a Legacy Appeal within VACOLS. There can be multiple VACOLS Issues attached to a Legacy Appeal. VACOLS Issues are eligible for AMA if they meet the Legacy Issue opt-in eligibility requirements + type: object + properties: + data: + type: object + properties: + id: + type: integer + description: "The unique identifier for an issue" + notice_of_disagreement_date: + type: datetime + description: "Receipt date of the appeal form. Used to determine which VACOLS issues are within the timeliness window to be appealed. Only VACOLS issues decided prior to the receipt date will show up as contestable issues." + legacy_appeal_status: + type: boolean + description: "Indicates the status of the Legacy Appeal." + legacy_appeal_soc_date: + type: datetime + description: "Date/Time Statement of the Case Issued" + legacy_appeal_ssoc_dates: + type: array + description: "Date/Time Supplemental Statement of the Cases Issued" + legacy_appeal_eligible_for_opt_in: + type: boolean + description: "Indicates whether a Legacy Appeal is eligible to withdraw matching issues from the Legacy process." + legacy_appeal_eligible_for_soc_opt_in_with_exemption: + type: boolean + description: "Indicates whether a Legacy Appeal is eligible to withdraw matching issues from the Legacy process due to an exemption." + vacols_id: + type: varchar + description: "The VACOLS ID (primary key) of the Legacy appeal that the VACOLS issue is part of." + vacols_sequence_id: + type: integer + description: "The sequence ID of the VACOLS issue on the legacy appeal. The vacols_id and vacols_sequence_id form a composite key to identify a specific VACOLS issue." + issue_eligible_for_soc_opt_in: + type: boolean + description: "Indicates whether a VACOLS Issue is eligible to withdraw matching issues from the Legacy process." + issue_eligible_for_soc_opt_in_with_exemption: + type: boolean + description: "Indicates whether a VACOLS Issue is eligible to withdraw matching issues from the Legacy process due to an exemption." + issue_close_date: + type: datetime + description: "The date the VACOLS issue received a disposition." + issue_description: + type: varchar + description: "The description for the VACOLS issue." + issue_disposition: + type: varchar + description: "The disposition for each issue on the Legacy appeal. There are fourteen dispositions in total: some controlled by BVA and others controlled by the field." + issue_note: + type: text + description: "Notes added to the VACOLS Issue. This may be used to capture handwritten notes on the form, New Material or other comments." diff --git a/app/controllers/api/v3/base_controller.rb b/app/controllers/api/v3/base_controller.rb index 49b88fe6c6b..7547016db09 100644 --- a/app/controllers/api/v3/base_controller.rb +++ b/app/controllers/api/v3/base_controller.rb @@ -9,6 +9,19 @@ def status_from_errors(errors) end end + rescue_from StandardError do |error| + Raven.capture_exception(error, extra: raven_extra_context) + + render json: { + "errors": [ + "status": "500", + "title": "Unknown error occured", + "detail": "Message: There was a server error. "\ + "Use the error uuid to submit a support ticket: #{Raven.last_event_id}" + ] + }, status: :internal_server_error + end + protect_from_forgery with: :null_session def render_errors(errors) diff --git a/app/controllers/api/v3/issues/ama/veterans_controller.rb b/app/controllers/api/v3/issues/ama/veterans_controller.rb new file mode 100644 index 00000000000..2c5c8caad4b --- /dev/null +++ b/app/controllers/api/v3/issues/ama/veterans_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# :reek:InstanceVariableAssumption +class Api::V3::Issues::Ama::VeteransController < Api::V3::BaseController + include ApiV3FeatureToggleConcern + + before_action do + api_released?(:api_v3_ama_issues) + 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 + end + + private + + def init_page + page = ActiveRecord::Base.sanitize_sql(params[:page].to_i) if params[:page] + # Disallow page(0) or negative. Page(0) == page(1) in kaminari. This is to avoid confusion. + if page.nil? || page <= 0 + page = 1 + end + page + end + + # :reek:FeatureEnvy + def init_per + per = ActiveRecord::Base.sanitize_sql(params[:per_page].to_i) if params[:per_page] + if per.nil? || per <= 0 || per > RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE + per = [RequestIssue.default_per_page, RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE].min + end + per + end + + def find_veteran + begin + Veteran.find_by!(participant_id: params[:participant_id]) + rescue ActiveRecord::RecordNotFound + render_errors( + status: 404, + code: :veteran_not_found, + title: "No Veteran found for the given identifier." + ) && return + end + end + + def render_request_issues(request_issues) + render json: request_issues.to_json + end +end diff --git a/app/controllers/api/v3/issues/vacols/veterans_controller.rb b/app/controllers/api/v3/issues/vacols/veterans_controller.rb new file mode 100644 index 00000000000..a9d7ee8b018 --- /dev/null +++ b/app/controllers/api/v3/issues/vacols/veterans_controller.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# :reek:InstanceVariableAssumption +class Api::V3::Issues::Vacols::VeteransController < Api::V3::BaseController + # The max amount of Issues that can be paginated on a single page + DEFAULT_UPPER_BOUND_PER_PAGE = ENV["REQUEST_ISSUE_DEFAULT_UPPER_BOUND_PER_PAGE"].to_i + include ApiV3FeatureToggleConcern + + before_action do + api_released?(:api_v3_vacols_issues) + end + + before_action :validate_headers, :validate_veteran_presence + + def validate_headers + render_missing_headers unless file_number + end + + def validate_veteran_presence + render_veteran_not_found unless veteran + end + + def veteran + @veteran ||= find_veteran + end + + def render_missing_headers + render_errors( + status: 422, + code: :missing_identifying_headers, + title: "Veteran file number header is required" + ) + end + + def file_number + @file_number ||= request.headers["X-VA-FILE-NUMBER"].presence + end + + rescue_from Caseflow::Error::InvalidFileNumber, BGS::ShareError do |error| + Raven.capture_exception(error, extra: raven_extra_context) + Rails.logger.error "Unable to find Veteran, please review the entered params: #{error}" + + render_veteran_not_found + 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 + end + + private + + def find_veteran + # may need to create Veteran if one doesn't exist in Caseflow but exists in BGS + Veteran.find_or_create_by_file_number_or_ssn(@file_number) + end + + def render_veteran_not_found + render_errors( + status: 404, + code: :veteran_not_found, + title: "No Veteran found for the given identifier." + ) && return + end + + def render_vacols_issues(dto) + vacols_issues = dto.hash_response + render json: vacols_issues + end +end diff --git a/app/models/decision_issue.rb b/app/models/decision_issue.rb index b2a8d76dc19..8b67997b6a5 100644 --- a/app/models/decision_issue.rb +++ b/app/models/decision_issue.rb @@ -30,7 +30,6 @@ class DecisionIssue < CaseflowRecord # NOTE: These are the string identifiers for remand dispositions returned from VBMS. # The characters and encoding are precise so don't change these unless you # know they match VBMS values. - DIFFERENCE_OF_OPINION = "Difference of Opinion" DTA_ERROR = "DTA Error" DTA_ERROR_EXAM_MO = "DTA Error - Exam/MO" diff --git a/app/models/issue.rb b/app/models/issue.rb index 8106a302538..68a470451f7 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -220,6 +220,28 @@ def intake_attributes } end + def vbms_attributes + { + id: id, + notice_of_disagreement_date: appeal.nod_date, + legacy_appeal_status: appeal.status, + legacy_appeal_soc_date: appeal.soc_date, + legacy_appeal_ssoc_dates: appeal.ssoc_dates, + legacy_appeal_eligible_for_opt_in: appeal.eligible_for_opt_in?(receipt_date: Time.zone.today), + legacy_appeal_eligible_for_soc_opt_in_with_exemption: appeal.eligible_for_opt_in?( + receipt_date: Time.zone.today, covid_flag: true + ), + vacols_id: id, + vacols_sequence_id: vacols_sequence_id, + eligible_for_soc_opt_in: eligible_for_opt_in?, + eligible_for_soc_opt_in_with_exemption: eligible_for_opt_in?(covid_flag: true), + description: friendly_description, + disposition: disposition, + close_date: close_date, + note: note + } + end + attr_writer :remand_reasons def remand_reasons @remand_reasons ||= self.class.remand_repository.load_remands_from_vacols(id, vacols_sequence_id) diff --git a/app/models/legacy_appeal.rb b/app/models/legacy_appeal.rb index 6c83e717938..b8fa4ab0814 100644 --- a/app/models/legacy_appeal.rb +++ b/app/models/legacy_appeal.rb @@ -1172,6 +1172,19 @@ def rollback_opt_in_on_decided_appeal(appeal:, user:, original_data:) ) end + # fetch_appeals_by_file_number method will retrieve VACOLS cases (appeals) and + # build Legacy Appeal records for each one if they don't already exist + def veteran_has_appeals_in_vacols?(veteran_file_number) + fetch_appeals_by_file_number(veteran_file_number).any? + rescue StandardError + cases = MetricsService.record("VACOLS: appeals_by_vbms_id", + service: :vacols, + name: "appeals_by_vbms_id") do + VACOLS::Case.where(bfcorlid: convert_file_number_to_vacols(veteran_file_number)) + end + cases.any? + end + private def close_single(appeal:, user:, closed_on:, disposition:) diff --git a/app/models/request_issue.rb b/app/models/request_issue.rb index 99fdbc5ad4b..ab269c4ea8b 100644 --- a/app/models/request_issue.rb +++ b/app/models/request_issue.rb @@ -13,6 +13,12 @@ class RequestIssue < CaseflowRecord include HasDecisionReviewUpdatedSince include SyncLock + # Pagination for VBMS API + paginates_per ENV["REQUEST_ISSUE_PAGINATION_OFFSET"].to_i + + # Max page per limit + DEFAULT_UPPER_BOUND_PER_PAGE = ENV["REQUEST_ISSUE_DEFAULT_UPPER_BOUND_PER_PAGE"].to_i + # how many days before we give up trying to sync decisions REQUIRES_PROCESSING_WINDOW_DAYS = 30 @@ -259,6 +265,13 @@ def status_active? end_product_establishment.status_active? end + def active? + eligible? && + closed_at.nil? && + (split_issue_status.nil? || + split_issue_status == "in_progress") + end + def rating? !!associated_rating_issue? || !!previous_rating_issue? || diff --git a/app/serializers/api/v3/issues/ama/request_issue_serializer.rb b/app/serializers/api/v3/issues/ama/request_issue_serializer.rb new file mode 100644 index 00000000000..b1345f0bd80 --- /dev/null +++ b/app/serializers/api/v3/issues/ama/request_issue_serializer.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# use via `Api::V3::Issues::Ama::RequestIssueSerializer.new(, +# include: [:decision_issues]).serializable_hash.to_json` +# or for a relation example: +# `Api::V3::Issues::Ama::RequestIssueSerializer.new( +# RequestIssue.includes(:decision_issues).where(veteran_participant_id: "574727696"), include: [:decision_issues] +# ).serializable_hash.to_json` +# or with pagination: +# `Api::V3::Issues::Ama::RequestIssueSerializer.new( +# RequestIssue.includes(:decision_issues).page(2), include: [:decision_issues] +# ).serializable_hash.to_json` +class Api::V3::Issues::Ama::RequestIssueSerializer + include FastJsonapi::ObjectSerializer + + attributes :id, :benefit_type, :closed_at, :closed_status, :contention_reference_id, :contested_decision_issue_id, + :contested_issue_description, :contested_rating_decision_reference_id, + :contested_rating_issue_diagnostic_code, :contested_rating_issue_profile_date, + :contested_rating_issue_reference_id, :corrected_by_request_issue_id, + :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, + :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 + + attribute :caseflow_considers_decision_review_active, &:status_active? + attribute :caseflow_considers_issue_active, &:active? + attribute :caseflow_considers_title_of_active_review, &:title_of_active_review + attribute :caseflow_considers_eligible, &:eligible? + + attribute :claimant_participant_id do |object| + object.decision_review.claimant.participant_id + end + + attribute :claim_id do |object| + object&.end_product_establishment&.reference_id + end + + attribute :decision_issues do |object| + object.decision_issues.map do |di| + { + id: di.id, + caseflow_decision_date: di.caseflow_decision_date, + created_at: di.created_at, + decision_text: di.decision_text, + deleted_at: di.deleted_at, + description: di.description, + diagnostic_code: di.diagnostic_code, + disposition: di.disposition, + 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_promulgation_date: di.rating_promulgation_date, + subject_text: di.subject_text, + updated_at: di.updated_at + } + end + end +end diff --git a/app/serializers/api/v3/issues/vacols/vacols_issue_serializer.rb b/app/serializers/api/v3/issues/vacols/vacols_issue_serializer.rb new file mode 100644 index 00000000000..d3526984c50 --- /dev/null +++ b/app/serializers/api/v3/issues/vacols/vacols_issue_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Api::V3::Issues::Vacols::VacolsIssueSerializer + include FastJsonapi::ObjectSerializer + + # attributes :vacols_issue + + attributes :vacols_issue, &:vbms_attributes +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 new file mode 100644 index 00000000000..da19e43676b --- /dev/null +++ b/app/services/api/v3/issues/ama/vbms_ama_dto_builder.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# This class is responsible for building the AMA Request Issue Response. + +#:reek:TooManyInstanceVariables +class Api::V3::Issues::Ama::VbmsAmaDtoBuilder + attr_reader :hash_response + + def initialize(veteran, page, per_page) + @page = page + @per_page = per_page + @veteran_participant_id = veteran.participant_id.to_s + @request_issue_count = total_request_issue_count + @request_issues = serialized_request_issues + @total_number_of_pages = (@request_issue_count / @per_page.to_f).ceil + @legacy_appeals_present_boolean = legacy_appeals_present?(veteran) + @hash_response = build_hash_response + end + + private + + def total_request_issue_count + RequestIssue.where(veteran_participant_id: @veteran_participant_id) + .where(benefit_type: %w[compensation pension fiduciary]) + .count + end + + 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]) + .page(page).per(per_page) + ).serializable_hash[:data] + + serialized_data.map { |issue| issue[:attributes] } + end + + def legacy_appeals_present?(veteran) + LegacyAppeal.veteran_has_appeals_in_vacols?(veteran.file_number) + end + + def build_hash_response + if @page > @total_number_of_pages + @page = @total_number_of_pages + @request_issues = serialized_request_issues(@total_number_of_pages, @per_page) + end + json_response + end + + def json_response + { + "page": @page, + "total_number_of_pages": @total_number_of_pages, + "total_request_issues_for_vet": @request_issue_count, + "max_request_issues_per_page": @per_page, + "veteran_participant_id": @veteran_participant_id, + "legacy_appeals_present": @legacy_appeals_present_boolean, + "request_issues": @request_issues + } + end +end diff --git a/app/services/api/v3/issues/vacols/vbms_vacols_dto_builder.rb b/app/services/api/v3/issues/vacols/vbms_vacols_dto_builder.rb new file mode 100644 index 00000000000..bf1eb3f6e02 --- /dev/null +++ b/app/services/api/v3/issues/vacols/vbms_vacols_dto_builder.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +#:reek:TooManyInstanceVariables +class Api::V3::Issues::Vacols::VbmsVacolsDtoBuilder + attr_reader :hash_response, :vacols_issue_count + + def initialize(veteran, page, per_page) + @page = page + @veteran_participant_id = veteran.participant_id&.to_s + @veteran_file_number = veteran.file_number&.to_s + @vacols_issue_count = total_vacols_issue_count + @offset = per_page || RequestIssue.default_per_page #LegacyIssues will be consistent with AMA RequestIssues + @vacols_issues = serialized_vacols_issues + @total_number_of_pages = (@vacols_issue_count / @offset.to_f).ceil + @hash_response = build_hash_response + end + + private + + def total_vacols_issue_count + vacols_veteran_file_number = LegacyAppeal.convert_file_number_to_vacols(@veteran_file_number) + vacols_cases = VACOLS::Case.where(bfcorlid: vacols_veteran_file_number) + vacols_ids = vacols_cases.map(&:bfkey) + VACOLS::CaseIssue.where(isskey: vacols_ids).size + end + + def serialized_vacols_issues(page = @page, offset = @offset) + vacols_issues = [] + v_ids = LegacyAppeal.fetch_appeals_by_file_number(@veteran_file_number).map(&:vacols_id) + v_ids.each do |id| + vacols_issues.push(AppealRepository.issues(id)) + end + + serialized_data = Api::V3::Issues::Vacols::VacolsIssueSerializer.new( + Kaminari.paginate_array(vacols_issues.flatten).page(page).per(offset) + ).serializable_hash[:data] + + extract_vacols_issues(serialized_data) + end + + def extract_vacols_issues(serialized_data) + wanted_attributes = serialized_data.map { |issue| issue[:attributes] } + + wanted_attributes.map { |issue| issue[:vacols_issue] } + end + + def build_hash_response + if @page > @total_number_of_pages + @page = @total_number_of_pages + @vacols_issues = serialized_vacols_issues(@total_number_of_pages, @offset) + end + json_response + end + + def json_response + { + "page": @page, + "total_number_of_pages": @total_number_of_pages, + "total_vacols_issues_for_vet": @vacols_issue_count, + "max_vacols_issues_per_page": @offset, + "veteran_participant_id": @veteran_participant_id, + "veteran_file_number": @veteran_file_number, + "vacols_issues": @vacols_issues + }.to_json + end +end diff --git a/config/environments/demo.rb b/config/environments/demo.rb index ae6be28d5e0..931531d74ac 100644 --- a/config/environments/demo.rb +++ b/config/environments/demo.rb @@ -93,6 +93,11 @@ ENV["BATCH_PROCESS_ERROR_DELAY"] ||= "12" # In number of hours ENV["BATCH_PROCESS_MAX_ERRORS_BEFORE_STUCK"] ||= "3" # When record errors for X time, it's declared stuck + # RequestIssue paginates_per offset (vbms intake) + ENV["REQUEST_ISSUE_PAGINATION_OFFSET"] ||= "10" + ENV["REQUEST_ISSUE_DEFAULT_UPPER_BOUND_PER_PAGE"] ||= "50" + + # Setup S3 config.s3_enabled = ENV["AWS_BUCKET_NAME"].present? config.s3_bucket_name = ENV["AWS_BUCKET_NAME"] diff --git a/config/environments/development.rb b/config/environments/development.rb index ac0e699c4d0..94bb7c0f9e1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -111,6 +111,10 @@ ENV["BATCH_PROCESS_ERROR_DELAY"] ||= "3" # In number of hours ENV["BATCH_PROCESS_MAX_ERRORS_BEFORE_STUCK"] ||= "3" # When record errors for X time, it's declared stuck + # RequestIssue paginates_per offset (vbms intake) + ENV["REQUEST_ISSUE_PAGINATION_OFFSET"] ||= "10" + ENV["REQUEST_ISSUE_DEFAULT_UPPER_BOUND_PER_PAGE"] ||= "50" + # Necessary vars needed to create virtual hearing links # Used by VirtualHearings::LinkService ENV["VIRTUAL_HEARING_PIN_KEY"] ||= "mysecretkey" diff --git a/config/environments/test.rb b/config/environments/test.rb index d973c4d9372..430df34ef54 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -90,6 +90,10 @@ ENV["BATCH_PROCESS_ERROR_DELAY"] ||= "3" # In number of hours ENV["BATCH_PROCESS_MAX_ERRORS_BEFORE_STUCK"] ||= "3" # When record errors for X time, it's declared stuck + # RequestIssue paginates_per offset (vbms intake) + ENV["REQUEST_ISSUE_PAGINATION_OFFSET"] ||= "10" + ENV["REQUEST_ISSUE_DEFAULT_UPPER_BOUND_PER_PAGE"] ||= "50" + config.active_job.queue_adapter = :test # Disable SqlTracker from creating tmp/sql_tracker-*.json files -- https://github.com/steventen/sql_tracker/pull/10 diff --git a/config/routes.rb b/config/routes.rb index b2e64431fe7..38d84b2578d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,10 +50,20 @@ resources :intake_statuses, only: :show get 'legacy_appeals', to: "legacy_appeals#index" end + namespace :issues do + namespace :ama do + get "find_by_veteran/:participant_id", to: "veterans#show" + end + namespace :vacols do + get 'find_by_veteran', to: "veterans#show" # passing in ssn/vfn as a header + end + end end namespace :docs do namespace :v3, defaults: { format: 'json' } do get 'decision_reviews', to: 'docs#decision_reviews' + get "ama_issues", to: "docs#ama_issues" + get "vacols_issues", to: "docs#vacols_issues" end end get "metadata", to: 'metadata#index' diff --git a/db/migrate/20231018115254_add_nonrating_issue_bgs_id_to_request_issues.rb b/db/migrate/20231018115254_add_nonrating_issue_bgs_id_to_request_issues.rb new file mode 100644 index 00000000000..2cb187cf531 --- /dev/null +++ b/db/migrate/20231018115254_add_nonrating_issue_bgs_id_to_request_issues.rb @@ -0,0 +1,5 @@ +class AddNonratingIssueBgsIdToRequestIssues < Caseflow::Migration + def change + add_column :request_issues, :nonrating_issue_bgs_id, :string, comment: "If the contested issue is a nonrating issue, this is the nonrating issue's reference id. Will be nil if this request issue contests a decision issue." + end +end diff --git a/db/schema.rb b/db/schema.rb index 669a8f86875..7ed283232da 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1626,6 +1626,7 @@ t.string "ineligible_reason", comment: "The reason for a Request Issue being ineligible. If a Request Issue has an ineligible_reason, it is still captured, but it will not get a contention in VBMS or a decision." t.boolean "is_predocket_needed", comment: "Indicates whether or not an issue has been selected to go to the pre-docket queue opposed to normal docketing." t.boolean "is_unidentified", comment: "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." + t.string "nonrating_issue_bgs_id", comment: "If the contested issue is a nonrating issue, this is the nonrating issue's reference id. Will be nil if this request issue contests a decision issue." t.string "nonrating_issue_category", comment: "The category selected for nonrating request issues. These vary by business line." t.string "nonrating_issue_description", comment: "The user entered description if the issue is a nonrating issue" t.text "notes", comment: "Notes added by the Claims Assistant when adding request issues. This may be used to capture handwritten notes on the form, or other comments the CA wants to capture." diff --git a/db/seeds.rb b/db/seeds.rb index bc57ab49692..11fa92ffe79 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -63,6 +63,7 @@ def seed call_and_log_seed_step Seeds::VbmsExtClaim call_and_log_seed_step Seeds::RemandedAmaAppeals call_and_log_seed_step Seeds::RemandedLegacyAppeals + call_and_log_seed_step Seeds::AmaIntake call_and_log_seed_step Seeds::Correspondence call_and_log_seed_step Seeds::CorrespondenceTypes call_and_log_seed_step Seeds::PackageDocumentTypes diff --git a/db/seeds/ama_intake.rb b/db/seeds/ama_intake.rb new file mode 100644 index 00000000000..b6527a39c5f --- /dev/null +++ b/db/seeds/ama_intake.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +# Disable :reek:InstanceVariableAssumption +# rubocop:disable Layout/LineLength +# rubocop:disable Lint/UselessAssignment +module Seeds + class AmaIntake < Base + def initialize + file_number_initial_value + end + + def seed! + create_veteran_with_no_legacy_appeals_and_many_request_and_decision_issues + create_veteran_with_legacy_appeals_and_many_request_and_decision_issues + create_veteran_with_no_legacy_appeals_and_request_issue_with_many_decision_issues + create_veteran_with_legacy_appeals_and_request_issue_with_many_decision_issues + create_veteran_with_no_legacy_appeals_and_decision_issue_with_many_request_issues + create_veteran_with_legacy_appeals_and_decision_issue_with_many_request_issues + create_veteran_without_request_issues + end + + private + + # Maintains previous file number values while allowing for reseeding + def file_number_initial_value + @file_number ||= 900_000 + # This seed file creates 6 new veterans on each run, 10 is sufficient margin to add more data + @file_number += 10 while Veteran.find_by(file_number: format("%09d", n: @file_number)) + end + + # Veteran with 420 Request Issues and 360 Decision Issues + def create_veteran_with_no_legacy_appeals_and_many_request_and_decision_issues + # Veteran with associated Decision Reviews, Request Issues, and Decision Issues + veteran = create_veteran + + # 1 Appeal containing 180 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 180.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 180 Request Issues each with a Decision Issue + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 180.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + + # 1 Supplemental Claim containing 60 Request Issues with no Decision Issues + supp_epe = create_end_product_establishment(:active_supp_with_dependent_claimant, veteran) + 60.times do + create_claim_review_request_issue(:nonrating, supp_epe, veteran) + end + end + + # Veteran with 5 Legacy Appeals, 420 Request Issues and 360 Decision Issues + def create_veteran_with_legacy_appeals_and_many_request_and_decision_issues + # Veteran with associated Legacy Appeals, Decision Reviews, Request Issues, and Decision Issues + veteran = create_veteran + + # 5 VACOLS Appeals + 5.times do + create_vacols_appeal(veteran) + end + + # 1 Appeal containing 180 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 180.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 180 Request Issues each with a Decision Issue + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 180.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + + # 1 Supplemental Claim containing 60 Request Issues with no Decision Issues + supp_epe = create_end_product_establishment(:active_supp_with_dependent_claimant, veteran) + 60.times do + create_claim_review_request_issue(:nonrating, supp_epe, veteran) + end + end + + # Veteran with 15 total Request Issues and 82 Decision Issues + # Each Request Issue contains 1 Decision Issue, except for one outlier containing 68 Decision Issues + def create_veteran_with_no_legacy_appeals_and_request_issue_with_many_decision_issues + # Veteran with associated Decision Reviews, Request Issues, and Decision Issues + veteran = create_veteran + + # 1 Appeal containing 7 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 7.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 8 total Request Issues + # Each Request Issue correlates to a single Decision Issue, except for one outlier correlating to 68 Decision Issues + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 7.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + # Outlier Request Issue with 68 Decision Issues + create_request_issue_with_many_decision_issues(:nonrating, hlr_epe, veteran, number_of_issues = 68) + end + + # Veteran with 5 VACOLS Appeals, 15 total Request Issues and 82 Decision Issues + # Each Request Issue contains 1 Decision Issue, except for one outlier containing 68 Decision Issues + def create_veteran_with_legacy_appeals_and_request_issue_with_many_decision_issues + # Veteran with associated Decision Reviews, Request Issues, and Decision Issues + veteran = create_veteran + + # 5 VACOLS Appeals + 5.times do + create_vacols_appeal(veteran) + end + + # 1 Appeal containing 7 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 7.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 8 total Request Issues + # Each Request Issue correlates to a single Decision Issue, except for one outlier correlating to 68 Decision Issues + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 7.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + # Outlier Request Issue with 68 Decision Issues + create_request_issue_with_many_decision_issues(:nonrating, hlr_epe, veteran, number_of_issues = 68) + end + + # Veteran with 35 total Request Issues and 5 Decision Issues + # Each Decision Issue contains 1 Request Issue, except for one outlier containing 31 Request Issues + def create_veteran_with_no_legacy_appeals_and_decision_issue_with_many_request_issues + # Veteran with associated Decision Reviews, Request issues, and Decision Issues + veteran = create_veteran + + # 1 Appeal containing 2 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 2.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 3 total Request Issues + # Each Decision Issue correlates to a single Request Issue, except for one outlier correlating to 31 Request Issues + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 2.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + # Outlier Decision Issue with 31 Request Issues + create_decision_issue_with_many_request_issues(:nonrating, hlr_epe, veteran, number_of_issues = 31) + end + + # Veteran with 5 VACOLS Appeals, 35 total Request Issues and 5 Decision Issues + # Each Decision Issue contains 1 Request Issue, except for one outlier containing 31 Request Issues + def create_veteran_with_legacy_appeals_and_decision_issue_with_many_request_issues + # Veteran with associated Decision Reviews, Request issues, and Decision Issues + veteran = create_veteran + + # 5 VACOLS Appeals + 5.times do + create_vacols_appeal(veteran) + end + + # 1 Appeal containing 2 Request Issues each with a Decision Issue + appeal = create_appeal(veteran) + 2.times do + create_appeal_request_issue(:rating, appeal, veteran, :with_associated_decision_issue) + end + + # 1 Higher Level Review containing 3 total Request Issues + # Each Decision Issue correlates to a single Request Issue, except for one outlier correlating to 31 Request Issues + hlr_epe = create_end_product_establishment(:cleared_hlr_with_veteran_claimant, veteran) + 2.times do + create_claim_review_request_issue(:rating, hlr_epe, veteran, :with_associated_decision_issue) + end + # Outlier Decision Issue with 31 Request Issues + create_decision_issue_with_many_request_issues(:nonrating, hlr_epe, veteran, number_of_issues = 31) + end + + def create_veteran_without_request_issues + veteran = create_veteran + end + + def create_veteran + veteran = create(:veteran, participant_id: format("%09d", n: @file_number), file_number: format("%09d", n: @file_number)) + @file_number += 1 + veteran + end + + def create_appeal(veteran) + create(:appeal, veteran_file_number: veteran.file_number) + end + + def create_end_product_establishment(synced_status_and_source, veteran) + create(:end_product_establishment, + synced_status_and_source, + veteran_file_number: veteran.file_number) + end + + # :reek:LongParameterList + def create_claim_review_request_issue(rating_or_nonrating_trait, claim_review, veteran, associated_decision_issue_trait = nil) + if associated_decision_issue_trait + create(:request_issue, + rating_or_nonrating_trait, + associated_decision_issue_trait, + veteran_participant_id: veteran.participant_id, + decision_review: claim_review.source, + end_product_establishment: claim_review) + else + create(:request_issue, + rating_or_nonrating_trait, + decision_review: claim_review.source, + veteran_participant_id: veteran.participant_id, + end_product_establishment: claim_review) + end + end + + # :reek:LongParameterList + def create_appeal_request_issue(rating_trait, appeal, veteran, associated_decision_issue_trait) + create(:request_issue, + rating_trait, + associated_decision_issue_trait, + veteran_participant_id: veteran.participant_id, + decision_review: appeal) + end + + # :reek:LongParameterList + def create_request_issue_with_many_decision_issues(rating_or_nonrating_trait, hlr_epe, veteran, number_of_issues) + decision_issues = create_list(:decision_issue, + number_of_issues, + participant_id: veteran.participant_id, + decision_review: hlr_epe.source) + request_issue = create(:request_issue, + rating_or_nonrating_trait, + decision_review: hlr_epe.source, + end_product_establishment: hlr_epe, + veteran_participant_id: veteran.participant_id, + decision_issues: decision_issues) + end + + # :reek:LongParameterList + def create_decision_issue_with_many_request_issues(rating_or_nonrating_trait, hlr_epe, veteran, number_of_issues) + request_issues = create_list(:request_issue, + number_of_issues, + rating_or_nonrating_trait, + decision_review: hlr_epe.source, + end_product_establishment: hlr_epe, + veteran_participant_id: veteran.participant_id) + decision_issue = create(:decision_issue, + participant_id: veteran.participant_id, + decision_review: hlr_epe.source, + request_issues: request_issues) + end + + def create_vacols_appeal(veteran) + vacols_appeals = create(:case, bfcorlid: "#{veteran.file_number}S") + end + end +end +# rubocop:enable Layout/LineLength +# rubocop:enable Lint/UselessAssignment diff --git a/spec/controllers/api/docs/v3/ama_issues_spec.rb b/spec/controllers/api/docs/v3/ama_issues_spec.rb new file mode 100644 index 00000000000..a7363e28a0b --- /dev/null +++ b/spec/controllers/api/docs/v3/ama_issues_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +context "api/docs/v3/ama_issues.yaml" do + let(:spec) do + YAML.safe_load(File.read(File.join(Rails.root, "app/controllers/api/docs/v3/ama_issues.yaml"))) + end + + it "exists and is valid yaml" do + expect { spec }.not_to raise_error + end + + it "has veteran participant id mentioned in parameters" do + parameters = spec.dig( + "paths", + "/api/v3/issues/ama/find_by_veteran/{veteran_participant_id}", + "get", + "parameters" + ) + expect(parameters.first["name"]).to eq "veteran_participant_id" + end + + it "has page mentioned in parameters" do + parameters = spec.dig( + "paths", + "/api/v3/issues/ama/find_by_veteran/{veteran_participant_id}", + "get", + "parameters" + ) + expect(parameters.second["name"]).to eq "page" + expect(parameters.second["schema"]["type"]).to eq "integer" + end + + it "has the status codes accounted for" do + responzez = spec.dig( + "paths", + "/api/v3/issues/ama/find_by_veteran/{veteran_participant_id}", + "get", + "responses" + ) + expect(responzez.map(&:first)).to match_array %w[200 401 404 500 501] + end + + it "has VeteranParticipantId schema" do + vpi = spec.dig( + "components", + "schemas", + "VeteranParticipantId" + ) + expect(vpi.empty?).to eq false + end + + it "has Errors schema" do + er = spec.dig( + "components", + "schemas", + "Errors" + ) + expect(er.empty?).to eq false + end + + it "has RequestIssue schema" do + ri = spec.dig( + "components", + "schemas", + "RequestIssue" + ) + expect(ri.empty?).to eq false + end + + it "has RequestIssue schema" do + di = spec.dig( + "components", + "schemas", + "DecisionIssue" + ) + expect(di.empty?).to eq false + end +end diff --git a/spec/factories/end_product_establishment.rb b/spec/factories/end_product_establishment.rb index cfaa3bf5332..c200f0c8dbc 100644 --- a/spec/factories/end_product_establishment.rb +++ b/spec/factories/end_product_establishment.rb @@ -42,6 +42,28 @@ source { create(:supplemental_claim, veteran_file_number: veteran_file_number) } end + trait :cleared_hlr_with_veteran_claimant do + synced_status { "CLR" } + established_at { 5.days.ago } + source do + create(:higher_level_review, + veteran_file_number: veteran_file_number, + claimant_type: :veteran_claimant) + end + end + + trait :active_supp_with_dependent_claimant do + synced_status { "PEND" } + established_at { 5.days.ago } + source do + create( + :supplemental_claim, + veteran_file_number: veteran_file_number, + claimant_type: :dependent_claimant + ) + end + end + trait :active_hlr_with_canceled_vbms_ext_claim do active_hlr modifier { "030" } diff --git a/spec/factories/request_issue.rb b/spec/factories/request_issue.rb index 4ba3828faf9..cfe76767e17 100644 --- a/spec/factories/request_issue.rb +++ b/spec/factories/request_issue.rb @@ -66,6 +66,14 @@ decision_sync_processed_at { nil } end + trait :with_associated_decision_issue do + decision_issues do + [create(:decision_issue, + decision_review: decision_review, + participant_id: veteran_participant_id)] + end + end + trait :with_rating_decision_issue do transient do veteran_participant_id { nil } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 966759d7185..28fd024ff49 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -456,5 +456,30 @@ it { is_expected.to be_truthy } end + + context "#vbms_attributes" do + subject { issue.vbms_attributes } + it "shows all the vbms attributes attached to the issue" do + is_expected.to eq( + id: issue.id, + notice_of_disagreement_date: appeal.nod_date, + legacy_appeal_status: appeal.status, + legacy_appeal_soc_date: appeal.soc_date, + legacy_appeal_ssoc_dates: appeal.ssoc_dates, + legacy_appeal_eligible_for_opt_in: appeal.eligible_for_opt_in?(receipt_date: Time.zone.today), + legacy_appeal_eligible_for_soc_opt_in_with_exemption: appeal.eligible_for_opt_in?( + receipt_date: Time.zone.today, covid_flag: true + ), + vacols_id: issue.id, + vacols_sequence_id: issue.vacols_sequence_id, + eligible_for_soc_opt_in: issue.eligible_for_opt_in?, + eligible_for_soc_opt_in_with_exemption: issue.eligible_for_opt_in?(covid_flag: true), + description: issue.friendly_description, + disposition: issue.disposition, + close_date: issue.close_date, + note: issue.note + ) + end + end end end diff --git a/spec/models/legacy_appeal_spec.rb b/spec/models/legacy_appeal_spec.rb index db42b828769..eaace098025 100644 --- a/spec/models/legacy_appeal_spec.rb +++ b/spec/models/legacy_appeal_spec.rb @@ -2952,4 +2952,56 @@ end end end + + describe "#veteran_has_appeals_in_vacols?(veteran_file_number)" do + subject { LegacyAppeal.veteran_has_appeals_in_vacols?(veteran_file_number) } + + let!(:vacols_case) do + create(:case, bfcorlid: "123456789S") + end + + context "fetch_appeals_by_file_number returns NoMethodError" do + before do + allow(LegacyAppeal).to receive(:fetch_appeals_by_file_number).and_raise(NoMethodError) + end + + it "raises ActiveRecord::RecordNotFound error" do + expect { LegacyAppeal.veteran_has_appeals_in_vacols("1234567890") }.to raise_error(NoMethodError) + end + end + + context "when appeals are returned from VACOLS for the given file_number" do + let(:veteran_file_number) { "123456789" } + + it "returns true" do + expect(subject).to eq(true) + end + end + + context "when appeals are NOT returned from VACOLS for the given file_number" do + let(:veteran_file_number) { "123456788" } + + it "returns false" do + expect(subject).to eq(false) + end + end + + context "when passed an invalid vbms id" do + context "length greater than 9" do + let(:veteran_file_number) { "1234567890" } + + it "raises Caseflow::Error::InvalidFileNumber error" do + expect { subject }.to raise_error(Caseflow::Error::InvalidFileNumber) + end + end + + context "length less than 3" do + let(:veteran_file_number) { "12" } + + it "raises Caseflow::Error::InvalidFileNumber error" do + expect { subject }.to raise_error(Caseflow::Error::InvalidFileNumber) + end + end + end + end end diff --git a/spec/models/request_issue_spec.rb b/spec/models/request_issue_spec.rb index 8ac3a78b62e..4d191d90157 100644 --- a/spec/models/request_issue_spec.rb +++ b/spec/models/request_issue_spec.rb @@ -1112,6 +1112,48 @@ end end + context "#active?" do + let(:request_issue) { create(:request_issue, ineligible_reason: nil, closed_at: nil, split_issue_status: nil) } + + context "all individual fields are looked at" do + it "should result in active? as true" do + expect(request_issue.eligible?).to eq true + expect(request_issue.closed_at).to eq nil + expect(request_issue.split_issue_status).to eq nil + expect(request_issue.split_issue_status).to_not eq "in_progress" + expect(request_issue.active?).to eq true + end + end + + context "when the current request issue has no ineligible reason, not closed, and no split issue status" do + it "should be active" do + expect(request_issue.active?).to eq true + end + end + + context "when the current request issue has no ineligible reason,\ + not closed, and has a split issue status of in_progress" do + it "should be active" do + request_issue.update!(split_issue_status: "in_progress") + expect(request_issue.active?).to eq true + end + end + + context "when the current request issue has an ineligible reason" do + it "should not be active" do + request_issue.update!(ineligible_reason: "untimely") + expect(request_issue.active?).to eq false + end + end + + context "when the current request issue has a closed_at status" do + it "should not be active" do + request_issue.update!(closed_at: Time.current) + expect(request_issue.active?).to eq false + end + end + end + context "#description" do subject { request_issue.description } diff --git a/spec/requests/api/docs/v3/docs_controller_spec.rb b/spec/requests/api/docs/v3/docs_controller_spec.rb index 8d5110e5b7b..f47d778e0a9 100644 --- a/spec/requests/api/docs/v3/docs_controller_spec.rb +++ b/spec/requests/api/docs/v3/docs_controller_spec.rb @@ -45,4 +45,25 @@ end end end + + describe "#ama_issues" do + it "should successfully return openapi spec" do + get "/api/docs/v3/ama_issues" + expect(response).to have_http_status(200) + json = JSON.parse(response.body) + expect(json["openapi"]).to eq("3.0.3") + end + describe "/issues/ama/find_by_veteran/{veteran_participant_id} documentation" do + before(:each) do + get "/api/docs/v3/ama_issues" + end + let(:ama_issues_find_by_veteran_doc) do + json = JSON.parse(response.body) + json["paths"]["/api/v3/issues/ama/find_by_veteran/{veteran_participant_id}"] + end + it "should have GET" do + expect(ama_issues_find_by_veteran_doc).to include("get") + end + end + end end diff --git a/spec/requests/api/v3/decision_reviews/higher_level_reviews_controller_spec.rb b/spec/requests/api/v3/decision_reviews/higher_level_reviews_controller_spec.rb index f8793b264df..04265c0fe56 100644 --- a/spec/requests/api/v3/decision_reviews/higher_level_reviews_controller_spec.rb +++ b/spec/requests/api/v3/decision_reviews/higher_level_reviews_controller_spec.rb @@ -180,7 +180,6 @@ def post_create(parameters = params) "/api/v3/decision_reviews/higher_level_reviews/#{uuid}", headers: authorization_header ) - request_issue = JSON.parse(response.body)["included"].find { |obj| obj["type"] == "RequestIssue" }["attributes"] rating_issue = rating.issues.find { |issue| issue.reference_id == request_issue["ratingIssueId"] } diff --git a/spec/requests/api/v3/issues/ama/veterans_controller_spec.rb b/spec/requests/api/v3/issues/ama/veterans_controller_spec.rb new file mode 100644 index 00000000000..2d95a1dbe40 --- /dev/null +++ b/spec/requests/api/v3/issues/ama/veterans_controller_spec.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +require "test_prof/recipes/rspec/let_it_be" + +# rubocop:disable Layout/LineLength +describe Api::V3::Issues::Ama::VeteransController, :postgres, type: :request do + let_it_be(:api_key) do + ApiKey.create!(consumer_name: "ApiV3 Test VBMS Consumer").key_string + end + + let_it_be(:authorization_header) do + { "Authorization" => "Token #{api_key}" } + end + + RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 50 + + describe "#show" do + context "when feature is not enabled" do + before { FeatureToggle.disable!(:api_v3_ama_issues) } + let!(:vet) { create(:veteran) } + + it "should return 'Not Implemented' error" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + expect(response).to have_http_status(501) + expect(response.body).to include("Not Implemented") + end + end + + context "when feature is enabled" do + before { FeatureToggle.enable!(:api_v3_ama_issues) } + after { FeatureToggle.disable!(:api_v3_ama_issues) } + + context "when the api key is missing in the header" do + let!(:vet) { create(:veteran) } + + it "should return 'Unauthorized' error" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}" + ) + expect(response).to have_http_status(401) + expect(response.body).to include("unauthorized") + end + end + + context "when the incorrect api key is present in the header" do + let!(:vet) { create(:veteran) } + authorization_header = { "Authorization" => "Token 99999999999900000000999999998888111000" } + + it "should return 'Unauthorized' error" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + expect(response).to have_http_status(401) + expect(response.body).to include("unauthorized") + end + end + + context "when a veteran is not found" do + it "should return veteran not found error" do + get( + "/api/v3/issues/ama/find_by_veteran/9999999999", + headers: authorization_header + ) + expect(response).to have_http_status(404) + expect(response.body).to include("No Veteran found for the given identifier.") + end + end + + context "when a veteran is found" do + context "when a veteran is found - but an unexpected error has happened." do + before { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = nil } + after { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 50 } + let(:vet) { create(:veteran) } + it "should return empty request issues array for veteran" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1", + headers: authorization_header + ) + expect(response).to have_http_status(500) + expect(response.body.include?("Use the error uuid to submit a support ticket")).to eq true + end + end + + context "when there is no error" do + before do + allow(Rails.logger).to receive(:info) + expect(Rails.logger).to receive(:info).with(/FINISHED Retrieving AMA Request Issues for Veteran:/) + end + + context "when a veteran is found - but has no request issues" do + let(:vet) { create(:veteran) } + it "should return empty request issues array for veteran" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + expect(response).to have_http_status(200) + response_hash = JSON.parse(response.body) + expect(response_hash["request_issues"].empty?).to eq true + end + end + + context "when a veteran has a legacy appeal" do + context "when a veteran has multiple request issues with multiple decision issues" do + let_it_be(:vet) { create(:veteran, file_number: "123456789") } + let_it_be(:vacols_case) { create(:case, bfcorlid: "123456789S") } + include_context :multiple_ri_multiple_di + let_it_be(:reqeust_issue_no_di) { create(:request_issue, id: 5000, veteran_participant_id: vet.participant_id) } + let_it_be(:request_issue_for_vet_count) { RequestIssue.where(veteran_participant_id: vet.participant_id).count } + + it_behaves_like :it_should_respond_with_legacy_present, true + it_behaves_like :it_should_respond_with_associated_request_issues, true, true + it_behaves_like :it_should_respond_with_multiple_decision_issues_per_request_issues, true, true + + include_context :number_of_request_issues_exceeds_paginates_per, true + end + + context "when a veteran has multiple decision issues with multiple request issues" do + let_it_be(:vet) { create(:veteran, file_number: "123456789") } + let_it_be(:vacols_case) { create(:case, bfcorlid: "123456789S") } + let_it_be(:decision_issues) { create_list(:decision_issue, 2, participant_id: vet.participant_id) } + include_context :multiple_di_multiple_ri + let_it_be(:reqeust_issue_no_di) { create(:request_issue, id: 5000, veteran_participant_id: vet.participant_id) } + let_it_be(:request_issue_for_vet_count) { RequestIssue.where(veteran_participant_id: vet.participant_id).count } + + it_behaves_like :it_should_respond_with_legacy_present, true + it_behaves_like :it_should_respond_with_associated_request_issues, true, true + it_behaves_like :it_should_respond_with_same_multiple_decision_issues_per_request_issue, true + + include_context :number_of_request_issues_exceeds_paginates_per, true + end + end + + context "when a veteran does not have a legacy appeal" do + context "when a veteran has multiple request issues with multiple decision issues" do + let_it_be(:vet) { create(:veteran) } + include_context :multiple_ri_multiple_di + let_it_be(:reqeust_issue_no_di) { create(:request_issue, id: 5000, veteran_participant_id: vet.participant_id) } + let_it_be(:request_issue_for_vet_count) { RequestIssue.where(veteran_participant_id: vet.participant_id).count } + + it_behaves_like :it_should_respond_with_legacy_present, false + it_behaves_like :it_should_respond_with_associated_request_issues, false, true + it_behaves_like :it_should_respond_with_multiple_decision_issues_per_request_issues, false, true + + include_context :number_of_request_issues_exceeds_paginates_per, false + end + + context "when a veteran has multiple decision issues with multiple request issues" do + let_it_be(:vet) { create(:veteran) } + let_it_be(:decision_issues) { create_list(:decision_issue, 2, participant_id: vet.participant_id) } + include_context :multiple_di_multiple_ri + let_it_be(:reqeust_issue_no_di) { create(:request_issue, id: 5000, veteran_participant_id: vet.participant_id) } + let_it_be(:request_issue_for_vet_count) { RequestIssue.where(veteran_participant_id: vet.participant_id).count } + + it_behaves_like :it_should_respond_with_legacy_present, false + it_behaves_like :it_should_respond_with_associated_request_issues, false, true + it_behaves_like :it_should_respond_with_same_multiple_decision_issues_per_request_issue, false + + include_context :number_of_request_issues_exceeds_paginates_per, false + end + end + end + end + end + end +end +# rubocop:enable Layout/LineLength diff --git a/spec/requests/api/v3/issues/vacols/veterans_controller_spec.rb b/spec/requests/api/v3/issues/vacols/veterans_controller_spec.rb new file mode 100644 index 00000000000..aa2978a66ce --- /dev/null +++ b/spec/requests/api/v3/issues/vacols/veterans_controller_spec.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require "test_prof/recipes/rspec/let_it_be" + +# rubocop:disable Layout/LineLength +describe Api::V3::Issues::Vacols::VeteransController, :postgres, type: :request do + let_it_be(:api_key) do + ApiKey.create!(consumer_name: "ApiV3 Test VBMS Consumer").key_string + end + + let_it_be(:authorization_token) do + "Token #{api_key}" + end + + describe "#show" do + context "when feature is not enabled" do + let!(:vet) { create(:veteran) } + + it "should return 'Not Implemented' error" do + FeatureToggle.disable!(:api_v3_vacols_issues) + + get_vacols_issues(file_number: vet.file_number) + expect(response).to have_http_status(501) + expect(response.body).to include("Not Implemented") + end + end + + context "when feature is enabled" do + before { FeatureToggle.enable!(:api_v3_vacols_issues) } + after { FeatureToggle.disable!(:api_v3_vacols_issues) } + + context "when no ApiKey is provided" do + it "returns a 401 error" do + get_vacols_issues(auth_token: nil) + + expect(response).to have_http_status(401) + end + end + + context "when no file_number provided" do + it "returns a 422 error" do + get_vacols_issues + errors = JSON.parse(response.body)["errors"][0] + + expect(errors["status"]).to eq 422 + expect(errors["title"]).to eq "Veteran file number header is required" + end + end + + context "when a veteran is not found" do + it "should return veteran not found error" do + get_vacols_issues(file_number: 999_999_999_9) + expect(response).to have_http_status(404) + expect(response.body).to include("No Veteran found for the given identifier") + end + + it "should return 404 error for non happy paths" do + get_vacols_issues(file_number: 87) + expect(response).to have_http_status(404) + + get_vacols_issues(file_number: 123_456_789_876_543_21) + expect(response).to have_http_status(404) + + get_vacols_issues(file_number: "fakevet") + expect(response).to have_http_status(404) + end + end + + context "when a veteran is found" do + context "when a veteran has no legacy issues(s)" do + let(:vet) { create(:veteran) } + it "should return a success status and an empty list of Issues" do + get_vacols_issues(file_number: vet.file_number) + + expect(response).to have_http_status(200) + response_hash = JSON.parse(response.body) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["total_vacols_issues_for_vet"]).to eq(0) + expect(response_hash["total_number_of_pages"]).to eq(0) + end + end + + context "when a veteran is found - but an unexpected error has happened." do + before { Api::V3::Issues::Vacols::VeteransController::DEFAULT_UPPER_BOUND_PER_PAGE = "breaking_the_api" } + after { Api::V3::Issues::Vacols::VeteransController::DEFAULT_UPPER_BOUND_PER_PAGE = 50 } + let(:vet) { create(:veteran) } + it "should return 500 error" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": vet.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=1&per_page=40", headers: headers) + expect(response).to have_http_status(500) + expect(response.body.include?("Use the error uuid to submit a support ticket")).to eq true + end + end + + context "when veterans have legacy issues(s)" do + let!(:veteran_with_legacy_issues) { create(:veteran, file_number: "123456789") } + let!(:veteran_file_number_legacy) { "123456789S" } + let!(:vacols_id) { "LEGACYID" } + before do + 12.times do + create(:case_issue, + isskey: vacols_id, + issprog: "02", + isscode: "15", + isslev1: "04") + end + end + let!(:case_issues) { VACOLS::CaseIssue.where(isskey: vacols_id) } + let!(:vacols_case) do + create(:case_with_soc, :status_advance, case_issues: case_issues, bfkey: vacols_id, bfcorlid: veteran_file_number_legacy) + end + let!(:appeal) { create(:legacy_appeal, vacols_case: vacols_case) } + + # Create Veteran with 2 Legacy Appeals + let!(:veteran_with_multiple_legacy_appeals) { create(:veteran, file_number: "222222222") } + let!(:veteran_file_number_legacy2) { "222222222S" } + let!(:vacols_id2) { "LEGACYID2" } + let!(:vacols_id3) { "LEGACYID3" } + before do + 7.times do + create(:case_issue, + isskey: vacols_id2, + issprog: "02", + isscode: "15", + isslev1: "04") + end + 7.times do + create(:case_issue, + isskey: vacols_id3, + issprog: "02", + isscode: "15", + isslev1: "04") + end + end + let!(:case_issues2) { VACOLS::CaseIssue.where(isskey: vacols_id2) } + let!(:case_issues3) { VACOLS::CaseIssue.where(isskey: vacols_id3) } + let!(:vacols_case2) do + create(:case_with_soc, :status_advance, case_issues: case_issues2, bfkey: vacols_id2, bfcorlid: veteran_file_number_legacy2) + end + let!(:vacols_case3) do + create(:case_with_soc, :status_advance, case_issues: case_issues3, bfkey: vacols_id3, bfcorlid: veteran_file_number_legacy2) + end + let!(:appeal2) { create(:legacy_appeal, vacols_case: vacols_case2) } + let!(:appeal3) { create(:legacy_appeal, vacols_case: vacols_case3) } + + it "the standard API call should return all their Issues if a Veteran only has 1 Legacy Appeal" do + expect(case_issues.count).to eq(12) + + get_vacols_issues(file_number: veteran_with_legacy_issues.file_number) + expect(response).to have_http_status(200) + response_hash = JSON.parse(response.body) + expect(response_hash["veteran_participant_id"]).to eq veteran_with_legacy_issues.participant_id + expect(response_hash["total_vacols_issues_for_vet"]).to eq(12) + expect(response_hash["total_number_of_pages"]).to eq(2) + end + + it "the standard API call should return all Issues across all Legacy Appeals for a given Veteran" do + get_vacols_issues(file_number: veteran_with_multiple_legacy_appeals.file_number) + expect(response).to have_http_status(200) + response_hash = JSON.parse(response.body) + expect(response_hash["veteran_participant_id"]).to eq veteran_with_multiple_legacy_appeals.participant_id + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["total_number_of_pages"]).to eq(2) + end + + it "API call works when you pass in a page param" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": veteran_with_multiple_legacy_appeals.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=1", headers: headers) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["total_number_of_pages"]).to eq(2) + expect(response_hash["vacols_issues"].count).to eq(10) + end + + it "API call returns the last page when you pass in a page param that exceeds it" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": veteran_with_multiple_legacy_appeals.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=9", headers: headers) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["page"]).to eq(2) + expect(response_hash["total_number_of_pages"]).to eq(2) + expect(response_hash["vacols_issues"].count).to eq(4) + end + + it "API call works when you pass in a page and per_page param" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": veteran_with_multiple_legacy_appeals.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=1&per_page=4", headers: headers) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["total_number_of_pages"]).to eq(4) + expect(response_hash["vacols_issues"].count).to eq(4) + end + + it "API call returns a maximum of 50 issues when you pass in a per_page param thats over the upper_limit of 50" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": veteran_with_multiple_legacy_appeals.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=1&per_page=100", headers: headers) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["total_number_of_pages"]).to eq(1) + expect(response_hash["vacols_issues"].count).to eq(14) + expect(response_hash["max_vacols_issues_per_page"]).to eq(50) + end + + it "API call returns the default max issues when you pass in a per_page param of 0" do + headers = { "Authorization": authorization_token, "X-VA-File-Number": veteran_with_multiple_legacy_appeals.file_number } + get("/api/v3/issues/vacols/find_by_veteran?page=1&per_page=0", headers: headers) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["total_vacols_issues_for_vet"]).to eq(14) + expect(response_hash["total_number_of_pages"]).to eq(2) + expect(response_hash["vacols_issues"].count).to eq(10) + expect(response_hash["max_vacols_issues_per_page"]).to eq(10) + end + end + end + end + + def get_vacols_issues(auth_token: authorization_token, file_number: nil) + headers = { "Authorization": auth_token, "X-VA-File-Number": file_number } + + get("/api/v3/issues/vacols/find_by_veteran", headers: headers) + end + end +end +# rubocop:enable Layout/LineLength diff --git a/spec/seeds/ama_intake_spec.rb b/spec/seeds/ama_intake_spec.rb new file mode 100644 index 00000000000..ea6ce20f4a8 --- /dev/null +++ b/spec/seeds/ama_intake_spec.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true + +describe Seeds::AmaIntake do + let(:seed) { Seeds::AmaIntake.new } + + let(:veteran_appeal_request_issues) do + RequestIssue.where(decision_review: Appeal.first, + veteran_participant_id: Veteran.first.participant_id) + end + + let(:veteran_decision_issues_for_appeal_request_issues) do + veteran_appeal_request_issues.map(&:decision_issues) + end + + let(:veteran_hlr_request_issues) do + RequestIssue.where(decision_review: HigherLevelReview.first, + veteran_participant_id: Veteran.first.participant_id) + end + + let(:veteran_decision_issues_for_hlr_request_issues) do + veteran_hlr_request_issues.map(&:decision_issues) + end + + let(:veteran_sc_request_issues) do + RequestIssue.where(decision_review: SupplementalClaim.first, + veteran_participant_id: Veteran.first.participant_id) + end + + let(:veteran_decision_issues_for_sc_request_issues) do + veteran_sc_request_issues.map(&:decision_issues) + end + + let(:veteran_hlr_decision_issues) do + DecisionIssue.where(decision_review: HigherLevelReview.first, + participant_id: Veteran.first.participant_id) + end + + let(:veteran_request_issues_for_hlr_decision_issues) do + veteran_hlr_decision_issues.map(&:request_issues) + end + + context "#seed!" do + it "seeds total of 7 Veterans, 15 Legacy Appeals, 6 Appeals, 6 Higher Level Reviews End Product Establishments, + 2 Supplemental Claim End Product Establishments, 940 Request Issues, and 894 Decision Issues" do + seed.seed! + expect(Veteran.count).to eq(7) + expect(VACOLS::Case.all.size).to eq(15) + expect(Appeal.count).to eq(6) + expect(HigherLevelReview.count).to eq(6) + expect(SupplementalClaim.count).to eq(2) + expect(EndProductEstablishment.count).to eq(8) + expect(RequestIssue.count).to eq(940) + expect(DecisionIssue.count).to eq(894) + end + end + + context "#create_veteran_with_no_legacy_appeals_and_many_request_and_decision_issues" do + before do + seed.send(:create_veteran_with_no_legacy_appeals_and_many_request_and_decision_issues) + end + + it "the Veteran has 3 decision reviews: one Appeal, one Higher Level Review, and one Supplemental Claim" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(SupplementalClaim.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran has no Legacy Appeals in VACOLS" do + expect(VACOLS::Case.all.size).to eq(0) + end + + it "the Veteran's Appeal has 180 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(180) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 180 Request Issues, each containing a Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(180) + expect(veteran_decision_issues_for_hlr_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Supplemental Claim has 60 Request Issues, none of which contain a Decision Issue" do + expect(veteran_sc_request_issues.size).to eq(60) + expect(veteran_decision_issues_for_sc_request_issues.all?(&:empty?)).to eq(true) + end + end + + context "#create_veteran_with_legacy_appeals_and_many_request_and_decision_issues" do + before do + seed.send(:create_veteran_with_legacy_appeals_and_many_request_and_decision_issues) + end + + it "the Veteran has 3 decision reviews: one Appeal, one Higher Level Review, and one Supplemental Claim" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(SupplementalClaim.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran has 5 Legacy Appeals in VACOLS" do + expect(VACOLS::Case.all.size).to eq(5) + end + + it "the Veteran's Appeal has 180 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(180) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 180 Request Issues, each containing a Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(180) + expect(veteran_decision_issues_for_hlr_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Supplemental Claim has 60 Request Issues, none of which contain a Decision Issue" do + expect(veteran_sc_request_issues.size).to eq(60) + expect(veteran_decision_issues_for_sc_request_issues.all?(&:empty?)).to eq(true) + end + end + + context "#create_veteran_with_no_legacy_appeals_and_request_issue_with_many_decision_issues" do + before do + seed.send(:create_veteran_with_no_legacy_appeals_and_request_issue_with_many_decision_issues) + end + + it "the Veteran has 2 decision reviews: one Appeal and one Higher Level Review" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran's Appeal has 7 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(7) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 8 Request Issues, 7 containing a single Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(8) + expect(veteran_decision_issues_for_hlr_request_issues.count { |array| array.size == 1 }).to eq(7) + end + + it "one Request Issue on the Veteran's Higher Level Review is associated with 68 Decision Issues" do + expect(veteran_decision_issues_for_hlr_request_issues.count { |array| array.size == 68 }).to eq(1) + end + end + + context "#create_veteran_with_legacy_appeals_and_request_issue_with_many_decision_issues" do + before do + seed.send(:create_veteran_with_legacy_appeals_and_request_issue_with_many_decision_issues) + end + + it "the Veteran has 2 decision reviews: one Appeal and one Higher Level Review" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran has 5 Legacy Appeals in VACOLS" do + expect(VACOLS::Case.all.size).to eq(5) + end + + it "the Veteran's Appeal has 7 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(7) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 8 Request Issues, 7 containing a single Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(8) + expect(veteran_decision_issues_for_hlr_request_issues.count { |array| array.size == 1 }).to eq(7) + end + + it "one Request Issue on the Veteran's Higher Level Review is associated with 68 Decision Issues" do + expect(veteran_decision_issues_for_hlr_request_issues.count { |array| array.size == 68 }).to eq(1) + end + end + + context "#create_veteran_with_no_legacy_appeals_and_decision_issue_with_many_request_issues" do + before do + seed.send(:create_veteran_with_no_legacy_appeals_and_decision_issue_with_many_request_issues) + end + + it "the Veteran has 2 decision reviews: one Appeal and one Higher Level Review" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran's Appeal has 2 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(2) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 33 Request Issues, two containing a single Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(33) + expect(veteran_request_issues_for_hlr_decision_issues.count { |array| array.size == 1 }).to eq(2) + end + + it "one Decision Issue on the Veteran's Higher Level Review is associated with 31 Request Issues" do + expect(veteran_hlr_decision_issues.count { |di| di.request_issues.size == 31 }).to eq(1) + end + end + + context "#create_veteran_with_legacy_appeals_and_decision_issue_with_many_request_issues" do + before do + seed.send(:create_veteran_with_legacy_appeals_and_decision_issue_with_many_request_issues) + end + + it "the Veteran has 2 decision reviews: one Appeal and one Higher Level Review" do + expect(Appeal.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + expect(HigherLevelReview.where(veteran_file_number: Veteran.first.file_number).size).to eq(1) + end + + it "the Veteran has 5 Legacy Appeals in VACOLS" do + expect(VACOLS::Case.all.size).to eq(5) + end + + it "the Veteran's Appeal has 2 Request Issues, each containing a Decision Issue" do + expect(veteran_appeal_request_issues.size).to eq(2) + expect(veteran_decision_issues_for_appeal_request_issues.all? { |di_array| di_array.size == 1 }).to eq(true) + end + + it "the Veteran's Higher Level Review has 33 Request Issues, two containing a single Decision Issue" do + expect(veteran_hlr_request_issues.size).to eq(33) + expect(veteran_request_issues_for_hlr_decision_issues.count { |array| array.size == 1 }).to eq(2) + end + + it "one Decision Issue on the Veteran's Higher Level Review is associated with 31 Request Issues" do + expect(veteran_hlr_decision_issues.count { |di| di.request_issues.size == 31 }).to eq(1) + end + end + + context "#create_veteran_without_request_issues" do + before do + seed.send(:create_veteran_without_request_issues) + end + + it "A Veteran is created with 0 Request Issues" do + expect(Veteran.count).to eq(1) + expect(veteran_appeal_request_issues.size).to eq(0) + end + end +end diff --git a/spec/serializers/api/v3/issues/ama/request_issue_serializer_spec.rb b/spec/serializers/api/v3/issues/ama/request_issue_serializer_spec.rb new file mode 100644 index 00000000000..fa5ca4cbef3 --- /dev/null +++ b/spec/serializers/api/v3/issues/ama/request_issue_serializer_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "test_prof/recipes/rspec/let_it_be" + +describe Api::V3::Issues::Ama::RequestIssueSerializer, :postgres do + context "request issue object" do + let(:vet) { create(:veteran) } + let(:request_issue) do + create(:request_issue, :with_associated_decision_issue, veteran_participant_id: vet.participant_id) + end + it "should have all eligiblity fields" do + serialized_request_issue = Api::V3::Issues::Ama::RequestIssueSerializer.new(request_issue) + .serializable_hash[:data][:attributes] + expect(serialized_request_issue.key?(:id)).to eq true + expect(serialized_request_issue.key?(:benefit_type)).to eq true + expect(serialized_request_issue.key?(:closed_status)).to eq true + expect(serialized_request_issue.key?(:contention_reference_id)).to eq true + expect(serialized_request_issue.key?(:contested_decision_issue_id)).to eq true + expect(serialized_request_issue.key?(:contested_issue_description)).to eq true + expect(serialized_request_issue.key?(:contested_rating_decision_reference_id)).to eq true + expect(serialized_request_issue.key?(:contested_rating_issue_diagnostic_code)).to eq true + expect(serialized_request_issue.key?(:contested_rating_issue_profile_date)).to eq true + expect(serialized_request_issue.key?(:contested_rating_issue_reference_id)).to eq true + expect(serialized_request_issue.key?(:corrected_by_request_issue_id)).to eq true + expect(serialized_request_issue.key?(:correction_type)).to eq true + expect(serialized_request_issue.key?(:created_at)).to eq true + expect(serialized_request_issue.key?(:decision_date)).to eq true + expect(serialized_request_issue.key?(:decision_review_id)).to eq true + expect(serialized_request_issue.key?(:decision_review_type)).to eq true + expect(serialized_request_issue.key?(:edited_description)).to eq true + expect(serialized_request_issue.key?(:end_product_establishment_id)).to eq true + expect(serialized_request_issue.key?(:ineligible_due_to_id)).to eq true + expect(serialized_request_issue.key?(:ineligible_reason)).to eq true + expect(serialized_request_issue.key?(:is_unidentified)).to eq true + expect(serialized_request_issue.key?(:nonrating_issue_bgs_id)).to eq true + expect(serialized_request_issue.key?(:nonrating_issue_category)).to eq true + expect(serialized_request_issue.key?(:nonrating_issue_description)).to eq true + expect(serialized_request_issue.key?(:notes)).to eq true + expect(serialized_request_issue.key?(:ramp_claim_id)).to eq true + expect(serialized_request_issue.key?(:split_issue_status)).to eq true + expect(serialized_request_issue.key?(:untimely_exemption)).to eq true + expect(serialized_request_issue.key?(:untimely_exemption_notes)).to eq true + expect(serialized_request_issue.key?(:updated_at)).to eq true + expect(serialized_request_issue.key?(:vacols_id)).to eq true + expect(serialized_request_issue.key?(:vacols_sequence_id)).to eq true + expect(serialized_request_issue.key?(:verified_unidentified_issue)).to eq true + expect(serialized_request_issue.key?(:veteran_participant_id)).to eq true + expect(serialized_request_issue.key?(:caseflow_considers_decision_review_active)).to eq true + expect(serialized_request_issue.key?(:caseflow_considers_issue_active)).to eq true + expect(serialized_request_issue.key?(:caseflow_considers_title_of_active_review)).to eq true + expect(serialized_request_issue.key?(:caseflow_considers_eligible)).to eq true + expect(serialized_request_issue.key?(:claimant_participant_id)).to eq true + expect(serialized_request_issue.key?(:decision_issues)).to eq true + expect(serialized_request_issue.key?(:claim_id)).to eq true + + serialized_decision_issue = serialized_request_issue[:decision_issues].first + expect(serialized_decision_issue.key?(:id)).to eq true + expect(serialized_decision_issue.key?(:caseflow_decision_date)).to eq true + expect(serialized_decision_issue.key?(:created_at)).to eq true + expect(serialized_decision_issue.key?(:decision_text)).to eq true + expect(serialized_decision_issue.key?(:deleted_at)).to eq true + expect(serialized_decision_issue.key?(:description)).to eq true + expect(serialized_decision_issue.key?(:diagnostic_code)).to eq true + expect(serialized_decision_issue.key?(:disposition)).to eq true + expect(serialized_decision_issue.key?(:end_product_last_action_date)).to eq true + expect(serialized_decision_issue.key?(:percent_number)).to eq true + expect(serialized_decision_issue.key?(:rating_issue_reference_id)).to eq true + expect(serialized_decision_issue.key?(:rating_profile_date)).to eq true + expect(serialized_decision_issue.key?(:rating_promulgation_date)).to eq true + expect(serialized_decision_issue.key?(:subject_text)).to eq true + expect(serialized_decision_issue.key?(:updated_at)).to eq true + end + end +end diff --git a/spec/serializers/api/v3/issues/vacols/vacols_issue_serializer_spec.rb b/spec/serializers/api/v3/issues/vacols/vacols_issue_serializer_spec.rb new file mode 100644 index 00000000000..59cba3b9142 --- /dev/null +++ b/spec/serializers/api/v3/issues/vacols/vacols_issue_serializer_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +describe Api::V3::Issues::Vacols::VacolsIssueSerializer, :postgres do + context "VACOLS issue object" do + let(:vacols_id) { "12345678" } + let(:disposition) { nil } + let(:disposition_date) { Time.zone.today } + let(:soc_date) { Time.zone.today } + let(:vacols_case) do + create(:case_with_soc, :status_advance, case_issues: [vacols_case_issue], bfkey: vacols_id, bfdsoc: soc_date) + end + let(:vacols_case_issue) do + create( + :case_issue, + isskey: vacols_id, + issdc: Issue.disposition_code_for_sym(disposition), + issdcls: disposition_date + ) + end + let!(:appeal) { create(:legacy_appeal, vacols_case: vacols_case) } + let(:issue) { Issue.load_from_vacols(vacols_case_issue.attributes) } + + it "should show all the fields" do + serialized_vacols_issue = Api::V3::Issues::Vacols::VacolsIssueSerializer.new(issue) + .serializable_hash[:data][:attributes][:vacols_issue] + + expect(serialized_vacols_issue).not_to eq nil + expect(serialized_vacols_issue[:id]).to eq "12345678" + expect(serialized_vacols_issue[:notice_of_disagreement_date]).to eq appeal.nod_date + expect(serialized_vacols_issue[:legacy_appeal_status]).to eq appeal.status + expect(serialized_vacols_issue[:legacy_appeal_soc_date]).to eq appeal.soc_date + expect(serialized_vacols_issue[:legacy_appeal_ssoc_dates]).to eq appeal.ssoc_dates + expect(serialized_vacols_issue[:legacy_appeal_eligible_for_opt_in]).to eq appeal.eligible_for_opt_in?( + receipt_date: Time.zone.today + ) + expect(serialized_vacols_issue[:legacy_appeal_eligible_for_soc_opt_in_with_exemption]).to eq( + appeal.eligible_for_opt_in?(receipt_date: Time.zone.today, covid_flag: true) + ) + expect(serialized_vacols_issue[:vacols_id]).to eq issue.id + expect(serialized_vacols_issue[:vacols_sequence_id]).to eq issue.vacols_sequence_id + expect(serialized_vacols_issue[:eligible_for_soc_opt_in]).to eq issue.eligible_for_opt_in? + expect(serialized_vacols_issue[:eligible_for_soc_opt_in_with_exemption]) + .to eq issue.eligible_for_opt_in?(covid_flag: true) + expect(serialized_vacols_issue[:description]).to eq issue.friendly_description + expect(serialized_vacols_issue[:disposition]).to eq issue.disposition + expect(serialized_vacols_issue[:close_date]).to eq issue.close_date + expect(serialized_vacols_issue[:note]).to eq issue.note + end + end +end diff --git a/spec/support/shared_context/ama_issues/issue_setup_spec.rb b/spec/support/shared_context/ama_issues/issue_setup_spec.rb new file mode 100644 index 00000000000..4ccd892bd7b --- /dev/null +++ b/spec/support/shared_context/ama_issues/issue_setup_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# rubocop:disable Layout/LineLength +require "test_prof/recipes/rspec/let_it_be" + +RSpec.shared_context :set_new_page_per do + before { RequestIssue.paginates_per(2) } + after { RequestIssue.paginates_per(ENV["REQUEST_ISSUE_PAGINATION_OFFSET"].to_i) } +end + +RSpec.shared_context :multiple_ri_multiple_di do + let_it_be(:request_issues) do + ri_list = create_list(:request_issue, 4, :with_associated_decision_issue, + veteran_participant_id: vet.participant_id) + ri_list.each do |ri| + di = create(:decision_issue, participant_id: ri.veteran_participant_id, decision_review: ri.decision_review) + create(:request_decision_issue, request_issue: ri, decision_issue: di) + end + end +end + +RSpec.shared_context :multiple_di_multiple_ri do + let_it_be(:request_issues) do + decision_issues.each do |di| + ri_list = create_list(:request_issue, 4, veteran_participant_id: vet.participant_id) + ri_list.each do |ri| + create(:request_decision_issue, request_issue: ri, decision_issue: di) + end + end + RequestIssue.where(veteran_participant_id: vet.participant_id) + end +end + +RSpec.shared_context :number_of_request_issues_exceeds_paginates_per do |legacy_appeals_present| + context "when number of request issues exceeds paginates_per value" do + include_context :set_new_page_per + + it_behaves_like :it_should_show_number_of_paginated_issues, legacy_appeals_present + it_behaves_like :it_should_show_remaining_issues, legacy_appeals_present + it_behaves_like :it_should_show_page_1_when_page_0, legacy_appeals_present + it_behaves_like :it_should_default_to_page_1, legacy_appeals_present + it_behaves_like :it_should_show_first_page_if_page_negatvie, legacy_appeals_present + it_behaves_like :it_should_show_last_page_if_page_larger_than_total, legacy_appeals_present + it_behaves_like :it_should_show_correct_total_number_of_pages_and_max_request_issues_per_page_on_per_change, legacy_appeals_present + it_behaves_like :it_should_show_default_limit_on_excessive_per_value, legacy_appeals_present + it_behaves_like :it_should_show_default_if_negative_per_param, legacy_appeals_present + it_behaves_like :it_should_show_default_if_0_per_param, legacy_appeals_present + it_behaves_like :it_should_show_correct_number_of_issues_on_page_increment_with_per, legacy_appeals_present + it_behaves_like :it_should_show_upper_bound_per_if_default_is_higher, legacy_appeals_present + end +end +# rubocop:enable Layout/LineLength diff --git a/spec/support/shared_examples/requests/api/v3/issues/ama/shared_examples_request_issues_spec.rb b/spec/support/shared_examples/requests/api/v3/issues/ama/shared_examples_request_issues_spec.rb new file mode 100644 index 00000000000..054c3b6dfa4 --- /dev/null +++ b/spec/support/shared_examples/requests/api/v3/issues/ama/shared_examples_request_issues_spec.rb @@ -0,0 +1,320 @@ +# frozen_string_literal: true + +# rubocop:disable Layout/LineLength +# rubocop:disable Lint/ParenthesesAsGroupedExpression +RSpec.shared_examples :it_should_show_upper_bound_per_if_default_is_higher do |legacy_appeals_present| + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + before { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 1 } + after { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 50 } + it "should show upper bound per if default is higher" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + total_number_of_pages = (request_issue_for_vet_count / 1.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 1 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 1 + end +end + +RSpec.shared_examples :it_should_show_correct_number_of_issues_on_page_increment_with_per do |legacy_appeals_present| + it "should show correct number of issues on page increment with per" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=2&per_page=1", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + total_number_of_pages = (request_issue_for_vet_count / 1.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 1 + expect(response_hash["page"]).to eq 2 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 1 + end +end + +RSpec.shared_examples :it_should_show_default_if_0_per_param do |legacy_appeals_present| + it "should show default if 0 per param is supplied" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1&per_page=0", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + total_number_of_pages = (request_issue_for_vet_count / 2.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 2 + end +end + +RSpec.shared_examples :it_should_show_default_if_negative_per_param do |legacy_appeals_present| + it "should show default if negative per param is supplied" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1&per_page=-1", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + total_number_of_pages = (request_issue_for_vet_count / 2.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 2 + end +end + +RSpec.shared_examples :it_should_show_default_limit_on_excessive_per_value do |legacy_appeals_present| + before { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 3 } + after { RequestIssue::DEFAULT_UPPER_BOUND_PER_PAGE = 50 } + it "should show default limit on excessive per value" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1&per_page=50", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + # default RequestIssue.default_per_page is set to 2 in set_new_page_per shared context + total_number_of_pages = (request_issue_for_vet_count / 2.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 2 + end +end + +RSpec.shared_examples :it_should_show_correct_total_number_of_pages_and_max_request_issues_per_page_on_per_change do |legacy_appeals_present| + it "should show correct total number of pages and max request issues per page on per change" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1&per_page=1", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + total_number_of_pages = (request_issue_for_vet_count / 1.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 1 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq 1 + end +end + +RSpec.shared_examples :it_should_show_first_page_if_page_negatvie do |legacy_appeals_present| + it "should show the first page if page is negative" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=-5", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end + +RSpec.shared_examples :it_should_show_last_page_if_page_larger_than_total do |legacy_appeals_present| + it "should show last page if page larger than total" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=5", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["page"]).to eq total_number_of_pages + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end + +RSpec.shared_examples :it_should_default_to_page_1 do |legacy_appeals_present| + it "should default to page 1" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end + +RSpec.shared_examples :it_should_respond_with_legacy_present do |legacy_appeals_present| + it "should respond with legacy_appeals_present" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + end +end + +RSpec.shared_examples :it_should_respond_with_associated_request_issues do |legacy_appeals_present, is_empty| + it "should respond with the associated request issues" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + request_issues_vet_participant_ids = response_hash["request_issues"].map { |ri| ri["veteran_participant_id"] } + request_issue_without_dis = response_hash["request_issues"].find { |ri| ri["id"] == 5000 } + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq request_issue_for_vet_count + expect(request_issue_without_dis["decision_issues"].empty?).to eq is_empty + expect(request_issues_vet_participant_ids).to eq ([].tap { |me| request_issue_for_vet_count.times { me << vet.participant_id } }) + end +end + +RSpec.shared_examples :it_should_respond_with_multiple_decision_issues_per_request_issues do |legacy_appeals_present, is_empty| + it "should respond with the multiple decision issues per request issue" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + request_issues_vet_participant_ids = response_hash["request_issues"].map do |ri| + ri["veteran_participant_id"] + end + request_issue_without_dis = response_hash["request_issues"].find { |ri| ri["id"] == 5000 } + request_issue_with_two_dis = response_hash["request_issues"].find { |ri| ri["decision_issues"].size == 2 } + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq request_issue_for_vet_count + expect(request_issue_without_dis["decision_issues"].empty?).to eq is_empty + expect(request_issues_vet_participant_ids).to eq ([].tap { |me| request_issue_for_vet_count.times { me << vet.participant_id } }) + expect(request_issue_with_two_dis).to_not eq nil + end +end + +RSpec.shared_examples :it_should_respond_with_same_multiple_decision_issues_per_request_issue do |legacy_appeals_present| + it "should respond with the same multiple decision issues per request issue" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + request_issues_vet_participant_ids = response_hash["request_issues"].map { |ri| ri["veteran_participant_id"] } + decision_issues_array = response_hash["request_issues"].map { |ri| ri["decision_issues"] } + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq request_issue_for_vet_count + expect(decision_issues_array.uniq.length).to be < decision_issues_array.length + expect(response_hash["request_issues"][3]["decision_issues"] == response_hash["request_issues"][5]["decision_issues"]).to eq false + expect(request_issues_vet_participant_ids).to eq ([].tap { |me| request_issue_for_vet_count.times { me << vet.participant_id } }) + end +end + +RSpec.shared_examples :it_should_show_page_1_when_page_0 do |legacy_appeals_present| + it "should show page 1 when attempting to get page 0" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=0", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end + +RSpec.shared_examples :it_should_show_remaining_issues do |legacy_appeals_present| + it "should only show remaining request issues on next page" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=2", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 2 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end + +RSpec.shared_examples :it_should_show_number_of_paginated_issues do |legacy_appeals_present| + it "should only show number of request issues listed in the paginates_per value on first page" do + get( + "/api/v3/issues/ama/find_by_veteran/#{vet.participant_id}?page=1", + headers: authorization_header + ) + response_hash = JSON.parse(response.body) + default_per_page = RequestIssue.default_per_page + total_number_of_pages = (request_issue_for_vet_count / default_per_page.to_f).ceil + expect(response).to have_http_status(200) + expect(response_hash["veteran_participant_id"]).to eq vet.participant_id + expect(response_hash["legacy_appeals_present"]).to eq legacy_appeals_present + expect(response_hash["request_issues"].size).to eq 2 + expect(response_hash["page"]).to eq 1 + expect(response_hash["total_number_of_pages"]).to eq total_number_of_pages + expect(response_hash["total_request_issues_for_vet"]).to eq request_issue_for_vet_count + expect(response_hash["max_request_issues_per_page"]).to eq default_per_page + end +end +# rubocop:enable Layout/LineLength +# rubocop:enable Lint/ParenthesesAsGroupedExpression