diff --git a/.rubocop.yml b/.rubocop.yml
index 7ef1e76199f..8a486b6dcc0 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -72,6 +72,10 @@ Rails/LexicallyScopedActionFilter:
Rails/SkipsModelValidations:
Enabled: false
+Rails/ApplicationController:
+ Exclude:
+ - app/controllers/idt/api/v1/base_controller.rb
+
Rails/FilePath:
Enabled: false
diff --git a/MAC_INTEL.md b/MAC_INTEL.md
index dc4a757491c..96e2951e042 100644
--- a/MAC_INTEL.md
+++ b/MAC_INTEL.md
@@ -3,6 +3,7 @@
[<< Back](README.md)
**Pre-requisites for setup:**
+**Some steps require completing VA On-boarding**
1. Create GitHub user account [VA github access process](https://department-of-veterans-affairs.github.io/github-handbook/guides/onboarding/getting-access)
@@ -20,46 +21,37 @@
2. Install Homebrew
* a. Using BAH Self Service if BAH employee Run ```brew install git-lfs .``` This is required to clone caseflow-facols repo
-3. Create a caseflow-setup folder by typing: `mkdir caseflow-setup` (step can be skipped if you have the file transfer files)
+3. Create an appeals folder by typing: `mkdir ~/appeals`
-4. Change directory to caseflow-setup by typing: `cd caseflow-setup`
+4. Change directory to appeals by typing: `cd appeals`
5. Navigate to [instant client](https://www.oracle.com/database/tecdchnologies/instant-client/linux-x86-64-downloads.html)
-6. Download the following zip files to caseflow-setup directory (Copy from downloads to the caseflow-setup directory if they download to downloads) (Step can be skipped if you received the file transfer files)
+6. Download the following zip files (Step can be skipped if you received the file transfer files)
* instantclient-basic-linux.x64-12.2.0.1.0.zip
* instantclient-sqlplus-linux.x64-12.2.0.1.0.zip
* instantclient-sdk-linux.x64-12.2.0.1.0.zip
-7. Clone caseflow repositories required for setup into caseflow-setup directory (caseflow-facols requires github account and the account has to be in the VA org in github and can be found in the file transfer files) pwd
- * HTTP protocol
- * `git clone https://github.com/department-of-veterans-affairs/caseflow.git`
- * `git clone https://github.com/department-of-veterans-affairs/caseflow-facols.git`
- * Upon completion, navigate to caseflow-facols (`cd ~/caseflow-setup/caseflow-facols`)
- * Run: `git lfs install` (needed to initialize large file storage in repo)
- * Run: `git lfs pull` (this will pull the large zipfile)
- * If you do not have VA access yet to clone caseflow-vacols can contact (Your Tech lead or the bid_appeals_mac_support channel) to receive a zip of the repository
+7. Clone the following caseflow repositories required into appeals directory
+ * https://github.com/department-of-veterans-affairs/caseflow
+ * https://github.com/department-of-veterans-affairs/caseflow-facols
-**SSH protocol**
+8. Upon completion, navigate to caseflow-facols (`cd ~/appeals/caseflow-facols`)
+ 1. Run: `git lfs install` (needed to initialize large file storage in repo)
+ 2. Run: `git lfs pull` (this will pull the large zipfile)
+ 3. Copy the `~/appeals/caseflow-facols/build_facols` directory into `~/appeals/caseflow/local/vacols/` directory.
-1. `git clone git@github.com:department-of-veterans-affairs/caseflow.git`
+9. Navigate to the caseflow directory in your terminal (type: `cd ~/appeals/caseflow`) and checkout the grant/setup-no-aws branch `git checkout grant/setup-no-aws`
-2. `git clone git@github.com:department-of-veterans-affairs/caseflow-facols.git`
- * Upon completion, navigate to caseflow-facols (`cd ~/caseflow-setup/caseflow-facols`)
- * Run: `git lfs install` (needed to initialize large file storage in repo)
- * Run: `git lfs pull` (this will pull the large zipfile)
+10. Navigate to caseflow/docker-bin directory (type: `cd docker-bin`)
-3. Navigate to the caseflow directory in your terminal (type: `cd ~/caseflow-setup/caseflow`) and checkout the grant/setup-no-aws branch `git checkout grant/setup-no-aws`
+11. Create oracle_libs subdirectory (type: `mkdir oracle_libs`)
-4. Navigate to caseflow/docker-bin directory (type: `cd docker-bin`)
+12. Copy the 3 instant-client zip files from step 6 into the oracle_libs directory
-5. Create oracle_libs subdirectory (type: `mkdir oracle_libs`)
+13. Navigate to the caseflow root directory (type: `cd ~/appeals/caseflow`)
-6. Copy the 3 instant-client zip files from the caseflow-setup directory into the oracle_libs directory
-
-7. Navigate to the caseflow root directory (type: `cd ..`)
-
-8. Run scripts/dev_env_setup_step1.sh script from bash terminal (How to run script in Mac Terminal) (Will be prompted for a password will be the SUDO password which is the password used to log into mac after restart)
+14. Run `scripts/dev_env_setup_step1.sh` script from bash terminal [[How to run script in Mac Terminal](https://apple.stackexchange.com/questions/235128/how-do-i-run-a-sh-or-command-file-in-terminal)] (Will be prompted for a password will be the SUDO password which is the password used to log into mac after restart)
* If/When mac says Chromedriver cannot be opened do this:
* Click cancel on the warning modal
* Push Command + Space
@@ -68,7 +60,7 @@
* Click General tab
* Click the lock icon and put in your BAH pin Click allow anyway on chromedriver warning Click the lock icon to re lock
-9. Setup Docker to use 4 CPUs and 8G memory and sign-in to your personal DockerHub account
+15. Setup Docker to use 4 CPUs and 8G memory and sign-in to your personal DockerHub account
* To get to these settings:
* Command + Space
* Type docker
@@ -76,42 +68,47 @@
* Click the gear icon
* Click Resources
-10. The script updated your bash profile and you need to resource it into the terminal by typing: `source ~/.bash_profile`
+16. The script updated your bash profile and you need to resource it into the terminal by typing: `source ~/.bash_profile`
* If using zsh, will need to update and `source ~/.zshrc` instead
-11. `brew install shared-mime-info`
+17. `brew install shared-mime-info`
-12. `brew install v8@3.15`
+18. `brew install v8@3.15`
-13. Run scripts/dev_env_setup_step2.sh script (may take a while to run)
+19. Run `scripts/dev_env_setup_step2.sh` script (may take a while to run)
-14. Run `gem install bundler`
- * Copy the caseflow-facols/build_facols directory to the caseflow/local/vacols subdirectory. (Ensure you have a caseflow/local/vacols/build_facols directory with all the files before continuing to the next step)
+20. Run `gem install bundler`
-15. Navigate to caseflow/local/vacols in terminal `cd ~/caseflow- setup/caseflow/local/vacols`
+21. Navigate to `~/appeals/caseflow/local/vacols` in terminal (type: `cd ~/appeals/caseflow/local/vacols`)
-16. Run `./build_push.sh local`
- * Requires the oracle database image to have been pulled after running scripts/dev_env_setup_step1.sh script
+22. To install the latest and enterprise Oracle Database version follow (https://seanstacey.org/deploying-an-oracle-database-19c-as-a-docker-container/2020/09/) guide.
+ 1. Go to http://container-registry.oracle.com/ (Here log in and opt for Database)
+ 2. On command line `docker login container-registry.oracle.com`
+ 3. On command line `docker pull container-registry.oracle.com/database/enterprise:latest`
-17. Navigate to caseflow root directory `cd ~/caseflow-setup/caseflow`
+23. Run `./build_push.sh local`
-18. Run `docker-compose up –d`
+24. Navigate to caseflow root directory `cd ~/appeals/caseflow`
-19. Run `bundle exec rake db:create`
- * If you get connection issues stating no file to be found, run the following:
- * `rm /opt/homebrew/var/postgres/postmaster.pid` or possibly `rm /usr/local/var/postgres/postmaster.pid`
- * `brew services restart postgresql`
+25. Run `ln -s Makefile.example Makefile`
-20. Run `bundle exec rake local:vacols:seed`
+26. Run `make up`
-21. Run `bundle exec rake db:schema:load db:seed`
+27. Run `make reset`
+ * If issues occur:
+ 1. Run `bundle exec rake db:create`
+ * If you get connection issues stating no file to be found, run the following:
+ * `rm /opt/homebrew/var/postgres/postmaster.pid` or possibly `rm /usr/local/var/postgres/postmaster.pid`
+ * `brew services restart postgresql`
+ 2. Run `bundle exec rake local:vacols:seed`
+ 3. Run `bundle exec rake db:schema:load db:seed`
-22. Open a new tab in terminal
+27. Open a new tab in terminal
-23. In new tab run make: ```run-backend```
+28. In new tab run make: ```run-backend```
-24. In the old tab run: ```make run-frontend```
+29. In the old tab run: ```make run-frontend```
-25. Navigate to localhost:3000 in browser to see the application
+30. Navigate to localhost:3000 in browser to see the application
[<< Back](README.md)
diff --git a/Makefile.example b/Makefile.example
index ac980f9923e..6b6ac721986 100644
--- a/Makefile.example
+++ b/Makefile.example
@@ -151,9 +151,21 @@ db: ## Connect to your dev postgres (caseflow) db
audit: ## Create caseflow_audit schema, tables, and triggers in postgres
bundle exec rails r db/scripts/audit/create_caseflow_audit_schema.rb
- bundle exec rails r db/scripts/audit/create_appeal_states_audit.rb
- bundle exec rails r db/scripts/audit/add_row_to_appeal_states_audit_table_function.rb
- bundle exec rails r db/scripts/audit/create_appeal_states_audit_trigger.rb
+ bundle exec rails r db/scripts/audit/tables/create_appeal_states_audit.rb
+ bundle exec rails r db/scripts/audit/tables/create_vbms_communication_packages_audit.rb
+ bundle exec rails r db/scripts/audit/tables/create_vbms_distributions_audit.rb
+ bundle exec rails r db/scripts/audit/tables/create_vbms_distribution_destinations_audit.rb
+ bundle exec rails r db/scripts/audit/tables/create_vbms_uploaded_documents_audit.rb
+ bundle exec rails r db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.rb
+ bundle exec rails r db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.rb
+ bundle exec rails r db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.rb
+ bundle exec rails r db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.rb
+ bundle exec rails r db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.rb
+ bundle exec rails r db/scripts/audit/triggers/create_appeal_states_audit_trigger.rb
+ bundle exec rails r db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.rb
+ bundle exec rails r db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.rb
+ bundle exec rails r db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.rb
+ bundle exec rails r db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.rb
audit-remove: ## Remove caseflow_audit schema, tables and triggers in postgres
bundle exec rails r db/scripts/audit/remove_caseflow_audit_schema.rb
diff --git a/app/controllers/api/docs/pacman/idt-pacman-spec.yml b/app/controllers/api/docs/pacman/idt-pacman-spec.yml
new file mode 100644
index 00000000000..0d094879c00
--- /dev/null
+++ b/app/controllers/api/docs/pacman/idt-pacman-spec.yml
@@ -0,0 +1,1281 @@
+openapi: 3.0.2
+info:
+ title: IDT-Caseflow-Package Manager Bridge API
+ description: >-
+ # [Caseflow Wiki Article](https://github.com/department-of-veterans-affairs/caseflow/wiki/Lighthouse-API-Implementation)
+
+ The document outlines a number of endpoints that can be utilized within a IDT-Caseflow-Package Manager workflow.
+ termsOfService: https://developer.va.gov/terms-of-service
+ license:
+ name: Apache 2.0
+ url: https://www.apache.org/licenses/LICENSE-2.0
+ version: 1.0.0
+servers:
+- url: https://appeals.cf.uat.ds.va.gov
+ description: UAT/Staging server
+- url: http://localhost:3000
+ description: Local Development server
+paths:
+ /idt/api/v1/addresses/validate:
+ post:
+ tags:
+ - Address Validation
+ summary: A passthrough to the Lighthouse Address Validation API - Validates Mailing Addresses
+ description: >-
+ This route is largely a passthrough to the Lighthouse Address Validation API. In most cases, any messages Caseflow receives from its upstream source will simply be forwarded back to the requestor (IDT).
+
+ The upstream Lighthouse Address Validation API adheres to [USPS Publication 28](https://pe.usps.com/text/pub28/welcome.htm) standards for domestic, military, and US territory address.
+
+
Data Definitions
+
+
+
Data
+
Description
+
+
+
request_address.address_line_1
+
Solely the first line of the requested address without city, state, or zip. Cannot be null if addressLine2 and addressLine3 are null.
+
+
+
request_address.address_line_2
+
Solely the second line of the requested address without city, state, or zip. Cannot be null if addressLine1 and addressLine3 are null.
+
+
+
request_address.address_line_3
+
Solely the third line of the requested address without city, state, or zip. Cannot be null if addressLine1 and addressLine2 are null.
+
+
+
request_address.city
+
The name of the city of the requested address. Must only contain letters.
+
+
+
request_address.zip_code_5
+
The five digit postal code of the requested address. Must only contain five digits and only used for domestic or military addresses.
+
+
+
request_address.zip_code_4
+
The four digit postal code of the requested address. Must only contain four digits and only used for domestic or military addresses.
+
+
+
request_address.international_postal_code
+
The postal code for an international address. This can contain numbers and letters and used for international addresses.
+
+
+
request_address.state_province.code
+
The two digit code for state/province of the requested address. Must only contain two digits.
+
+
+
request_address.state_province.name
+
The name of the state/province of the requested address.
+
+
+
request_address.country_name
+
The name of the country of the requested address.
+
+
+
request_address.country_code
+
The ISO2, ISO3, or FIPS country code of the requested address. Must only contain two or three letters.
+
+
+
request_address.address_pou
+
Should be either RESIDENCE, CHOICE, or CORRESPONDENCE. Optional
+
+
+
+
Upstream Data Input Requirements
+ One of:
+
+
Address Line 1
+
Address Line 2
+
Address Line 3
+
+ AND:
+
+
country
+
+
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AddressValidationRequest'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CandidateAddressResponseV2'
+ '400':
+ description: An error occurred either between IDT and Caseflow, or Caseflow and the Lighthouse API.
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/LighthouseGenericError'
+ - $ref: '#/components/schemas/CaseflowMissingTokenError'
+ examples:
+ LighthouseGenericError:
+ summary: Whenever Caseflow experiences an error while communicating with the Lighthouse API.
+ value:
+ errors:
+ - status: 400
+ title: 'An unexpected error occurred.'
+ message: 'An unexpected error occurred.'
+ CaseflowMissingTokenError:
+ summary: Whenever a token is not provided to the Caseflow IDT endpoint.
+ value:
+ message: Missing token
+ '403':
+ description: The token used for authorization with the Caseflow IDT API is invalid.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example:
+ message: Invalid token
+ '429':
+ description: Caseflow is being rate-limited by the Lighthouse API.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ errors:
+ type: array
+ items:
+ type: object
+ properties:
+ status:
+ type: number
+ format: int32
+ example: 429
+ title:
+ type: string
+ example: Service is temporarily unavailable, please try again later.
+ detail:
+ type: string
+ example: Service is temporarily unavailable, please try again later.
+ '500':
+ description: There was an error encountered processing the request. This will mean that Caseflow ran into an issue while contacting the Lighthouse Address Validation API.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example:
+ message: 'Lighthouse API Error ID: 322483ae-7953-4d3c-9738-e108506d52c2 An unexpected error occurred, please try again.'
+ /idt/api/v1/appeals/{appeal_id}/upload_document:
+ post:
+ tags:
+ - Document Posting
+ summary: Used for uploading documents to eFolder for specific appeals/veterans
+ parameters:
+ - in: path
+ name: appeal_id
+ schema:
+ type: string
+ required: true
+ requestBody:
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/UploadDocumentRequestWithRecipientInformation'
+ - $ref: '#/components/schemas/UploadDocumentRequest'
+ examples:
+ "With recipient information for Package Manager":
+ $ref: '#/components/schemas/UploadDocumentRequestWithRecipientInformation'
+ "Without recipient information for Package Manager":
+ value:
+ veteran_identifier: '555555555'
+ document_type: 'BVA Decision'
+ document_subject: 'Test'
+ document_name: 'Test Doc'
+ file: 'VGhpcyBpcyBhIHRlc3QuIERvIG5vdCBiZSBhbGFybWVkLgo='
+ responses:
+ '200':
+ description: 'Document was successfully placed into an S3 bucket and queued for uploading to eFolder.'
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Document successfully queued for upload."
+ - $ref: '#/components/schemas/DistributionUUIDList'
+ '400':
+ description: 'An unknown, blank, or invalid required parameter was provided.'
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Document type is not recognized"
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "File can't be blank"
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "The appeal was unable to be found."
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "The veteran was unable to be found."
+ examples:
+ DocumentTypeNotRecognized:
+ summary: Document type not recognized
+ value:
+ message: Document type is not recognized
+ FileBlank:
+ summary: File is blank
+ value:
+ message: File can't be blank
+ AppealNotFound:
+ summary: Appeal does not exist
+ value:
+ message: The appeal was unable to be found.
+ VeteranNotFound:
+ summary: Veteran does not exist
+ value:
+ message: The veteran was unable to be found.
+ InvalidCopiesValue:
+ summary: "Scenario: Copies out of range (1-500 inclusive, default value of 1)"
+ value:
+ message: "IDT Exception ID: 67ce1137-4d94-43ec-ba0a-1daf43fc6a65 Recipient information received was invalid or incomplete."
+ errors: "Copies must be between 1 and 500 (inclusive)"
+ RecipientTypeIsAbsent:
+ summary: 'Scenario: recipient_type is not included in the list: ["organization", "person", "system", "ro-colocated"]'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Recipient type is not included in the list"
+ NameBlank:
+ summary: 'Scenario: recipient_type is "system", "organization", or "ro-colocated" and name is blank'
+ value:
+ message: "IDT Exception ID: d8cecfbd-c664-440e-919b-bb1d91e77f1d Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Name can't be blank"
+ PoaCodeBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and poa_code is blank'
+ value:
+ message: "IDT Exception ID: 8af4913f-1e96-4437-9294-0c31b0f545df Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Poa code can't be blank"
+ ClaimantStationOfJurisdictionBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and claimant_station_of_jurisdiction is blank'
+ value:
+ message: "IDT Exception ID: cf9e0208-e5de-4fb6-9dd5-2e182acdb8c3 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Claimant station of jurisdiction can't be blank"
+ DestinationTypeInvalid:
+ summary: 'Scenario: destination_type is not included in the list: ["domesticAddress", "internationalAddress", "militaryAddress", "derived"]'
+ value:
+ message: "IDT Exception ID: d7d581fa-4e31-445c-a513-ff40c49a3b95 Recipient information received was invalid or incomplete."
+ errors":
+ "distribution 1": "Destination type is not included in the list"
+ AddressLine1Blank:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and address_line_1 is blank"'
+ value:
+ message: "IDT Exception ID: 578abf44-0292-49b6-a77f-27878d734145 Recipient information received was invalid or incomplete."
+ "errors":
+ "distribution 1": "Address line 1 can't be blank"
+ PersonRecipientMissingFirstName:
+ summary: 'Scenario: recipient_type is "person" and first_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "First name can't be blank"
+ PersonRecipientMissingLastName:
+ summary: 'Scenario: recipient_type is "person" and last_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Last name can't be blank"
+ CityIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and city is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "City can't be blank"
+ CountryCodeIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code can't be blank, Country code is not a valid ISO 3166-2 code"
+ ProvidedCountryCodeIsInvalid:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsNonBlankState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State can't be blank, State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsValidState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsPostalCode:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and postal_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Postal code can't be blank"
+ InternationalAddressNeedsCountryName:
+ summary: 'Scenario: destination_type is "internationalAddress" and country_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country name can't be blank"
+ TreatLine2AsAddresseeTrueMissingAddressLine2:
+ summary: 'Scenario: treat_line_2_as_addressee is true and address_line_2 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 2 can't be blank"
+ TreatLine3AsAddresseeTrueMissingAddressLine3:
+ summary: 'Scenario: treat_line_3_as_addressee is true and address_line_3 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Treat line 2 as addressee cannot be false if line 3 is treated as addressee"
+ TreatLine3AsAddresseeDependentValueMissing:
+ summary: 'Scenario: treat_line_3_as_addressee is true and treat_line_2_as_addressee is false'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 3 can't be blank"
+ '500':
+ description: 'A server error occurred.'
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Unexpected error: job error"
+ /idt/api/v1/upload_document:
+ post:
+ tags:
+ - Document Posting
+ summary: Used for uploading documents to eFolder for specific appeals/veterans
+ requestBody:
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/UploadDocumentRequestWithRecipientInformation'
+ - $ref: '#/components/schemas/UploadDocumentRequest'
+ examples:
+ "With recipient information for Package Manager":
+ $ref: '#/components/schemas/UploadDocumentRequestWithRecipientInformation'
+ "Without recipient information for Package Manager":
+ value:
+ veteran_identifier: '555555555'
+ document_type: 'BVA Decision'
+ document_subject: 'Test'
+ document_name: 'Test Doc'
+ file: 'VGhpcyBpcyBhIHRlc3QuIERvIG5vdCBiZSBhbGFybWVkLgo='
+ responses:
+ '200':
+ description: 'Document was successfully placed into an S3 bucket and queued for uploading to eFolder.'
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Document successfully queued for upload."
+ - $ref: '#/components/schemas/DistributionUUIDList'
+ examples:
+ WithRecipientInformationPresent:
+ summary: "With recipient information for Package Manager"
+ value:
+ message: "Document successfully queued for upload."
+ distribution_ids:
+ - "1234"
+ - "1235"
+ - "1236"
+ WithoutRecipientInformationPresent:
+ summary: "Without recipient information for Package Manager"
+ value:
+ message: "Document successfully queued for upload."
+ '400':
+ description: 'An unknown, blank, or invalid required parameter was provided.'
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Document type is not recognized"
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "File can't be blank"
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "The appeal was unable to be found."
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "The veteran was unable to be found."
+ examples:
+ DocumentTypeNotRecognized:
+ summary: Document type not recognized
+ value:
+ message: Document type is not recognized
+ FileBlank:
+ summary: File is blank
+ value:
+ message: File can't be blank
+ AppealNotFound:
+ summary: Appeal does not exist
+ value:
+ message: The appeal was unable to be found.
+ VeteranNotFound:
+ summary: Veteran does not exist
+ value:
+ message: The veteran was unable to be found.
+ InvalidCopiesValue:
+ summary: "Scenario: Copies out of range (1-500 inclusive, default value of 1)"
+ value:
+ message: "IDT Exception ID: 67ce1137-4d94-43ec-ba0a-1daf43fc6a65 Recipient information received was invalid or incomplete."
+ errors: "Copies must be between 1 and 500 (inclusive)"
+ RecipientTypeIsAbsent:
+ summary: 'Scenario: recipient_type is not included in the list: ["organization", "person", "system", "ro-colocated"]'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Recipient type is not included in the list"
+ NameBlank:
+ summary: 'Scenario: recipient_type is "system", "organization", or "ro-colocated" and name is blank'
+ value:
+ message: "IDT Exception ID: d8cecfbd-c664-440e-919b-bb1d91e77f1d Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Name can't be blank"
+ PoaCodeBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and poa_code is blank'
+ value:
+ message: "IDT Exception ID: 8af4913f-1e96-4437-9294-0c31b0f545df Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Poa code can't be blank"
+ ClaimantStationOfJurisdictionBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and claimant_station_of_jurisdiction is blank'
+ value:
+ message: "IDT Exception ID: cf9e0208-e5de-4fb6-9dd5-2e182acdb8c3 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Claimant station of jurisdiction can't be blank"
+ DestinationTypeInvalid:
+ summary: 'Scenario: destination_type is not included in the list: ["domesticAddress", "internationalAddress", "militaryAddress", "derived"]'
+ value:
+ message: "IDT Exception ID: d7d581fa-4e31-445c-a513-ff40c49a3b95 Recipient information received was invalid or incomplete."
+ errors":
+ "distribution 1": "Destination type is not included in the list"
+ AddressLine1Blank:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and address_line_1 is blank"'
+ value:
+ message: "IDT Exception ID: 578abf44-0292-49b6-a77f-27878d734145 Recipient information received was invalid or incomplete."
+ "errors":
+ "distribution 1": "Address line 1 can't be blank"
+ PersonRecipientMissingFirstName:
+ summary: 'Scenario: recipient_type is "person" and first_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "First name can't be blank"
+ PersonRecipientMissingLastName:
+ summary: 'Scenario: recipient_type is "person" and last_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Last name can't be blank"
+ CityIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and city is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "City can't be blank"
+ CountryCodeIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code can't be blank, Country code is not a valid ISO 3166-2 code"
+ ProvidedCountryCodeIsInvalid:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsNonBlankState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State can't be blank, State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsValidState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsPostalCode:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and postal_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Postal code can't be blank"
+ InternationalAddressNeedsCountryName:
+ summary: 'Scenario: destination_type is "internationalAddress" and country_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country name can't be blank"
+ TreatLine2AsAddresseeTrueMissingAddressLine2:
+ summary: 'Scenario: treat_line_2_as_addressee is true and address_line_2 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 2 can't be blank"
+ TreatLine3AsAddresseeTrueMissingAddressLine3:
+ summary: 'Scenario: treat_line_3_as_addressee is true and address_line_3 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Treat line 2 as addressee cannot be false if line 3 is treated as addressee"
+ TreatLine3AsAddresseeDependentValueMissing:
+ summary: 'Scenario: treat_line_3_as_addressee is true and treat_line_2_as_addressee is false'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 3 can't be blank"
+
+ /idt/api/v2/appeals/{appeal_id}/outcode:
+ post:
+ tags:
+ - Document Posting
+ summary: Route used for outcoding decision reviews
+ parameters:
+ - in: path
+ name: appeal_id
+ schema:
+ type: string
+ required: true
+ requestBody:
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/OutcodeRequestWithRecipientInformation'
+ - $ref: '#/components/schemas/OutcodeRequest'
+ examples:
+ "With recipient information for Package Manager":
+ $ref: '#/components/schemas/OutcodeRequestWithRecipientInformation'
+ "Without recipient information for Package Manager":
+ value:
+ citation_number: "A19062122"
+ decision_date: "December 21, 2022"
+ redacted_document_location: "\\path.va.gov\\archdata$\\some-file.pdf"
+ file: "VGVzdGluZyAxMjMK"
+ responses:
+ '200':
+ description: 'Decision review has been successfully outcoded.'
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Success!"
+ - $ref: '#/components/schemas/DistributionUUIDList'
+ examples:
+ WithRecipientInformationPresent:
+ summary: "With recipient information for Package Manager"
+ value:
+ message: "Success!"
+ distribution_ids:
+ - "1234"
+ - "1235"
+ - "1236"
+ WithoutRecipientInformationPresent:
+ summary: "Without recipient information for Package Manager"
+ value:
+ message: "Success!"
+
+ '400':
+ description: 'Occurs if a valid decision review-BVA Dispatch Task combination cannot be located given the params provided.'
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: array
+ items:
+ type: object
+ properties:
+ title:
+ type: string
+ example: "Appeal 12345, task ID 54321 has already been outcoded. Cannot outcode the same appeal and task combination more than once"
+ detail:
+ type: string
+ example: "Appeal 12345, task ID 54321 has already been outcoded. Cannot outcode the same appeal and task combination more than once"
+ - type: array
+ items:
+ type: object
+ properties:
+ title:
+ type: string
+ example: "Expected 1 BvaDispatchTask received 0 tasks for appeal 12345, user 1"
+ detail:
+ type: string
+ example: "Expected 1 BvaDispatchTask received 0 tasks for appeal 12345, user 1"
+ - type: array
+ items:
+ type: object
+ properties:
+ title:
+ type: string
+ example: "Citation number already exists"
+ detail:
+ type: string
+ example: "Citation number already exists"
+ examples:
+ AlreadyOutcodedExample:
+ summary: "Whenever a decision review has already been outcoded."
+ value:
+ - title: "Appeal 12345, task ID 54321 has already been outcoded. Cannot outcode the same appeal and task combination more than once"
+ detail: "Appeal 12345, task ID 54321 has already been outcoded. Cannot outcode the same appeal and task combination more than once"
+ IncorrectBvaDispatchTasksExample:
+ summary: "User has either more or fewer than 1 BvaDispatchTask assigned to them for the decision review being outcoded."
+ value:
+ - title: "Expected 1 BvaDispatchTask received 0 tasks for appeal 12345, user 1"
+ detail: "Expected 1 BvaDispatchTask received 0 tasks for appeal 12345, user 1"
+ CitationNumberExistsExample:
+ summary: "Citation number provided is already associated with another outcoded appeal."
+ value:
+ - title: "Citation number already exists"
+ detail: "Citation number already exists"
+ CitationNumberInvalidExample:
+ summary: "Thrown whenever citation number provided fails to match with a set regular expression."
+ value:
+ message: "Citation number is invalid"
+ MissingParamsExample:
+ summary: Whenever one or more required parameters are absent.
+ value:
+ message: "Decision date can't be blank, Redacted document location can't be blank, File can't be blank"
+ InvalidCopiesValue:
+ summary: "Scenario: Copies out of range (1-500 inclusive, default value of 1)"
+ value:
+ message: "IDT Exception ID: 67ce1137-4d94-43ec-ba0a-1daf43fc6a65 Recipient information received was invalid or incomplete."
+ errors: "Copies must be between 1 and 500 (inclusive)"
+ RecipientTypeIsAbsent:
+ summary: 'Scenario: recipient_type is not included in the list: ["organization", "person", "system", "ro-colocated"]'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Recipient type is not included in the list"
+ NameBlank:
+ summary: 'Scenario: recipient_type is "system", "organization", or "ro-colocated" and name is blank'
+ value:
+ message: "IDT Exception ID: d8cecfbd-c664-440e-919b-bb1d91e77f1d Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Name can't be blank"
+ PoaCodeBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and poa_code is blank'
+ value:
+ message: "IDT Exception ID: 8af4913f-1e96-4437-9294-0c31b0f545df Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Poa code can't be blank"
+ ClaimantStationOfJurisdictionBlank:
+ summary: 'Scenario: recipient_type is "ro-colocated" and claimant_station_of_jurisdiction is blank'
+ value:
+ message: "IDT Exception ID: cf9e0208-e5de-4fb6-9dd5-2e182acdb8c3 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Claimant station of jurisdiction can't be blank"
+ DestinationTypeInvalid:
+ summary: 'Scenario: destination_type is not included in the list: ["domesticAddress", "internationalAddress", "militaryAddress", "derived"]'
+ value:
+ message: "IDT Exception ID: d7d581fa-4e31-445c-a513-ff40c49a3b95 Recipient information received was invalid or incomplete."
+ errors":
+ "distribution 1": "Destination type is not included in the list"
+ AddressLine1Blank:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and address_line_1 is blank"'
+ value:
+ message: "IDT Exception ID: 578abf44-0292-49b6-a77f-27878d734145 Recipient information received was invalid or incomplete."
+ "errors":
+ "distribution 1": "Address line 1 can't be blank"
+ PersonRecipientMissingFirstName:
+ summary: 'Scenario: recipient_type is "person" and first_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "First name can't be blank"
+ PersonRecipientMissingLastName:
+ summary: 'Scenario: recipient_type is "person" and last_name is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Last name can't be blank"
+ CityIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and city is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "City can't be blank"
+ CountryCodeIsMissing:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code can't be blank, Country code is not a valid ISO 3166-2 code"
+ ProvidedCountryCodeIsInvalid:
+ summary: 'Scenario: destination_type is "domesticAddress", "internationalAddress", or "militaryAddress" and county_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country code is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsNonBlankState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State can't be blank, State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsValidState:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and state_code is not a valid ISO 3166-code'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "State is not a valid ISO 3166-2 code"
+ DomesticOrMilitaryNeedsPostalCode:
+ summary: 'Scenario: destination_type is "domesticAddress" or "militaryAddress" and postal_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Postal code can't be blank"
+ InternationalAddressNeedsCountryName:
+ summary: 'Scenario: destination_type is "internationalAddress" and country_code is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Country name can't be blank"
+ TreatLine2AsAddresseeTrueMissingAddressLine2:
+ summary: 'Scenario: treat_line_2_as_addressee is true and address_line_2 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 2 can't be blank"
+ TreatLine3AsAddresseeTrueMissingAddressLine3:
+ summary: 'Scenario: treat_line_3_as_addressee is true and address_line_3 is blank'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Treat line 2 as addressee cannot be false if line 3 is treated as addressee"
+ TreatLine3AsAddresseeDependentValueMissing:
+ summary: 'Scenario: treat_line_3_as_addressee is true and treat_line_2_as_addressee is false'
+ value:
+ message: "IDT Exception ID: 2b26f07f-b742-4056-8781-be2a8ccb5d35 Recipient information received was invalid or incomplete."
+ errors:
+ "distribution 1": "Address line 3 can't be blank"
+ '500':
+ description: 'The server encountered an error.'
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: array
+ items:
+ type: object
+ properties:
+ title:
+ type: string
+ example: "VBMS::FilenumberDoesNotExist"
+ detail:
+ type: string
+ example: "The veteran file number does not match the file number in VBMS"
+ /idt/api/v2/distributions/{distribution_id}:
+ get:
+ tags:
+ - Distribution Tracking
+ summary: Queries Package Manager for information on a specified distribution.
+ parameters:
+ - in: path
+ name: distribution_id
+ schema:
+ type: string
+ required: true
+ responses:
+ '200':
+ description: 'Distribution information has been successfully obtained.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DistributionStatusResponse'
+ examples:
+ DistributionIsEstablishedExample:
+ summary: Distribution is established
+ $ref: '#/components/schemas/DistributionStatusResponse'
+ DistributionIsPendingEstablishmentInPacManExample:
+ summary: Distribution is pending establishment
+ value:
+ id: 1234
+ status: "PENDING_ESTABLISHMENT"
+ '403':
+ description: 'IDT token provided is invalid.'
+ '400':
+ description: 'Distribution ID provided is of an invalid format.'
+ '404':
+ description: 'A distribution could not be located given the ID provided.'
+ '500':
+ description: 'An error has occurred while trying to process the request.'
+ content:
+ application/json:
+ examples:
+ ServerError:
+ summary: Server Error
+ value:
+ message: 'An error has occurred while trying to process the request. Sentry ID: 02658f1e-31f1-4162-8903-63e70b81364d'
+ DistributionEstablishmentFailed:
+ summary: Distribution establishment failed
+ value:
+ id: 1234
+ status: "ESTABLISHMENT_FAILED"
+ message: "The error message given to us by Package Manager."
+components:
+ schemas:
+ DistributionUUIDList:
+ type: object
+ properties:
+ distribution_ids:
+ type: array
+ items:
+ type: string
+ format: number
+ DistributionStatusResponse:
+ $ref: '#/components/schemas/Distribution'
+ Distribution:
+ type: object
+ properties:
+ id:
+ type: integer
+ example: 1234
+ description: The primary key of the distribution in our database. There is an initial delay while we establish \
+ the distribution in Package Manager where its UUID is unavailable. This is why we utilize our primary key instead.
+ distribution_uuid:
+ type: string
+ format: uuid
+ recipient:
+ $ref: '#/components/schemas/DistributionRecipient'
+ destinations:
+ type: array
+ items:
+ $ref: '#/components/schemas/DistributionDestination'
+ status:
+ type: string
+ enum: ['IN_PROGRESS', 'SUCCESS', 'DRAFT', 'FAIL', 'ELECTRONIC_NOTIFICATION']
+ sent_to_cbcm_date:
+ type: string
+ DistributionRecipient:
+ type: object
+ properties:
+ type:
+ type: string
+ enum: ['organization', 'person', 'system', 'ro-colocated']
+ id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ DistributionDestination:
+ type: object
+ properties:
+ type:
+ type: string
+ address_line_1:
+ type: string
+ address_line_2:
+ type: string
+ address_line_3:
+ type: string
+ address_line_4:
+ type: string
+ address_line_5:
+ type: string
+ address_line_6:
+ type: string
+ treat_line_2_as_addressee:
+ type: boolean
+ treat_line_3_as_addressee:
+ type: boolean
+ city:
+ type: string
+ state:
+ type: string
+ postal_code:
+ type: string
+ country_name:
+ type: string
+ country_code:
+ type: string
+ UploadDocumentRequestWithRecipientInformation:
+ allOf:
+ - $ref: '#/components/schemas/UploadDocumentRequest'
+ - type: object
+ properties:
+ recipient_info:
+ type: array
+ items:
+ type: object
+ $ref: '#/components/schemas/RecipientRequestInformation'
+ UploadDocumentRequest:
+ type: object
+ properties:
+ veteran_identifier:
+ type: string
+ example: '555555555'
+ document_type:
+ type: string
+ example: 'BVA Decision'
+ document_subject:
+ type: string
+ example: 'Test'
+ document_name:
+ type: string
+ example: 'Test Doc'
+ file:
+ type: string
+ format: base64
+ example: 'VGhpcyBpcyBhIHRlc3QuIERvIG5vdCBiZSBhbGFybWVkLgo='
+ required:
+ - document_type
+ - file
+ OutcodeRequestWithRecipientInformation:
+ allOf:
+ - $ref: '#/components/schemas/OutcodeRequest'
+ - type: object
+ properties:
+ recipient_info:
+ type: array
+ items:
+ type: object
+ $ref: '#/components/schemas/RecipientRequestInformation'
+ RecipientRequestInformation:
+ type: object
+ properties:
+ recipient_type:
+ type: string
+ enum: ['organization', 'person', 'system', 'ro-colocated']
+ name:
+ description: 'Required if recipient_type is organization, system, or ro-colocated. Unused for people.'
+ type: string
+ first_name:
+ type: string
+ middle_name:
+ type: string
+ last_name:
+ type: string
+ claimant_station_of_jurisdiction:
+ type: string
+ description: 'Required if recipient_type is ro-colocated.'
+ postal_code:
+ type: string
+ description: 'Required if recipient_type is ro-colocated.'
+ destination_type:
+ type: string
+ enum: ['domesticAddress', 'internationalAddress', 'militaryAddress']
+ address_line_1:
+ type: string
+ address_line_2:
+ type: string
+ address_line_3:
+ type: string
+ address_line_4:
+ type: string
+ address_line_5:
+ type: string
+ address_line_6:
+ type: string
+ treat_line_2_as_addressee:
+ type: boolean
+ treat_line_3_as_addressee:
+ type: boolean
+ city:
+ type: string
+ state:
+ type: string
+ country_name:
+ type: string
+ country_code:
+ type: string
+ copies:
+ type: integer
+ example: 1
+ OutcodeRequest:
+ type: object
+ properties:
+ citation_number:
+ type: string
+ format: /\AA?\d{8}\Z/i
+ decision_date:
+ type: string
+ example: "December 21, 2022"
+ file:
+ type: string
+ format: base64
+ redacted_document_location:
+ type: string
+ LighthouseGenericError:
+ type: object
+ properties:
+ errors:
+ type: array
+ items:
+ type: object
+ properties:
+ status:
+ type: number
+ format: int32
+ title:
+ type: string
+ detail:
+ type: string
+ CaseflowMissingTokenError:
+ type: object
+ properties:
+ message:
+ type: string
+ example: Missing token
+ Message:
+ required:
+ - code
+ - key
+ - severity
+ type: object
+ properties:
+ code:
+ type: string
+ key:
+ type: string
+ text:
+ type: string
+ severity:
+ type: string
+ enum:
+ - INFO
+ - WARN
+ - ERROR
+ - FATAL
+ potentiallySelfCorrectingOnRetry:
+ type: boolean
+ ServiceResponse:
+ type: object
+ properties:
+ messages:
+ type: array
+ items:
+ $ref: '#/components/schemas/Message'
+ AddressValidationRequest:
+ required:
+ - request_address
+ type: object
+ properties:
+ request_address:
+ $ref: '#/components/schemas/RequestAddress'
+ description: Request format to describe the address to correct and validate.
+ RequestAddress:
+ type: object
+ properties:
+ address_line_1:
+ type: string
+ address_line_2:
+ type: string
+ address_line_3:
+ type: string
+ city:
+ type: string
+ zip_code_5:
+ type: string
+ zip_code_4:
+ type: string
+ international_post_code:
+ type: string
+ state_province:
+ $ref: '#/components/schemas/StateProvince'
+ request_country:
+ $ref: '#/components/schemas/RequestCountry'
+ address_pou:
+ type: string
+ enum:
+ - RESIDENCE/CHOICE
+ - CORRESPONDENCE
+ RequestCountry:
+ type: object
+ properties:
+ country_code:
+ type: string
+ StateProvince:
+ type: object
+ properties:
+ code:
+ type: string
+ Address:
+ required:
+ - country
+ - county
+ - state_province
+ type: object
+ properties:
+ address_line_1:
+ type: string
+ address_line_2:
+ type: string
+ address_line_3:
+ type: string
+ city:
+ type: string
+ zip_code_5:
+ type: string
+ zip_code_4:
+ type: string
+ international_post_code:
+ type: string
+ county:
+ $ref: '#/components/schemas/County'
+ state_province:
+ $ref: '#/components/schemas/StateProvince'
+ country:
+ $ref: '#/components/schemas/Country'
+ AddressMetaData:
+ type: object
+ properties:
+ confidence_score:
+ type: number
+ format: double
+ address_type:
+ type: string
+ delivery_point_validation:
+ type: string
+ enum:
+ - CONFIRMED
+ - STREET_NUMBER_VALIDATED_BUT_MISSING_UNIT_NUMBER
+ - STREET_NUMBER_VALIDATED_BUT_BAD_UNIT_NUMBER
+ - MULTIPLE_MATCHES_FOUND
+ - UNDELIVERABLE
+ - MISSING_ZIP
+ - FALSE_POSITIVE
+ residential_delivery_indicator:
+ type: string
+ enum:
+ - RESIDENTIAL
+ - BUSINESS
+ - MIXED
+ non_postal_input_data:
+ maxItems: 100
+ minItems: 0
+ type: array
+ items:
+ type: string
+ validation_key:
+ type: integer
+ format: int32
+ AddressValidationResponse:
+ type: object
+ properties:
+ messages:
+ type: array
+ items:
+ $ref: '#/components/schemas/Message'
+ address:
+ $ref: '#/components/schemas/Address'
+ geocode:
+ $ref: '#/components/schemas/Geocode'
+ us_congressional_district:
+ type: string
+ address_metadata:
+ $ref: '#/components/schemas/AddressMetaData'
+ CandidateAddressResponseV2:
+ type: object
+ properties:
+ messages:
+ type: array
+ items:
+ $ref: '#/components/schemas/Message'
+ candidate_addresses:
+ maxItems: 100
+ minItems: 0
+ type: array
+ items:
+ $ref: '#/components/schemas/AddressValidationResponse'
+ Country:
+ type: object
+ properties:
+ name:
+ type: string
+ code:
+ type: string
+ fips_code:
+ type: string
+ iso2_code:
+ type: string
+ iso3_code:
+ type: string
+ County:
+ type: object
+ properties:
+ name:
+ type: string
+ county_fips_code:
+ type: string
+ Geocode:
+ type: object
+ properties:
+ calc_date:
+ type: string
+ format: date-time
+ location_precision:
+ type: number
+ format: double
+ latitude:
+ type: number
+ format: double
+ longitude:
+ type: number
+ format: double
+ securitySchemes:
+ ApiKeyAuth:
+ type: apiKey
+ in: header
+ name: TOKEN
diff --git a/app/controllers/concerns/mail_package_concern.rb b/app/controllers/concerns/mail_package_concern.rb
new file mode 100644
index 00000000000..12461498df3
--- /dev/null
+++ b/app/controllers/concerns/mail_package_concern.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+# shared code for building mail packages to submit to Package Manager external service
+
+module MailPackageConcern
+ extend ActiveSupport::Concern
+
+ private
+
+ def recipient_info
+ params[:recipient_info]
+ end
+
+ def copies
+ # Default value of 1 for copies
+ return 1 if params[:copies].blank?
+
+ params[:copies]
+ end
+
+ def mail_package
+ return nil if recipient_info.blank?
+
+ { distributions: json_mail_requests, copies: copies, created_by_id: user.id }
+ end
+
+ # Purpose: - Creates and validates a MailRequest object for each recipient
+ # - Calls #call method on each MailRequest to save corresponding VbmsDistirbution and
+ # VbmsDistributionDestination to the db
+ # - Stores the distribution IDs (to be returned to the IDT user as an immediate means of tracking
+ # each distribution)
+ #
+ def build_mail_package
+ return if recipient_info.blank?
+
+ throw_error_if_copies_out_of_range
+ mail_requests.map do |request|
+ request.call
+ distribution_ids << request.vbms_distribution_id
+ end
+ end
+
+ def mail_requests
+ @mail_requests ||= create_mail_requests_and_track_errors
+ end
+
+ def json_mail_requests
+ mail_requests.map(&:to_json)
+ end
+
+ def create_mail_requests_and_track_errors
+ requests = recipient_info.map.with_index do |recipient, idx|
+ MailRequest.new(recipient).tap do |request|
+ if request.invalid?
+ recipient_errors["distribution #{idx + 1}"] = request.errors.full_messages.join(", ")
+ end
+ end
+ end
+ throw_error_if_recipient_info_invalid
+ requests
+ end
+
+ def throw_error_if_copies_out_of_range
+ unless (1..500).cover?(copies)
+ fail Caseflow::Error::MissingRecipientInfo, "Copies must be between 1 and 500 (inclusive)".to_json
+ end
+ end
+
+ def throw_error_if_recipient_info_invalid
+ return unless recipient_errors.any?
+
+ fail Caseflow::Error::MissingRecipientInfo, recipient_errors.to_json
+ end
+
+ def recipient_errors
+ @recipient_errors ||= {}
+ end
+
+ def distribution_ids
+ @distribution_ids ||= []
+ end
+end
diff --git a/app/controllers/idt/api/v1/base_controller.rb b/app/controllers/idt/api/v1/base_controller.rb
index 8b2f72cf16f..03fd90fe8d4 100644
--- a/app/controllers/idt/api/v1/base_controller.rb
+++ b/app/controllers/idt/api/v1/base_controller.rb
@@ -16,7 +16,9 @@ class Idt::Api::V1::BaseController < ActionController::Base
if error.class.method_defined?(:serialize_response)
render(error.serialize_response)
else
- render json: { message: "IDT Standard Error ID: " + uuid + " Unexpected error: #{error.message}" }, status: :internal_server_error
+ render json: {
+ message: "IDT Standard Error ID: " + uuid + " Unexpected error: #{error.message}"
+ }, status: :internal_server_error
end
end
# :nocov:
@@ -32,7 +34,19 @@ class Idt::Api::V1::BaseController < ActionController::Base
log_error(error)
uuid = SecureRandom.uuid
Rails.logger.error("IDT Standard Error ID: " + uuid)
- render(json: { message: "IDT Standard Error ID: " + uuid + " Please enter a file number in the 'FILENUMBER' header" }, status: :unprocessable_entity)
+ render(json:
+ { message:
+ "IDT Standard Error ID: " +
+ uuid +
+ " Please enter a file number in the 'FILENUMBER' header" },
+ status: :unprocessable_entity)
+ end
+
+ rescue_from Caseflow::Error::MissingRecipientInfo do |error|
+ log_error(error)
+ uuid = SecureRandom.uuid
+ render(json: { message: "IDT Exception ID: " + uuid + " Recipient information received was invalid or incomplete.",
+ errors: JSON.parse(error.message) }, status: :bad_request)
end
rescue_from Caseflow::Error::VeteranNotFound do |error|
diff --git a/app/controllers/idt/api/v1/upload_vbms_document_controller.rb b/app/controllers/idt/api/v1/upload_vbms_document_controller.rb
index a0ac88a66a2..a3b6aa80834 100644
--- a/app/controllers/idt/api/v1/upload_vbms_document_controller.rb
+++ b/app/controllers/idt/api/v1/upload_vbms_document_controller.rb
@@ -2,40 +2,73 @@
class Idt::Api::V1::UploadVbmsDocumentController < Idt::Api::V1::BaseController
include ApiRequestLoggingConcern
+ include MailPackageConcern
protect_from_forgery with: :exception
skip_before_action :verify_authenticity_token, only: [:create]
before_action :verify_access
- def bgs
- @bgs ||= BGSService.new
- end
-
def create
- appeal = nil
- # Find veteran from appeal id and check with db
- if params["appeal_id"].present?
- appeal = LegacyAppeal.find_by_vacols_id(params["appeal_id"]) || Appeal.find_by_uuid(params["appeal_id"])
- if appeal.nil?
- fail Caseflow::Error::AppealNotFound, "IDT Standard Error ID: " + SecureRandom.uuid + " The appeal was unable to be found."
- else
- params["veteran_file_number"] = appeal.veteran_file_number
- end
-
- else
- file_number = bgs.fetch_veteran_info(params["veteran_identifier"])&.dig(:file_number) || bgs.fetch_file_number_by_ssn(params["veteran_identifier"])
- if file_number.nil?
- fail Caseflow::Error::VeteranNotFound, "IDT Standard Error ID: " + SecureRandom.uuid + " The veteran was unable to be found."
- end
+ # Create distributions for Package Manager mail service if recipient info present
+ build_mail_package
- params["veteran_file_number"] = file_number
- end
- result = PrepareDocumentUploadToVbms.new(params, current_user, appeal).call
+ result = PrepareDocumentUploadToVbms.new(params, current_user, appeal, mail_package).call
if result.success?
- render json: { message: "Document successfully queued for upload." }
+ success_message = { message: "Document successfully queued for upload." }
+ if recipient_info.present?
+ success_message[:distribution_ids] = distribution_ids
+ end
+ render json: success_message
else
render json: result.errors[0], status: :bad_request
end
end
+
+ private
+
+ # Find veteran from appeal id and check with db
+ def appeal
+ if appeal_id.blank?
+ find_file_number_by_veteran_identifier
+ return nil
+ end
+
+ @appeal ||= find_veteran_by_appeal_id
+ end
+
+ def appeal_id
+ params[:appeal_id]
+ end
+
+ def veteran_identifier
+ params[:veteran_identifier]
+ end
+
+ def bgs
+ @bgs ||= BGSService.new
+ end
+
+ def find_veteran_by_appeal_id
+ appeal = LegacyAppeal.find_by_vacols_id(appeal_id) || Appeal.find_by_uuid(appeal_id)
+ throw_not_found_error(Caseflow::Error::AppealNotFound, "appeal") if appeal.nil?
+ update_veteran_file_number(appeal.veteran_file_number)
+ appeal
+ end
+
+ def find_file_number_by_veteran_identifier
+ file_number = bgs.fetch_veteran_info(veteran_identifier)&.dig(:file_number) ||
+ bgs.fetch_file_number_by_ssn(veteran_identifier)
+ throw_not_found_error(Caseflow::Error::VeteranNotFound, "veteran") if file_number.nil?
+ update_veteran_file_number(file_number)
+ end
+
+ def update_veteran_file_number(file_number)
+ params["veteran_file_number"] = file_number
+ end
+
+ def throw_not_found_error(error, name)
+ uuid = SecureRandom.uuid
+ fail error, uuid + " The #{name} was unable to be found."
+ end
end
diff --git a/app/controllers/idt/api/v2/appeals_controller.rb b/app/controllers/idt/api/v2/appeals_controller.rb
index 628dafc81d9..ce1d9526698 100644
--- a/app/controllers/idt/api/v2/appeals_controller.rb
+++ b/app/controllers/idt/api/v2/appeals_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Idt::Api::V2::AppealsController < Idt::Api::V1::BaseController
+ include MailPackageConcern
+
protect_from_forgery with: :exception
before_action :verify_access
@@ -23,10 +25,17 @@ def details
end
def outcode
- result = BvaDispatchTask.outcode(appeal, outcode_params, user)
+ # Create distributions for Package Manager mail service if recipient info present
+ build_mail_package
+
+ result = BvaDispatchTask.outcode(appeal, outcode_params, user, mail_package)
if result.success?
- return render json: { message: "Success!" }
+ success_response = { message: "Successful dispatch!" }
+ if recipient_info.present?
+ success_response[:distribution_ids] = distribution_ids
+ end
+ return render json: success_response
end
render json: { message: result.errors[0] }, status: :bad_request
@@ -151,4 +160,17 @@ def load_tags_by_doc_id
def outcode_params
params.permit(:citation_number, :decision_date, :redacted_document_location, :file)
end
+
+ def mail_params
+ params.permit(:copies, recipient_info: recipient_keys)
+ end
+
+ def recipient_keys
+ [
+ :recipient_type, :name, :first_name, :last_name, :claimant_station_of_jurisdiction, :postal_code,
+ :destination_type, :address_line_1, :address_line_2, :address_line_3, :address_line_4, :address_line_5,
+ :address_line_6, :treat_line_2_as_addressee, :treat_line_3_as_addressee, :city, :state, :country_name,
+ :country_code
+ ]
+ end
end
diff --git a/app/controllers/idt/api/v2/distributions_controller.rb b/app/controllers/idt/api/v2/distributions_controller.rb
new file mode 100644
index 00000000000..2b35a8435ff
--- /dev/null
+++ b/app/controllers/idt/api/v2/distributions_controller.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class Idt::Api::V2::DistributionsController < Idt::Api::V1::BaseController
+ protect_from_forgery with: :exception
+ before_action :verify_access
+
+ def distribution
+ distribution_id = params[:distribution_id]
+ # Checks if the distribution id is blank and if it exists with the database
+ if distribution_id.blank? || !valid_id?(distribution_id)
+ return render_error(400, "Distribution Does Not Exist Or Id is blank", distribution_id)
+ end
+
+ distribution_uuid = distribution_uuid_from_id(distribution_id)
+
+ return pending_establishment(distribution_id) unless distribution_uuid
+
+ begin
+ # Retrieves the distribution package from the PacMan API
+ distribution_response = PacmanService.get_distribution_request(distribution_uuid)
+
+ response_code = distribution_response.code
+
+ fail StandardError if response_code != 200
+ # Handles errors when making any requests both from Pacman and the DB
+ rescue StandardError
+ return render_error(response_code, "Internal Server Error", distribution_id)
+ end
+
+ render json: format_response(distribution_response)
+ end
+
+ private
+
+ def pending_establishment(distribution_id)
+ render json: { id: distribution_id, status: "PENDING_ESTABLISHMENT" }, status: :ok
+ end
+
+ def format_response(response)
+ response_body = response.raw_body
+
+ begin
+ parsed_response = JSON.parse(response_body)
+
+ # Convert keys from camelCase to snake_case
+ parsed_response.deep_transform_keys do |key|
+ key.to_s.underscore.gsub(/e(\d)/, 'e_\1')
+ end
+ rescue JSON::ParseError => error
+ log_error(error + " Distribution ID: #{params[:distribution_id]}")
+
+ response_body
+ end
+ end
+
+ # Checks if the distribution exists in the database before sending request to Pacman
+ def valid_id?(distribution_id)
+ VbmsDistribution.exists?(id: distribution_id)
+ end
+
+ def distribution_uuid_from_id(pk_id)
+ VbmsDistribution.find(pk_id).uuid
+ end
+
+ # Renders errors and logs and tracks the here within Raven
+ # :reek:FeatureEnvy
+ def render_error(status, message, distribution_id)
+ error_uuid = SecureRandom.uuid
+ error_message = "[IDT] Http Status Code: #{status}, #{message}, (Distribution ID: #{distribution_id})"
+ Rails.logger.error(error_message.to_s + "Error ID: " + error_uuid)
+ Raven.capture_exception(error_message, extra: { error_uuid: error_uuid })
+ render json: { message: error_message + " #{error_uuid}" }, status: status
+ end
+end
diff --git a/app/jobs/mail_request_job.rb b/app/jobs/mail_request_job.rb
new file mode 100644
index 00000000000..962cc47bcea
--- /dev/null
+++ b/app/jobs/mail_request_job.rb
@@ -0,0 +1,223 @@
+# frozen_string_literal: true
+
+class MailRequestJob < CaseflowJob
+ queue_with_priority :low_priority
+ application_attr :api
+
+ # Purpose: performs job
+ #
+ # takes in VbmsUploadedDocument object and JSON payload
+ # mail_package looks like this:
+ # {
+ # "distributions": [
+ # {
+ # "recipient_info": json of MailRequest object
+ # }
+ # ]
+ # "copies": integer value,
+ # "created_by_id": integer value
+ # }
+ #
+ # Response: n/a
+ def perform(document_to_mail, mail_package)
+ begin
+ package_response = PacmanService.send_communication_package_request(
+ document_to_mail.veteran_file_number,
+ get_package_name(document_to_mail),
+ document_referenced(document_to_mail.document_version_reference_id, mail_package[:copies])
+ )
+ log_info(package_response)
+
+ fail Caseflow::Error::PacmanApiError if package_response.error?
+ rescue Caseflow::Error::PacmanApiError => error
+ log_error(error)
+ else
+ ActiveRecord::Base.transaction do
+ vbms_comm_package = create_package(document_to_mail, mail_package)
+ vbms_comm_package.update!(status: "success", uuid: parse_pacman_id(package_response))
+ create_distribution_request(vbms_comm_package.id, mail_package)
+ end
+ end
+ end
+
+ private
+
+ # Purpose: Parses responses from the PacMan API and pulls out the
+ #
+ # ID (UUID) of the entity created in the request.
+ #
+ # Response: The UUID of the communication package or distribution just created
+ def parse_pacman_id(pacman_response)
+ response_body = pacman_response.body
+
+ parsed_body = if response_body.is_a?(ActiveSupport::HashWithIndifferentAccess)
+ response_body
+ else
+ JSON.parse(response_body)
+ end
+
+ parsed_body.with_indifferent_access[:id]
+ end
+
+ # Purpose: arranges id and copies to pass into package post request
+ #
+ # takes in VbmsUploadedDocument id and copies integer
+ #
+ # Response: Array of json with document id and copies
+ def document_referenced(doc_id, copies)
+ [{ "id": doc_id, "copies": copies }]
+ end
+
+ # Purpose: Creates new VbmsCommunicationPackage
+ #
+ # takes in VbmsUploadedDocument object and MailRequest object
+ #
+ # Response: new VbmsCommunicationPackage object
+ # :reek:FeatureEnvy
+ def create_package(document_to_mail, mail_package)
+ VbmsCommunicationPackage.new(
+ comm_package_name: get_package_name(document_to_mail),
+ created_at: Time.zone.now,
+ created_by_id: mail_package[:created_by_id],
+ copies: mail_package[:copies],
+ file_number: document_to_mail.veteran_file_number,
+ status: nil,
+ updated_at: Time.zone.now,
+ updated_by_id: mail_package[:created_by_id],
+ document_mailable_via_pacman: document_to_mail
+ )
+ end
+
+ def get_package_name(document_to_mail)
+ "#{document_to_mail.document_name}_#{Time.now.utc.strftime('%Y%m%d%k%M%S')}"
+ end
+
+ # Purpose: Find a VbmsDistribution from various input formats
+ #
+ # Response: The corresponding VbmsDistribution record
+ def find_associated_vbms_distribution_record(distribution_info)
+ parsed_distro = if [ActiveSupport::HashWithIndifferentAccess, Hash].include?(distribution_info.class)
+ distribution_info
+ else
+ JSON.parse(distribution_info)
+ end
+
+ VbmsDistribution.find(parsed_distro.with_indifferent_access[:vbms_distribution_id])
+ end
+
+ # Purpose: sends distribution POST request to Pacman API
+ #
+ # takes in VbmsCommunicationPackage id (string) and MailRequest object
+ #
+ # Response: n/a
+ def create_distribution_request(package_id, mail_package)
+ distributions = mail_package[:distributions]
+
+ distributions.each do |dist|
+ begin
+ distribution = find_associated_vbms_distribution_record(dist)
+ distribution_responses = PacmanService.send_distribution_request(
+ VbmsCommunicationPackage.find(package_id).uuid,
+ get_recipient_hash(distribution),
+ get_destinations_hash(dist)
+ )
+ distribution_responses.each do |response|
+ log_info(response)
+ distribution.update!(vbms_communication_package_id: package_id, uuid: parse_pacman_id(response))
+ end
+ rescue Caseflow::Error::PacmanApiError => error
+ log_error(error)
+ end
+ end
+ end
+
+ # Purpose: creates recipient hash from VbmsDistribution attributes
+ #
+ # takes in VbmsDistribution object
+ #
+ # Response: hash that is needed in Pacman API distribution POST requests
+ def get_recipient_hash(distribution)
+ {
+ type: distribution.recipient_type,
+ name: distribution.name,
+ first_name: distribution.first_name,
+ middle_name: distribution.middle_name,
+ last_name: distribution.last_name,
+ participant_id: distribution.participant_id,
+ poa_code: distribution.poa_code,
+ claimant_station_of_jurisdiction: distribution.claimant_station_of_jurisdiction
+ }
+ end
+
+ # Purpose: Find root of available destination information
+ #
+ # Response: Hash containing the root of available destination information
+ def parse_recipient_info_from_destinaton(destination_info)
+ parsed_destination = if [ActiveSupport::HashWithIndifferentAccess, Hash].include?(destination_info.class)
+ destination_info
+ else
+ JSON.parse(destination_info).with_indifferent_access
+ end
+
+ parsed_destination[:recipient_info] || parsed_destination["recipient_info"] || parsed_destination
+ end
+
+ # Purpose: creates destination hash from VbmsDistributionDestination attributes
+ #
+ # takes in VbmsDistributionDestination object
+ #
+ # Response: array that holds a hash
+ def get_destinations_hash(destination)
+ recipient_info = parse_recipient_info_from_destinaton(destination)
+
+ [{
+ type: recipient_info[:destination_type],
+ addressLine1: recipient_info[:address_line_1],
+ addressLine2: recipient_info[:address_line_2],
+ addressLine3: recipient_info[:address_line_3],
+ addressLine4: recipient_info[:address_line_4],
+ addressLine5: recipient_info[:address_line_5],
+ addressLine6: recipient_info[:address_line_6],
+ treatLine2AsAddressee: recipient_info[:treat_line_2_as_addressee],
+ treatLine3AsAddressee: recipient_info[:treat_line_3_as_addressee],
+ city: recipient_info[:city],
+ state: recipient_info[:state],
+ postalCode: recipient_info[:postal_code],
+ countryName: recipient_info[:country_name],
+ countryCode: recipient_info[:country_code]
+ }]
+ end
+
+ # Purpose: logging error in Rails and in Raven
+ #
+ # takes in error message (string)
+ #
+ # Response: n/a
+ def log_error(error)
+ uuid = SecureRandom.uuid
+ error_msg = ERROR_MESSAGES[error.code] || "#{error.code} Unknown error has occurred."
+
+ Rails.logger.error(error_msg + "Error ID: " + uuid)
+ Raven.capture_exception(error, extra: { error_uuid: uuid })
+ end
+
+ ERROR_MESSAGES = {
+ 400 => "400 PacmanBadRequestError The server cannot create the new communication package due to a client error.",
+ 403 => "403 PacmanForbiddenError The server cannot create the new communication package" \
+ "due to insufficient privileges.",
+ 404 => "404 PacmanNotFoundError The communication package could not be found but may be available" \
+ "again in the future. Subsequent requests by the client are permissible.",
+ 500 => "500 PacmanInternalServerError The request was unable to be completed."
+ }.freeze
+
+ # Purpose: logs information in Rails logger
+ #
+ # takes in info message (string)
+ #
+ # Response: n/a
+ def log_info(info_message)
+ uuid = SecureRandom.uuid
+
+ Rails.logger.info("#{info_message.body} - ID: #{uuid}")
+ end
+end
diff --git a/app/jobs/process_decision_document_job.rb b/app/jobs/process_decision_document_job.rb
index 742f42cdd48..e2d0e9f0cee 100644
--- a/app/jobs/process_decision_document_job.rb
+++ b/app/jobs/process_decision_document_job.rb
@@ -4,10 +4,10 @@ class ProcessDecisionDocumentJob < CaseflowJob
queue_with_priority :low_priority
application_attr :intake
- def perform(decision_document_id)
+ def perform(decision_document_id, mail_package = nil)
RequestStore.store[:application] = "idt"
RequestStore.store[:current_user] = User.system_user
- DecisionDocument.find(decision_document_id).process!
+ DecisionDocument.find(decision_document_id).process!(mail_package)
end
end
diff --git a/app/jobs/upload_document_to_vbms_job.rb b/app/jobs/upload_document_to_vbms_job.rb
index fa66a304c83..e0337e31ad4 100644
--- a/app/jobs/upload_document_to_vbms_job.rb
+++ b/app/jobs/upload_document_to_vbms_job.rb
@@ -8,21 +8,37 @@ class UploadDocumentToVbmsJob < CaseflowJob
# Params: document_id - integer to search for VbmsUploadedDocument
# initiator_css_id - string to find a user by css_id
# application - string with a default value of "idt" but can be overwritten
+ # mail_package - Payload with distributions value (array of JSON-formatted MailRequest objects),
+ # copies value (integer), and created_by_id value (integer) to be submitted to
+ # Package Manager if optional recipient info is present
#
# Return: nil
- def perform(document_id:, initiator_css_id:, application: "idt")
+ def perform(params)
+ @params = params
RequestStore.store[:application] = application
RequestStore.store[:current_user] = User.system_user
-
- @document = VbmsUploadedDocument.find_by(id: document_id)
- @initiator = User.find_by_css_id(initiator_css_id)
+ @document = VbmsUploadedDocument.find(params[:document_id])
+ @initiator = User.find_by_css_id(params[:initiator_css_id])
add_context_to_sentry
UploadDocumentToVbms.new(document: document).call
+ queue_mail_request_job(mail_package) unless mail_package.nil?
end
private
- attr_reader :document, :initiator
+ attr_reader :document, :initiator, :params
+
+ def application
+ return "idt" if params[:application].blank?
+
+ params[:application]
+ end
+
+ def mail_package
+ return nil if params[:mail_package].blank?
+
+ params[:mail_package]
+ end
def add_context_to_sentry
if initiator.present?
@@ -39,4 +55,17 @@ def add_context_to_sentry
veteran_file_number: document.veteran_file_number
)
end
+
+ def queue_mail_request_job(mail_package)
+ return unless document.uploaded_to_vbms_at
+
+ MailRequestJob.perform_later(document, mail_package)
+ info_message = "MailRequestJob for document #{document.id} queued for submission to Package Manager"
+ log_info(info_message)
+ end
+
+ def log_info(info_message)
+ uuid = SecureRandom.uuid
+ Rails.logger.info(info_message + " ID: " + uuid)
+ end
end
diff --git a/app/models/decision_document.rb b/app/models/decision_document.rb
index 1be7c4c54c8..e81ebc23075 100644
--- a/app/models/decision_document.rb
+++ b/app/models/decision_document.rb
@@ -19,6 +19,7 @@ class NotYetSubmitted < StandardError; end
S3_SUB_BUCKET = "decisions"
delegate :veteran, to: :appeal
+ delegate :file_number, to: :veteran, prefix: true
include BelongsToPolymorphicAppealConcern
# Sets up belongs_to association with :appeal and provides `ama_appeal` used by `has_many` call
@@ -26,6 +27,22 @@ class NotYetSubmitted < StandardError; end
has_many :ama_decision_issues, -> { includes(:ama_decision_documents).references(:decision_documents) },
through: :ama_appeal, source: :decision_issues
+ has_many :vbms_communication_packages, as: :document_mailable_via_pacman
+
+ def self.create_document!(params, mail_package)
+ create!(params).tap { |document| document.add_mail_package(mail_package) }
+ end
+
+ def add_mail_package(mail_package)
+ @mail_package = mail_package
+ end
+
+ def pdf_name
+ appeal.external_id + ".pdf"
+ end
+
+ alias document_name pdf_name
+
def decision_issues
ama_decision_issues if appeal_type == "Appeal"
# LegacyAppeals do not have decision_issue records
@@ -53,17 +70,18 @@ def submit_for_processing!(delay: processing_delay)
super
if not_processed_or_decision_date_not_in_the_future?
- ProcessDecisionDocumentJob.perform_later(id)
+ ProcessDecisionDocumentJob.perform_later(id, mail_package)
end
end
- def process!
+ def process!(mail_package)
return if processed?
fail NotYetSubmitted unless submitted_and_ready?
attempted!
upload_to_vbms!
+ queue_mail_request_job!(mail_package) unless mail_package.nil?
if appeal.is_a?(Appeal)
create_board_grant_effectuations!
@@ -109,6 +127,8 @@ def all_contention_records(epe)
private
+ attr_reader :mail_package
+
def create_board_grant_effectuations!
appeal.decision_issues.granted.each do |granted_decision_issue|
BoardGrantEffectuation.find_or_create_by(granted_decision_issue: granted_decision_issue)
@@ -134,12 +154,13 @@ def update_decision_issue_decision_dates!
def upload_to_vbms!
return if uploaded_to_vbms_at
- VBMSService.upload_document_to_vbms(appeal, self)
- update!(uploaded_to_vbms_at: Time.zone.now)
- end
+ response = VBMSService.upload_document_to_vbms(appeal, self)
- def pdf_name
- appeal.external_id + ".pdf"
+ update!(
+ uploaded_to_vbms_at: Time.zone.now,
+ document_version_reference_id: response.dig(:upload_document_response, :@new_document_version_ref_id),
+ document_series_reference_id: response.dig(:upload_document_response, :@document_series_ref_id)
+ )
end
def s3_location
@@ -184,4 +205,18 @@ def send_outcode_email(appeal)
Rails.logger.warn("BVADispatchEmail #{log}")
end
end
+
+ # Queues mail request job if recipient info present and dispatch completed
+ def queue_mail_request_job!(mail_package)
+ return unless uploaded_to_vbms_at
+
+ MailRequestJob.perform_later(self, mail_package)
+ info_message = "MailRequestJob for citation #{citation_number} queued for submission to Package Manager"
+ log_info(info_message)
+ end
+
+ def log_info(info_message)
+ uuid = SecureRandom.uuid
+ Rails.logger.info(info_message + " ID: " + uuid)
+ end
end
diff --git a/app/models/etl/decision_document.rb b/app/models/etl/decision_document.rb
index 6a6ab0b65d2..3cf81fac966 100644
--- a/app/models/etl/decision_document.rb
+++ b/app/models/etl/decision_document.rb
@@ -6,6 +6,9 @@ class ETL::DecisionDocument < ETL::Record
class << self
private
+ ATTRS_TO_OMIT = %w[created_at updated_at
+ document_series_reference_id document_version_reference_id].freeze
+
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
@@ -14,7 +17,7 @@ def merge_original_attributes_to_target(original, target)
# To-do: ETL legacy appeals; AMA appeals are sufficient for now
return unless original.appeal_type == "Appeal"
- target.attributes = original.attributes.reject { |key| %w[created_at updated_at].include?(key) }
+ target.attributes = original.attributes.reject { |key| ATTRS_TO_OMIT.include?(key) }
target.decision_document_created_at = original.created_at
target.decision_document_updated_at = original.updated_at
diff --git a/app/models/prepend/va_notify/appeal_decision_mailed.rb b/app/models/prepend/va_notify/appeal_decision_mailed.rb
index 48a8674bfc9..034e4d32acd 100644
--- a/app/models/prepend/va_notify/appeal_decision_mailed.rb
+++ b/app/models/prepend/va_notify/appeal_decision_mailed.rb
@@ -12,7 +12,7 @@ module AppealDecisionMailed
# Params: none
#
# Response: returns true if successfully processed, returns false if not successfully processed (will not notify)
- def process!
+ def process!(mail_package = nil)
super_return_value = super
if processed?
AppellantNotification.appeal_mapper(appeal.id, appeal.class.to_s, "decision_mailed")
diff --git a/app/models/tasks/bva_dispatch_task.rb b/app/models/tasks/bva_dispatch_task.rb
index 3d475f407c1..71ddb629232 100644
--- a/app/models/tasks/bva_dispatch_task.rb
+++ b/app/models/tasks/bva_dispatch_task.rb
@@ -37,11 +37,12 @@ def ready_for_dispatch?(appeal)
true
end
- def outcode(appeal, params, user)
+ # Passes mail distributions to Package Manager service if recipient info present
+ def outcode(appeal, params, user, mail_package = nil)
if appeal.is_a?(Appeal)
- AmaAppealDispatch.new(appeal: appeal, user: user, params: params).call
+ AmaAppealDispatch.new(appeal: appeal, params: params, user: user, mail_package: mail_package).call
elsif appeal.is_a?(LegacyAppeal)
- LegacyAppealDispatch.new(appeal: appeal, params: params).call
+ LegacyAppealDispatch.new(appeal: appeal, params: params, mail_package: mail_package).call
end
end
end
diff --git a/app/models/vbms_communication_package.rb b/app/models/vbms_communication_package.rb
new file mode 100644
index 00000000000..f09be6354b8
--- /dev/null
+++ b/app/models/vbms_communication_package.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class VbmsCommunicationPackage < CaseflowRecord
+ belongs_to :document_mailable_via_pacman, polymorphic: true, optional: false
+ has_many :vbms_distributions
+
+ validates :file_number, :comm_package_name, :copies, presence: true
+ validates :comm_package_name, length: { in: 1..255 }, format: { with: /\A[\w !*+,-.:;=?]{1,255}\Z/ }
+ validates :copies, numericality: { only_integer: true, greater_than: 0, less_than: 501 }
+end
diff --git a/app/models/vbms_distribution.rb b/app/models/vbms_distribution.rb
new file mode 100644
index 00000000000..f7af0f5ef39
--- /dev/null
+++ b/app/models/vbms_distribution.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class VbmsDistribution < CaseflowRecord
+ include MailRequestValidator::Distribution
+
+ belongs_to :vbms_communication_package
+ has_many :vbms_distribution_destinations
+end
diff --git a/app/models/vbms_distribution_destination.rb b/app/models/vbms_distribution_destination.rb
new file mode 100644
index 00000000000..80bbd6b1730
--- /dev/null
+++ b/app/models/vbms_distribution_destination.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class VbmsDistributionDestination < CaseflowRecord
+ include MailRequestValidator::DistributionDestination
+
+ belongs_to :vbms_distribution, optional: false
+end
diff --git a/app/models/vbms_uploaded_document.rb b/app/models/vbms_uploaded_document.rb
index 6a767c13c2f..dc5f9a55c73 100644
--- a/app/models/vbms_uploaded_document.rb
+++ b/app/models/vbms_uploaded_document.rb
@@ -4,6 +4,8 @@ class VbmsUploadedDocument < CaseflowRecord
include BelongsToPolymorphicAppealConcern
belongs_to_polymorphic_appeal :appeal
+ has_many :vbms_communication_packages, as: :document_mailable_via_pacman
+
validates :document_type, presence: true
attribute :file, :string
diff --git a/app/services/concerns/jwt_generator.rb b/app/services/concerns/jwt_generator.rb
new file mode 100644
index 00000000000..102ab0fa91f
--- /dev/null
+++ b/app/services/concerns/jwt_generator.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module JwtGenerator
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Purpose: Remove any illegal characters and keeps source at proper format
+ #
+ # Params: string
+ #
+ # Return: sanitized string
+ def base64url(source)
+ encoded_source = Base64.encode64(source)
+ encoded_source = encoded_source.sub(/=+$/, "")
+ encoded_source = encoded_source.tr("+", "-")
+ encoded_source = encoded_source.tr("/", "_")
+ encoded_source
+ end
+ end
+end
diff --git a/app/services/external_api/pacman_service.rb b/app/services/external_api/pacman_service.rb
new file mode 100644
index 00000000000..b309d4a8b2f
--- /dev/null
+++ b/app/services/external_api/pacman_service.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+
+require "json"
+require "base64"
+require "digest"
+
+class ExternalApi::PacmanService
+ include JwtGenerator
+
+ BASE_URL = ENV["PACMAN_API_URL"]
+ SEND_DISTRIBUTION_ENDPOINT = "/package-manager-service/distribution"
+ SEND_PACKAGE_ENDPOINT = "/package-manager-service/communication-package"
+ GET_DISTRIBUTION_ENDPOINT = "/package-manager-service/distribution/"
+ HEADERS = {
+ "Content-Type": "application/json", Accept: "application/json"
+ }.freeze
+
+ class << self
+ # Purpose: Creates and sends communication package
+ # POST: /package-manager-service/communication-package
+ #
+ # takes in file_number(string), name(string), document_reference(array of strings)
+ #
+ # Response: JSON of created package from Pacman API
+ # Example response can be seen in lib/fakes/pacman_service.rb under 'fake_package_request' method
+ def send_communication_package_request(file_number, name, document_references)
+ request = package_request(file_number, name, document_references.first)
+ send_pacman_request(request)
+ end
+
+ # Purpose: Creates and sends distribution
+ # POST: /package-manager-service/distribution
+ #
+ # takes in package_id(string), recipient(json of strings), destinations(array of strings)
+ #
+ # Response: JSON of created distribution from Pacman API
+ # Example response can be seen in lib/fakes/pacman_service.rb under 'fake_distribution_request' method
+ def send_distribution_request(package_id, recipient, destinations)
+ destinations.map do |destination|
+ request = distribution_request(package_id, recipient, destination)
+ send_pacman_request(request)
+ end
+ end
+
+ # Purpose: Gets distribution from distribution id
+ # POST: /package-manager-service/distribution
+ #
+ # takes in distribution_uuid(string)
+ #
+ # Response: JSON of distribution from Pacman API
+ # Example response can be seen in lib/fakes/pacman_service.rb under 'fake_distribution_response' method
+ def get_distribution_request(distribution_uuid)
+ request = {
+ endpoint: GET_DISTRIBUTION_ENDPOINT + distribution_uuid, method: :get
+ }
+ send_pacman_request(request)
+ end
+
+ private
+
+ # Purpose: Builds package request
+ #
+ # takes in file_number(string), name(string), document_reference(array of strings)
+ #
+ # Response: package request hash
+ def package_request(file_number, name, document_reference)
+ {
+ body: {
+ fileNumber: file_number,
+ name: name,
+ documentReferences: [{
+ id: document_reference[:id],
+ copies: document_reference[:copies]
+ }]
+ },
+ headers: HEADERS,
+ endpoint: SEND_PACKAGE_ENDPOINT, method: :post
+ }
+ end
+
+ # Purpose: Builds distribution request
+ #
+ # takes in package_id(string), recipient(json of strings), destinations(array of strings)
+ #
+ # Response: Distribution request hash
+ def distribution_request(package_id, recipient, destination)
+ {
+ body: {
+ communicationPackageId: package_id,
+ recipient: recipient_data(recipient),
+ destinations: destinations_data(destination)
+ },
+ headers: HEADERS,
+ endpoint: SEND_DISTRIBUTION_ENDPOINT, method: :post
+ }.compact
+ end
+
+ # Purpose: Builds recipient json for distribution request
+ #
+ # takes in recipient(json of strings)
+ #
+ # Response: json of recipient data for distribution request hash
+ def recipient_data(recipient)
+ {
+ type: recipient[:type],
+ name: recipient[:name],
+ firstName: recipient[:first_name],
+ middleName: recipient[:middle_name],
+ lastName: recipient[:last_name],
+ participantId: recipient[:participant_id],
+ poaCode: recipient[:poa_code],
+ claimantStationOfJurisdiction: recipient[:claimant_station_of_jurisdiction]
+ }
+ end
+
+ # Purpose: Builds destinations array for distribution request
+ #
+ # takes in destination(array of strings)
+ #
+ # Response: array of destination data for distribution request hashh
+ def destinations_data(destination)
+ [{
+ type: destination[:type],
+ addressLine1: destination[:addressLine1],
+ addressLine2: destination[:addressLine2],
+ addressLine3: destination[:addressLine3],
+ addressLine4: destination[:addressLine4],
+ addressLine5: destination[:addressLine5],
+ addressLine6: destination[:addressLine6],
+ treatLine2AsAddressee: destination[:treatLine2AsAddressee],
+ treatLine3AsAddressee: destination[:treatLine3AsAddressee],
+ city: destination[:city],
+ state: destination[:state],
+ postalCode: destination[:postalCode],
+ countryName: destination[:countryName],
+ countryCode: destination[:countryCode]
+ }]
+ end
+
+ def jwt_payload
+ current_epoch_timestamp = DateTime.now.strftime("%Q").to_i / 1000.floor
+
+ {
+ iat: current_epoch_timestamp,
+ iss: ENV["PACMAN_API_TOKEN_ISSUER"],
+ aud: ENV["PACMAN_API_TOKEN_ISSUER"],
+ samlToken: ENV["PACMAN_API_SAML_TOKEN"]&.encode("UTF-8"),
+ externalSystemSource: ENV["PACMAN_API_SYS_ACCOUNT"]
+ }
+ end
+
+ # Purpose: Generate the JWT token
+ #
+ # Params: none
+ #
+ # Return: token needed for authentication
+ def generate_token
+ header = {
+ alg: ENV["PACMAN_API_TOKEN_ALG"]
+ }
+
+ stringified_header = header.to_json.encode("UTF-8")
+ encoded_header = base64url(stringified_header)
+ stringified_data = jwt_payload.to_json.encode("UTF-8")
+ encoded_data = base64url(stringified_data)
+ token = "#{encoded_header}.#{encoded_data}"
+ signature = OpenSSL::HMAC.digest("SHA512", ENV["PACMAN_API_TOKEN_SECRET"], token)
+
+ # Signed Token
+ "#{token}.#{base64url(signature)}"
+ end
+
+ # Purpose: Build and send the request to the server
+ #
+ # Params: general requirements for HTTP request
+ #
+ # Return: service_response: JSON from Pacman or error
+ # :reek:LongParameterList
+ def send_pacman_request(headers: {}, endpoint:, method: :get, body: nil)
+ url = BASE_URL + endpoint
+ request = HTTPI::Request.new(url)
+ request.open_timeout = 30
+ request.read_timeout = 30
+ request.body = body.to_json unless body.nil?
+ request.auth.ssl.ssl_version = :TLSv1_2
+ request.auth.ssl.ca_cert_file = ENV["SSL_CERT_FILE"]
+ request.headers = headers.merge("X-Forwarded-User": ENV["PACMAN_API_JWT"])
+ sleep 1
+
+ MetricsService.record("Pacman Service #{method.to_s.upcase} request to #{url}",
+ service: :pacman,
+ name: endpoint) do
+ case method
+ when :get
+ HTTPI.get(request)
+ when :post
+ HTTPI.post(request)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/external_api/pacman_service/response.rb b/app/services/external_api/pacman_service/response.rb
new file mode 100644
index 00000000000..1e491e5c836
--- /dev/null
+++ b/app/services/external_api/pacman_service/response.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+class ExternalApi::PacmanService::Response
+ attr_reader :resp, :code
+
+ def initialize(resp)
+ @resp = resp
+ @code = @resp.code
+ end
+
+ def data; end
+
+ # Wrapper method to check for errors
+ def error
+ check_for_error
+ end
+
+ # Checks if there is no error
+ def success?
+ !resp.error?
+ end
+
+ # Parses response body to an object
+ def body
+ @body ||= begin
+ JSON.parse(resp.body).with_indifferent_access
+ rescue JSON::ParserError
+ log(JSON::ParserError)
+ {}
+ end
+ end
+
+ private
+
+ # Error codes and their associated error
+ ERROR_LOOKUP = {
+ 400 => Caseflow::Error::PacmanBadRequestError,
+ 403 => Caseflow::Error::PacmanForbiddenError,
+ 404 => Caseflow::Error::PacmanNotFoundError,
+ 500 => Caseflow::Error::PacmanInternalServerError
+ }.freeze
+
+ # Checks for error and returns if found
+ def check_for_error
+ return if success?
+
+ message = error_message
+
+ if ERROR_LOOKUP.key? code
+ ERROR_LOOKUP[code].new(code: code, message: message)
+ else
+ Caseflow::Error::PacmanApiError.new(code: code, message: message)
+ end
+ end
+
+ def log_error(error)
+ uuid = SecureRandom.uuid
+ Rails.logger.error(error.name + " " + error.message + "Error ID: " + uuid)
+ Raven.capture_exception(error.name + " " + error.message, extra: { error_uuid: uuid })
+ end
+
+ # Gets the error message from the response
+ def error_message
+ return "No error message from Pacman" if body.empty?
+
+ body&.error || "No error message from Pacman"
+ end
+end
diff --git a/app/services/external_api/va_notify_service.rb b/app/services/external_api/va_notify_service.rb
index 38a6febf294..5f38a03e0c0 100644
--- a/app/services/external_api/va_notify_service.rb
+++ b/app/services/external_api/va_notify_service.rb
@@ -4,6 +4,8 @@
require "base64"
require "digest"
class ExternalApi::VANotifyService
+ include JwtGenerator
+
BASE_URL = ENV["VA_NOTIFY_API_URL"]
CLIENT_SECRET = ENV["VA_NOTIFY_API_KEY"]
SERVICE_ID = ENV["VA_NOTIFY_SERVICE_ID"]
@@ -100,19 +102,6 @@ def generate_token
signed_token
end
- # Purpose: Remove any illegal characters and keeps source at proper format
- #
- # Params: string
- #
- # Return: sanitized string
- def base64url(source)
- encoded_source = Base64.encode64(source)
- encoded_source = encoded_source.sub(/=+$/, "")
- encoded_source = encoded_source.tr("+", "-")
- encoded_source = encoded_source.tr("/", "_")
- encoded_source
- end
-
# Purpose: Build an email request object
#
# Params: Details from appeal for notification
diff --git a/app/validators/mail_request_validator.rb b/app/validators/mail_request_validator.rb
new file mode 100644
index 00000000000..3f3acbcc273
--- /dev/null
+++ b/app/validators/mail_request_validator.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module MailRequestValidator
+ # Validations for VbmsDistribution model and MailRequest object
+ module Distribution
+ extend ActiveSupport::Concern
+
+ included do
+ with_options presence: true do
+ validates :recipient_type, inclusion: { in: %w[organization person system ro-colocated] }
+ validates :first_name, :last_name, if: -> { recipient_type == "person" }
+ validates :name, if: :not_a_person?
+ validates :poa_code, :claimant_station_of_jurisdiction, if: -> { recipient_type == "ro-colocated" }
+ end
+ end
+
+ private
+
+ def not_a_person?
+ %w[organization system ro-colocated].include?(recipient_type)
+ end
+ end
+
+ # Validations for VbmsDistributionDestination model and MailRequest object
+ module DistributionDestination
+ extend ActiveSupport::Concern
+
+ included do
+ with_options presence: true do
+ validates :destination_type, inclusion: { in: %w[domesticAddress internationalAddress militaryAddress derived] }
+ validates :address_line_1, :city, :country_code, if: :physical_mail?
+ validates :address_line_2, if: :treat_line_2_as_addressee
+ validates :address_line_3, if: :treat_line_3_as_addressee
+ validates :state, :postal_code, if: :us_address?
+ validates :country_name, if: -> { destination_type == "internationalAddress" }
+ end
+
+ validates :treat_line_2_as_addressee,
+ inclusion: { in: [true], message: "cannot be false if line 3 is treated as addressee" },
+ if: -> { treat_line_3_as_addressee == true }
+
+ validate :valid_country_code?, if: :physical_mail?
+ validate :valid_us_state_code?, if: :us_address?
+ end
+
+ private
+
+ def physical_mail?
+ %w[domesticAddress internationalAddress militaryAddress].include?(destination_type)
+ end
+
+ def us_address?
+ %w[domesticAddress militaryAddress].include?(destination_type)
+ end
+
+ def valid_country_code?
+ unless iso_country_codes.include?(country_code)
+ errors.add(:country_code, "is not a valid ISO 3166-2 code")
+ end
+ end
+
+ def valid_us_state_code?
+ unless iso_us_state_codes.include?(state)
+ errors.add(:state, "is not a valid ISO 3166-2 code")
+ end
+ end
+
+ def iso_country_codes
+ ISO3166::Country.codes
+ end
+
+ def iso_us_state_codes
+ ISO3166::Country.find_country_by_alpha2("US").subdivisions.keys
+ end
+ end
+end
diff --git a/app/views/queue/index.html.erb b/app/views/queue/index.html.erb
index 88b5f34ca42..e0ba7a1038a 100644
--- a/app/views/queue/index.html.erb
+++ b/app/views/queue/index.html.erb
@@ -52,7 +52,8 @@
split_appeal_workflow: FeatureToggle.enabled?(:split_appeal_workflow, user: current_user),
cavc_remand_granted_substitute_appellant: FeatureToggle.enabled?(:cavc_remand_granted_substitute_appellant, user: current_user),
cavc_dashboard_workflow: FeatureToggle.enabled?(:cavc_dashboard_workflow, user: current_user),
- cc_appeal_workflow: FeatureToggle.enabled?(:cc_appeal_workflow, user: current_user)
+ cc_appeal_workflow: FeatureToggle.enabled?(:cc_appeal_workflow, user: current_user),
+ cc_vacatur_visibility: FeatureToggle.enabled?(:cc_vacatur_visibility, user: current_user)
}
}) %>
<% end %>
diff --git a/app/workflows/ama_appeal_dispatch.rb b/app/workflows/ama_appeal_dispatch.rb
index cd10f06950d..01618ee3627 100644
--- a/app/workflows/ama_appeal_dispatch.rb
+++ b/app/workflows/ama_appeal_dispatch.rb
@@ -4,14 +4,11 @@ class AmaAppealDispatch
include ActiveModel::Model
include DecisionDocumentValidator
- def initialize(appeal:, params:, user:)
- @appeal = appeal
+ def initialize(appeal:, params:, user:, mail_package: nil)
@params = params.merge(appeal_id: appeal.id, appeal_type: "Appeal")
+ @appeal = appeal
@user = user
- @citation_number = params[:citation_number]
- @decision_date = params[:decision_date]
- @redacted_document_location = params[:redacted_document_location]
- @file = params[:file]
+ @mail_package = mail_package
end
def call
@@ -27,8 +24,23 @@ def call
private
- attr_reader :appeal, :params, :user, :success, :citation_number,
- :decision_date, :redacted_document_location, :file
+ attr_reader :params, :appeal, :user, :mail_package, :success
+
+ def citation_number
+ params[:citation_number]
+ end
+
+ def decision_date
+ params[:decision_date]
+ end
+
+ def redacted_document_location
+ params[:redacted_document_location]
+ end
+
+ def file
+ params[:file]
+ end
def dispatch_tasks
@dispatch_tasks ||= BvaDispatchTask.not_cancelled.where(appeal: appeal, assigned_to: user)
@@ -73,7 +85,7 @@ def outcode_appeal
end
def create_decision_document_and_submit_for_processing!(params)
- DecisionDocument.create!(params).tap(&:submit_for_processing!)
+ DecisionDocument.create_document!(params, mail_package).tap(&:submit_for_processing!)
end
def complete_dispatch_task!
diff --git a/app/workflows/legacy_appeal_dispatch.rb b/app/workflows/legacy_appeal_dispatch.rb
index 086d73d6b7e..67d087f6099 100644
--- a/app/workflows/legacy_appeal_dispatch.rb
+++ b/app/workflows/legacy_appeal_dispatch.rb
@@ -4,13 +4,10 @@ class LegacyAppealDispatch
include ActiveModel::Model
include DecisionDocumentValidator
- def initialize(appeal:, params:)
- @appeal = appeal
+ def initialize(appeal:, params:, mail_package: nil)
@params = params.merge(appeal_id: appeal.id, appeal_type: "LegacyAppeal")
- @citation_number = params[:citation_number]
- @decision_date = params[:decision_date]
- @redacted_document_location = params[:redacted_document_location]
- @file = params[:file]
+ @appeal = appeal
+ @mail_package = mail_package
end
def call
@@ -26,11 +23,26 @@ def call
private
- attr_reader :appeal, :params, :success, :citation_number,
- :decision_date, :redacted_document_location, :file
+ attr_reader :params, :appeal, :mail_package, :success
+
+ def citation_number
+ params[:citation_number]
+ end
+
+ def decision_date
+ params[:decision_date]
+ end
+
+ def redacted_document_location
+ params[:redacted_document_location]
+ end
+
+ def file
+ params[:file]
+ end
def create_decision_document_and_submit_for_processing!(params)
- DecisionDocument.create!(params).tap(&:submit_for_processing!)
+ DecisionDocument.create_document!(params, mail_package).tap(&:submit_for_processing!)
end
def complete_root_task!
diff --git a/app/workflows/mail_request.rb b/app/workflows/mail_request.rb
new file mode 100644
index 00000000000..81da60840b4
--- /dev/null
+++ b/app/workflows/mail_request.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+class MailRequest
+ include ActiveModel::Model
+ include ActiveModel::Validations
+
+ include MailRequestValidator::Distribution
+ include MailRequestValidator::DistributionDestination
+
+ attr_reader :vbms_distribution_id, :comm_package_id
+
+ # Purpose: initializes a mail_request object making use of the passed in hash and also initializing
+ # the attributes of vbms_distribution_id and a comm_package_id. Both set to nil until set
+ # otherwise.
+ #
+ # Params: recipient_and_destination_hash - expected parameters that that hold information
+ # that will be used to create a valid VbmsDistribution and valid VbmsDistributionDestination.
+ #
+ # Return: nil
+ def initialize(recipient_and_destination_hash)
+ @recipient_info = recipient_and_destination_hash
+ @vbms_distribution_id = nil
+ @comm_package_id = nil
+ end
+
+ # Purpose: With the passed in parameters, the call method creates both a valid VBMSDistribution and
+ # valid VBMSDistributionDestination. If there is an error it will fail and that information will be provided
+ # to the IDT user.
+ #
+ def call
+ if valid?
+ distribution = create_a_vbms_distribution
+ @vbms_distribution_id = distribution.id
+ create_a_vbms_distribution_destination
+ else
+ fail Caseflow::Error::MissingRecipientInfo
+ end
+ end
+
+ private
+
+ def create_a_vbms_distribution
+ VbmsDistribution.create!(recipient_params_parse)
+ end
+
+ def create_a_vbms_distribution_destination
+ VbmsDistributionDestination.create!(destination_params_parse)
+ end
+
+ def destination_params_parse
+ {
+ destination_type: destination_type,
+ address_line_1: address_line_1,
+ address_line_2: address_line_2,
+ address_line_3: address_line_3,
+ address_line_4: address_line_4,
+ address_line_5: address_line_5,
+ address_line_6: address_line_6,
+ city: city,
+ country_code: country_code,
+ postal_code: postal_code,
+ state: state,
+ treat_line_2_as_addressee: treat_line_2_as_addressee,
+ treat_line_3_as_addressee: treat_line_3_as_addressee,
+ country_name: country_name,
+ vbms_distribution_id: vbms_distribution_id
+ }
+ end
+
+ def recipient_params_parse
+ {
+ recipient_type: recipient_type,
+ name: name,
+ first_name: first_name,
+ middle_name: middle_name,
+ last_name: last_name,
+ participant_id: participant_id,
+ poa_code: poa_code,
+ claimant_station_of_jurisdiction: claimant_station_of_jurisdiction,
+ created_by_id: RequestStore[:current_user].id
+ }
+ end
+
+ def recipient_type
+ @recipient_info[:recipient_type]
+ end
+
+ def name
+ @recipient_info[:name]
+ end
+
+ def first_name
+ @recipient_info[:first_name]
+ end
+
+ def middle_name
+ @recipient_info[:middle_name]
+ end
+
+ def last_name
+ @recipient_info[:last_name]
+ end
+
+ def participant_id
+ @recipient_info[:participant_id]
+ end
+
+ def poa_code
+ @recipient_info[:poa_code]
+ end
+
+ def claimant_station_of_jurisdiction
+ @recipient_info[:claimant_station_of_jurisdiction]
+ end
+
+ def destination_type
+ @recipient_info[:destination_type]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_1
+ @recipient_info[:address_line_1]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_2
+ @recipient_info[:address_line_2]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_3
+ @recipient_info[:address_line_3]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_4
+ @recipient_info[:address_line_4]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_5
+ @recipient_info[:address_line_5]
+ end
+
+ # :reek:UncommunicativeMethodName
+ def address_line_6
+ @recipient_info[:address_line_6]
+ end
+
+ def city
+ @recipient_info[:city]
+ end
+
+ def country_code
+ @recipient_info[:country_code]
+ end
+
+ def postal_code
+ @recipient_info[:postal_code]
+ end
+
+ def state
+ @recipient_info[:state]
+ end
+
+ def treat_line_2_as_addressee
+ @recipient_info[:treat_line_2_as_addressee]
+ end
+
+ def treat_line_3_as_addressee
+ @recipient_info[:treat_line_3_as_addressee]
+ end
+
+ def country_name
+ @recipient_info[:country_name]
+ end
+end
diff --git a/app/workflows/prepare_document_upload_to_vbms.rb b/app/workflows/prepare_document_upload_to_vbms.rb
index f83f59f8ed6..8270e8ca595 100644
--- a/app/workflows/prepare_document_upload_to_vbms.rb
+++ b/app/workflows/prepare_document_upload_to_vbms.rb
@@ -10,11 +10,15 @@ class PrepareDocumentUploadToVbms
# Params: params - hash containing file and document_type at minimum
# user - current user that is preparing the document for upload
# appeal - Appeal object (optional if ssn or file number are passed into params)
- def initialize(params, user, appeal = nil)
+ # mail_package - Payload with distributions value (array of JSON-formatted MailRequest objects),
+ # copies value (integer), and created_by_id value (integer) to be submitted to
+ # Package Manager if optional recipient info is present
+ #
+ def initialize(params, user, appeal = nil, mail_package = nil)
@params = params.slice(:veteran_file_number, :document_type, :document_subject, :document_name, :file, :application)
- @document_type = @params[:document_type]
@user = user
@appeal = appeal
+ @mail_package = mail_package
end
# Purpose: Queues a job to upload a document to vbms
@@ -28,11 +32,7 @@ def call
@params[:veteran_file_number] = throw_error_if_file_number_not_match_bgs
VbmsUploadedDocument.create(document_params).tap do |document|
document.cache_file
- UploadDocumentToVbmsJob.perform_later(
- document_id: document.id,
- initiator_css_id: user.css_id,
- application: @params[:application]
- )
+ UploadDocumentToVbmsJob.perform_later(upload_job_params(document))
end
end
@@ -42,22 +42,26 @@ def call
private
attr_accessor :success
- attr_reader :document_type, :params, :user
+ attr_reader :params, :user, :mail_package, :document
def veteran_file_number
- @params[:veteran_file_number]
+ params[:veteran_file_number]
end
def document_subject
- @params[:document_subject]
+ params[:document_subject]
end
def document_name
- @params[:document_name]
+ params[:document_name]
end
def file
- @params[:file]
+ params[:file]
+ end
+
+ def document_type
+ params[:document_type]
end
def valid_document_type
@@ -84,6 +88,15 @@ def document_params
}
end
+ def upload_job_params(document)
+ {
+ document_id: document.id,
+ initiator_css_id: user.css_id,
+ application: params[:application],
+ mail_package: mail_package
+ }
+ end
+
def response_errors
return if success
diff --git a/app/workflows/upload_document_to_vbms.rb b/app/workflows/upload_document_to_vbms.rb
index dfcda597753..c95b815569d 100644
--- a/app/workflows/upload_document_to_vbms.rb
+++ b/app/workflows/upload_document_to_vbms.rb
@@ -15,6 +15,7 @@ def call
submit_for_processing!
upload_to_vbms!
set_processed_at_to_current_time
+ log_info("Document #{document.id} uploaded to VBMS")
rescue StandardError => error
save_rescued_error!(error.to_s)
raise error
@@ -88,6 +89,11 @@ def file_number
document.veteran_file_number
end
+ def log_info(info_message)
+ uuid = SecureRandom.uuid
+ Rails.logger.info(info_message + " ID: " + uuid)
+ end
+
# Purpose: Get the s3_sub_bucket based on the document type
# S3_SUB_BUCKET was previously a constant defined for this class.
#
diff --git a/client/COPY.json b/client/COPY.json
index dafc32b9699..9bd85705be1 100644
--- a/client/COPY.json
+++ b/client/COPY.json
@@ -386,6 +386,12 @@
"MTV_CHECKOUT_RETURN_TO_JUDGE_MODAL_INSTRUCTIONS_LABEL": "Provide instructions and context for this action",
"MTV_CHECKOUT_RETURN_TO_JUDGE_SUCCESS_TITLE": "%s's Motion to Vacate has been returned to %s",
"MTV_CHECKOUT_RETURN_TO_JUDGE_SUCCESS_DETAILS": "If you made a mistake, please email your judge to resolve the issue.",
+
+ "MTV_TASK_INSTRUCTIONS": "**Motion To Vacate:** \n",
+ "MTV_TASK_INSTRUCTIONS_TYPE": "**Type:** ",
+ "MTV_TASK_INSTRUCTIONS_DETAIL": "**Detail:** ",
+ "MTV_TASK_INSTRUCTIONS_HYPERLINK": "**Hyperlink:** ",
+
"VACATE_AND_DE_NOVO_TASK_LABEL": "Vacate and De Novo",
"VACATE_AND_READJUDICATION_TASK_LABEL": "Vacate and Readjudication",
"STRAIGHT_VACATE_TASK_LABEL": "Straight Vacate",
diff --git a/client/app/queue/QueueApp.jsx b/client/app/queue/QueueApp.jsx
index f78d4574909..f1b6d83a52c 100644
--- a/client/app/queue/QueueApp.jsx
+++ b/client/app/queue/QueueApp.jsx
@@ -719,7 +719,7 @@ class QueueApp extends React.PureComponent {
/>
diff --git a/client/app/queue/constants.js b/client/app/queue/constants.js
index 7fa63f479e9..f02c465abb3 100644
--- a/client/app/queue/constants.js
+++ b/client/app/queue/constants.js
@@ -179,7 +179,7 @@ export const PAGE_TITLES = {
ATTORNEY: 'Select Remand Reasons'
},
REVIEW_CASES: 'Review Cases',
- UNASSIGED_CASES: 'Unassgined Cases',
+ UNASSIGNED_CASES: 'Unassigned Cases',
CASE_DETAILS: 'Case Details',
DRAFT_DECISION: 'Draft Decision',
EVALUATE_DECISION: 'Evaluate Decision',
diff --git a/client/app/queue/mtv/AddressMotionToVacateView.jsx b/client/app/queue/mtv/AddressMotionToVacateView.jsx
index 825fd14c2d5..8cf14c6e30a 100644
--- a/client/app/queue/mtv/AddressMotionToVacateView.jsx
+++ b/client/app/queue/mtv/AddressMotionToVacateView.jsx
@@ -18,6 +18,9 @@ export const AddressMotionToVacateView = () => {
const task = useSelector((state) => taskById(state, { taskId }));
const appeal = useSelector((state) => appealWithDetailSelector(state, { appealId }));
+ const vacateTypeFeatureToggle = useSelector(
+ (state) => state.ui.featureToggles.cc_vacatur_visibility
+ );
const { selected, options } = taskActionData({ task,
match });
@@ -42,6 +45,7 @@ export const AddressMotionToVacateView = () => {
task={task}
attorneys={attyOptions}
selectedAttorney={selected}
+ vacateTypeFeatureToggle = {vacateTypeFeatureToggle}
appeal={appeal}
onSubmit={handleSubmit}
returnToLitSupportLink={`${match.url}/${JUDGE_RETURN_TO_LIT_SUPPORT.value}`}
diff --git a/client/app/queue/mtv/MTVJudgeDisposition.jsx b/client/app/queue/mtv/MTVJudgeDisposition.jsx
index eddde279cc9..ec89ec4d81a 100644
--- a/client/app/queue/mtv/MTVJudgeDisposition.jsx
+++ b/client/app/queue/mtv/MTVJudgeDisposition.jsx
@@ -13,9 +13,13 @@ import {
JUDGE_ADDRESS_MTV_VACATE_TYPE_LABEL,
JUDGE_ADDRESS_MTV_HYPERLINK_LABEL,
JUDGE_ADDRESS_MTV_DISPOSITION_NOTES_LABEL,
- JUDGE_ADDRESS_MTV_ASSIGN_ATTORNEY_LABEL
+ JUDGE_ADDRESS_MTV_ASSIGN_ATTORNEY_LABEL,
+ MTV_TASK_INSTRUCTIONS,
+ MTV_TASK_INSTRUCTIONS_TYPE,
+ MTV_TASK_INSTRUCTIONS_DETAIL,
+ MTV_TASK_INSTRUCTIONS_HYPERLINK
} from '../../../COPY';
-import { DISPOSITION_TEXT, VACATE_TYPE_OPTIONS } from '../../../constants/MOTION_TO_VACATE';
+import { DISPOSITION_TIMELINE_TEXT, VACATE_TYPE_OPTIONS } from '../../../constants/MOTION_TO_VACATE';
import { JUDGE_RETURN_TO_LIT_SUPPORT } from '../../../constants/TASK_ACTIONS';
import SearchableDropdown from '../../components/SearchableDropdown';
import AppSegment from '@department-of-veterans-affairs/caseflow-frontend-toolkit/components/AppSegment';
@@ -28,6 +32,7 @@ import StringUtil from '../../util/StringUtil';
import { ReturnToLitSupportAlert } from './ReturnToLitSupportAlert';
import { grantTypes, dispositionStrings } from './mtvConstants';
import { sprintf } from 'sprintf-js';
+import { isEmpty } from 'lodash';
const vacateTypeText = (val) => {
const opt = VACATE_TYPE_OPTIONS.find((i) => i.value === val);
@@ -35,19 +40,33 @@ const vacateTypeText = (val) => {
return opt && opt.displayText;
};
-const formatInstructions = ({ disposition, vacateType, hyperlink, instructions }) => {
- const parts = [`I am proceeding with a ${DISPOSITION_TEXT[disposition]}.`];
+const formatInstructions = ({ vacateTypeFeatureToggle, disposition, vacateType, hyperlink, instructions }) => {
+ const parts = [`${MTV_TASK_INSTRUCTIONS}${DISPOSITION_TIMELINE_TEXT[disposition]}\n`];
switch (disposition) {
case 'granted':
case 'partially_granted':
- parts.push(`This will be a ${vacateTypeText(vacateType)}`);
- parts.push(instructions);
+ if (!vacateTypeFeatureToggle) {
+ parts.push(MTV_TASK_INSTRUCTIONS_TYPE);
+ parts.push(`${vacateTypeText(vacateType)}\n`);
+
+ }
+ if (isEmpty(instructions) === false) {
+ parts.push(MTV_TASK_INSTRUCTIONS_DETAIL);
+ parts.push(`${instructions}\n`);
+ }
break;
+ case 'denied':
+ case 'dismissed':
default:
- parts.push(instructions);
- parts.push('\nHere is the hyperlink to the signed denial document');
- parts.push(hyperlink);
+ if (isEmpty(instructions) === false) {
+ parts.push(MTV_TASK_INSTRUCTIONS_DETAIL);
+ parts.push(`${instructions}\n`);
+ }
+ if (hyperlink !== null) {
+ parts.push(MTV_TASK_INSTRUCTIONS_HYPERLINK);
+ parts.push(`${hyperlink}\n`);
+ }
break;
}
@@ -62,10 +81,12 @@ const styles = {
};
export const MTVJudgeDisposition = ({
+
attorneys,
selectedAttorney,
task,
appeal,
+ vacateTypeFeatureToggle,
onSubmit = () => null,
submitting = false,
returnToLitSupportLink = JUDGE_RETURN_TO_LIT_SUPPORT.value
@@ -81,6 +102,7 @@ export const MTVJudgeDisposition = ({
const handleSubmit = () => {
const formattedInstructions = formatInstructions({
+ vacateTypeFeatureToggle,
disposition,
vacateType,
hyperlink,
@@ -180,7 +202,8 @@ export const MTVJudgeDisposition = ({
setInstructions(val)}
value={instructions}
className={['mtv-decision-instructions']}
@@ -227,6 +250,7 @@ MTVJudgeDisposition.propTypes = {
onSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool,
task: PropTypes.object.isRequired,
+ vacateTypeFeatureToggle: PropTypes.bool,
appeal: PropTypes.object.isRequired,
attorneys: PropTypes.array.isRequired,
selectedAttorney: PropTypes.object,
diff --git a/client/constants/MOTION_TO_VACATE.json b/client/constants/MOTION_TO_VACATE.json
index eb7d4c76893..e5b1d8e44c0 100644
--- a/client/constants/MOTION_TO_VACATE.json
+++ b/client/constants/MOTION_TO_VACATE.json
@@ -39,6 +39,14 @@
"denied": "denial of all issues for vacatur",
"dismissed": "dismissal"
},
+
+ "DISPOSITION_TIMELINE_TEXT": {
+ "granted": "Full vacatur",
+ "partially_granted": "Partial vacatur",
+ "denied": "Deny all issues for vacatur",
+ "dismissed": "Dismiss all issues for vacatur"
+ },
+
"DISPOSITION_RECOMMENDATIONS": {
"granted": "I recommend granting a vacatur.",
"partially_granted": "I recommend granting a partial vacatur.",
diff --git a/client/test/app/queue/ColocatedTaskListView.test.js b/client/test/app/queue/ColocatedTaskListView.test.js
index 6660012b6c0..570ec2d2e87 100644
--- a/client/test/app/queue/ColocatedTaskListView.test.js
+++ b/client/test/app/queue/ColocatedTaskListView.test.js
@@ -31,8 +31,9 @@ const WrapperComponent = ({ children }) => (
);
-// Date constructor uses zero-based offset for months — this is 2021-03-17
-const fakeDate = new Date(2021, 2, 17, 12);
+// Date constructor uses zero-based offset for months — this is 2021-03-17. The time (11:30pm) is to ensure
+// that the crossover between days doesn't affect the front end calculations for when tasks were assigned
+const fakeDate = new Date(2021, 2, 17, 23, 30, 0, 0);
beforeAll(() => {
// Ensure consistent handling of dates across tests
diff --git a/client/test/app/queue/mtv/MTVJudgeDisposition.test.js b/client/test/app/queue/mtv/MTVJudgeDisposition.test.js
index 42025286c26..306965c3da0 100644
--- a/client/test/app/queue/mtv/MTVJudgeDisposition.test.js
+++ b/client/test/app/queue/mtv/MTVJudgeDisposition.test.js
@@ -15,40 +15,99 @@ const task = generateAmaTask({
type: 'VacateMotionMailTask',
instructions: ['Lorem ipsum dolor sit amet, consectetur adipiscing'],
});
+let linkField = /Insert Caseflow Reader document hyperlink to/;
+let instructionsField = /Provide context and instructions on which issues should be/;
+
+const selectRadioField = (radioSelection) => {
+ const radioFieldToSelect = screen.getByLabelText(radioSelection);
+
+ userEvent.click(radioFieldToSelect);
+};
+
+const enterAdditionalContext = (text, selectedField) => {
+ const textField = screen.getByText(selectedField);
+
+ userEvent.type(textField, text);
+};
+
+const selectDisposition = async (disposition = 'grant all') => {
+ userEvent.click(
+ screen.getByLabelText(new RegExp(disposition, 'i'))
+ );
+
+ if ((/grant/i).test(disposition)) {
+ await waitFor(() => {
+ expect(
+ screen.getByText(/what type of vacate/i)
+ ).toBeInTheDocument();
+ });
+ } else {
+ await waitFor(() => {
+ expect(
+ screen.getByLabelText(/insert caseflow reader document hyperlink/i)
+ ).toBeInTheDocument();
+ });
+ }
+};
+
+const fillForm = async (disposition, vacateType, vacateIssues, hyperlink, instructions) => {
+ userEvent.click(
+ screen.getByLabelText(new RegExp(disposition, 'i'))
+ );
+
+ if ((/grant all/i).test(disposition)) {
+ await waitFor(() => {
+ expect(
+ screen.getByText(/what type of vacate/i)
+ ).toBeInTheDocument();
+ });
+
+ selectRadioField(vacateType);
+
+ } else if ((/grant partial/i).test(disposition)) {
+ await waitFor(() => {
+ expect(
+ screen.getByText(/which issues would you like to vacate/i)
+ ).toBeInTheDocument();
+ });
+
+ selectRadioField(vacateType);
+ selectRadioField(vacateIssues);
+
+ } else {
+ await waitFor(() => {
+ expect(
+ screen.getByLabelText(/insert caseflow reader document hyperlink/i)
+ ).toBeInTheDocument();
+ });
+
+ enterAdditionalContext(hyperlink, linkField);
+
+ }
+
+ enterAdditionalContext(instructions, instructionsField);
+
+ await userEvent.click(
+ screen.getByText('Submit')
+ );
+};
describe('MTVJudgeDisposition', () => {
+
const onSubmit = jest.fn();
+
const defaults = {
appeal: amaAppeal,
attorneys: generateAttorneys(5),
task,
onSubmit,
};
+
const setup = (props) =>
render(, {
wrapper: BrowserRouter,
});
- const selectDisposition = async (disposition = 'grant all') => {
- await userEvent.click(
- screen.getByLabelText(new RegExp(disposition, 'i'))
- );
-
- if ((/grant/i).test(disposition)) {
- await waitFor(() => {
- expect(
- screen.getByText(/what type of vacate/i)
- ).toBeInTheDocument();
- });
- } else {
- await waitFor(() => {
- expect(
- screen.getByLabelText(/insert caseflow reader document hyperlink/i)
- ).toBeInTheDocument();
- });
- }
- };
-
describe('default view', () => {
it('renders correctly', () => {
const { container } = setup();
@@ -67,7 +126,7 @@ describe('MTVJudgeDisposition', () => {
describe.each(DISPOSITION_OPTIONS.map((item) => [item.value, item]))(
'with %s disposition selected',
- (disposition, { displayText: label }) => {
+ ({ displayText: label }) => {
it('renders correctly', async () => {
const { container } = setup();
@@ -87,4 +146,237 @@ describe('MTVJudgeDisposition', () => {
});
}
);
+
+ describe('Case timeline instructions, feature toggle enabled', () => {
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('grant or partial grant instructions sent', () => {
+ it('sends the correct instructions based on grant all disposition', async () => {
+ const disposition = 'grant all';
+ let vacateType = 'Vacate and De Novo (2 documents)';
+ let vacateIssues;
+ let hyperlink;
+ const instructions = 'instructions from judge';
+
+ setup({ vacateTypeFeatureToggle: false });
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** ' +
+ '\nFull vacatur' +
+ '\n' +
+ '\n**Type:** ' +
+ '\nVacate and De Novo (2 documents)' +
+ '\n' +
+ '\n**Detail:** ' +
+ '\ninstructions from judge' +
+ '\n'
+ );
+ });
+
+ it('sends the correct instructions based on partially granted disposition', async () => {
+ const disposition = 'Grant partial vacatur';
+ const vacateType = 'Straight Vacate (1 document)';
+ const vacateIssues = '1. This is a description of the decision';
+ let hyperlink;
+ const instructions = 'some instructions from judge';
+
+ setup({ vacateTypeFeatureToggle: false });
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch('**Motion To Vacate:** ' +
+ '\nPartial vacatur' +
+ '\n' +
+ '\n**Type:** ' +
+ '\nStraight Vacate (1 document)' +
+ '\n' +
+ '\n**Detail:** ' +
+ '\nsome instructions from judge' +
+ '\n'
+ );
+ });
+ });
+
+ describe('deny or dismiss instructions sent', () => {
+ it('sends the correct instructions based on denied disposition', async () => {
+ const disposition = 'deny';
+ let vacateType;
+ let vacateIssues;
+ const hyperlink = 'www.caseflow.com';
+ const instructions = 'testing';
+
+ setup();
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** \n' +
+ 'Deny all issues for vacatur\n\n' +
+ '**Detail:** \ntesting\n\n' +
+ '**Hyperlink:** \nwww.caseflow.com\n'
+ );
+ });
+
+ it('sends the correct instructions based on dismissed disposition', async () => {
+ const disposition = 'dismiss';
+ let vacateType;
+ let vacateIssues;
+ const hyperlink = 'www.google.com';
+ const instructions = 'new instructions from judge';
+
+ setup();
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** \n' +
+ 'Dismiss all issues for vacatur\n\n**Detail:** \nnew instructions from judge\n\n' +
+ '**Hyperlink:** \nwww.google.com\n'
+ );
+ });
+ });
+ });
+
+ describe('Case timeline instructions, feature toggle disabled', () => {
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ describe('grant or partial grant instructions sent', () => {
+ it('sends the correct instructions based on grant all disposition', async () => {
+
+ const disposition = 'grant all';
+ let vacateType = 'Vacate and De Novo (2 documents)';
+ let vacateIssues;
+ let hyperlink;
+ const instructions = 'instructions from judge';
+
+ setup({ vacateTypeFeatureToggle: true });
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** ' +
+ '\nFull vacatur' +
+ '\n' +
+ '\n**Detail:** ' +
+ '\ninstructions from judge' +
+ '\n'
+ );
+
+ });
+
+ it('sends the correct instructions based on partially granted disposition', async () => {
+ const disposition = 'Grant partial vacatur';
+ let vacateType = 'Vacate and De Novo (2 documents)';
+ let vacateIssues = '1. This is a description of the decision';
+ let hyperlink;
+ const instructions = 'some instructions from judge';
+
+ setup({ vacateTypeFeatureToggle: true });
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** ' +
+ '\nPartial vacatur' +
+ '\n' +
+ '\n**Detail:** ' +
+ '\nsome instructions from judge' +
+ '\n'
+ );
+ });
+ });
+
+ describe('deny or dismiss instructions sent', () => {
+ it('sends the correct instructions based on denied disposition', async () => {
+ const disposition = 'deny';
+ let vacateType;
+ let vacateIssues;
+ const hyperlink = 'www.caseflow.com';
+ const instructions = 'testing';
+
+ setup({ vacateTypeFeatureToggle: true });
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** \n' +
+ 'Deny all issues for vacatur\n\n' +
+ '**Detail:** \ntesting\n\n' +
+ '**Hyperlink:** \nwww.caseflow.com\n'
+ );
+ });
+
+ it('sends the correct instructions based on dismissed disposition', async () => {
+ const disposition = 'dismiss';
+ let vacateType;
+ let vacateIssues;
+ const hyperlink = 'www.google.com';
+ const instructions = 'new instructions from judge';
+
+ setup({ vacateTypeFeatureToggle: true });
+
+ await fillForm(
+ disposition,
+ vacateType,
+ vacateIssues,
+ hyperlink,
+ instructions
+ );
+
+ expect(onSubmit.mock.calls[0][0].instructions).toMatch(
+ '**Motion To Vacate:** \n' +
+ 'Dismiss all issues for vacatur\n\n**Detail:** \nnew instructions from judge\n\n' +
+ '**Hyperlink:** \nwww.google.com\n'
+ );
+ });
+ });
+ });
});
diff --git a/client/test/app/queue/mtv/__snapshots__/MTVJudgeDisposition.test.js.snap b/client/test/app/queue/mtv/__snapshots__/MTVJudgeDisposition.test.js.snap
index 94920f806ba..01fe1fb42f9 100644
--- a/client/test/app/queue/mtv/__snapshots__/MTVJudgeDisposition.test.js.snap
+++ b/client/test/app/queue/mtv/__snapshots__/MTVJudgeDisposition.test.js.snap
@@ -422,31 +422,77 @@ exports[`MTVJudgeDisposition with denied disposition selected renders correctly
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -456,7 +502,7 @@ exports[`MTVJudgeDisposition with denied disposition selected renders correctly
>
- Provide context and instructions on which issues should be denied
+ Provide context and instructions on which issues should be granted
@@ -470,14 +516,114 @@ exports[`MTVJudgeDisposition with denied disposition selected renders correctly
name="instructions"
/>
+
+
+
+
+
+
+
+
+ Select attorney
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -719,7 +911,7 @@ exports[`MTVJudgeDisposition with dismissed disposition selected renders correct
>
- Provide context and instructions on which issues should be dismissed
+ Provide context and instructions on which issues should be granted
@@ -733,14 +925,114 @@ exports[`MTVJudgeDisposition with dismissed disposition selected renders correct
name="instructions"
/>
+
+
+
+
+
+
+
+
+ Select attorney
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -1478,7 +1729,7 @@ exports[`MTVJudgeDisposition with partially_granted disposition selected renders
>
- Provide context and instructions on which issues should be partially_granted
+ Provide context and instructions on which issues should be granted
diff --git a/client/test/data/queue/taskLists/index.js b/client/test/data/queue/taskLists/index.js
index 08250c39455..1b99fb70dad 100644
--- a/client/test/data/queue/taskLists/index.js
+++ b/client/test/data/queue/taskLists/index.js
@@ -18,7 +18,7 @@ const getAmaTaskTemplate = ({ id = 1 } = {}) => ({
placed_on_hold_at: null,
on_hold_duration: null,
status: null,
- assigned_at: formatISO(sub(new Date(), { hours: 47 })),
+ assigned_at: formatISO(sub(new Date(), { days: 2 })),
closest_regional_office: null,
assigned_to: {
css_id: null,
diff --git a/config/environments/development.rb b/config/environments/development.rb
index d4ef038d7bb..02e591e960e 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -98,6 +98,13 @@
# Notifications page eFolder link
ENV["CLAIM_EVIDENCE_EFOLDER_BASE_URL"] ||= "https://vefs-claimevidence-ui-uat.stage.bip.va.gov"
+ ENV["PACMAN_API_SAML_TOKEN"] ||= "our-saml-token"
+ ENV["PACMAN_API_TOKEN_SECRET"] ||= "client-secret"
+ ENV["PACMAN_API_TOKEN_ALG"] ||= "HS512"
+ ENV["PACMAN_API_TOKEN_ISSUER"] ||= "issuer-of-our-token"
+ ENV["PACMAN_API_SYS_ACCOUNT"] ||= "CSS_ID_OF_OUR_ACCOUNT"
+ ENV["PACMAN_API_URL"] ||= "https://pacman-uat.dev.bip.va.gov/"
+
if ENV["WITH_TEST_EMAIL_SERVER"]
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 1e65fed84ad..89a089dabb7 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -112,4 +112,12 @@
ENV["CLAIM_EVIDENCE_EFOLDER_BASE_URL"] ||= "https://vefs-claimevidence-ui-uat.stage.bip.va.gov"
ENV['TEST_VACOLS_HOST'] ||= "localhost"
+
+ # Pacman environment variables
+ ENV["PACMAN_API_TOKEN_ALG"] ||= "HS512"
+ ENV["PACMAN_API_URL"] ||= "https://pacman-uat.dev.bip.va.gov"
+ ENV["PACMAN_API_SAML_TOKEN"] ||= "our-saml-token"
+ ENV["PACMAN_API_TOKEN_SECRET"] ||= "client-secret"
+ ENV["PACMAN_API_TOKEN_ISSUER"] ||= "issuer-of-our-token"
+ ENV["PACMAN_API_SYS_ACCOUNT"] ||= "CSS_ID_OF_OUR_ACCOUNT"
end
diff --git a/config/initializers/pacman.rb b/config/initializers/pacman.rb
new file mode 100644
index 00000000000..481795e510d
--- /dev/null
+++ b/config/initializers/pacman.rb
@@ -0,0 +1 @@
+PacmanService = (ApplicationController.dependencies_faked? ? Fakes::PacmanService : ExternalApi::PacmanService)
diff --git a/config/routes.rb b/config/routes.rb
index b3390b552a2..81670f08808 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -80,6 +80,7 @@
post 'appeals/:appeal_id/outcode', to: 'appeals#outcode'
get 'appeals/:appeal_id/documents', to: 'appeals#appeal_documents'
get 'appeals/:appeal_id/documents/:document_id', to: 'appeals#appeals_single_document'
+ get 'distributions/:distribution_id', to: 'distributions#distribution'
end
end
end
diff --git a/db/migrate/20230425144000_create_pacman_integration.rb b/db/migrate/20230425144000_create_pacman_integration.rb
new file mode 100644
index 00000000000..8eda26cf944
--- /dev/null
+++ b/db/migrate/20230425144000_create_pacman_integration.rb
@@ -0,0 +1,54 @@
+class CreatePacmanIntegration < Caseflow::Migration
+ def change
+ create_table :vbms_communication_packages do |t|
+ t.string :file_number, comment: "number associated with the documents."
+ t.bigint :copies, default: 1
+ t.string :status
+ t.string :comm_package_name, null: false
+ t.timestamps
+
+ t.references :vbms_uploaded_document, index: true, foreign_key: { to_table: :vbms_uploaded_documents }
+ t.references :created_by , index: true, foreign_key: { to_table: :users }
+ t.references :updated_by, index: true, foreign_key: { to_table: :users }
+ end
+
+ create_table :vbms_distributions do |t|
+ t.string :recipient_type, null: false, comment: "Must be one of [person, organization, ro-colocated, System]."
+ t.string :name, comment: "should only be used for non-person entity names. Not null if [recipient_type] is organization, ro-colocated, or System."
+ t.string :first_name, comment: "recipient's first name. If Type is [person] then it cant be null."
+ t.string :middle_name, comment: "recipient's middle name."
+ t.string :last_name, comment: "recipient's last name. If Type is [person] then it cant be null."
+ t.string :participant_id, comment: "recipient's participant id."
+ t.string :poa_code, comment: "Can't be null if [recipient_type] is ro-colocated. The recipients POA code"
+ t.string :claimant_station_of_jurisdiction, comment: "Can't be null if [recipient_type] is ro-colocated."
+ t.timestamps
+
+ t.references :vbms_communication_package, index: true, foreign_key: { to_table: :vbms_communication_packages }
+ t.references :created_by , index: true, foreign_key: { to_table: :users }
+ t.references :updated_by, index: true, foreign_key: { to_table: :users }
+
+ end
+
+ create_table :vbms_distribution_destinations do |t|
+ t.string :destination_type, null: false, comment: "Must be 'domesticAddress', 'internationalAddress', 'militaryAddress', 'derived', 'email', or 'sms'. Cannot be 'physicalAddress'."
+ t.string :address_line_1, null: false, comment: "PII. If destination_type is domestic, international, or military then Must not be null."
+ t.string :address_line_2, comment: "PII. If treatLine2AsAddressee is [true] then must not be null"
+ t.string :address_line_3, comment: "PII. If treatLine3AsAddressee is [true] then must not be null"
+ t.string :address_line_4, comment: "PII."
+ t.string :address_line_5, comment: "PII."
+ t.string :address_line_6, comment: "PII."
+ t.boolean :treat_line_2_as_addressee
+ t.boolean :treat_line_3_as_addressee, comment: "If true, treatLine2AsAddressee must also be true"
+ t.string :city, comment: "PII. If type is [domestic, international, military] then Must not be null"
+ t.string :state, comment: "PII. Must be exactly two-letter ISO 3166-2 code. If destination_type is domestic or military then Must not be null"
+ t.string :postal_code
+ t.string :country_name
+ t.string :country_code,comment: "Must be exactly two-letter ISO 3166 code."
+ t.timestamps
+
+ t.references :vbms_distribution, index: true, foreign_key: { to_table: :vbms_distributions }
+ t.references :created_by, index: true, foreign_key: { to_table: :users }
+ t.references :updated_by, index: true, foreign_key: { to_table: :users }
+ end
+ end
+end
diff --git a/db/migrate/20230627203547_add_uuid_to_pacman_tables.rb b/db/migrate/20230627203547_add_uuid_to_pacman_tables.rb
new file mode 100644
index 00000000000..743876eeb23
--- /dev/null
+++ b/db/migrate/20230627203547_add_uuid_to_pacman_tables.rb
@@ -0,0 +1,18 @@
+class AddUuidToPacmanTables < Caseflow::Migration
+ def up
+ add_column :vbms_communication_packages,
+ :uuid,
+ :string,
+ comment: "UUID of the communication package in Package Manager (Pacman)"
+
+ add_column :vbms_distributions,
+ :uuid,
+ :string,
+ comment: "UUID of the distrubtion in Package Manager (Pacman)"
+ end
+
+ def down
+ remove_column :vbms_communication_packages, :uuid
+ remove_column :vbms_distributions, :uuid
+ end
+end
diff --git a/db/migrate/20230629172100_add_doc_reference_and_series_ids_to_decision_documents.rb b/db/migrate/20230629172100_add_doc_reference_and_series_ids_to_decision_documents.rb
new file mode 100644
index 00000000000..b2d0f285f2b
--- /dev/null
+++ b/db/migrate/20230629172100_add_doc_reference_and_series_ids_to_decision_documents.rb
@@ -0,0 +1,27 @@
+# Adds columns to the decision_documents table to retain the
+# documentVersionReferenceId and documentSeriesReferenceId values that are
+# returned once a document is uploaded to VBMS eFolder.
+#
+# These values can be used to refer to documents and
+# update documents via the eFolder API.
+#
+
+class AddDocReferenceAndSeriesIdsToDecisionDocuments < Caseflow::Migration
+ def up
+ add_column :decision_documents,
+ :document_version_reference_id,
+ :string,
+ comment: "UUID that is provided by eFolder that represents the specific version of the document."
+
+ add_column :decision_documents,
+ :document_series_reference_id,
+ :string,
+ comment: "UUID that is provided by eFolder that represents the group of documents" \
+ "this document belongs to. Think of a series as a stack of versions."
+ end
+
+ def down
+ remove_column :decision_documents, :document_version_reference_id
+ remove_column :decision_documents, :document_series_reference_id
+ end
+end
diff --git a/db/migrate/20230629183146_add_polymorphic_document_association_to_comm_package_table.rb b/db/migrate/20230629183146_add_polymorphic_document_association_to_comm_package_table.rb
new file mode 100644
index 00000000000..bb14cc52ea1
--- /dev/null
+++ b/db/migrate/20230629183146_add_polymorphic_document_association_to_comm_package_table.rb
@@ -0,0 +1,16 @@
+class AddPolymorphicDocumentAssociationToCommPackageTable < ActiveRecord::Migration[5.2]
+ def change
+ remove_index :vbms_communication_packages, :vbms_uploaded_document_id
+
+ add_reference :vbms_communication_packages, :document_mailable_via_pacman, polymorphic: true, index: false
+
+ VbmsCommunicationPackage.find_each do |vcp|
+ unless vcp.vbms_uploaded_document_id.nil?
+ vcp.update_attribute(:document_mailable_via_pacman_type, "VbmsUploadedDocument")
+ vcp.document_mailable_via_pacman_id = vcp.vbms_uploaded_document_id
+ end
+ end
+
+ safety_assured { remove_column :vbms_communication_packages, :vbms_uploaded_document_id }
+ end
+end
diff --git a/db/migrate/20230629184615_add_index_to_polymorphic_document_association_in_comm_package_table.rb b/db/migrate/20230629184615_add_index_to_polymorphic_document_association_in_comm_package_table.rb
new file mode 100644
index 00000000000..ca7140946ae
--- /dev/null
+++ b/db/migrate/20230629184615_add_index_to_polymorphic_document_association_in_comm_package_table.rb
@@ -0,0 +1,10 @@
+class AddIndexToPolymorphicDocumentAssociationInCommPackageTable < ActiveRecord::Migration[5.2]
+ disable_ddl_transaction!
+
+ def change
+ add_index :vbms_communication_packages,
+ [:document_mailable_via_pacman_type, :document_mailable_via_pacman_id],
+ name: "index_vbms_communication_packages_on_pacman_document_id",
+ algorithm: :concurrently
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f5bf3fde845..0fa2ef9ac77 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2023_05_08_224138) do
+ActiveRecord::Schema.define(version: 2023_06_29_184615) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -571,6 +571,8 @@
t.string "citation_number", null: false, comment: "Unique identifier for decision document"
t.datetime "created_at", null: false
t.date "decision_date", null: false
+ t.string "document_series_reference_id", comment: "UUID that is provided by eFolder that represents the group of documentsthis document belongs to. Think of a series as a stack of versions."
+ t.string "document_version_reference_id", comment: "UUID that is provided by eFolder that represents the specific version of the document."
t.string "error", comment: "Message captured from a failed attempt"
t.datetime "last_submitted_at", comment: "When the job is eligible to run (can be reset to restart the job)"
t.datetime "processed_at", comment: "When the job has concluded"
@@ -1789,6 +1791,68 @@
t.index ["updated_at"], name: "index_users_on_updated_at"
end
+ create_table "vbms_communication_packages", force: :cascade do |t|
+ t.string "comm_package_name", null: false
+ t.bigint "copies", default: 1
+ t.datetime "created_at", null: false
+ t.bigint "created_by_id"
+ t.bigint "document_mailable_via_pacman_id"
+ t.string "document_mailable_via_pacman_type"
+ t.string "file_number", comment: "number associated with the documents."
+ t.string "status"
+ t.datetime "updated_at", null: false
+ t.bigint "updated_by_id"
+ t.string "uuid", comment: "UUID of the communication package in Package Manager (Pacman)"
+ t.index ["created_by_id"], name: "index_vbms_communication_packages_on_created_by_id"
+ t.index ["document_mailable_via_pacman_type", "document_mailable_via_pacman_id"], name: "index_vbms_communication_packages_on_pacman_document_id"
+ t.index ["updated_by_id"], name: "index_vbms_communication_packages_on_updated_by_id"
+ end
+
+ create_table "vbms_distribution_destinations", force: :cascade do |t|
+ t.string "address_line_1", null: false, comment: "PII. If destination_type is domestic, international, or military then Must not be null."
+ t.string "address_line_2", comment: "PII. If treatLine2AsAddressee is [true] then must not be null"
+ t.string "address_line_3", comment: "PII. If treatLine3AsAddressee is [true] then must not be null"
+ t.string "address_line_4", comment: "PII."
+ t.string "address_line_5", comment: "PII."
+ t.string "address_line_6", comment: "PII."
+ t.string "city", comment: "PII. If type is [domestic, international, military] then Must not be null"
+ t.string "country_code", comment: "Must be exactly two-letter ISO 3166 code."
+ t.string "country_name"
+ t.datetime "created_at", null: false
+ t.bigint "created_by_id"
+ t.string "destination_type", null: false, comment: "Must be 'domesticAddress', 'internationalAddress', 'militaryAddress', 'derived', 'email', or 'sms'. Cannot be 'physicalAddress'."
+ t.string "postal_code"
+ t.string "state", comment: "PII. Must be exactly two-letter ISO 3166-2 code. If destination_type is domestic or military then Must not be null"
+ t.boolean "treat_line_2_as_addressee"
+ t.boolean "treat_line_3_as_addressee", comment: "If true, treatLine2AsAddressee must also be true"
+ t.datetime "updated_at", null: false
+ t.bigint "updated_by_id"
+ t.bigint "vbms_distribution_id"
+ t.index ["created_by_id"], name: "index_vbms_distribution_destinations_on_created_by_id"
+ t.index ["updated_by_id"], name: "index_vbms_distribution_destinations_on_updated_by_id"
+ t.index ["vbms_distribution_id"], name: "index_vbms_distribution_destinations_on_vbms_distribution_id"
+ end
+
+ create_table "vbms_distributions", force: :cascade do |t|
+ t.string "claimant_station_of_jurisdiction", comment: "Can't be null if [recipient_type] is ro-colocated."
+ t.datetime "created_at", null: false
+ t.bigint "created_by_id"
+ t.string "first_name", comment: "recipient's first name. If Type is [person] then it cant be null."
+ t.string "last_name", comment: "recipient's last name. If Type is [person] then it cant be null."
+ t.string "middle_name", comment: "recipient's middle name."
+ t.string "name", comment: "should only be used for non-person entity names. Not null if [recipient_type] is organization, ro-colocated, or System."
+ t.string "participant_id", comment: "recipient's participant id."
+ t.string "poa_code", comment: "Can't be null if [recipient_type] is ro-colocated. The recipients POA code"
+ t.string "recipient_type", null: false, comment: "Must be one of [person, organization, ro-colocated, System]."
+ t.datetime "updated_at", null: false
+ t.bigint "updated_by_id"
+ t.string "uuid", comment: "UUID of the distrubtion in Package Manager (Pacman)"
+ t.bigint "vbms_communication_package_id"
+ t.index ["created_by_id"], name: "index_vbms_distributions_on_created_by_id"
+ t.index ["updated_by_id"], name: "index_vbms_distributions_on_updated_by_id"
+ t.index ["vbms_communication_package_id"], name: "index_vbms_distributions_on_vbms_communication_package_id"
+ end
+
create_table "vbms_uploaded_documents", force: :cascade do |t|
t.bigint "appeal_id", comment: "Appeal/LegacyAppeal ID; use as FK to appeals/legacy_appeals"
t.string "appeal_type", comment: "'Appeal' or 'LegacyAppeal'"
@@ -2070,6 +2134,14 @@
add_foreign_key "unrecognized_appellants", "users", column: "created_by_id"
add_foreign_key "user_quotas", "team_quotas"
add_foreign_key "user_quotas", "users"
+ add_foreign_key "vbms_communication_packages", "users", column: "created_by_id"
+ add_foreign_key "vbms_communication_packages", "users", column: "updated_by_id"
+ add_foreign_key "vbms_distribution_destinations", "users", column: "created_by_id"
+ add_foreign_key "vbms_distribution_destinations", "users", column: "updated_by_id"
+ add_foreign_key "vbms_distribution_destinations", "vbms_distributions"
+ add_foreign_key "vbms_distributions", "users", column: "created_by_id"
+ add_foreign_key "vbms_distributions", "users", column: "updated_by_id"
+ add_foreign_key "vbms_distributions", "vbms_communication_packages"
add_foreign_key "virtual_hearing_establishments", "virtual_hearings"
add_foreign_key "virtual_hearings", "users", column: "created_by_id"
add_foreign_key "virtual_hearings", "users", column: "updated_by_id"
diff --git a/db/scripts/audit/add_row_to_appeal_states_audit_table_function.rb b/db/scripts/audit/add_row_to_appeal_states_audit_table_function.rb
deleted file mode 100644
index ec98d929ba0..00000000000
--- a/db/scripts/audit/add_row_to_appeal_states_audit_table_function.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require "pg"
-
-conn = CaseflowRecord.connection
-conn.execute(
- "create or replace function caseflow_audit.add_row_to_appeal_states_audit() returns trigger
- as
- $appeal_states_audit$
- begin
- if (TG_OP = 'DELETE') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'D', OLD.*;
- elsif (TG_OP = 'UPDATE') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'U', NEW.*;
- elsif (TG_OP = 'INSERT') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'I', NEW.*;
- end if;
- return null;
- end;
- $appeal_states_audit$
- language plpgsql;"
-)
diff --git a/db/scripts/audit/add_row_to_appeal_states_audit_table_function.sql b/db/scripts/audit/add_row_to_appeal_states_audit_table_function.sql
deleted file mode 100644
index d3cb5a534d5..00000000000
--- a/db/scripts/audit/add_row_to_appeal_states_audit_table_function.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-create or replace function caseflow_audit.add_row_to_appeal_states_audit() returns trigger
-as
-$appeal_states_audit$
-begin
- if (TG_OP = 'DELETE') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'D', OLD.*;
- elsif (TG_OP = 'UPDATE') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'U', NEW.*;
- elsif (TG_OP = 'INSERT') then
- insert into caseflow_audit.appeal_states_audit select nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass), 'I', NEW.*;
- end if;
- return null;
-end;
-$appeal_states_audit$
-language plpgsql;
\ No newline at end of file
diff --git a/db/scripts/audit/create_caseflow_audit_schema.rb b/db/scripts/audit/create_caseflow_audit_schema.rb
index 034de127e11..fc70b54e1a8 100644
--- a/db/scripts/audit/create_caseflow_audit_schema.rb
+++ b/db/scripts/audit/create_caseflow_audit_schema.rb
@@ -4,3 +4,4 @@
conn = CaseflowRecord.connection
conn.execute("create schema caseflow_audit;")
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.rb b/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.rb
new file mode 100644
index 00000000000..d729b2a7111
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create or replace function caseflow_audit.add_row_to_appeal_states_audit() returns trigger
+ as
+ $add_row$
+ begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.appeal_cancelled,
+ OLD.appeal_docketed,
+ OLD.appeal_id,
+ OLD.appeal_type,
+ OLD.created_at,
+ OLD.created_by_id,
+ OLD.decision_mailed,
+ OLD.hearing_postponed,
+ OLD.hearing_scheduled,
+ OLD.hearing_withdrawn,
+ OLD.privacy_act_complete,
+ OLD.privacy_act_pending,
+ OLD.scheduled_in_error,
+ OLD.updated_at,
+ OLD.updated_by_id,
+ OLD.vso_ihp_complete,
+ OLD.vso_ihp_pending;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.appeal_cancelled,
+ NEW.appeal_docketed,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.created_at,
+ NEW.created_by_id,
+ NEW.decision_mailed,
+ NEW.hearing_postponed,
+ NEW.hearing_scheduled,
+ NEW.hearing_withdrawn,
+ NEW.privacy_act_complete,
+ NEW.privacy_act_pending,
+ NEW.scheduled_in_error,
+ NEW.updated_at,
+ NEW.updated_by_id,
+ NEW.vso_ihp_complete,
+ NEW.vso_ihp_pending;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.appeal_cancelled,
+ NEW.appeal_docketed,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.created_at,
+ NEW.created_by_id,
+ NEW.decision_mailed,
+ NEW.hearing_postponed,
+ NEW.hearing_scheduled,
+ NEW.hearing_withdrawn,
+ NEW.privacy_act_complete,
+ NEW.privacy_act_pending,
+ NEW.scheduled_in_error,
+ NEW.updated_at,
+ NEW.updated_by_id,
+ NEW.vso_ihp_complete,
+ NEW.vso_ihp_pending;
+ end if;
+ return null;
+ end;
+ $add_row$
+ language plpgsql;"
+)
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.sql b/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.sql
new file mode 100644
index 00000000000..22c40f0218d
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_appeal_states_audit_table_function.sql
@@ -0,0 +1,78 @@
+create or replace function caseflow_audit.add_row_to_appeal_states_audit() returns trigger
+as
+$add_row$
+begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.appeal_cancelled,
+ OLD.appeal_docketed,
+ OLD.appeal_id,
+ OLD.appeal_type,
+ OLD.created_at,
+ OLD.created_by_id,
+ OLD.decision_mailed,
+ OLD.hearing_postponed,
+ OLD.hearing_scheduled,
+ OLD.hearing_withdrawn,
+ OLD.privacy_act_complete,
+ OLD.privacy_act_pending,
+ OLD.scheduled_in_error,
+ OLD.updated_at,
+ OLD.updated_by_id,
+ OLD.vso_ihp_complete,
+ OLD.vso_ihp_pending;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.appeal_cancelled,
+ NEW.appeal_docketed,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.created_at,
+ NEW.created_by_id,
+ NEW.decision_mailed,
+ NEW.hearing_postponed,
+ NEW.hearing_scheduled,
+ NEW.hearing_withdrawn,
+ NEW.privacy_act_complete,
+ NEW.privacy_act_pending,
+ NEW.scheduled_in_error,
+ NEW.updated_at,
+ NEW.updated_by_id,
+ NEW.vso_ihp_complete,
+ NEW.vso_ihp_pending;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.appeal_states_audit
+ select
+ nextval('caseflow_audit.appeal_states_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.appeal_cancelled,
+ NEW.appeal_docketed,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.created_at,
+ NEW.created_by_id,
+ NEW.decision_mailed,
+ NEW.hearing_postponed,
+ NEW.hearing_scheduled,
+ NEW.hearing_withdrawn,
+ NEW.privacy_act_complete,
+ NEW.privacy_act_pending,
+ NEW.scheduled_in_error,
+ NEW.updated_at,
+ NEW.updated_by_id,
+ NEW.vso_ihp_complete,
+ NEW.vso_ihp_pending;
+ end if;
+ return null;
+end;
+$add_row$
+language plpgsql;
diff --git a/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.rb b/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.rb
new file mode 100644
index 00000000000..8a0b5105fa6
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create or replace function caseflow_audit.add_row_to_vbms_communication_packages_audit() returns trigger
+ as
+ $add_row$
+ begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.file_number,
+ OLD.copies,
+ OLD.status,
+ OLD.comm_package_name,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.document_mailable_via_pacman_id,
+ OLD.document_mailable_via_pacman_type,
+ OLD.created_by_id,
+ OLD.updated_by_id,
+ OLD.uuid;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.file_number,
+ NEW.copies,
+ NEW.status,
+ NEW.comm_package_name,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.document_mailable_via_pacman_id,
+ NEW.document_mailable_via_pacman_type,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.file_number,
+ NEW.copies,
+ NEW.status,
+ NEW.comm_package_name,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.document_mailable_via_pacman_id,
+ NEW.document_mailable_via_pacman_type,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ end if;
+ return null;
+ end;
+ $add_row$
+ language plpgsql;"
+)
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.sql b/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.sql
new file mode 100644
index 00000000000..fd8841b5bf9
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_communication_packages_audit_table_function.sql
@@ -0,0 +1,60 @@
+create or replace function caseflow_audit.add_row_to_vbms_communication_packages_audit() returns trigger
+as
+$add_row$
+begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.file_number,
+ OLD.copies,
+ OLD.status,
+ OLD.comm_package_name,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.document_mailable_via_pacman_id,
+ OLD.document_mailable_via_pacman_type,
+ OLD.created_by_id,
+ OLD.updated_by_id,
+ OLD.uuid;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.file_number,
+ NEW.copies,
+ NEW.status,
+ NEW.comm_package_name,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.document_mailable_via_pacman_id,
+ NEW.document_mailable_via_pacman_type,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_communication_packages_audit
+ select
+ nextval('caseflow_audit.vbms_communication_packages_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.file_number,
+ NEW.copies,
+ NEW.status,
+ NEW.comm_package_name,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.document_mailable_via_pacman_id,
+ NEW.document_mailable_via_pacman_type,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ end if;
+ return null;
+end;
+$add_row$
+language plpgsql;
diff --git a/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.rb b/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.rb
new file mode 100644
index 00000000000..b84153084ef
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create or replace function caseflow_audit.add_row_to_vbms_distribution_destinations_audit() returns trigger
+ as
+ $add_row$
+ begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.destination_type,
+ OLD.address_line_1,
+ OLD.address_line_2,
+ OLD.address_line_3,
+ OLD.address_line_4,
+ OLD.address_line_5,
+ OLD.address_line_6,
+ OLD.treat_line_2_as_addressee,
+ OLD.treat_line_3_as_addressee,
+ OLD.city,
+ OLD.state,
+ OLD.postal_code,
+ OLD.country_name,
+ OLD.country_code,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.vbms_distribution_id,
+ OLD.created_by_id,
+ OLD.updated_by_id;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.destination_type,
+ NEW.address_line_1,
+ NEW.address_line_2,
+ NEW.address_line_3,
+ NEW.address_line_4,
+ NEW.address_line_5,
+ NEW.address_line_6,
+ NEW.treat_line_2_as_addressee,
+ NEW.treat_line_3_as_addressee,
+ NEW.city,
+ NEW.state,
+ NEW.postal_code,
+ NEW.country_name,
+ NEW.country_code,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_distribution_id,
+ NEW.created_by_id,
+ NEW.updated_by_id;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.destination_type,
+ NEW.address_line_1,
+ NEW.address_line_2,
+ NEW.address_line_3,
+ NEW.address_line_4,
+ NEW.address_line_5,
+ NEW.address_line_6,
+ NEW.treat_line_2_as_addressee,
+ NEW.treat_line_3_as_addressee,
+ NEW.city,
+ NEW.state,
+ NEW.postal_code,
+ NEW.country_name,
+ NEW.country_code,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_distribution_id,
+ NEW.created_by_id,
+ NEW.updated_by_id;
+ end if;
+ return null;
+ end;
+ $add_row$
+ language plpgsql;"
+)
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.sql b/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.sql
new file mode 100644
index 00000000000..6c8f9b1aa9e
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_distribution_destinations_audit_table_function.sql
@@ -0,0 +1,84 @@
+create or replace function caseflow_audit.add_row_to_vbms_distribution_destinations_audit() returns trigger
+as
+$add_row$
+begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.destination_type,
+ OLD.address_line_1,
+ OLD.address_line_2,
+ OLD.address_line_3,
+ OLD.address_line_4,
+ OLD.address_line_5,
+ OLD.address_line_6,
+ OLD.treat_line_2_as_addressee,
+ OLD.treat_line_3_as_addressee,
+ OLD.city,
+ OLD.state,
+ OLD.postal_code,
+ OLD.country_name,
+ OLD.country_code,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.vbms_distribution_id,
+ OLD.created_by_id,
+ OLD.updated_by_id;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.destination_type,
+ NEW.address_line_1,
+ NEW.address_line_2,
+ NEW.address_line_3,
+ NEW.address_line_4,
+ NEW.address_line_5,
+ NEW.address_line_6,
+ NEW.treat_line_2_as_addressee,
+ NEW.treat_line_3_as_addressee,
+ NEW.city,
+ NEW.state,
+ NEW.postal_code,
+ NEW.country_name,
+ NEW.country_code,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_distribution_id,
+ NEW.created_by_id,
+ NEW.updated_by_id;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_distribution_destinations_audit
+ select
+ nextval('caseflow_audit.vbms_distribution_destinations_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.destination_type,
+ NEW.address_line_1,
+ NEW.address_line_2,
+ NEW.address_line_3,
+ NEW.address_line_4,
+ NEW.address_line_5,
+ NEW.address_line_6,
+ NEW.treat_line_2_as_addressee,
+ NEW.treat_line_3_as_addressee,
+ NEW.city,
+ NEW.state,
+ NEW.postal_code,
+ NEW.country_name,
+ NEW.country_code,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_distribution_id,
+ NEW.created_by_id,
+ NEW.updated_by_id;
+ end if;
+ return null;
+end;
+$add_row$
+language plpgsql;
diff --git a/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.rb b/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.rb
new file mode 100644
index 00000000000..93f1d2e4fe9
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create or replace function caseflow_audit.add_row_to_vbms_distributions_audit() returns trigger
+ as
+ $add_row$
+ begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.recipient_type,
+ OLD.name,
+ OLD.first_name,
+ OLD.middle_name,
+ OLD.last_name,
+ OLD.participant_id,
+ OLD.poa_code,
+ OLD.claimant_station_of_jurisdiction,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.vbms_communication_package_id,
+ OLD.created_by_id,
+ OLD.updated_by_id,
+ OLD.uuid;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.recipient_type,
+ NEW.name,
+ NEW.first_name,
+ NEW.middle_name,
+ NEW.last_name,
+ NEW.participant_id,
+ NEW.poa_code,
+ NEW.claimant_station_of_jurisdiction,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_communication_package_id,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.recipient_type,
+ NEW.name,
+ NEW.first_name,
+ NEW.middle_name,
+ NEW.last_name,
+ NEW.participant_id,
+ NEW.poa_code,
+ NEW.claimant_station_of_jurisdiction,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_communication_package_id,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ end if;
+ return null;
+ end;
+ $add_row$
+ language plpgsql;"
+)
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.sql b/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.sql
new file mode 100644
index 00000000000..b5b74937baf
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_distributions_audit_table_function.sql
@@ -0,0 +1,69 @@
+create or replace function caseflow_audit.add_row_to_vbms_distributions_audit() returns trigger
+as
+$add_row$
+begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.recipient_type,
+ OLD.name,
+ OLD.first_name,
+ OLD.middle_name,
+ OLD.last_name,
+ OLD.participant_id,
+ OLD.poa_code,
+ OLD.claimant_station_of_jurisdiction,
+ OLD.created_at,
+ OLD.updated_at,
+ OLD.vbms_communication_package_id,
+ OLD.created_by_id,
+ OLD.updated_by_id,
+ OLD.uuid;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.recipient_type,
+ NEW.name,
+ NEW.first_name,
+ NEW.middle_name,
+ NEW.last_name,
+ NEW.participant_id,
+ NEW.poa_code,
+ NEW.claimant_station_of_jurisdiction,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_communication_package_id,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_distributions_audit
+ select
+ nextval('caseflow_audit.vbms_distributions_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.recipient_type,
+ NEW.name,
+ NEW.first_name,
+ NEW.middle_name,
+ NEW.last_name,
+ NEW.participant_id,
+ NEW.poa_code,
+ NEW.claimant_station_of_jurisdiction,
+ NEW.created_at,
+ NEW.updated_at,
+ NEW.vbms_communication_package_id,
+ NEW.created_by_id,
+ NEW.updated_by_id,
+ NEW.uuid;
+ end if;
+ return null;
+end;
+$add_row$
+language plpgsql;
diff --git a/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.rb b/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.rb
new file mode 100644
index 00000000000..1b13c9a74f4
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create or replace function caseflow_audit.add_row_to_vbms_uploaded_documents_audit() returns trigger
+ as
+ $add_row$
+ begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.appeal_id,
+ OLD.appeal_type,
+ OLD.attempted_at,
+ OLD.canceled_at,
+ OLD.created_at,
+ OLD.document_name,
+ OLD.document_series_reference_id,
+ OLD.document_subject,
+ OLD.document_type,
+ OLD.document_version_reference_id,
+ OLD.error,
+ OLD.last_submitted_at,
+ OLD.processed_at,
+ OLD.submitted_at,
+ OLD.updated_at,
+ OLD.uploaded_to_vbms_at,
+ OLD.veteran_file_number;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.attempted_at,
+ NEW.canceled_at,
+ NEW.created_at,
+ NEW.document_name,
+ NEW.document_series_reference_id,
+ NEW.document_subject,
+ NEW.document_type,
+ NEW.document_version_reference_id,
+ NEW.error,
+ NEW.last_submitted_at,
+ NEW.processed_at,
+ NEW.submitted_at,
+ NEW.updated_at,
+ NEW.uploaded_to_vbms_at,
+ NEW.veteran_file_number;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.attempted_at,
+ NEW.canceled_at,
+ NEW.created_at,
+ NEW.document_name,
+ NEW.document_series_reference_id,
+ NEW.document_subject,
+ NEW.document_type,
+ NEW.document_version_reference_id,
+ NEW.error,
+ NEW.last_submitted_at,
+ NEW.processed_at,
+ NEW.submitted_at,
+ NEW.updated_at,
+ NEW.uploaded_to_vbms_at,
+ NEW.veteran_file_number;
+ end if;
+ return null;
+ end;
+ $add_row$
+ language plpgsql;"
+)
+conn.close
diff --git a/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.sql b/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.sql
new file mode 100644
index 00000000000..2b366270a34
--- /dev/null
+++ b/db/scripts/audit/functions/add_row_to_vbms_uploaded_documents_audit_table_function.sql
@@ -0,0 +1,78 @@
+create or replace function caseflow_audit.add_row_to_vbms_uploaded_documents_audit() returns trigger
+as
+$add_row$
+begin
+ if (TG_OP = 'DELETE') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'D',
+ OLD.id,
+ OLD.appeal_id,
+ OLD.appeal_type,
+ OLD.attempted_at,
+ OLD.canceled_at,
+ OLD.created_at,
+ OLD.document_name,
+ OLD.document_series_reference_id,
+ OLD.document_subject,
+ OLD.document_type,
+ OLD.document_version_reference_id,
+ OLD.error,
+ OLD.last_submitted_at,
+ OLD.processed_at,
+ OLD.submitted_at,
+ OLD.updated_at,
+ OLD.uploaded_to_vbms_at,
+ OLD.veteran_file_number;
+ elsif (TG_OP = 'UPDATE') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'U',
+ NEW.id,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.attempted_at,
+ NEW.canceled_at,
+ NEW.created_at,
+ NEW.document_name,
+ NEW.document_series_reference_id,
+ NEW.document_subject,
+ NEW.document_type,
+ NEW.document_version_reference_id,
+ NEW.error,
+ NEW.last_submitted_at,
+ NEW.processed_at,
+ NEW.submitted_at,
+ NEW.updated_at,
+ NEW.uploaded_to_vbms_at,
+ NEW.veteran_file_number;
+ elsif (TG_OP = 'INSERT') then
+ insert into caseflow_audit.vbms_uploaded_documents_audit
+ select
+ nextval('caseflow_audit.vbms_uploaded_documents_audit_id_seq'::regclass),
+ 'I',
+ NEW.id,
+ NEW.appeal_id,
+ NEW.appeal_type,
+ NEW.attempted_at,
+ NEW.canceled_at,
+ NEW.created_at,
+ NEW.document_name,
+ NEW.document_series_reference_id,
+ NEW.document_subject,
+ NEW.document_type,
+ NEW.document_version_reference_id,
+ NEW.error,
+ NEW.last_submitted_at,
+ NEW.processed_at,
+ NEW.submitted_at,
+ NEW.updated_at,
+ NEW.uploaded_to_vbms_at,
+ NEW.veteran_file_number;
+ end if;
+ return null;
+end;
+$add_row$
+language plpgsql;
diff --git a/db/scripts/audit/pacman_integration_teardown.sql b/db/scripts/audit/pacman_integration_teardown.sql
new file mode 100644
index 00000000000..386008b3bb0
--- /dev/null
+++ b/db/scripts/audit/pacman_integration_teardown.sql
@@ -0,0 +1,12 @@
+drop trigger vbms_communication_packages_audit_trigger;
+drop trigger vbms_distributions_audit_trigger;
+drop trigger vbms_distribution_destinations_audit_trigger;
+drop trigger vbms_uploaded_documents_audit_trigger;
+drop function caseflow_audit.add_row_to_vbms_communication_packages_audit
+drop function caseflow_audit.add_row_to_vbms_distributions_audit
+drop function caseflow_audit.add_row_to_vbms_distribution_destinations_audit
+drop function caseflow_audit.add_row_to_vbms_uploaded_documents_audit
+drop table caseflow_audit.vbms_communication_packages_audit;
+drop table caseflow_audit.vbms_distributions_audit;
+drop table caseflow_audit.vbms_distribution_destinations_audit;
+drop table caseflow_audit.vbms_uploaded_documents_audit;
diff --git a/db/scripts/audit/remove_caseflow_audit_schema.rb b/db/scripts/audit/remove_caseflow_audit_schema.rb
index 78df1a206aa..344617e8aa8 100644
--- a/db/scripts/audit/remove_caseflow_audit_schema.rb
+++ b/db/scripts/audit/remove_caseflow_audit_schema.rb
@@ -6,3 +6,4 @@
conn.execute(
"drop schema IF EXISTS caseflow_audit CASCADE;"
)
+conn.close
diff --git a/db/scripts/audit/create_appeal_states_audit.rb b/db/scripts/audit/tables/create_appeal_states_audit.rb
similarity index 98%
rename from db/scripts/audit/create_appeal_states_audit.rb
rename to db/scripts/audit/tables/create_appeal_states_audit.rb
index b72634dabc8..6a8c61dc23a 100644
--- a/db/scripts/audit/create_appeal_states_audit.rb
+++ b/db/scripts/audit/tables/create_appeal_states_audit.rb
@@ -25,3 +25,4 @@
vso_ihp_complete boolean not null,
vso_ihp_pending boolean not null
);")
+conn.close
diff --git a/db/scripts/audit/create_appeal_states_audit.sql b/db/scripts/audit/tables/create_appeal_states_audit.sql
similarity index 100%
rename from db/scripts/audit/create_appeal_states_audit.sql
rename to db/scripts/audit/tables/create_appeal_states_audit.sql
diff --git a/db/scripts/audit/tables/create_vbms_communication_packages_audit.rb b/db/scripts/audit/tables/create_vbms_communication_packages_audit.rb
new file mode 100644
index 00000000000..bf0b979682e
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_communication_packages_audit.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute("create table caseflow_audit.vbms_communication_packages_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_communication_package_id bigint not null,
+ file_number varchar NULL,
+ copies int8 NULL DEFAULT 1,
+ status varchar NULL,
+ comm_package_name varchar NOT NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ document_mailable_via_pacman_id bigint not NULL,
+ document_mailable_via_pacman_type varchar not NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL,
+ uuid varchar NULL
+ );")
+conn.close
diff --git a/db/scripts/audit/tables/create_vbms_communication_packages_audit.sql b/db/scripts/audit/tables/create_vbms_communication_packages_audit.sql
new file mode 100644
index 00000000000..87a778d05d1
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_communication_packages_audit.sql
@@ -0,0 +1,16 @@
+create table caseflow_audit.vbms_communication_packages_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_communication_package_id bigint not null,
+ file_number varchar NULL,
+ copies int8 NULL DEFAULT 1,
+ status varchar NULL,
+ comm_package_name varchar NOT NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ document_mailable_via_pacman_id bigint not NULL,
+ document_mailable_via_pacman_type varchar not NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL,
+ uuid varchar NULL
+ );
diff --git a/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.rb b/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.rb
new file mode 100644
index 00000000000..ec1e1e3973b
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute("create table caseflow_audit.vbms_distribution_destinations_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_distribution_destinations_id bigint not null,
+ destination_type varchar NOT NULL,
+ address_line_1 varchar NOT NULL,
+ address_line_2 varchar NULL,
+ address_line_3 varchar NULL,
+ address_line_4 varchar NULL,
+ address_line_5 varchar NULL,
+ address_line_6 varchar NULL,
+ treat_line_2_as_addressee bool NULL,
+ treat_line_3_as_addressee bool NULL,
+ city varchar NULL,
+ state varchar NULL,
+ postal_code varchar NULL,
+ country_name varchar NULL,
+ country_code varchar NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ vbms_distribution_id int8 NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL
+ );")
+conn.close
diff --git a/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.sql b/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.sql
new file mode 100644
index 00000000000..bc27fea7588
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_distribution_destinations_audit.sql
@@ -0,0 +1,24 @@
+create table caseflow_audit.vbms_distribution_destinations_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_distribution_destinations_id bigint not null,
+ destination_type varchar NOT NULL,
+ address_line_1 varchar NOT NULL,
+ address_line_2 varchar NULL,
+ address_line_3 varchar NULL,
+ address_line_4 varchar NULL,
+ address_line_5 varchar NULL,
+ address_line_6 varchar NULL,
+ treat_line_2_as_addressee bool NULL,
+ treat_line_3_as_addressee bool NULL,
+ city varchar NULL,
+ state varchar NULL,
+ postal_code varchar NULL,
+ country_name varchar NULL,
+ country_code varchar NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ vbms_distribution_id int8 NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL
+ );
diff --git a/db/scripts/audit/tables/create_vbms_distributions_audit.rb b/db/scripts/audit/tables/create_vbms_distributions_audit.rb
new file mode 100644
index 00000000000..c8ddddc7879
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_distributions_audit.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute("create table caseflow_audit.vbms_distributions_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_distributions_id bigint not null,
+ recipient_type varchar NOT NULL,
+ name varchar NULL,
+ first_name varchar NULL,
+ middle_name varchar NULL,
+ last_name varchar NULL,
+ participant_id varchar NULL,
+ poa_code varchar NULL,
+ claimant_station_of_jurisdiction varchar NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ vbms_communication_package_id int8 NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL,
+ uuid varchar NULL
+ );")
+conn.close
diff --git a/db/scripts/audit/tables/create_vbms_distributions_audit.sql b/db/scripts/audit/tables/create_vbms_distributions_audit.sql
new file mode 100644
index 00000000000..817c9ff2d73
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_distributions_audit.sql
@@ -0,0 +1,19 @@
+create table caseflow_audit.vbms_distributions_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_distributions_id bigint not null,
+ recipient_type varchar NOT NULL,
+ name varchar NULL,
+ first_name varchar NULL,
+ middle_name varchar NULL,
+ last_name varchar NULL,
+ participant_id varchar NULL,
+ poa_code varchar NULL,
+ claimant_station_of_jurisdiction varchar NULL,
+ created_at timestamp NOT NULL,
+ updated_at timestamp NOT NULL,
+ vbms_communication_package_id int8 NULL,
+ created_by_id int8 NULL,
+ updated_by_id int8 NULL,
+ uuid varchar NULL
+ );
diff --git a/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.rb b/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.rb
new file mode 100644
index 00000000000..34d53368952
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute("create table caseflow_audit.vbms_uploaded_documents_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_uploaded_documents_id bigint not null,
+ appeal_id int8 NULL,
+ appeal_type varchar NULL,
+ attempted_at timestamp NULL,
+ canceled_at timestamp NULL,
+ created_at timestamp NOT NULL,
+ document_name varchar NULL,
+ document_series_reference_id varchar NULL,
+ document_subject varchar NULL,
+ document_type varchar NOT NULL,
+ document_version_reference_id varchar NULL,
+ error varchar NULL,
+ last_submitted_at timestamp NULL,
+ processed_at timestamp NULL,
+ submitted_at timestamp NULL,
+ updated_at timestamp NOT NULL,
+ uploaded_to_vbms_at timestamp NULL,
+ veteran_file_number varchar NULL
+ );")
+conn.close
diff --git a/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.sql b/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.sql
new file mode 100644
index 00000000000..8c467e9851d
--- /dev/null
+++ b/db/scripts/audit/tables/create_vbms_uploaded_documents_audit.sql
@@ -0,0 +1,22 @@
+create table caseflow_audit.vbms_uploaded_documents_audit (
+ id BIGSERIAL PRIMARY KEY,
+ type_of_change CHAR(1) not null,
+ vbms_uploaded_documents_id bigint not null,
+ appeal_id int8 NULL,
+ appeal_type varchar NULL,
+ attempted_at timestamp NULL,
+ canceled_at timestamp NULL,
+ created_at timestamp NOT NULL,
+ document_name varchar NULL,
+ document_series_reference_id varchar NULL,
+ document_subject varchar NULL,
+ document_type varchar NOT NULL,
+ document_version_reference_id varchar NULL,
+ error varchar NULL,
+ last_submitted_at timestamp NULL,
+ processed_at timestamp NULL,
+ submitted_at timestamp NULL,
+ updated_at timestamp NOT NULL,
+ uploaded_to_vbms_at timestamp NULL,
+ veteran_file_number varchar NULL
+ );
diff --git a/db/scripts/audit/create_appeal_states_audit_trigger.rb b/db/scripts/audit/triggers/create_appeal_states_audit_trigger.rb
similarity index 96%
rename from db/scripts/audit/create_appeal_states_audit_trigger.rb
rename to db/scripts/audit/triggers/create_appeal_states_audit_trigger.rb
index 29b9b32a5a6..08c226671ce 100644
--- a/db/scripts/audit/create_appeal_states_audit_trigger.rb
+++ b/db/scripts/audit/triggers/create_appeal_states_audit_trigger.rb
@@ -9,3 +9,4 @@
for each row
execute procedure caseflow_audit.add_row_to_appeal_states_audit();"
)
+conn.close
diff --git a/db/scripts/audit/create_appeal_states_audit_trigger.sql b/db/scripts/audit/triggers/create_appeal_states_audit_trigger.sql
similarity index 100%
rename from db/scripts/audit/create_appeal_states_audit_trigger.sql
rename to db/scripts/audit/triggers/create_appeal_states_audit_trigger.sql
diff --git a/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.rb b/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.rb
new file mode 100644
index 00000000000..fee24fae47b
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create trigger vbms_communication_packages_audit_trigger
+ after insert or update or delete on public.vbms_communication_packages
+ for each row
+ execute procedure caseflow_audit.add_row_to_vbms_communication_packages_audit();"
+)
+conn.close
diff --git a/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.sql b/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.sql
new file mode 100644
index 00000000000..6628fc6661b
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_communication_packages_audit_trigger.sql
@@ -0,0 +1,4 @@
+create trigger vbms_communication_packages_audit_trigger
+after insert or update or delete on public.vbms_communication_packages
+for each row
+execute procedure caseflow_audit.add_row_to_vbms_communication_packages_audit();
diff --git a/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.rb b/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.rb
new file mode 100644
index 00000000000..d118a5e982f
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create trigger vbms_distribution_destinations_audit_trigger
+ after insert or update or delete on public.vbms_distribution_destinations
+ for each row
+ execute procedure caseflow_audit.add_row_to_vbms_distribution_destinations_audit();"
+)
+conn.close
diff --git a/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.sql b/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.sql
new file mode 100644
index 00000000000..506a3703b07
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_distribution_destinations_audit_trigger.sql
@@ -0,0 +1,4 @@
+create trigger vbms_distribution_destinations_audit_trigger
+after insert or update or delete on public.vbms_distribution_destinations
+for each row
+execute procedure caseflow_audit.add_row_to_vbms_distribution_destinations_audit();
diff --git a/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.rb b/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.rb
new file mode 100644
index 00000000000..77a47db6060
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create trigger vbms_distributions_audit_trigger
+ after insert or update or delete on public.vbms_distributions
+ for each row
+ execute procedure caseflow_audit.add_row_to_vbms_distributions_audit();"
+)
+conn.close
diff --git a/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.sql b/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.sql
new file mode 100644
index 00000000000..03c9f8eecd6
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_distributions_audit_trigger.sql
@@ -0,0 +1,4 @@
+create trigger vbms_distributions_audit_trigger
+after insert or update or delete on public.vbms_distributions
+for each row
+execute procedure caseflow_audit.add_row_to_vbms_distributions_audit();
diff --git a/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.rb b/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.rb
new file mode 100644
index 00000000000..6856ec44376
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "pg"
+
+conn = CaseflowRecord.connection
+conn.execute(
+ "create trigger vbms_uploaded_documents_audit_trigger
+ after insert or update or delete on public.vbms_uploaded_documents
+ for each row
+ execute procedure caseflow_audit.add_row_to_vbms_uploaded_documents_audit();"
+)
+conn.close
diff --git a/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.sql b/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.sql
new file mode 100644
index 00000000000..5ae25d271e5
--- /dev/null
+++ b/db/scripts/audit/triggers/create_vbms_uploaded_documents_audit_trigger.sql
@@ -0,0 +1,4 @@
+create trigger vbms_uploaded_documents_audit_trigger
+after insert or update or delete on public.vbms_uploaded_documents
+for each row
+execute procedure caseflow_audit.add_row_to_vbms_uploaded_documents_audit();
diff --git a/lib/caseflow/error.rb b/lib/caseflow/error.rb
index cc884eb3af4..df77673dd3b 100644
--- a/lib/caseflow/error.rb
+++ b/lib/caseflow/error.rb
@@ -340,6 +340,7 @@ class MustImplementInSubclass < StandardError; end
class AttributeNotLoaded < StandardError; end
class VeteranNotFound < StandardError; end
class AppealNotFound < StandardError; end
+ class MissingRecipientInfo < StandardError; end
class EstablishClaimFailedInVBMS < StandardError
attr_reader :error_code
@@ -447,4 +448,14 @@ class VANotifyNotFoundError < VANotifyApiError; end
class VANotifyInternalServerError < VANotifyApiError; end
class VANotifyRateLimitError < VANotifyApiError; end
class EmptyQueueError < StandardError; end
+
+ # Pacman errors
+ class PacmanApiError < StandardError
+ include Caseflow::Error::ErrorSerializer
+ attr_accessor :code, :message
+ end
+ class PacmanBadRequestError < PacmanApiError; end
+ class PacmanForbiddenError < PacmanApiError; end
+ class PacmanNotFoundError < PacmanApiError; end
+ class PacmanInternalServerError < PacmanApiError; end
end
diff --git a/lib/fakes/pacman_service.rb b/lib/fakes/pacman_service.rb
new file mode 100644
index 00000000000..9da83eedef7
--- /dev/null
+++ b/lib/fakes/pacman_service.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+class Fakes::PacmanService < ExternalApi::PacmanService
+ COMMUNICATION_PACKAGE_UUID = "24eb6a66-3833-4de6-bea4-4b614e55d5ac"
+ DISTRIBUTION_UUID = "201cef13-49ba-4f40-8741-97d06cee0270"
+
+ class << self
+ def send_communication_package_request(file_number, name, document_references)
+ fake_package_request(file_number, name, document_references)
+ end
+
+ def send_distribution_request(package_id, recipient, destinations)
+ [fake_distribution_request(package_id, recipient, destinations)]
+ end
+
+ def get_distribution_request(distribution_uuid)
+ distribution = VbmsDistribution.find_by(uuid: distribution_uuid)
+
+ return distribution_not_found_response unless distribution
+
+ fake_distribution_response(distribution.uuid)
+ end
+
+ private
+
+ def bad_request_response
+ HTTPI::Response.new(
+ 400,
+ {},
+ {
+ "error": "BadRequestError",
+ "message": "participant id is not valid"
+ }.with_indifferent_access
+ )
+ end
+
+ def bad_access_response
+ HTTPI::Response.new(
+ 403,
+ {},
+ {
+ "error": "BadRequestError",
+ "message": "package cannot be created because of insufficient privileges"
+ }.with_indifferent_access
+ )
+ end
+
+ def distribution_not_found_response
+ HTTPI::Response.new(
+ 404,
+ {},
+ {
+ "error": "BadRequestError",
+ "message": "distribution does not exist at this time"
+ }.with_indifferent_access
+ )
+ end
+
+ # POST: /package-manager-service/communication-package
+ def fake_package_request(file_number, name, document_references)
+ HTTPI::Response.new(
+ 201,
+ {},
+ {
+ "id" => COMMUNICATION_PACKAGE_UUID,
+ "fileNumber": file_number,
+ "name": name,
+ "documentReferences": document_references,
+ "status": "NEW",
+ "createDate": ""
+ }.with_indifferent_access
+ )
+ end
+
+ # POST: /package-manager-service/distribution
+ def fake_distribution_request(package_id, recipient, destinations)
+ HTTPI::Response.new(
+ 201,
+ {},
+ {
+ "id": DISTRIBUTION_UUID,
+ "recipient": recipient,
+ "description": "bad",
+ "communicationPackageId": package_id,
+ "destinations": destinations,
+ "status": "",
+ "sentToCbcmDate": ""
+ }.with_indifferent_access
+ )
+ end
+
+ # rubocop:disable Metrics/MethodLength
+ # GET: /package-manager-service/distribution/{id}
+ def fake_distribution_response(_distribution_id)
+ HTTPI::Response.new(
+ 200,
+ {},
+ {
+ "id": DISTRIBUTION_UUID,
+ "recipient": {
+ "type": "system",
+ "id": "a050a21e-23f6-4743-a1ff-aa1e24412eff",
+ "name": "VBMS-C"
+ },
+ "description": "Staging Mailing Distribution",
+ "communicationPackageId": 1,
+ "destinations": [{
+ "type": "physicalAddress",
+ "id": "28440040-51a5-4d2a-81a2-28730827be14",
+ "status": "",
+ "cbcmSendAttemptDate": "2022-06-06T16:35:27.996",
+ "addressLine1": "POSTMASTER GENERAL",
+ "addressLine2": "UNITED STATES POSTAL SERVICE",
+ "addressLine3": "475 LENFANT PLZ SW RM 10022",
+ "addressLine4": "SUITE 123",
+ "addressLine5": "APO AE 09001-5275",
+ "addressLine6": "",
+ "treatLine2AsAddressee": true,
+ "treatLine3AsAddressee": true,
+ "city": "WASHINGTON DC",
+ "state": "DC",
+ "postalCode": "12345",
+ "countryName": "UNITED STATES",
+ "countryCode": "us"
+ }],
+ "status": "",
+ "sentToCbcmDate": ""
+ }.with_indifferent_access
+ )
+ end
+ # rubocop:enable Metrics/MethodLength
+ end
+end
diff --git a/lib/helpers/report_load_end_product_sync.rb b/lib/helpers/report_load_end_product_sync.rb
index 145aaff7b3b..eafe06669c8 100644
--- a/lib/helpers/report_load_end_product_sync.rb
+++ b/lib/helpers/report_load_end_product_sync.rb
@@ -29,14 +29,14 @@ def run_by_report_load(report_load)
# The next two methods are part of the APPEALS-22696 initiative to priority sync
# all EPs in Caseflow with VBMS
# The following method is priority syncing cleared EPs
- def run_for_cleared_eps(batch_limit, env)
+ def run_for_cleared_eps(batch_limit)
RequestStore[:current_user] = User.system_user
conn = ActiveRecord::Base.connection
@error_log = []
@run_log = []
- error_ids = get_error_ids(env)
+ error_ids = get_error_ids
eps_queried = get_cleared_eps(batch_limit, error_ids, conn)
eps_queried.each do |x|
@@ -53,14 +53,14 @@ def run_for_cleared_eps(batch_limit, env)
end
# Priority sync for cancelled EPs
- def run_for_cancelled_eps(batch_limit, env)
+ def run_for_cancelled_eps(batch_limit)
RequestStore[:current_user] = User.system_user
conn = ActiveRecord::Base.connection
@error_log = []
@run_log = []
- error_ids = get_error_ids(env)
+ error_ids = get_error_ids
eps_queried = get_cancelled_eps(batch_limit, error_ids, conn)
eps_queried.each do |x|
@@ -128,14 +128,20 @@ def call_sync_by_report_load(ep_ref, rep_load, conn)
####################################################################
# Grab txt file of previously errored EP reference ids from s3 and return as an array
- def get_error_ids(env)
- # Set Client Resources for AWS
- Aws.config.update(region: "us-gov-west-1")
- s3client = Aws::S3::Client.new
- key_name = "ep_establishment_workaround/#{env}/ep_priority_sync/error_ids.txt"
-
- filepath = s3client.get_object(bucket:'appeals-dbas', key:key_name)
- filepath.body.read.gsub("\r","").split("\n").map{ |obj| obj[1...-1] }
+ def get_error_ids
+ error_txt = S3Service.fetch_content(S3_BUCKET_NAME + "/error_ids.txt")
+ error_txt.gsub("\r","").split("\n").map{ |obj| obj[1...-1] }
+ end
+
+ # Method to log errors to S3 error_ids txt file in real time in case of
+ # sync failure mid batch processing
+ # S3 does not support appending or modifying files in any way so the current txt file
+ # of errors has to be pulled and stored locally, modified locally, and then re-uploaded
+ def realtime_log_error_to_s3(reference_id)
+ error_txt = S3Service.fetch_content(S3_BUCKET_NAME + "/error_ids.txt")
+ error_txt << "\r\n"
+ error_txt << '"' + "#{reference_id}"+ '"'
+ S3Service.store_file(S3_BUCKET_NAME + "/error_ids.txt", error_txt)
end
# Grab cleared EPs that are out of sync
@@ -228,6 +234,7 @@ def call_priority_sync(ep_ref, conn)
prev_synced_status: sync_status_before,
error: error.message
)
+ realtime_log_error_to_s3(epe.reference_id)
end
end
diff --git a/lib/helpers/scripts/priority_sync_cancelled_eps.sh b/lib/helpers/scripts/priority_sync_cancelled_eps.sh
index 867b2a57767..a0fe67b19ad 100644
--- a/lib/helpers/scripts/priority_sync_cancelled_eps.sh
+++ b/lib/helpers/scripts/priority_sync_cancelled_eps.sh
@@ -1,5 +1,5 @@
#! /bin/bash
cd /opt/caseflow-certification/src; bin/rails c << DONETOKEN
x = WarRoom::ReportLoadEndProductSync.new
-x.run_for_cancelled_eps("$1", "$2")
+x.run_for_cancelled_eps("$1")
DONETOKEN
diff --git a/lib/helpers/scripts/priority_sync_cleared_eps.sh b/lib/helpers/scripts/priority_sync_cleared_eps.sh
index 4917258ce8e..893e0ec87c9 100644
--- a/lib/helpers/scripts/priority_sync_cleared_eps.sh
+++ b/lib/helpers/scripts/priority_sync_cleared_eps.sh
@@ -1,5 +1,5 @@
#! /bin/bash
cd /opt/caseflow-certification/src; bin/rails c << DONETOKEN
x = WarRoom::ReportLoadEndProductSync.new
-x.run_for_cleared_eps("$1", "$2")
+x.run_for_cleared_eps("$1")
DONETOKEN
diff --git a/scripts/dev_env_setup_step1.sh b/scripts/dev_env_setup_step1.sh
index 57259bf0bc6..e29b26b96da 100755
--- a/scripts/dev_env_setup_step1.sh
+++ b/scripts/dev_env_setup_step1.sh
@@ -98,13 +98,10 @@ echo "1. Run Docker and go into advanced preferences to limit Docker's resources
Recommended settings are 4 CPUs, 8 GiB of internal memory, and 512 MiB of swap.
"
-echo "2. In a new terminal, run:
- docker login -u dsvaappeals
- The password is in the DSVA 1Password account.
- Note you can use your personal account as well, you'll just have to accept
- the license agreement for the Oracle Database docker image.
- https://store.docker.com/images/oracle-database-enterprise-edition
- To accept the agreement, checkout with the Oracle image on the docker store.
+echo "2. To install the latest and enterprise Oracle Database version follow (https://seanstacey.org/deploying-an-oracle-database-19c-as-a-docker-container/2020/09/) guide.
+ 1. Go to http://container-registry.oracle.com/ (Here log in and opt for Database)
+ 2. On command line docker login container-registry.oracle.com
+ 3. On command line docker pull container-registry.oracle.com/database/enterprise:latest
"
echo "==> Close this terminal, open a new terminal, and run ./dev_env_setup_step2.sh
diff --git a/scripts/dev_env_setup_step2.sh b/scripts/dev_env_setup_step2.sh
index bbbce1ddc84..7df7a9dab77 100755
--- a/scripts/dev_env_setup_step2.sh
+++ b/scripts/dev_env_setup_step2.sh
@@ -46,11 +46,7 @@ ln -s Makefile.example Makefile
echo "
===================================
-You must do the following manually:
-
-AWS access is needed starting at this point.
-If you need to get AWS access, follow these instructions:
- https://github.com/department-of-veterans-affairs/appeals-deployment/wiki/New-Hires
+Congratulations
"
echo 'Finish the manual set up from "Database environment setup":
diff --git a/scripts/enable_features_dev.rb b/scripts/enable_features_dev.rb
index 5a0a2ddf6c3..f6053eedb20 100644
--- a/scripts/enable_features_dev.rb
+++ b/scripts/enable_features_dev.rb
@@ -54,11 +54,12 @@ def call
# - they make significantly drastic changes in Dev/Demo compared to Production
# - the work around the feature has been paused
# - the flag is only being used to disable functionality
-disabled_flags = [
- "legacy_das_deprecation",
- "cavc_dashboard_workflow",
- "poa_auto_refresh",
- "interface_version_2"
+disabled_flags = %w[
+ legacy_das_deprecation
+ cavc_dashboard_workflow
+ poa_auto_refresh
+ interface_version_2
+ cc_vacatur_visibility
]
all_features = AllFeatureToggles.new.call.flatten.uniq
diff --git a/spec/controllers/appeals_controller_spec.rb b/spec/controllers/appeals_controller_spec.rb
index d8c685e64b9..616680c5549 100644
--- a/spec/controllers/appeals_controller_spec.rb
+++ b/spec/controllers/appeals_controller_spec.rb
@@ -218,11 +218,8 @@
end
context "when request header contains nonexistent Veteran file number" do
- it "returns 404 error", skip: "flake" do
- appeal = create(:appeal, claimants: [build(:claimant, participant_id: "CLAIMANT_WITH_PVA_AS_VSO")])
- create(:supplemental_claim, veteran_file_number: appeal.veteran_file_number)
-
- request.headers["HTTP_CASE_SEARCH"] = "123"
+ it "returns 404 error" do
+ request.headers["HTTP_CASE_SEARCH"] = "123456789"
expect_any_instance_of(Fakes::BGSService).to_not receive(:fetch_poas_by_participant_id)
diff --git a/spec/controllers/hearings_controller_spec.rb b/spec/controllers/hearings_controller_spec.rb
index a863d585317..aa3546a0646 100644
--- a/spec/controllers/hearings_controller_spec.rb
+++ b/spec/controllers/hearings_controller_spec.rb
@@ -453,12 +453,12 @@
expect(response.status).to eq 200
end
- it "should return a 200 and update aod if provided", :aggregate_failures, skip: "flake AOD present" do
+ it "should return a 200 and update aod if provided", :aggregate_failures do
params = {
id: ama_hearing.external_id,
advance_on_docket_motion: {
user_id: user.id,
- person_id: ama_hearing.appeal.appellant.id,
+ person_id: ama_hearing.appeal.appellant.person.id,
reason: Constants.AOD_REASONS.age,
granted: true
},
@@ -467,7 +467,7 @@
patch :update, as: :json, params: params
expect(response.status).to eq 200
ama_hearing.reload
- expect(ama_hearing.advance_on_docket_motion.person.id).to eq ama_hearing.appeal.appellant.id
+ expect(ama_hearing.advance_on_docket_motion.person.id).to eq ama_hearing.appeal.appellant.person.id
expect(ama_hearing.advance_on_docket_motion.reason).to eq Constants.AOD_REASONS.age
expect(ama_hearing.advance_on_docket_motion.granted).to eq true
end
diff --git a/spec/controllers/idt/api/v1/upload_vbms_document_controller_spec.rb b/spec/controllers/idt/api/v1/upload_vbms_document_controller_spec.rb
index 0c1b55944e9..33251249622 100644
--- a/spec/controllers/idt/api/v1/upload_vbms_document_controller_spec.rb
+++ b/spec/controllers/idt/api/v1/upload_vbms_document_controller_spec.rb
@@ -1,18 +1,63 @@
# frozen_string_literal: true
RSpec.describe Idt::Api::V1::UploadVbmsDocumentController, :all_dbs, type: :controller do
+ include ActiveJob::TestHelper
+
describe "POST /idt/api/v1/appeals/:appeal_id/upload_document" do
let(:user) { create(:user) }
let(:appeal) { create(:appeal) }
let(:veteran) { appeal.veteran }
let(:file_number) { appeal.veteran.file_number }
+ let(:file) { "JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YW" }
let(:valid_document_type) { "BVA Decision" }
let(:params) do
{ appeal_id: appeal.external_id,
- file: "JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YW",
+ file: file,
document_type: valid_document_type }
end
+ let(:mail_request_params) do
+ { veteran_identifier: veteran.file_number,
+ file: file,
+ document_type: valid_document_type,
+ recipient_info: [
+ {
+ recipient_type: "person",
+ first_name: "Bob",
+ last_name: "Smithmets",
+ participant_id: "487470002",
+ destination_type: "domesticAddress",
+ address_line_1: "1235 Main Street",
+ treat_line_2_as_addressee: false,
+ treat_line_3_as_addressee: false,
+ city: "Orlando",
+ state: "FL",
+ postal_code: "13246",
+ country_code: "US"
+ }
+ ] }
+ end
+
+ let(:invalid_mail_request_params) do
+ { veteran_identifier: veteran.file_number,
+ file: "JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YW",
+ document_type: valid_document_type,
+ recipient_info: [
+ {
+ recipient_type: "person",
+ participant_id: "487470002",
+ destination_type: "domesticAddress",
+ address_line_1: "1234 Main Street",
+ treat_line_2_as_addressee: false,
+ treat_line_3_as_addressee: false,
+ city: "Orlando",
+ state: "FL",
+ postal_code: "12345",
+ country_code: "US"
+ }
+ ] }
+ end
+
let(:params_identifier) do
{ veteran_identifier: veteran.file_number,
file: "JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YW",
@@ -115,6 +160,18 @@
end
end
+ context "when the recipient_info parameters are incomplete" do
+ it "returns a descriptive error to the IDT user" do
+ expect(Raven).to receive(:capture_exception)
+ post :create, params: invalid_mail_request_params, as: :json
+ validation_error_msgs = JSON.parse(response.body)["errors"]
+ expect(validation_error_msgs).to eq(
+ "distribution 1" => "First name can't be blank, Last name can't be blank"
+ )
+ expect(response.status).to eq(400)
+ end
+ end
+
context "all parameters are valid" do
let(:uploaded_document) { instance_double(VbmsUploadedDocument, id: 1) }
let(:document_params) do
@@ -123,13 +180,30 @@
appeal_type: appeal.class.name,
veteran_file_number: file_number,
document_type: params[:document_type],
- file: params[:file],
+ file: file,
document_name: nil,
document_subject: nil
}
end
shared_examples "success_with_valid_parameters" do
+ before do
+ RequestStore.store[:current_user] = User.system_user
+ end
+
+ it "creates a new Mail Request object when optional params exist" do
+ expect_any_instance_of(MailRequest).to receive(:call)
+ post :create, params: mail_request_params, as: :json
+ end
+
+ it "returns a list of vbms_distribution ids alongside a success message" do
+ post :create, params: mail_request_params, as: :json
+ success_message = JSON.parse(response.body)["message"]
+ success_id = JSON.parse(response.body)["distribution_ids"]
+ expect(success_message).to eq "Document successfully queued for upload."
+ expect(success_id).not_to eq([])
+ end
+
it "returns a successful message and creates a new VbmsUploadedDocument" do
expect { post :create, params: params }.to change(VbmsUploadedDocument, :count).by(1)
@@ -144,7 +218,8 @@
expect(UploadDocumentToVbmsJob).to receive(:perform_later).with(
document_id: uploaded_document.id,
initiator_css_id: user.css_id,
- application: anything
+ application: anything,
+ mail_package: nil
)
expect(uploaded_document).to receive(:cache_file)
@@ -165,6 +240,48 @@
it_behaves_like "success_with_valid_parameters"
end
end
+
+ context "queues async mail request job" do
+ let(:recipient_info) { mail_request_params[:recipient_info] }
+ let(:mail_request) { MailRequest.new(recipient_info[0]) }
+ let(:mail_package) do
+ { distributions: [mail_request.to_json],
+ copies: 1,
+ created_by_id: user.id }
+ end
+ let(:uploaded_document) { create(:vbms_uploaded_document) }
+ let(:upload_job_params) do
+ { document_id: uploaded_document.id,
+ initiator_css_id: user.css_id,
+ application: nil,
+ mail_package: mail_package }
+ end
+
+ context "document is associated with a mail package" do
+ it "calls #perform_later on MailRequestJob" do
+ post :create, params: mail_request_params, as: :json
+ expect(MailRequestJob).to receive(:perform_later)
+ perform_enqueued_jobs do
+ UploadDocumentToVbmsJob.perform_later(upload_job_params)
+ end
+ end
+ end
+
+ context "document is not associated with a mail package" do
+ it "does not call #perform_later on MailRequestJob" do
+ mail_request_params[:recipient_info] = []
+ post :create, params: mail_request_params, as: :json
+ expect(MailRequestJob).to_not receive(:perform_later)
+ end
+ end
+
+ context "recipient info is incorrect" do
+ it "does not call #perform_later on MailRequestJob" do
+ post :create, params: invalid_mail_request_params, as: :json
+ expect(MailRequestJob).to_not receive(:perform_later)
+ end
+ end
+ end
end
end
end
diff --git a/spec/controllers/idt/api/v2/appeals_controller_spec.rb b/spec/controllers/idt/api/v2/appeals_controller_spec.rb
index 15fe3342605..c8f293f78b8 100644
--- a/spec/controllers/idt/api/v2/appeals_controller_spec.rb
+++ b/spec/controllers/idt/api/v2/appeals_controller_spec.rb
@@ -510,16 +510,17 @@
citation_number: citation_number,
decision_date: Date.new(1989, 12, 13).to_s,
file: "JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YW",
- redacted_document_location: "C://Windows/User/BLOBLAW/Documents/Decision.docx" }
+ redacted_document_location: "C://Windows/User/BLOBLAW/Documents/Decision.docx",
+ recipient_info: [] }
end
before do
- allow(controller).to receive(:verify_access).and_return(true)
BvaDispatch.singleton.add_user(user)
key, t = Idt::Token.generate_one_time_key_and_proposed_token
Idt::Token.activate_proposed_token(key, user.css_id)
request.headers["TOKEN"] = t
+ create(:staff, :attorney_role, sdomainid: user.css_id)
end
context "when some params are missing" do
@@ -567,7 +568,6 @@
it "should complete the BvaDispatchTask assigned to the User and the task assigned to the BvaDispatch org" do
post :outcode, params: params
-
expect(response.status).to eq(200)
tasks = BvaDispatchTask.where(appeal: root_task.appeal, assigned_to: user)
@@ -580,7 +580,60 @@
expect(task.parent.status).to eq("completed")
expect(S3Service.files["decisions/" + root_task.appeal.external_id + ".pdf"]).to_not eq nil
expect(DecisionDocument.find_by(appeal_id: root_task.appeal.id)&.submitted_at).to_not be_nil
- expect(JSON.parse(response.body)["message"]).to eq("Success!")
+ expect(JSON.parse(response.body)["message"]).to eq("Successful dispatch!")
+ end
+
+ context "when dispatch is associated with a mail request" do
+ include ActiveJob::TestHelper
+
+ let(:recipient) do
+ { recipient_type: "person",
+ first_name: "Bob",
+ last_name: "Smithmetz",
+ participant_id: "487470002",
+ destination_type: "domesticAddress",
+ address_line_1: "1234 Main Street",
+ treat_line_2_as_addressee: false,
+ treat_line_3_as_addressee: false,
+ city: "Orlando",
+ state: "FL",
+ postal_code: "12345",
+ country_code: "US" }
+ end
+
+ before { params[:recipient_info] << recipient }
+
+ it "calls #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to receive(:perform_later)
+
+ perform_enqueued_jobs { post :outcode, params: params, as: :json }
+ end
+
+ context "recipient info is incorrect" do
+ it "returns validation errors and does not call #perform_later on MailRequestJob" do
+ recipient[:first_name] = nil
+ expect(MailRequestJob).to_not receive(:perform_later)
+ perform_enqueued_jobs { post :outcode, params: params, as: :json }
+ error_message = JSON.parse(response.body)["errors"]["distribution 1"]
+ expect(error_message).to eq("First name can't be blank")
+ end
+ end
+
+ context "when dispatch is not successfully processed" do
+ let(:citation_number) { "INVALID" }
+ it "does not call #perform_later on MailRequestJob" do
+ perform_enqueued_jobs { expect(MailRequestJob).to_not receive(:perform_later) }
+ post :outcode, params: params
+ end
+ end
+ end
+
+ context "when dispatch is not associated with a mail request" do
+ it "does not call #perform_later on MailRequestJob" do
+ params[:recipient_info] = []
+ expect(MailRequestJob).to_not receive(:perform_later)
+ post :outcode, params: params
+ end
end
end
diff --git a/spec/controllers/idt/api/v2/distributions_controller_spec.rb b/spec/controllers/idt/api/v2/distributions_controller_spec.rb
new file mode 100644
index 00000000000..8c60e4c8ed9
--- /dev/null
+++ b/spec/controllers/idt/api/v2/distributions_controller_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+# This is the command to run rspec in the console
+# bundle exec rspec spec/controllers/idt/api/v2/distributions_controller_spec.rb
+
+RSpec.describe Idt::Api::V2::DistributionsController, type: :controller do
+ describe "#distribution" do
+ let(:user) { create(:user) }
+ let(:error_uuid) { "a9df0251-8350-464b-9aa4-a7d56a8ac173" }
+ let(:distro_uuid) { "df7fc6b2-8be3-4124-a796-6a77bdd8f66a" }
+
+ before do
+ allow(SecureRandom).to receive(:uuid).and_return(error_uuid)
+
+ key, t = Idt::Token.generate_one_time_key_and_proposed_token
+ Idt::Token.activate_proposed_token(key, user.css_id)
+
+ request.headers["TOKEN"] = t
+ create(:staff, :attorney_role, sdomainid: user.css_id)
+ end
+
+ context "when distribution_id is blank or invalid" do
+ let(:distribution_id) { "" }
+ let(:error_msg) do
+ "[IDT] Http Status Code: 400, Distribution Does Not Exist Or Id is blank," \
+ " (Distribution ID: #{distribution_id}) #{error_uuid}"
+ end
+
+ it "renders an error with status 400" do
+ get :distribution, params: { distribution_id: distribution_id }
+
+ expect(response.code).to eq "400"
+ expect(JSON.parse(response.body)).to eq(
+ "message" => error_msg
+ )
+ end
+ end
+
+ context "when PacmanService fails with a 404 error" do
+ let!(:vbms_distribution) { create(:vbms_distribution) }
+
+ it "renders the expected response with status 200, Pacman api has a 404" do
+ expected_response = {
+ "id" => vbms_distribution.id.to_s,
+ "status" => "PENDING_ESTABLISHMENT"
+ }
+
+ get :distribution, params: { distribution_id: vbms_distribution.id }
+
+ expect(response).to have_http_status(200)
+ expect(JSON.parse(response.body)).to eq(expected_response)
+ end
+ end
+
+ context "when PacmanService fails with a 500 error" do
+ let(:error_msg) do
+ "[IDT] Http Status Code: 500, Internal Server Error," \
+ " (Distribution ID: #{vbms_distribution.id}) #{error_uuid}"
+ end
+ let(:vbms_distribution) { create(:vbms_distribution, uuid: distro_uuid) }
+
+ it "renders an error with status 500" do
+ allow(PacmanService).to receive(:get_distribution_request).with(vbms_distribution.uuid) do
+ OpenStruct.new(code: 500)
+ end
+
+ get :distribution, params: { distribution_id: vbms_distribution.id }
+
+ expect(response.code).to eq "500"
+ expect(JSON.parse(response.body)).to eq(
+ "message" => error_msg
+ )
+ end
+ end
+
+ context "when converting the distribution" do
+ let(:vbms_distribution) { create(:vbms_distribution, uuid: distro_uuid) }
+ let(:expected_response) do
+ {
+ "id": Fakes::PacmanService::DISTRIBUTION_UUID,
+ "recipient":
+ {
+ "type": "system",
+ "id": "a050a21e-23f6-4743-a1ff-aa1e24412eff",
+ "name": "VBMS-C"
+ },
+ "description": "Staging Mailing Distribution",
+ "communication_package_id": 1,
+ "destinations": [
+ {
+ "type": "physicalAddress",
+ "id": "28440040-51a5-4d2a-81a2-28730827be14",
+ "status": "",
+ "cbcm_send_attempt_date": "2022-06-06T16:35:27.996",
+ "address_line_1": "POSTMASTER GENERAL",
+ "address_line_2": "UNITED STATES POSTAL SERVICE",
+ "address_line_3": "475 LENFANT PLZ SW RM 10022",
+ "address_line_4": "SUITE 123",
+ "address_line_5": "APO AE 09001-5275",
+ "address_line_6": "",
+ "treat_line_2_as_addressee": true,
+ "treat_line_3_as_addressee": true,
+ "city": "WASHINGTON DC",
+ "state": "DC",
+ "postal_code": "12345",
+ "country_name": "UNITED STATES",
+ "country_code": "us"
+ }
+ ],
+ "status": "",
+ "sent_to_cbcm_date": ""
+ }
+ end
+
+ it "returns the expected converted response" do
+ get :distribution, params: { distribution_id: vbms_distribution.id }
+
+ expect(response).to have_http_status(200)
+ expect(JSON.parse(response.body.to_json)).to eq(expected_response.to_json)
+ end
+ end
+
+ context "render_error" do
+ let(:status) { 500 }
+ let(:message) { "Internal Server Error" }
+ let(:vbms_distribution) { create(:vbms_distribution, uuid: distro_uuid) }
+
+ it "renders the error response with correct status, message, and distribution ID" do
+ error_message = "[IDT] Http Status Code: #{status}, #{message}, (Distribution ID: #{vbms_distribution.id})"
+ expect(Rails.logger).to receive(:error).with("#{error_message}Error ID: #{error_uuid}")
+
+ allow(PacmanService).to receive(:get_distribution_request).with(distro_uuid) do
+ OpenStruct.new(code: 500)
+ end
+
+ get :distribution, params: { distribution_id: vbms_distribution.id }
+
+ expect(response).to have_http_status(status)
+ expect(JSON.parse(response.body)).to eq(
+ "message" => error_message + " #{error_uuid}"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/controllers/organizations/users_controller_spec.rb b/spec/controllers/organizations/users_controller_spec.rb
index 132826bf5af..11cee50f66e 100644
--- a/spec/controllers/organizations/users_controller_spec.rb
+++ b/spec/controllers/organizations/users_controller_spec.rb
@@ -274,7 +274,7 @@
let!(:params) { { organization_url: org.url, id: user.id } }
let(:org) { create(:judge_team, :has_judge_team_lead_as_admin) }
- let!(:user) do
+ let(:user) do
create(:user).tap do |user|
org.add_user(user)
end
@@ -300,7 +300,9 @@
end
end
- context "when user is the judge in the organization", skip: "Flake" do
+ context "when user is the judge in the organization" do
+ let(:user) { org.admin }
+
it "returns an error" do
subject
diff --git a/spec/factories/mail_request.rb b/spec/factories/mail_request.rb
new file mode 100644
index 00000000000..256c80ed196
--- /dev/null
+++ b/spec/factories/mail_request.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :mail_request do
+ recipient_type { "person" }
+ first_name { "Bob" }
+ last_name { "Smithcole" }
+ participant_id { "487470002" }
+ destination_type { "domesticAddress" }
+ address_line_1 { "1234 Main Street" }
+ city { "Orlando" }
+ country_code { "US" }
+ postal_code { "12345" }
+ state { "FL" }
+ treat_line_2_as_addressee { false }
+ treat_line_3_as_addressee { false }
+
+ trait :nil_recipient_type do
+ recipient_type { nil }
+ end
+
+ initialize_with { new(attributes) }
+ end
+end
diff --git a/spec/factories/vacols/case.rb b/spec/factories/vacols/case.rb
index 101e71ad02b..9fc01dfe2b5 100644
--- a/spec/factories/vacols/case.rb
+++ b/spec/factories/vacols/case.rb
@@ -175,6 +175,19 @@
end
end
+ factory :case_with_multi_decision do
+ bfddec { 1.day.ago }
+
+ transient do
+ decision_document do
+ [
+ create(:document, type: "BVA Decision", received_at: 1.day.ago),
+ create(:document, type: "BVA Decision", received_at: 1.day.ago)
+ ]
+ end
+ end
+ end
+
factory :case_with_old_decision do
bfddec { 1.day.ago }
diff --git a/spec/factories/vbms_communication_package.rb b/spec/factories/vbms_communication_package.rb
new file mode 100644
index 00000000000..143cce7de44
--- /dev/null
+++ b/spec/factories/vbms_communication_package.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :vbms_communication_package do
+ association :document_mailable_via_pacman, factory: :vbms_uploaded_document
+ comm_package_name { "DocumentName_" + Time.zone.now.to_s }
+ copies { 1 }
+ created_at { Time.zone.now }
+ created_by_id { create(:user).id }
+ file_number { generate :veteran_file_number }
+ status { nil }
+ updated_at { Time.zone.now }
+ updated_by_id { nil }
+ uuid { nil }
+ end
+end
diff --git a/spec/factories/vbms_distribution.rb b/spec/factories/vbms_distribution.rb
new file mode 100644
index 00000000000..512d9234622
--- /dev/null
+++ b/spec/factories/vbms_distribution.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :vbms_distribution do
+ claimant_station_of_jurisdiction { nil }
+ created_at { Time.zone.now }
+ created_by_id { nil }
+ first_name { "Bob" }
+ last_name { "Bobjoe" }
+ middle_name { "Joe" }
+ name { nil }
+ participant_id { generate :participant_id }
+ poa_code { nil }
+ recipient_type { "person" }
+ updated_at { Time.zone.now }
+ updated_by_id { nil }
+ vbms_communication_package_id { nil }
+ uuid { nil }
+ end
+end
diff --git a/spec/factories/vbms_distribution_destination.rb b/spec/factories/vbms_distribution_destination.rb
new file mode 100644
index 00000000000..0dd3b64bd9f
--- /dev/null
+++ b/spec/factories/vbms_distribution_destination.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :vbms_distribution_destination do
+ association :vbms_distribution, factory: :vbms_distribution
+ address_line_1 { "POSTMASTER GENERAL" }
+ address_line_2 { "UNITED STATES POSTAL SERVICE" }
+ address_line_3 { "475 LENFANT PLZ SW RM 10022" }
+ address_line_4 { "SUITE 123" }
+ address_line_5 { "APO AE 09001-5275" }
+ address_line_6 { nil }
+ city { "WASHINGTON DC" }
+ country_code { "US" }
+ country_name { "UNITED STATES" }
+ created_at { Time.zone.now }
+ created_by_id { nil }
+ destination_type { "domesticAddress" }
+ postal_code { "12345" }
+ state { "DC" }
+ treat_line_2_as_addressee { true }
+ treat_line_3_as_addressee { true }
+ updated_at { Time.zone.now }
+ updated_by_id { nil }
+ end
+end
diff --git a/spec/factories/vbms_uploaded_document.rb b/spec/factories/vbms_uploaded_document.rb
index f6e10724896..3a2ef598365 100644
--- a/spec/factories/vbms_uploaded_document.rb
+++ b/spec/factories/vbms_uploaded_document.rb
@@ -6,6 +6,9 @@
document_type { "Status Letter" }
appeal { create(:appeal) }
+ document_version_reference_id { "{#{SecureRandom.uuid.upcase}}" }
+ document_series_reference_id { "{#{SecureRandom.uuid.upcase}}" }
+
trait :for_legacy_appeal do
appeal { create(:legacy_appeal, vacols_case: create(:case)) }
end
diff --git a/spec/feature/dispatch/establish_claim_spec.rb b/spec/feature/dispatch/establish_claim_spec.rb
index 085afb747a5..15442585c9a 100644
--- a/spec/feature/dispatch/establish_claim_spec.rb
+++ b/spec/feature/dispatch/establish_claim_spec.rb
@@ -27,7 +27,7 @@
let(:folder) { build(:folder, tioctime: 23.days.ago.midnight) }
let(:case_remand) do
- create(:case_with_decision, :status_remand, folder: folder)
+ create(:case_with_multi_decision, :status_remand, folder: folder)
end
let(:appeal_remand) do
@@ -491,9 +491,8 @@
Generators::Document.build(type: "BVA Decision", received_at: 6.days.ago)
]
end
- # :nocov:
- scenario "Review page lets users choose which document to use",
- skip: "This test is failing because of a stale element reference" do
+
+ scenario "Review page lets users choose which document to use" do
visit "/dispatch/establish-claim"
click_on "Establish next claim"
@@ -508,8 +507,7 @@
expect(page).to have_content("Benefit Type")
end
- scenario "the EP creation page has a link back to decision review",
- skip: "This test is failing because of a stale element reference" do
+ scenario "the EP creation page has a link back to decision review" do
visit "/dispatch/establish-claim"
click_on "Establish next claim"
@@ -518,7 +516,6 @@
click_on "< Back to Review Decision"
expect(page).to have_content("Multiple Decision Documents")
end
- # :nocov:
end
context "For a full grant" do
@@ -629,10 +626,11 @@
scenario "Assigning it to complete the claims establishment", skip: "flakey hang" do
visit "/dispatch/establish-claim"
click_on "Establish next claim"
- expect(page).to have_current_path("/dispatch/establish-claim/#{task.id}")
click_on "Route claim"
expect(page).to have_current_path("/dispatch/establish-claim/#{task.id}")
+ expect(page).to have_content("Route Claim")
+ expect(page).to have_selector(:link_or_button, "Assign to Claim")
click_on "Assign to Claim" # unknown reason sometimes hangs here
expect(page).to have_content("Success!")
@@ -652,8 +650,7 @@
aasm_state: "unassigned")
end
- scenario "Establish a new claim routed to ARC",
- skip: "This test is failing because of a stale element reference" do
+ scenario "Establish a new claim routed to ARC", :aggregate_failure do
# Mock the claim_id returned by VBMS's create end product
Fakes::VBMSService.end_product_claim_id = "CLAIM_ID_123"
@@ -700,9 +697,11 @@
suppress_acknowledgement_letter: true,
claimant_participant_id: nil,
limited_poa_code: nil,
- limited_poa_access: nil
+ limited_poa_access: nil,
+ status_type_code: nil
},
- veteran_hash: task.appeal.veteran.to_vbms_hash
+ veteran_hash: task.appeal.veteran.to_vbms_hash,
+ user: RequestStore[:current_user]
)
expect(AppealRepository).to have_received(:update_vacols_after_dispatch!)
@@ -713,11 +712,11 @@
expect(task.appeal.reload.dispatched_to_station).to eq("397")
- click_on "Caseflow Dispatch"
- expect(page).to have_current_path("/dispatch/establish-claim")
+ expect(page).to have_current_path("/dispatch/establish-claim/#{task.id}")
# No tasks left
- expect(page).to have_content("Way to go! You have completed all the claims assigned to you.")
+ expect(page).to have_content("Way to go!")
+ expect(page).to have_content("You have completed all of the total cases assigned to you today")
expect(page).to have_css(".usa-button-disabled")
end
diff --git a/spec/feature/hearings/daily_docket/build_hearsched_spec.rb b/spec/feature/hearings/daily_docket/build_hearsched_spec.rb
index 0d64bb1f58b..e02dcfac20b 100644
--- a/spec/feature/hearings/daily_docket/build_hearsched_spec.rb
+++ b/spec/feature/hearings/daily_docket/build_hearsched_spec.rb
@@ -72,10 +72,10 @@
let!(:hearing) { create(:hearing, :with_tasks) }
let!(:postponed_hearing_day) { create(:hearing_day, scheduled_for: Date.new(2019, 3, 3)) }
- scenario "User can update fields", skip: "flake" do
+ scenario "User can update fields" do
visit "hearings/schedule/docket/" + hearing.hearing_day.id.to_s
find("textarea", id: "#{hearing.external_id}-notes").click.send_keys("This is a note about the hearing!")
- find("label", text: "9:00 am").click
+ find("label", text: "9:00 AM Eastern Time (US & Canada)").click
find("label", text: "Transcript Requested").click
click_button("Save")
expect(page).to have_content("You have successfully updated")
@@ -87,7 +87,7 @@
expect(page).to have_content("No Show")
expect(page).to have_content("This is a note about the hearing!", wait: 10) # flake
expect(find_field("Transcript Requested", visible: false)).to be_checked
- expect(find_field("9:00 am", visible: false)).to be_checked
+ expect(find_field("9:00 AM", visible: false)).to be_checked
end
end
diff --git a/spec/feature/out_of_service_spec.rb b/spec/feature/out_of_service_spec.rb
index 9ebeaf5be29..ae38d2b836a 100644
--- a/spec/feature/out_of_service_spec.rb
+++ b/spec/feature/out_of_service_spec.rb
@@ -11,10 +11,13 @@
User.unauthenticate!
end
- scenario "When out of service is disabled, it shows Caseflow Home page",
- skip: "This test is failing because of a bad feature toggle set somewhere" do
+ scenario "When out of service is disabled, it shows Caseflow Home page", :aggregate_failures do
visit "/"
- expect(page).to have_content("Caseflow Help")
+ expect(page).to have_current_path("/")
+ expect(page).to have_content("BVAAABSHIRE (DSUSER)")
+ expect(page).to have_link("Queue")
+ expect(page).to have_content("Search case")
+ expect(page).to have_content("Send feedback")
expect(page.has_no_content?("Technical Difficulties")).to eq(true)
end
diff --git a/spec/feature/queue/case_details_spec.rb b/spec/feature/queue/case_details_spec.rb
index 9688f477954..635e80d969b 100644
--- a/spec/feature/queue/case_details_spec.rb
+++ b/spec/feature/queue/case_details_spec.rb
@@ -153,7 +153,7 @@ def wait_for_page_render
expect(details_link.text).to eq(COPY::CASE_DETAILS_HEARING_DETAILS_LINK_COPY)
end
- context "the user has a VSO role", skip: "re-enable when pagination is fixed" do
+ context "the user has a VSO role" do
let!(:vso) { create(:vso, name: "VSO", role: "VSO", url: "vso-url", participant_id: "8054") }
let!(:vso_user) { create(:user, :vso_role) }
let!(:vso_task) { create(:ama_vso_task, :in_progress, assigned_to: vso, appeal: appeal) }
@@ -655,9 +655,10 @@ def wait_for_page_render
Fakes::BGSService.inaccessible_appeal_vbms_ids << appeal.veteran_file_number
allow_any_instance_of(Fakes::BGSService).to receive(:fetch_veteran_info)
.and_raise(BGS::ShareError, "NonUniqueResultException")
+ appeal.veteran&.multiple_phone_numbers?
end
- scenario "access the appeal's case details", skip: "flake" do
+ scenario "access the appeal's case details" do
visit "/queue/appeals/#{appeal.external_id}"
expect(page).to have_content(COPY::DUPLICATE_PHONE_NUMBER_TITLE)
diff --git a/spec/feature/queue/colocated_task_queue_spec.rb b/spec/feature/queue/colocated_task_queue_spec.rb
index d0f15e562a7..88a5af39375 100644
--- a/spec/feature/queue/colocated_task_queue_spec.rb
+++ b/spec/feature/queue/colocated_task_queue_spec.rb
@@ -56,6 +56,7 @@
find(".cf-form-dropdown", text: "Select number of tasks to assign").click
find("option", text: "1 (all available tasks)").click
find("button", id: "Bulk-Assign-Tasks-button-id-1").click # going by text is an ambiguous match
+ expect(page).to have_content("You have bulk assigned 1 Poa Clarification Colocated Task tasks")
# Visit case details page for VLJ support staff.
User.authenticate!(user: vlj_support_staff)
@@ -84,8 +85,8 @@
# verify that the instructions from the VLJ appear on the case timeline
expect(page).to have_css("h2", text: "Case Timeline")
scroll_to(find("h2", text: "Case Timeline"))
- poa_task = PoaClarificationColocatedTask.find_by(assigned_to_type: User.name)
- click_button(COPY::TASK_SNAPSHOT_VIEW_TASK_INSTRUCTIONS_LABEL, id: poa_task.id)
+ poa_task = PoaClarificationColocatedTask.last
+ click_button(text: COPY::TASK_SNAPSHOT_VIEW_TASK_INSTRUCTIONS_LABEL, id: poa_task.id)
expect(page).to have_content(return_instructions)
# Expect to see draft decision option.
find(".cf-select__control", text: "Select an action…").click
diff --git a/spec/feature/queue/motion_to_vacate_spec.rb b/spec/feature/queue/motion_to_vacate_spec.rb
index c07feb9b71d..1841031eaf7 100644
--- a/spec/feature/queue/motion_to_vacate_spec.rb
+++ b/spec/feature/queue/motion_to_vacate_spec.rb
@@ -435,6 +435,7 @@ def submit_and_fetch_task(judge)
expect(new_task.available_actions(motions_attorney)).to include(
Constants.TASK_ACTIONS.LIT_SUPPORT_PULAC_CERULLO.to_h
)
+
expect(new_task.instructions.join("")).to eq(instructions)
end
end
diff --git a/spec/jobs/mail_request_job_spec.rb b/spec/jobs/mail_request_job_spec.rb
new file mode 100644
index 00000000000..7a5f24c0694
--- /dev/null
+++ b/spec/jobs/mail_request_job_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+describe MailRequestJob do
+ include ActiveJob::TestHelper
+
+ let!(:current_user) { User.authenticate! }
+ let!(:vbms_file) { create(:vbms_uploaded_document) }
+ let!(:mail_request) { build(:mail_request) }
+
+ context "Successful execution of MailRequestJob" do
+ it "creates a new VbmsCommunicationPackage" do
+ mail_request.call
+ mail_package = { distributions: [mail_request.to_json], copies: 1, created_by_id: current_user.id }
+
+ expect do
+ perform_enqueued_jobs { MailRequestJob.perform_later(vbms_file, mail_package) }
+ end.to change { VbmsCommunicationPackage.count }.by(1)
+
+ expect(find_comm_package_via_distribution_id(mail_request.vbms_distribution_id).status).to eq("success")
+ end
+ end
+
+ context "Unsuccessful execution of MailRequestJob" do
+ it "VbmsCommunicationPackage is not created. VbmsDistribution's vbms_communication_package_id remains nil." do
+ mail_request.call
+ mail_package = { distributions: [mail_request.to_json], copies: 1, created_by_id: current_user.id }
+
+ allow(PacmanService)
+ .to receive(:send_communication_package_request)
+ .and_raise(Caseflow::Error::PacmanApiError.new(code: 500, message: "Fake Error"))
+
+ expect do
+ perform_enqueued_jobs { MailRequestJob.perform_later(vbms_file, mail_package) }
+ end.to change { VbmsCommunicationPackage.count }.by(0)
+
+ distribution = VbmsDistribution.find(mail_request.vbms_distribution_id)
+
+ expect(distribution.vbms_communication_package_id).to be_nil
+ end
+ end
+ def find_comm_package_via_distribution_id(distro_id)
+ distribution = VbmsDistribution.find(distro_id)
+
+ VbmsCommunicationPackage.find(distribution.vbms_communication_package_id)
+ end
+end
diff --git a/spec/jobs/upload_document_to_vbms_job_spec.rb b/spec/jobs/upload_document_to_vbms_job_spec.rb
index 2cb2a50cc67..025b6c18485 100644
--- a/spec/jobs/upload_document_to_vbms_job_spec.rb
+++ b/spec/jobs/upload_document_to_vbms_job_spec.rb
@@ -5,8 +5,20 @@
let(:document) { create(:vbms_uploaded_document) }
let(:service) { instance_double(UploadDocumentToVbms) }
let(:user) { create(:user) }
+ let(:mail_request) { instance_double(MailRequest) }
+ let(:mail_package) do
+ { distributions: [mail_request.to_json],
+ copies: 1,
+ created_by_id: user.id }
+ end
+
+ let(:params) do
+ { document_id: document.id,
+ initiator_css_id: user.css_id,
+ mail_package: mail_package }
+ end
- subject { UploadDocumentToVbmsJob.perform_now(document_id: document.id, initiator_css_id: user.css_id) }
+ subject { UploadDocumentToVbmsJob.perform_now(params) }
it "calls #call on UploadDocumentToVbms instance" do
expect(UploadDocumentToVbms).to receive(:new).with(document: document).and_return(service)
@@ -15,5 +27,28 @@
expect(service).to receive(:call)
subject
end
+
+ context "document is associated with a mail package" do
+ it "calls #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to receive(:perform_later).with(document, mail_package)
+ subject
+ end
+ end
+
+ context "document is not associated with a mail package" do
+ let(:mail_package) { nil }
+ it "does not call #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to_not receive(:perform_later)
+ subject
+ end
+ end
+
+ context "document is not successfully uploaded to vbms" do
+ it "does not call #perform_later on MailRequestJob" do
+ allow(VBMSService).to receive(:upload_document_to_vbms_veteran).and_raise(StandardError)
+ expect(MailRequestJob).to_not receive(:perform_later)
+ expect { subject }.to raise_error(StandardError)
+ end
+ end
end
end
diff --git a/spec/models/appeal_series_issues_spec.rb b/spec/models/appeal_series_issues_spec.rb
index 9e40ca71516..b9215160f0b 100644
--- a/spec/models/appeal_series_issues_spec.rb
+++ b/spec/models/appeal_series_issues_spec.rb
@@ -68,7 +68,27 @@
end
let(:combined_issues) do
- AppealSeriesIssues.new(appeal_series: series).all.sort_by { |issue_hash| issue_hash[:diagnostic_code] }
+ AppealSeriesIssues.new(appeal_series: series).all
+ end
+
+ let(:combined_issues_array) do
+ [
+ {
+ description: "Service connection, limitation of thigh motion (flexion)",
+ diagnosticCode: "5252",
+ active: true,
+ lastAction: :remand,
+ date: 6.months.ago.to_date
+ },
+ {
+ description: "New and material evidence to reopen claim for service connection,"\
+ " shoulder or arm muscle injury",
+ diagnosticCode: "5301",
+ active: false,
+ lastAction: :allowed,
+ date: 6.months.ago.to_date
+ }
+ ]
end
context "#all" do
@@ -77,18 +97,7 @@
context "when an issue spans a remand" do
it "combines issues together" do
expect(subject.length).to eq(2)
- expect(subject.first[:description]).to eq(
- "Service connection, limitation of thigh motion (flexion)"
- )
- expect(subject.first[:active]).to be_truthy
- expect(subject.first[:lastAction]).to eq(:remand)
- expect(subject.first[:date]).to eq(6.months.ago.to_date)
- expect(subject.last[:description]).to eq(
- "New and material evidence to reopen claim for service connection, shoulder or arm muscle injury"
- )
- expect(subject.last[:active]).to be_falsey
- expect(subject.last[:lastAction]).to eq(:allowed)
- expect(subject.last[:date]).to eq(6.months.ago.to_date)
+ expect(subject).to match_array(combined_issues_array)
end
context "when there is a draft decision" do
@@ -105,9 +114,7 @@
it "does not show the draft disposition" do
expect(subject.length).to eq(2)
- expect(subject.first[:active]).to be_truthy
- expect(subject.first[:date]).to eq(6.months.ago.to_date)
- expect(subject.first[:lastAction]).to eq(:remand)
+ expect(subject).to match_array(combined_issues_array)
end
end
end
@@ -116,7 +123,7 @@
let(:appeals) { [original] }
it "is marked as active" do
- expect(subject.first[:active]).to be_truthy
+ expect(subject).to match_array(combined_issues_array)
end
end
@@ -124,6 +131,7 @@
let(:original_issues) { [] }
it "returns issues" do
+ expect(subject.length).to eq(1)
expect(subject.first[:description]).to eq("Service connection, limitation of thigh motion (flexion)")
end
end
@@ -151,7 +159,15 @@
end
it "does not show as a last_action" do
- expect(subject.first[:lastAction]).to eq(:remand)
+ expect(subject.length).to eq(3)
+ other_hash = {
+ description: "Other",
+ diagnosticCode: nil,
+ active: false,
+ lastAction: nil,
+ date: nil
+ }
+ expect(subject).to match_array(combined_issues_array.push(other_hash))
end
end
@@ -161,8 +177,13 @@
end
it "appears as the last action" do
- expect(subject.first[:lastAction]).to eq(:cavc_remand)
- expect(subject.first[:date]).to eq(1.month.ago.to_date)
+ expect(subject.length).to eq(2)
+ expect(subject).to include(
+ a_hash_including(
+ lastAction: :cavc_remand,
+ date: 1.month.ago.to_date
+ )
+ )
end
end
end
diff --git a/spec/models/certification_spec.rb b/spec/models/certification_spec.rb
index 08b6942717b..7329f8f16e9 100644
--- a/spec/models/certification_spec.rb
+++ b/spec/models/certification_spec.rb
@@ -6,7 +6,7 @@
end
let(:vacols_case) do
- create(:case_with_ssoc)
+ create(:case_with_ssoc, :representative_american_legion)
end
let(:certification) do
@@ -356,9 +356,7 @@
context "#fetch_power_of_attorney!" do
subject { certification }
-
- it "returns true when bgs address is found",
- skip: "VACOLS rep test fails sometimes, will be changed with #5185 so ignoring it for now" do
+ it "fetches the power of attorney from bgs and vacols" do
certification.async_start!
expect(subject.bgs_rep_city).to eq "SAN FRANCISCO"
expect(subject.bgs_representative_type).to eq "Attorney"
diff --git a/spec/models/dispatch/task_spec.rb b/spec/models/dispatch/task_spec.rb
index 2ed2640fb83..7f7eef117e5 100644
--- a/spec/models/dispatch/task_spec.rb
+++ b/spec/models/dispatch/task_spec.rb
@@ -56,7 +56,7 @@ def should_invalidate?
let!(:unassigned_task) { FakeTask.create!(aasm_state: :unassigned, prepared_at: Date.yesterday) }
let!(:reviewed_task) { FakeTask.create!(aasm_state: :reviewed, prepared_at: Date.yesterday) }
- it { is_expected.to eq([unassigned_task, reviewed_task]) }
+ it { is_expected.to match_array [unassigned_task, reviewed_task] }
end
context ".completed_on" do
@@ -79,7 +79,7 @@ def should_invalidate?
end
# .sort added in case ActiveRecord returns tasks out of order in subject
- it { is_expected.to eq([task_completed_this_morning, task_completed_tonight].sort) }
+ it { is_expected.to match_array [task_completed_this_morning, task_completed_tonight] }
end
context ".completed_success" do
diff --git a/spec/models/docket_spec.rb b/spec/models/docket_spec.rb
index 620fe365327..78e6734650b 100644
--- a/spec/models/docket_spec.rb
+++ b/spec/models/docket_spec.rb
@@ -213,8 +213,7 @@
end
context "blocking mail tasks with status completed or cancelled" do
- it "includes those appeals",
- skip: "https://github.com/department-of-veterans-affairs/caseflow/issues/10516#issuecomment-503269122" do
+ it "includes those appeals" do
with_blocking_but_closed_tasks = create(:appeal,
:with_post_intake_tasks,
docket_type: Constants.AMA_DOCKETS.direct_review)
diff --git a/spec/models/judge_case_review_spec.rb b/spec/models/judge_case_review_spec.rb
index 9e9f79e57e4..5d8131d4308 100644
--- a/spec/models/judge_case_review_spec.rb
+++ b/spec/models/judge_case_review_spec.rb
@@ -23,7 +23,7 @@ def complete_params(judge:, attorney:, location:, vacols_issue1:, vacols_issue2:
# rubocop:disable Metrics/AbcSize
def expect_decass_to_be_up_to_date(decass)
decass.reload
- expect(decass.demdusr).to eq "CFS456"
+ expect(decass.demdusr).to eq vacols_judge.slogid
expect(decass.defdiff).to eq "3"
expect(decass.deoq).to eq "1"
expect(decass.deqr2).to eq "Y"
@@ -35,7 +35,6 @@ def expect_decass_to_be_up_to_date(decass)
expect(decass.decomp).to eq VacolsHelper.local_date_with_utc_timezone
expect(decass.detrem).to eq "N"
end
-# rubocop:enable Metrics/AbcSize
def expect_case_to_be_update_to_date(vacols_case, decass)
expect(vacols_case.bfmemid).to eq(decass.dememid)
@@ -43,6 +42,20 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
expect(vacols_case.bfboard).to eq(decass.deteam)
end
+def expect_case_review_to_match_params(case_review)
+ expect(case_review.valid?).to eq true
+ expect(case_review.complexity).to eq "hard"
+ expect(case_review.quality).to eq "does_not_meet_expectations"
+ expect(case_review.comment).to eq "do this"
+ expect(case_review.factors_not_considered).to match_array %w[theory_contention relevant_records]
+ expect(case_review.areas_for_improvement).to match_array ["process_violations"]
+ expect(case_review.judge).to eq judge
+ expect(case_review.attorney).to eq attorney
+ expect(case_review.appeal_type).to eq "LegacyAppeal"
+ expect(case_review.appeal_id).to eq LegacyAppeal.find_by_vacols_id(vacols_case.bfkey).id
+end
+# rubocop:enable Metrics/AbcSize
+
describe JudgeCaseReview, :all_dbs do
before do
Timecop.freeze(Time.utc(2019, 1, 1, 12, 0, 0))
@@ -84,8 +97,10 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
end
context ".create" do
- let(:judge) { User.create(css_id: "CFS123", station_id: User::BOARD_STATION_ID) }
- let(:attorney) { User.create(css_id: "CFS456", station_id: "317") }
+ let(:judge) { create(:user, :judge) }
+ let!(:vacols_judge) { create(:staff, :judge_role, user: judge) }
+ let(:attorney) { create(:user, station_id: "317") }
+ let!(:vacols_attorney) { create(:staff, :attorney_role, user: attorney) }
let(:probability) { JudgeCaseReview::QUALITY_REVIEW_SELECTION_PROBABILITY }
subject { JudgeCaseReview.complete(params) }
@@ -129,7 +144,7 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
let!(:decass) do
create(:decass,
deadtim: "2013-12-06".to_date,
- defolder: "123456",
+ defolder: vacols_case.bfkey,
deprod: work_product,
deatty: "102",
deteam: "BB",
@@ -137,9 +152,8 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
dedeadline: 6.days.ago)
end
let!(:vacols_case) { create(:case, bfkey: "123456") }
- let(:vacols_issue1) { create(:case_issue, isskey: "123456") }
- let(:vacols_issue2) { create(:case_issue, isskey: "123456") }
- let!(:judge_staff) { create(:staff, :judge_role, slogid: "CFS456", sdomainid: judge.css_id, sattyid: "AA") }
+ let(:vacols_issue1) { create(:case_issue, isskey: vacols_case.bfkey) }
+ let(:vacols_issue2) { create(:case_issue, isskey: vacols_case.bfkey) }
context "when all parameters are present to sign a decision and VACOLS update is successful" do
before do
@@ -173,63 +187,42 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
it "should create judge case review and change the location to quality review" do
allow_any_instance_of(JudgeCaseReview).to receive(:rand).and_return(probability / 2)
- expect(subject.valid?).to eq true
expect(subject.location).to eq "quality_review"
- expect(subject.complexity).to eq "hard"
- expect(subject.quality).to eq "does_not_meet_expectations"
- expect(subject.comment).to eq "do this"
- expect(subject.factors_not_considered).to eq %w[theory_contention relevant_records]
- expect(subject.areas_for_improvement).to eq ["process_violations"]
- expect(subject.judge).to eq judge
- expect(subject.attorney).to eq attorney
-
- expect(subject.appeal_type).to eq "LegacyAppeal"
- expect(subject.appeal_id).to eq LegacyAppeal.find_by_vacols_id(vacols_case.bfkey).id
+ expect_case_review_to_match_params(subject)
- expect(decass.reload.demdusr).to eq "CFS456"
- expect(decass.defdiff).to eq "3"
- expect(decass.deoq).to eq "1"
- expect(decass.deqr2).to eq "Y"
- expect(decass.deqr6).to eq "Y"
- expect(decass.deqr9).to eq "Y"
- expect(decass.deqr1).to eq nil
- expect(decass.deqr3).to eq nil
- expect(decass.deqr4).to eq nil
- expect(decass.dememid).to eq "AA"
- expect(decass.decomp).to eq VacolsHelper.local_date_with_utc_timezone
- expect(decass.detrem).to eq "N"
+ expect_decass_to_be_up_to_date(decass)
expect(vacols_case.reload.bfcurloc).to eq "48"
- expect(vacols_case.bfmemid).to eq "AA"
+ expect(vacols_case.bfmemid).to eq vacols_judge.sattyid
expect(vacols_case.bfattid).to eq "102"
expect(vacols_case.bfboard).to eq "BB"
- vacols_issues = VACOLS::CaseIssue.where(isskey: "123456")
+ vacols_issues = VACOLS::CaseIssue.where(isskey: vacols_case.bfkey)
# 1 vacated, 1 remanded and 1 blank issue created because of vacated disposition
expect(vacols_issues.size).to eq 3
vacols_issue = vacols_issues.find_by(issseq: 1)
expect(vacols_issue.issdc).to eq "5"
expect(vacols_issue.issseq).to eq vacols_issue1.issseq
- expect(vacols_issue.issmduser).to eq "CFS456"
+ expect(vacols_issue.issmduser).to eq vacols_judge.slogid
vacols_issue = vacols_issues.find_by(issseq: 2)
expect(vacols_issue.issdc).to eq "3"
expect(vacols_issue.issseq).to eq vacols_issue2.issseq
- expect(vacols_issue.issmduser).to eq "CFS456"
+ expect(vacols_issue.issmduser).to eq vacols_judge.slogid
vacols_issue = vacols_issues.find_by(issseq: 3)
expect(vacols_issue.issdc).to eq nil
expect(vacols_issue.issseq).to eq(vacols_issue2.issseq + 1)
- expect(vacols_issue.issaduser).to eq "CFS456"
+ expect(vacols_issue.issaduser).to eq vacols_judge.slogid
remand_reasons = VACOLS::RemandReason.where(rmdkey: "123456", rmdissseq: vacols_issue2.issseq)
expect(remand_reasons.size).to eq 1
expect(remand_reasons.first.rmdissseq).to eq vacols_issue2.issseq
- expect(remand_reasons.first.rmdmdusr).to eq "CFS456"
+ expect(remand_reasons.first.rmdmdusr).to eq vacols_judge.slogid
quality_review_record = VACOLS::DecisionQualityReview.find_by(qrfolder: vacols_case.bfkey)
- expect(quality_review_record.qrsmem).to eq "AA"
+ expect(quality_review_record.qrsmem).to eq vacols_judge.sattyid
expect(quality_review_record.qrteam).to eq "BB"
expect(quality_review_record.qrseldate).to eq VacolsHelper.local_date_with_utc_timezone
expect(quality_review_record.qryymm).to eq "1901"
@@ -250,18 +243,8 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
it "should create Judge Case Review" do
allow_any_instance_of(JudgeCaseReview).to receive(:rand).and_return(probability + probability)
- expect(subject.valid?).to eq true
expect(subject.location).to eq "bva_dispatch"
- expect(subject.judge).to eq judge
- expect(subject.attorney).to eq attorney
- expect(subject.complexity).to eq "hard"
- expect(subject.quality).to eq "does_not_meet_expectations"
- expect(subject.comment).to eq "do this"
- expect(subject.factors_not_considered).to eq %w[theory_contention relevant_records]
- expect(subject.areas_for_improvement).to eq ["process_violations"]
-
- expect(subject.appeal_type).to eq "LegacyAppeal"
- expect(subject.appeal_id).to eq LegacyAppeal.find_by_vacols_id(vacols_case.bfkey).id
+ expect_case_review_to_match_params(subject)
expect_decass_to_be_up_to_date(decass)
@@ -269,26 +252,26 @@ def expect_case_to_be_update_to_date(vacols_case, decass)
expect(vacols_case.bfcurloc).to eq "4E"
expect_case_to_be_update_to_date(vacols_case, decass)
- vacols_issues = VACOLS::CaseIssue.where(isskey: "123456")
+ vacols_issues = VACOLS::CaseIssue.where(isskey: vacols_case.bfkey)
# 1 vacated, 1 remanded and 1 blank issue created because of vacated disposition
expect(vacols_issues.size).to eq 3
- expect(vacols_issues.first.issdc).to eq "5"
- expect(vacols_issues.first.issseq).to eq vacols_issue1.issseq
- expect(vacols_issues.first.issmduser).to eq "CFS456"
+ vacols_issue = vacols_issues.find_by(issseq: vacols_issue1.issseq)
+ expect(vacols_issue.issdc).to eq "5"
+ expect(vacols_issue.issmduser).to eq vacols_judge.slogid
- expect(vacols_issues.second.issdc).to eq "3"
- expect(vacols_issues.second.issseq).to eq vacols_issue2.issseq
- expect(vacols_issues.second.issmduser).to eq "CFS456"
+ vacols_issue = vacols_issues.find_by(issseq: vacols_issue2.issseq)
+ expect(vacols_issue.issdc).to eq "3"
+ expect(vacols_issue.issmduser).to eq vacols_judge.slogid
- expect(vacols_issues.third.issdc).to eq nil
- expect(vacols_issues.third.issseq).to eq(vacols_issue2.issseq + 1)
- expect(vacols_issues.third.issaduser).to eq "CFS456"
+ vacols_issue = vacols_issues.find_by(issseq: vacols_issue2.issseq + 1)
+ expect(vacols_issue.issdc).to eq nil
+ expect(vacols_issue.issaduser).to eq vacols_judge.slogid
remand_reasons = VACOLS::RemandReason.where(rmdkey: "123456", rmdissseq: vacols_issue2.issseq)
expect(remand_reasons.size).to eq 1
expect(remand_reasons.first.rmdissseq).to eq vacols_issue2.issseq
- expect(remand_reasons.first.rmdmdusr).to eq "CFS456"
+ expect(remand_reasons.first.rmdmdusr).to eq vacols_judge.slogid
expect(VACOLS::DecisionQualityReview.find_by(qrfolder: vacols_case.bfkey)).to eq nil
end
diff --git a/spec/models/task_filter_spec.rb b/spec/models/task_filter_spec.rb
index 4204ffecc2c..98e7036659b 100644
--- a/spec/models/task_filter_spec.rb
+++ b/spec/models/task_filter_spec.rb
@@ -381,7 +381,7 @@ def create_cached_appeals_for_tasks(tasks, case_type)
["col=#{Constants.QUEUE_CONFIG.COLUMNS.APPEAL_TYPE.name}&val=#{case_types['1']}|#{case_types['2']}"]
end
- it "returns tasks with Original or Supplemental case types", skip: "flakey" do
+ it "returns tasks with Original or Supplemental case types" do
expect(subject.map(&:id)).to match_array(tasks_type_original.map(&:id) + tasks_type_supplemental.map(&:id))
end
end
diff --git a/spec/models/tasks/bva_dispatch_task_spec.rb b/spec/models/tasks/bva_dispatch_task_spec.rb
index cf3d50caba9..9124d11eaf8 100644
--- a/spec/models/tasks/bva_dispatch_task_spec.rb
+++ b/spec/models/tasks/bva_dispatch_task_spec.rb
@@ -113,7 +113,7 @@
decision_document = DecisionDocument.find_by(appeal_id: root_task.appeal.id)
expect(ProcessDecisionDocumentJob).to have_received(:perform_later)
- .with(decision_document.id).exactly(:once)
+ .with(decision_document.id, nil).exactly(:once)
expect(decision_document).to_not eq nil
expect(decision_document.document_type).to eq "BVA Decision"
expect(decision_document.source).to eq "BVA"
@@ -144,7 +144,7 @@
decision_document = DecisionDocument.find_by(appeal_id: legacy_appeal.id)
expect(ProcessDecisionDocumentJob).to have_received(:perform_later)
- .with(decision_document.id).exactly(:once)
+ .with(decision_document.id, nil).exactly(:once)
expect(decision_document).to_not eq nil
expect(decision_document.document_type).to eq "BVA Decision"
expect(decision_document.source).to eq "BVA"
@@ -248,7 +248,7 @@
decision_document = DecisionDocument.find_by(appeal_id: root_task.appeal.id)
expect(ProcessDecisionDocumentJob).to have_received(:perform_later)
- .with(decision_document.id).exactly(:once)
+ .with(decision_document.id, nil).exactly(:once)
expect(decision_document).to_not eq nil
expect(decision_document.document_type).to eq "BVA Decision"
expect(decision_document.source).to eq "BVA"
diff --git a/spec/models/validators/intake_start_validator_spec.rb b/spec/models/validators/intake_start_validator_spec.rb
index fc8fba85b9c..45c0056b69c 100644
--- a/spec/models/validators/intake_start_validator_spec.rb
+++ b/spec/models/validators/intake_start_validator_spec.rb
@@ -21,7 +21,7 @@
context "when BGS shows a station conflict" do
let(:station_conflict) { true }
- it "sets error_code \"veteran_not_modifiable\" when BGS shows a station conflict", skip: "Flake" do
+ it "sets error_code \"veteran_not_modifiable\" when BGS shows a station conflict" do
subject
expect(intake.error_code).to eq "veteran_not_modifiable"
@@ -33,7 +33,7 @@
context "intake user is on the BVA Intake team" do
before { BvaIntake.singleton.add_user(user) }
- it "sets a veteran_not_modifiable error code", skip: "Flake" do
+ it "sets a veteran_not_modifiable error code" do
subject
expect(intake.error_code).to eq "veteran_not_modifiable"
diff --git a/spec/models/vbms_communication_package_spec.rb b/spec/models/vbms_communication_package_spec.rb
new file mode 100644
index 00000000000..ce427dc7327
--- /dev/null
+++ b/spec/models/vbms_communication_package_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+describe VbmsCommunicationPackage, :postgres do
+ let(:package) do
+ VbmsCommunicationPackage.new(
+ file_number: "329780002",
+ comm_package_name: "test package name",
+ copies: 1,
+ document_mailable_via_pacman: VbmsUploadedDocument.new
+ )
+ end
+
+ it "is valid with valid attributes" do
+ expect(package).to be_valid
+ end
+
+ it "is not valid without a filenumber" do
+ package.file_number = nil
+ expect(package).to_not be_valid
+ expect(package.errors[:file_number]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without a communication package name" do
+ package.comm_package_name = nil
+ expect(package).to_not be_valid
+ expect(package.errors[:comm_package_name]).to eq(
+ [
+ "can't be blank",
+ "is too short (minimum is 1 character)",
+ "is invalid"
+ ]
+ )
+ end
+
+ it "is not valid if communication package name exceeds 255 characters" do
+ package.comm_package_name = "x" * 256
+ expect(package).to_not be_valid
+ expect(package.errors[:comm_package_name]).to eq(["is too long (maximum is 255 characters)", "is invalid"])
+ end
+
+ it "is not valid without a user friendly communication package name" do
+ package.comm_package_name = "(test package name with parentheses)"
+ expect(package).to_not be_valid
+ expect(package.errors[:comm_package_name]).to eq(["is invalid"])
+ end
+
+ it "is not valid without a copies attribute" do
+ package.copies = nil
+ expect(package).to_not be_valid
+ expect(package.errors[:copies]).to eq(["can't be blank", "is not a number"])
+ end
+
+ it "is not valid with less than one copy" do
+ package.copies = 0
+ expect(package).to_not be_valid
+ expect(package.errors[:copies]).to eq(["must be greater than 0"])
+ end
+
+ it "is not valid with more than 500 copies" do
+ package.copies = 500
+ expect(package).to be_valid
+
+ package.copies = 501
+ expect(package).to_not be_valid
+ expect(package.errors[:copies]).to eq(["must be less than 501"])
+ end
+
+ it "is not valid without an associated document mailable via pacman" do
+ package.document_mailable_via_pacman = nil
+ expect(package).to_not be_valid
+ expect(package.errors[:document_mailable_via_pacman]).to eq(["must exist"])
+ end
+end
diff --git a/spec/models/vbms_distribution_destination_spec.rb b/spec/models/vbms_distribution_destination_spec.rb
new file mode 100644
index 00000000000..e23f2f1c18f
--- /dev/null
+++ b/spec/models/vbms_distribution_destination_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+describe VbmsDistributionDestination, :postgres do
+ let(:distribution) { VbmsDistribution.new }
+
+ shared_examples "destination has valid attributes" do
+ it "is valid with valid attributes" do
+ expect(destination).to be_valid
+ end
+ end
+
+ let(:destination) do
+ VbmsDistributionDestination.new(
+ destination_type: "domesticAddress",
+ vbms_distribution: distribution,
+ address_line_1: "address line 1",
+ city: "city",
+ state: "NY",
+ postal_code: "11385",
+ country_code: "US"
+ )
+ end
+
+ include_examples "destination has valid attributes"
+
+ it "is not valid without a destination type" do
+ destination.destination_type = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:destination_type]).to eq(["can't be blank", "is not included in the list"])
+ end
+
+ it "is not valid with incorrect destination type" do
+ destination.destination_type = "DomesticAddress"
+ expect(destination).to_not be_valid
+ expect(destination.errors[:destination_type]).to eq(["is not included in the list"])
+ end
+
+ it "is not valid without an associated VbmsDistribution" do
+ destination.vbms_distribution = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:vbms_distribution]).to eq(["must exist"])
+ end
+
+ shared_examples "destination is a physical mailing address" do
+ it "is not valid without an address line 1" do
+ destination.address_line_1 = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:address_line_1]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without an address line 2 if treat_line_2_as_addressee is true" do
+ destination.treat_line_2_as_addressee = true
+ destination.address_line_2 = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:address_line_2]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without an address line 3 if treat_line_3_as_addressee is true" do
+ destination.treat_line_3_as_addressee = true
+ destination.address_line_3 = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:address_line_3]).to eq(["can't be blank"])
+ end
+
+ it "is not valid if treat_line_3_as_addressee is true and treat_line_2_as_addressee is false" do
+ destination.treat_line_3_as_addressee = true
+ destination.treat_line_2_as_addressee = false
+ expect(destination).to_not be_valid
+ expect(destination.errors[:treat_line_2_as_addressee])
+ .to eq(["cannot be false if line 3 is treated as addressee"])
+ end
+
+ it "is not valid without a city" do
+ destination.city = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:city]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without a country code" do
+ destination.country_code = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:country_code]).to eq(["can't be blank", "is not a valid ISO 3166-2 code"])
+ end
+
+ it "is not valid without a two-letter ISO 3166-2 country code" do
+ destination.country_code = "XX"
+ expect(destination).to_not be_valid
+ expect(destination.errors[:country_code]).to eq(["is not a valid ISO 3166-2 code"])
+ end
+ end
+
+ shared_examples "destination is a US address" do
+ it "is not valid without a state" do
+ destination.state = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:state]).to eq(["can't be blank", "is not a valid ISO 3166-2 code"])
+ end
+
+ it "is not valid without a two-letter ISO 3166-2 state code" do
+ destination.state = "XX"
+ expect(destination).to_not be_valid
+ expect(destination.errors[:state]).to eq(["is not a valid ISO 3166-2 code"])
+ end
+
+ it "is not valid without a postal code" do
+ destination.postal_code = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:postal_code]).to eq(["can't be blank"])
+ end
+ end
+
+ context "destination type is domesticAddress" do
+ include_examples "destination has valid attributes"
+ include_examples "destination is a physical mailing address"
+ include_examples "destination is a US address"
+ end
+
+ context "destination type is militaryAddress" do
+ let(:destination) do
+ VbmsDistributionDestination.new(
+ destination_type: "militaryAddress",
+ vbms_distribution: distribution,
+ address_line_1: "address line 1",
+ city: "city",
+ state: "NY",
+ postal_code: "11385",
+ country_code: "US"
+ )
+ end
+
+ include_examples "destination has valid attributes"
+ include_examples "destination is a physical mailing address"
+ include_examples "destination is a US address"
+ end
+
+ context "destination type is internationalAddress" do
+ let(:destination) do
+ VbmsDistributionDestination.new(
+ destination_type: "internationalAddress",
+ vbms_distribution: distribution,
+ address_line_1: "address line 1",
+ city: "city",
+ country_name: "France",
+ country_code: "FR"
+ )
+ end
+
+ include_examples "destination has valid attributes"
+ include_examples "destination is a physical mailing address"
+
+ it "is not valid without a country name" do
+ destination.country_name = nil
+ expect(destination).to_not be_valid
+ expect(destination.errors[:country_name]).to eq(["can't be blank"])
+ end
+ end
+end
diff --git a/spec/models/vbms_distribution_spec.rb b/spec/models/vbms_distribution_spec.rb
new file mode 100644
index 00000000000..7182708ceab
--- /dev/null
+++ b/spec/models/vbms_distribution_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+describe VbmsDistribution, :postgres do
+ let(:package) { VbmsCommunicationPackage.new }
+
+ shared_examples "distribution has valid attributes" do
+ it "is valid with valid attributes" do
+ expect(distribution).to be_valid
+ end
+ end
+
+ let(:distribution) do
+ VbmsDistribution.new(
+ recipient_type: "person",
+ vbms_communication_package: package,
+ first_name: "First",
+ last_name: "Last"
+ )
+ end
+
+ include_examples "distribution has valid attributes"
+
+ it "is valid without an associated VbmsCommunicationPackage" do
+ distribution.vbms_communication_package = nil
+ expect(distribution).to be_valid
+ end
+
+ it "is not valid without a recipient type" do
+ distribution.recipient_type = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:recipient_type]).to eq(["can't be blank", "is not included in the list"])
+ end
+
+ it "is not valid with incorrect recipient type" do
+ distribution.recipient_type = "Person"
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:recipient_type]).to eq(["is not included in the list"])
+ end
+
+ context "recipient type is person" do
+ it "is not valid without a first name" do
+ distribution.first_name = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:first_name]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without a last name" do
+ distribution.last_name = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:last_name]).to eq(["can't be blank"])
+ end
+ end
+
+ shared_examples "recipient type is not person" do
+ it "is not valid without a name" do
+ distribution.name = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:name]).to eq(["can't be blank"])
+ end
+ end
+
+ context "recipient type is organization" do
+ let(:distribution) do
+ VbmsDistribution.new(
+ recipient_type: "organization",
+ vbms_communication_package: package,
+ name: "Organization"
+ )
+ end
+
+ include_examples "distribution has valid attributes"
+ include_examples "recipient type is not person"
+ end
+
+ context "recipient type is system" do
+ let(:distribution) do
+ VbmsDistribution.new(
+ recipient_type: "system",
+ vbms_communication_package: package,
+ name: "System"
+ )
+ end
+
+ include_examples "distribution has valid attributes"
+ include_examples "recipient type is not person"
+ end
+
+ context "recipient is ro-colocated" do
+ let(:distribution) do
+ VbmsDistribution.new(
+ recipient_type: "ro-colocated",
+ vbms_communication_package: package,
+ name: "Ro-Colocated",
+ poa_code: "poa code",
+ claimant_station_of_jurisdiction: "claimant station"
+ )
+ end
+
+ include_examples "distribution has valid attributes"
+ include_examples "recipient type is not person"
+
+ it "is not valid without a poa code" do
+ distribution.poa_code = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:poa_code]).to eq(["can't be blank"])
+ end
+
+ it "is not valid without a claimant station of jurisdiction" do
+ distribution.claimant_station_of_jurisdiction = nil
+ expect(distribution).to_not be_valid
+ expect(distribution.errors[:claimant_station_of_jurisdiction]).to eq(["can't be blank"])
+ end
+ end
+end
diff --git a/spec/services/external_api/pacman_service_spec.rb b/spec/services/external_api/pacman_service_spec.rb
new file mode 100644
index 00000000000..81409e28efa
--- /dev/null
+++ b/spec/services/external_api/pacman_service_spec.rb
@@ -0,0 +1,212 @@
+# frozen_string_literal: true
+
+describe ExternalApi::PacmanService do
+ let(:error_response_body) { { "result": "error", "message": { "token": ["error"] } }.as_json }
+ let(:error_response) do
+ HTTPI::Response.new(400, {}, error_response_body)
+ end
+ let(:forbidden_response) do
+ HTTPI::Response.new(403, {}, error_response_body)
+ end
+ let(:not_found_response) do
+ HTTPI::Response.new(404, {}, error_response_body)
+ end
+
+ let(:distribution) do
+ {
+ "id" => Fakes::PacmanService::DISTRIBUTION_UUID,
+ "recipient" => {
+ "type" => "system",
+ "id" => "a050a21e-23f6-4743-a1ff-aa1e24412eff",
+ "name" => "VBMS-C"
+ },
+ "description" => "Staging Mailing Distribution",
+ "communicationPackageId" => 1,
+ "destinations" => [{
+ "type" => "physicalAddress",
+ "id" => "28440040-51a5-4d2a-81a2-28730827be14",
+ "status" => "",
+ "cbcmSendAttemptDate" => "2022-06-06T16:35:27.996",
+ "addressLine1" => "POSTMASTER GENERAL",
+ "addressLine2" => "UNITED STATES POSTAL SERVICE",
+ "addressLine3" => "475 LENFANT PLZ SW RM 10022",
+ "addressLine4" => "SUITE 123",
+ "addressLine5" => "APO AE 09001-5275",
+ "addressLine6" => "",
+ "treatLine2AsAddressee" => true,
+ "treatLine3AsAddressee" => true,
+ "city" => "WASHINGTON DC",
+ "state" => "DC",
+ "postalCode" => "12345",
+ "countryName" => "UNITED STATES",
+ "countryCode" => "us"
+ }],
+ "status" => "",
+ "sentToCbcmDate" => ""
+ }.as_json
+ end
+
+ let(:vbms_distribution) do
+ create(:vbms_distribution, uuid: SecureRandom.uuid)
+ end
+
+ let(:distribution_post_request) do
+ {
+ "communicationPackageId" => "673c8b4a-cb7d-4fdf-bc4d-998d6d5d7431",
+ "recipient" => {
+ "type" => nil,
+ "name" => nil,
+ "firstName" => nil,
+ "middleName" => nil,
+ "lastName" => nil,
+ "participantId" => nil,
+ "poaCode" => nil,
+ "claimantStationOfJurisdiction" => nil
+ },
+ "destinations" => [{
+ "type" => nil,
+ "addressLine1" => nil,
+ "addressLine2" => nil,
+ "addressLine3" => nil,
+ "addressLine4" => nil,
+ "addressLine5" => nil,
+ "addressLine6" => nil,
+ "treatLine2AsAddressee" => nil,
+ "treatLine3AsAddressee" => nil,
+ "city" => nil,
+ "state" => nil,
+ "postalCode" => nil,
+ "countryName" => nil,
+ "countryCode" => nil
+ }]
+ }.as_json
+ end
+
+ let(:distribution_post_response) do
+ {
+ "id" => "673c8b4a-cb7d-4fdf-bc4d-998d6d5d7431",
+ "recipient" => {
+ "type" => "system",
+ "id" => "2c6592fc-b3af-48ff-8263-c581c2f0a68b",
+ "name" => "VBMS-C"
+ },
+ "description" => "Staging Distribution",
+ "communicationPackageId" => "673c8b4a-cb7d-4fdf-bc4d-998d6d5d7431",
+ "destinations" => [{
+ "type" => "physicalAddress",
+ "id" => "5378bfbd-eff5-470c-bbc4-c7fd3c863a50",
+ "status" => "null",
+ "cbcmSendAttemptDate" => "2022-06-06T16:35:28.017",
+ "addressLine1" => "POSTMASTER GENERAL",
+ "addressLine2" => "UNITED STATES POSTAL SERVICE",
+ "addressLine3" => "475 LENFANT PLZ SW RM 10022",
+ "addressLine4" => "SUITE 123",
+ "addressLine5" => "APO AE 09001-5275",
+ "addressLine6" => "",
+ "treatLine2AsAddressee" => false,
+ "treatLine3AsAddressee" => false,
+ "city" => "WASHINGTON DC",
+ "state" => "DC",
+ "postalCode" => "12345",
+ "countryName" => "UNITED STATES",
+ "countryCode" => "us"
+ }],
+ "status" => "null",
+ "sentToCbcmDate" => "null"
+ }.as_json
+ end
+
+ let(:package_post_request) do
+ {
+ "fileNumber" => "123456789",
+ "name" => "ABC abc 1234 !*+,-.:;=?",
+ "documentReferences" => [{
+ "id" => "3aec91cc-a88d-4b9c-9183-84bed583bbcc",
+ "copies" => 1
+ }]
+ }.as_json
+ end
+
+ let(:package_post_response) do
+ {
+ "id" => "24eb6a66-3833-4de6-bea4-4b614e55d5ac",
+ "fileNumber" => "123456789",
+ "documentReferences" => [{
+ "id" => "23233175-6a87-4cd4-b327-f20cf5ef1222",
+ "copies" => 1
+ }],
+ "status" => "NEW",
+ "createDate" => ""
+ }.as_json
+ end
+
+ let(:get_distribution_success_response) do
+ HTTPI::Response.new(200, {}, distribution)
+ end
+
+ let(:post_distribution_success_response) do
+ HTTPI::Response.new(201, {}, distribution_post_response)
+ end
+
+ let(:post_package_success_response) do
+ HTTPI::Response.new(201, {}, package_post_response)
+ end
+
+ context "get distribution" do
+ subject { Fakes::PacmanService.get_distribution_request(vbms_distribution.uuid) }
+ it "gets correct distribution" do
+ expect(subject.body.as_json).to eq(get_distribution_success_response.body)
+ end
+ context "not found" do
+ subject { Fakes::PacmanService.get_distribution_request("fake") }
+ it "returns 404 PacmanNotFoundError" do
+ expect(subject.code).to eq(not_found_response.code)
+ end
+ end
+ end
+
+ context "creates and submits distribution" do
+ subject do
+ ExternalApi::PacmanService.send_distribution_request(distribution_post_request["communicationPackageId"],
+ distribution_post_request["recipient"],
+ distribution_post_request["destinations"])
+ end
+ it "successfully sends distribution" do
+ allow(HTTPI)
+ .to receive(:post) do |req|
+ expect(JSON.parse(req.body)).to eq distribution_post_request
+ end.and_return(post_distribution_success_response)
+ expect(subject.first.body.as_json).to eq(post_distribution_success_response.body)
+ end
+ end
+
+ context "creates and sends communication package" do
+ subject do
+ ExternalApi::PacmanService.send_communication_package_request(package_post_request["file_number"],
+ package_post_request["name"],
+ package_post_request["documentReferences"])
+ end
+ it "successfully sends package" do
+ allow(HTTPI).to receive(:post).and_return(post_package_success_response)
+ expect(subject.body.as_json).to include(post_package_success_response.body)
+ end
+ end
+
+ describe "response failure" do
+ subject { ExternalApi::PacmanService.get_distribution_request(vbms_distribution.uuid) }
+
+ context "400" do
+ it "throws Caseflow::Error::PacmanBadRequestError" do
+ allow(HTTPI).to receive(:get).and_return(error_response)
+ expect(subject).to eq(error_response)
+ end
+ end
+
+ context "403" do
+ it "throws Caseflow::Error::PacmanForbiddenError" do
+ allow(HTTPI).to receive(:get).and_return(forbidden_response)
+ expect(subject).to eq(forbidden_response)
+ end
+ end
+ end
+end
diff --git a/spec/support/queue_helpers.rb b/spec/support/queue_helpers.rb
index bfd61ed4c25..39d1cbabb0c 100644
--- a/spec/support/queue_helpers.rb
+++ b/spec/support/queue_helpers.rb
@@ -9,6 +9,10 @@ def disposition_text
mtv_const.DISPOSITION_TEXT.to_h
end
+ def disposition_timeline_text
+ mtv_const.DISPOSITION_TIMELINE_TEXT.to_h
+ end
+
def recommendation_text
mtv_const.DISPOSITION_RECOMMENDATIONS.to_h
end
@@ -32,14 +36,26 @@ def format_mtv_attorney_instructions(notes:, disposition:, hyperlinks: [])
end
def format_mtv_judge_instructions(notes:, disposition:, vacate_type: nil, hyperlink: nil)
- parts = ["I am proceeding with a #{disposition_text[disposition.to_sym]}."]
-
- parts += case disposition
- when "granted", "partial"
- ["This will be a #{vacate_types[vacate_type.to_sym]}", notes]
- else
- [notes, "\nHere is the hyperlink to the signed denial document", hyperlink]
- end
+ parts = ["**Motion To Vacate:** \n#{disposition_timeline_text[disposition.to_sym]}\n"]
+
+ case disposition
+ when "granted", "partially_granted"
+ parts += ["**Type:** "]
+ parts += ["#{vacate_types[vacate_type.to_sym]}\n"]
+ if !notes.empty?
+ parts += ["**Detail:** "]
+ parts += ["#{notes}\n"]
+ end
+ when "denied", "dismissed"
+ if !notes.empty?
+ parts += ["**Detail:** "]
+ parts += ["#{notes}\n"]
+ end
+ if hyperlink.present?
+ parts += ["**Hyperlink:** "]
+ parts += ["#{hyperlink}\n"]
+ end
+ end
parts.join("\n")
end
diff --git a/spec/workflows/ama_appeal_dispatch_spec.rb b/spec/workflows/ama_appeal_dispatch_spec.rb
index 05b4231b62a..62a1e336e61 100644
--- a/spec/workflows/ama_appeal_dispatch_spec.rb
+++ b/spec/workflows/ama_appeal_dispatch_spec.rb
@@ -1,32 +1,77 @@
# frozen_string_literal: true
describe AmaAppealDispatch, :postgres do
+ include ActiveJob::TestHelper
+
+ let(:user) { User.authenticate! }
+ let(:appeal) { create(:appeal, :advanced_on_docket_due_to_age) }
+ let(:root_task) { create(:root_task, appeal: appeal) }
+ let(:poa_participant_id) { "600153863" }
+ let(:bgs_poa) { instance_double(BgsPowerOfAttorney) }
+ let(:params) do
+ { citation_number: "A18123456",
+ decision_date: Time.zone.now,
+ redacted_document_location: "C://Windows/User/BLOBLAW/Documents/Decision.docx",
+ file: "12345678" }
+ end
+ let(:mail_package) do
+ { distributions: [build(:mail_request).call.to_json],
+ copies: 1,
+ created_by_id: user.id }
+ end
+
+ before do
+ BvaDispatch.singleton.add_user(user)
+ BvaDispatchTask.create_from_root_task(root_task)
+ allow(BgsPowerOfAttorney).to receive(:find_or_create_by_file_number)
+ .with(appeal.veteran_file_number).and_return(bgs_poa)
+ allow(bgs_poa).to receive(:participant_id).and_return(poa_participant_id)
+ end
+
+ before(:all) { Seeds::NotificationEvents.new.seed! }
+
+ subject do
+ perform_enqueued_jobs do
+ AmaAppealDispatch.new(appeal: appeal, params: params, user: user, mail_package: mail_package).call
+ end
+ end
+
describe "#call" do
it "stores current POA participant ID in the Appeals table" do
- user = create(:user)
- BvaDispatch.singleton.add_user(user)
- appeal = create(:appeal, :advanced_on_docket_due_to_age)
- root_task = create(:root_task, appeal: appeal)
- BvaDispatchTask.create_from_root_task(root_task)
- poa_participant_id = "600153863"
-
- bgs_poa = instance_double(BgsPowerOfAttorney)
- allow(BgsPowerOfAttorney).to receive(:find_or_create_by_file_number)
- .with(appeal.veteran_file_number).and_return(bgs_poa)
- allow(bgs_poa).to receive(:participant_id).and_return(poa_participant_id)
-
- params = {
- appeal_id: appeal.id,
- appeal_type: "Appeal",
- citation_number: "A18123456",
- decision_date: Time.zone.now,
- redacted_document_location: "C://Windows/User/BLOBLAW/Documents/Decision.docx",
- file: "12345678"
- }
-
- AmaAppealDispatch.new(appeal: appeal, params: params, user: user).call
-
- expect(appeal.reload.poa_participant_id).to eq poa_participant_id
+ subject
+ expect(appeal.poa_participant_id).to eq poa_participant_id
+ end
+
+ context "document is associated with a mail request" do
+ it "calls #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to receive(:perform_later) do |doc, pkg|
+ expect(doc).to be_a DecisionDocument
+ expect(doc.appeal_type).to eq "Appeal"
+ expect(doc.appeal_id).to eq appeal.id
+ expect(doc.redacted_document_location).to eq params[:redacted_document_location]
+ expect(doc.citation_number).to eq params[:citation_number]
+
+ expect(pkg).to eq mail_package
+ end
+
+ subject
+ end
+ end
+
+ context "document is not associated with a mail request" do
+ let(:mail_package) { nil }
+ it "does not call #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to_not receive(:perform_later)
+ subject
+ end
+ end
+
+ context "document is not successfully processed" do
+ it "does not call #perform_later on MailRequestJob" do
+ allow(ProcessDecisionDocumentJob).to receive(:perform_later).and_raise(StandardError)
+ expect(MailRequestJob).to_not receive(:perform_later)
+ expect { subject }.to raise_error(StandardError)
+ end
end
end
end
diff --git a/spec/workflows/legacy_appeal_dispatch_spec.rb b/spec/workflows/legacy_appeal_dispatch_spec.rb
index 59b729349b9..81c40e35793 100644
--- a/spec/workflows/legacy_appeal_dispatch_spec.rb
+++ b/spec/workflows/legacy_appeal_dispatch_spec.rb
@@ -1,61 +1,108 @@
# frozen_string_literal: true
-describe LegacyAppealDispatch do
+describe LegacyAppealDispatch, :all_dbs do
+ include ActiveJob::TestHelper
+
describe "#call" do
- context "invalid citation number" do
- it "returns an object with validation errors" do
- legacy_appeal = build_stubbed(:legacy_appeal)
+ let(:user) { User.authenticate! }
+ let(:legacy_appeal) do
+ create(:legacy_appeal,
+ :with_veteran,
+ vacols_case: create(:case, :aod, :type_cavc_remand, bfregoff: "RO13",
+ folder: create(:folder, tinum: "13 11-265")))
+ end
+ let(:root_task) { create(:root_task, appeal: legacy_appeal) }
+ let(:params) do
+ { appeal_id: legacy_appeal.id,
+ citation_number: "A18123456",
+ decision_date: Time.zone.today,
+ redacted_document_location: "some/filepath",
+ file: "some file" }
+ end
+ let(:mail_package) do
+ { distributions: [build(:mail_request).call.to_json],
+ copies: 1,
+ created_by_id: user.id }
+ end
- params = {
- appeal_id: legacy_appeal.id,
- citation_number: "123",
- decision_date: Time.zone.today,
- redacted_document_location: "some/filepath",
- file: "some file"
- }
+ before(:all) { Seeds::NotificationEvents.new.seed! }
- dispatch = LegacyAppealDispatch.new(appeal: legacy_appeal, params: params).call
+ before do
+ BvaDispatch.singleton.add_user(user)
+ BvaDispatchTask.create_from_root_task(root_task)
+ end
- expect(dispatch).to_not be_success
- expect(dispatch.errors[0]).to eq "Citation number is invalid"
+ subject do
+ perform_enqueued_jobs do
+ LegacyAppealDispatch.new(appeal: legacy_appeal, params: params, mail_package: mail_package).call
end
end
- context "citation number already exists" do
- it "returns an object with validation errors" do
- legacy_appeal = build_stubbed(:legacy_appeal)
-
- params = {
- appeal_id: legacy_appeal.id,
- citation_number: "A18123456",
- decision_date: Time.zone.today,
- redacted_document_location: "some/filepath",
- file: "some file"
- }
+ context "valid parameters" do
+ it "successfully outcodes dispatch" do
+ expect(subject).to be_success
+ end
+ end
- dispatch = LegacyAppealDispatch.new(appeal: legacy_appeal, params: params)
- allow(dispatch).to receive(:unique_citation_number?).and_return(false)
+ context "invalid citation number" do
+ it "returns an object with validation errors" do
+ params[:citation_number] = "123"
+ expect(subject).to_not be_success
+ expect(subject.errors[0]).to eq "Citation number is invalid"
+ end
+ end
- expect(dispatch.call).to_not be_success
- expect(dispatch.call.errors[0]).to eq "Citation number already exists"
+ context "citation number already exists" do
+ it "returns an object with validation errors" do
+ allow_any_instance_of(LegacyAppealDispatch).to receive(:unique_citation_number?).and_return(false)
+ expect(subject).to_not be_success
+ expect(subject.errors[0]).to eq "Citation number already exists"
end
end
context "missing required parameters" do
it "returns an object with validation errors" do
- legacy_appeal = build_stubbed(:legacy_appeal)
-
- params = {
- appeal_id: legacy_appeal.id,
- citation_number: "A18123456"
- }
+ params[:decision_date] = nil
+ params[:redacted_document_location] = nil
+ params[:file] = nil
- dispatch = LegacyAppealDispatch.new(appeal: legacy_appeal, params: params).call
error_message = "Decision date can't be blank, Redacted document " \
"location can't be blank, File can't be blank"
- expect(dispatch).to_not be_success
- expect(dispatch.errors[0]).to eq error_message
+ expect(subject).to_not be_success
+ expect(subject.errors[0]).to eq error_message
+ end
+ end
+
+ context "dispatch is associated with a mail request" do
+ it "calls #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to receive(:perform_later) do |doc, pkg|
+ expect(doc).to be_a DecisionDocument
+ expect(doc.appeal_type).to eq "LegacyAppeal"
+ expect(doc.appeal_id).to eq params[:appeal_id]
+ expect(doc.citation_number).to eq params[:citation_number]
+ expect(doc.redacted_document_location).to eq params[:redacted_document_location]
+
+ expect(pkg).to eq mail_package
+ end
+
+ subject
+ end
+ end
+
+ context "document is not associated with a mail request" do
+ let(:mail_package) { nil }
+ it "does not call #perform_later on MailRequestJob" do
+ expect(MailRequestJob).to_not receive(:perform_later)
+ subject
+ end
+ end
+
+ context "document is not successfully processed" do
+ it "does not call #perform_later on MailRequestJob" do
+ allow(ProcessDecisionDocumentJob).to receive(:perform_later).and_raise(StandardError)
+ expect(MailRequestJob).to_not receive(:perform_later)
+ expect { subject }.to raise_error(StandardError)
end
end
end
diff --git a/spec/workflows/mail_request_spec.rb b/spec/workflows/mail_request_spec.rb
new file mode 100644
index 00000000000..ef43959f4e7
--- /dev/null
+++ b/spec/workflows/mail_request_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+describe MailRequest, :postgres do
+ let(:mail_request_params) do
+ ActionController::Parameters.new(
+ recipient_type: "person",
+ first_name: "Bob",
+ last_name: "Smithmetz",
+ participant_id: "487470002",
+ destination_type: "domesticAddress",
+ address_line_1: "1234 Main Street",
+ treat_line_2_as_addressee: false,
+ treat_line_3_as_addressee: false,
+ city: "Orlando",
+ state: "FL",
+ postal_code: "12345",
+ country_code: "US"
+ )
+ end
+
+ let(:invalid_mail_request_params) do
+ ActionController::Parameters.new(
+ recipient_type: nil,
+ last_name: "Smithmetz",
+ participant_id: "487470002",
+ destination_type: "domesticAddress",
+ address_line_1: "1234 Main Street",
+ treat_line_2_as_addressee: false,
+ treat_line_3_as_addressee: false,
+ city: "Orlando",
+ state: "FL",
+ postal_code: nil,
+ country_code: "US"
+ )
+ end
+
+ shared_examples "mail request has valid attributes" do
+ let(:mail_request_spec_object) { build(:mail_request) }
+ it "is valid with valid attributes" do
+ expect(mail_request_spec_object).to be_valid
+ end
+ end
+
+ let(:mail_request_spec_object_1) { build(:mail_request, :nil_recipient_type) }
+ include_examples "mail request has valid attributes"
+ it "is not valid without a recipient type" do
+ expect(mail_request_spec_object_1).to_not be_valid
+ end
+
+ describe "#call" do
+ context "when valid parameters are passed into the mail requests initialize method." do
+ subject { described_class.new(mail_request_params).call }
+
+ before do
+ RequestStore.store[:current_user] = User.system_user
+ end
+
+ it "creates a vbms_distribution" do
+ expect { subject }.to change(VbmsDistribution, :count).by(1)
+ end
+
+ it "creates a vbms_distribution_destination" do
+ expect { subject }.to change(VbmsDistributionDestination, :count).by(1)
+ end
+ end
+
+ context "when invalid parameters are passed into the mail requests initialize method." do
+ subject { described_class.new(invalid_mail_request_params).call }
+ it "raises an error" do
+ expect { subject }.to raise_error(Caseflow::Error::MissingRecipientInfo)
+ end
+ end
+ end
+end