diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 536bfee49a7..5f3c1446369 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -505,6 +505,7 @@ app/swagger/swagger/requests/mdot @department-of-veterans-affairs/va-cto-health-
app/swagger/swagger/requests/medical_copays.rb @department-of-veterans-affairs/vsa-debt-resolution @department-of-veterans-affairs/backend-review-group
app/swagger/swagger/requests/messages @department-of-veterans-affairs/vfs-mhv-secure-messaging @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/swagger/swagger/requests/mvi_users.rb @department-of-veterans-affairs/octo-identity
+app/swagger/swagger/requests/my_va/submission_statuses.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/swagger/swagger/requests/onsite_notifications.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/swagger/swagger/v1/requests/post911_gi_bill_statuses.rb @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-iir
app/swagger/swagger/requests/ppiu.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -642,10 +643,11 @@ app/sidekiq/feature_cleaner_job.rb @department-of-veterans-affairs/va-api-engine
app/sidekiq/form1010cg @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/form1095 @department-of-veterans-affairs/vfs-1095-b @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/form526_confirmation_email_job.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/sidekiq/form526_failure_state_snapshot_job.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/form526_paranoid_success_polling_job.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
-app/sidekiq/form526_state_logging_job.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/form526_status_polling_job.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/form526_submission_failed_email_job.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/sidekiq/form526_submission_processing_report_job.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/gi_bill_feedback_submission_job.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/hca @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/identity @department-of-veterans-affairs/octo-identity
@@ -839,6 +841,7 @@ lib/benefits_intake_service @department-of-veterans-affairs/benefits-dependents-
lib/bgs @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/bid @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/bip_claims @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+lib/burials @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/carma @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/caseflow @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/central_mail @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1343,9 +1346,10 @@ spec/sidekiq/facilities @department-of-veterans-affairs/vfs-facilities-frontend
spec/sidekiq/form1010cg @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/form1095 @department-of-veterans-affairs/vfs-1095-b @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/form526_confirmation_email_job_spec.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/sidekiq/form526_failure_state_snapshot_job_spec.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/form526_paranoid_success_polling_job_spec.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
-spec/sidekiq/form526_state_logging_job_spec.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/form526_status_polling_job_spec.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/sidekiq/form526_submission_processing_report_job_spec.rb @department-of-veterans-affairs/disability-experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/form5655 @department-of-veterans-affairs/vsa-debt-resolution @department-of-veterans-affairs/backend-review-group
spec/sidekiq/gi_bill_feedback_submission_job_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/hca @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
@@ -1379,6 +1383,7 @@ spec/lib/bgs @department-of-veterans-affairs/benefits-dependents-management @dep
spec/lib/bid @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/bip_claims @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/breakers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/lib/burials @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/carma @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/caseflow @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/central_mail @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -2126,4 +2131,5 @@ README.md @department-of-veterans-affairs/backend-review-group
modules/accredited_representative_portal/spec/services/accredited_representative_portal/representative_user_loader_spec.rb @department-of-veterans-affairs/octo-identity
modules/accredited_representative_portal/app/services/accredited_representative_portal/representative_user_loader.rb @department-of-veterans-affairs/octo-identity
config/form_profile_mappings/FORM-MOCK-AE-DESIGN-PATTERNS.yml @department-of-veterans-affairs/tmf-auth-exp-design-patterns @department-of-veterans-affairs/backend-review-group
-vets-api.pm-collection.json @department-of-veterans-affairs/backend-review-group
+postman/vets-api.pm-collection.json @department-of-veterans-affairs/backend-review-group
+postman/Dockerfile @department-of-veterans-affairs/backend-review-group
diff --git a/.github/workflows/build-and-publish.yaml b/.github/workflows/build-and-publish.yaml
index 296cf3f2731..70dfa194115 100644
--- a/.github/workflows/build-and-publish.yaml
+++ b/.github/workflows/build-and-publish.yaml
@@ -33,7 +33,17 @@ jobs:
uses: aws-actions/amazon-ecr-login@v2.0.1
with:
mask-password: true
- - name: Build Docker Image
+ - name: Build Postman Image
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ with:
+ context: ./postman
+ file: ./postman/Dockerfile
+ push: true
+ tags: |
+ ${{ steps.ecr-login.outputs.registry }}/dsva/vets-api-postman:${{ steps.version.outputs.version }}
+ - name: Build vets-api Docker Image
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_SUMMARY: false
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 452b541e5f0..8d2e829b591 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,8 +30,17 @@ jobs:
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
-
- - name: Build Docker Image
+ - name: Build Postman Docker Image
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ with:
+ context: ./postman
+ file: ./postman/Dockerfile
+ push: true
+ tags: |
+ ${{ steps.login-ecr.outputs.registry }}/dsva/vets-api-postman:${{ github.sha }}
+ - name: Build vets-api Docker Image
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_SUMMARY: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 689c092dacf..bfdfd1c39ba 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -512,9 +512,14 @@ GEM
thor (>= 0.20, < 2.a)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
- google-protobuf (4.28.1)
+ google-protobuf (4.28.2)
bigdecimal
rake (>= 13)
+ google-protobuf (4.28.2-java)
+ bigdecimal
+ ffi (~> 1)
+ ffi-compiler (~> 1)
+ rake (>= 13)
googleauth (1.11.0)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
@@ -622,7 +627,7 @@ GEM
llhttp-ffi (0.5.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
- lockbox (1.4.0)
+ lockbox (1.4.1)
logger (1.6.1)
loofah (2.22.0)
crass (~> 1.0.2)
@@ -647,7 +652,7 @@ GEM
mini_mime (1.1.5)
mini_portile2 (2.8.7)
minitest (5.25.1)
- mock_redis (0.44.0)
+ mock_redis (0.45.0)
msgpack (1.7.2)
msgpack (1.7.2-java)
multi_json (1.15.0)
@@ -764,9 +769,9 @@ GEM
psych (5.1.2-java)
jar-dependencies (>= 0.1.7)
public_suffix (6.0.1)
- puma (6.4.2)
+ puma (6.4.3)
nio4r (~> 2.0)
- puma (6.4.2-java)
+ puma (6.4.3-java)
nio4r (~> 2.0)
pundit (2.4.0)
activesupport (>= 3.0.0)
@@ -930,7 +935,7 @@ GEM
rubocop-factory_bot (2.26.1)
rubocop (~> 1.61)
rubocop-junit-formatter (0.1.4)
- rubocop-rails (2.26.1)
+ rubocop-rails (2.26.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.52.0, < 2.0)
diff --git a/app/controllers/v0/apidocs_controller.rb b/app/controllers/v0/apidocs_controller.rb
index 5989652d783..da5ce23d00b 100644
--- a/app/controllers/v0/apidocs_controller.rb
+++ b/app/controllers/v0/apidocs_controller.rb
@@ -164,6 +164,7 @@ class ApidocsController < ApplicationController
Swagger::Requests::Messages::TriageTeams,
Swagger::Requests::MviUsers,
Swagger::Requests::OnsiteNotifications,
+ Swagger::Requests::MyVA::SubmissionStatuses,
Swagger::Requests::IncomeAndAssetsClaims,
Swagger::Requests::PPIU,
Swagger::Requests::PreneedsClaims,
diff --git a/app/controllers/v0/backend_statuses_controller.rb b/app/controllers/v0/backend_statuses_controller.rb
index 0c10422fc80..70568051fba 100644
--- a/app/controllers/v0/backend_statuses_controller.rb
+++ b/app/controllers/v0/backend_statuses_controller.rb
@@ -7,41 +7,48 @@ module V0
class BackendStatusesController < ApplicationController
service_tag 'maintenance-windows'
skip_before_action :authenticate
+ before_action :validate_service, only: [:show]
- # NOTE: this endpoint is somewhat misleading. Index gets data from PagerDuty and
- # show only looks at GI bill scheduled downtime (and gets no data from PagerDuty)
def index
- statuses = ExternalServicesRedis::Status.new.fetch_or_cache
- maintenance_windows = MaintenanceWindow.end_after(Time.zone.now)
-
options = { params: { maintenance_windows: } }
- render json: BackendStatusesSerializer.new(statuses, options)
+ render json: BackendStatusesSerializer.new(backend_statuses, options)
end
# GET /v0/backend_statuses/:service
def show
- @backend_service = params[:service]
- raise Common::Exceptions::RecordNotFound, @backend_service unless recognized_service?
+ render json: BackendStatusSerializer.new(backend_status)
+ end
- # get status
- be_status = BackendStatus.new(name: @backend_service)
- case @backend_service
- when BackendServices::GI_BILL_STATUS
- be_status.is_available = BenefitsEducation::Service.within_scheduled_uptime?
- be_status.uptime_remaining = BenefitsEducation::Service.seconds_until_downtime
- else
- # default service is up!
- be_status.is_available = true
- be_status.uptime_remaining = 0
- end
+ private
- render json: BackendStatusSerializer.new(be_status)
+ # NOTE: Data is from PagerDuty
+ def backend_statuses
+ @backend_statuses ||= ExternalServicesRedis::Status.new.fetch_or_cache
end
- private
+ def maintenance_windows
+ @maintenance_windows ||= MaintenanceWindow.end_after(Time.zone.now)
+ end
+
+ # NOTE: Data is GI bill scheduled downtime
+ def backend_status
+ @backend_status ||= BackendStatus.new(name: backend_service)
+ end
+
+ def backend_service
+ params[:service]
+ end
+
+ def validate_service
+ raise Common::Exceptions::RecordNotFound, backend_service unless recognized_service?
+ end
def recognized_service?
- BackendServices.all.include?(@backend_service)
+ BackendServices.all.include?(backend_service)
+ end
+
+ def backend_status_is_available
+ backend_service == BackendServices::GI_BILL_STATUS
end
end
end
diff --git a/app/controllers/v0/burial_claims_controller.rb b/app/controllers/v0/burial_claims_controller.rb
index 5e3b70df986..132bddd4b01 100644
--- a/app/controllers/v0/burial_claims_controller.rb
+++ b/app/controllers/v0/burial_claims_controller.rb
@@ -1,52 +1,61 @@
# frozen_string_literal: true
require 'pension_burial/tag_sentry'
+require 'burials/monitor'
module V0
class BurialClaimsController < ClaimsBaseController
service_tag 'burial-application'
def show
- submission_attempt = determine_submission_attempt
+ claim = claim_class.find_by!(guid: params[:id])
+ form_submission = claim&.form_submissions&.last
+ submission_attempt = form_submission&.form_submission_attempts&.last
if submission_attempt
state = submission_attempt.aasm_state == 'failure' ? 'failure' : 'success'
render(json: { data: { attributes: { state: } } })
elsif central_mail_submission
render json: CentralMailSubmissionSerializer.new(central_mail_submission)
- else
- Rails.logger.error("ActiveRecord::RecordNotFound: Claim submission not found for claim_id: #{params[:id]}")
- render(json: { data: { attributes: { state: 'not found' } } }, status: :not_found)
end
+ rescue ActiveRecord::RecordNotFound => e
+ monitor.track_show404(params[:id], current_user, e)
+ render(json: { data: { attributes: { state: 'not found' } } }, status: :not_found)
rescue => e
- Rails.logger.error(e.to_s)
+ monitor.track_show_error(params[:id], current_user, e)
render(json: { data: { attributes: { state: 'error processing request' } } }, status: :unprocessable_entity)
end
def create
PensionBurial::TagSentry.tag_sentry
- claim = if Flipper.enabled?(:va_burial_v2)
- # cannot parse a nil form, to pass unit tests do a check for form presence
- form = filtered_params[:form]
- claim_class.new(form:, formV2: form.present? ? JSON.parse(form)['formV2'] : nil)
- else
- claim_class.new(form: filtered_params[:form])
- end
-
- unless claim.save
- StatsD.increment("#{stats_key}.failure")
- Sentry.set_tags(team: 'benefits-memorial-1') # tag sentry logs with team name
- Rails.logger.error('Burial claim was not saved', { error_messages: claim.errors,
- user_uuid: current_user&.uuid,
- in_progress_form_id: in_progress_form&.id })
- raise Common::Exceptions::ValidationErrors, claim
- end
+ claim = create_claim
+ monitor.track_create_attempt(claim, current_user)
+
+ track_claim_save_failure(claim) unless claim.save
+
# this method also calls claim.process_attachments!
claim.submit_to_structured_data_services!
Rails.logger.info "ClaimID=#{claim.confirmation_number} Form=#{claim.form_id}"
+
+ in_progress_form = current_user ? InProgressForm.form_for_user(claim.form_id, current_user) : nil
+ claim.form_start_date = in_progress_form.created_at if in_progress_form
+ monitor.track_create_success(in_progress_form, claim, current_user)
+
clear_saved_form(claim.form_id)
render json: SavedClaimSerializer.new(claim)
+ rescue => e
+ monitor.track_create_error(in_progress_form, claim, current_user, e)
+ raise e
+ end
+
+ def create_claim
+ if Flipper.enabled?(:va_burial_v2)
+ form = filtered_params[:form]
+ claim_class.new(form:, formV2: form.present? ? JSON.parse(form)['formV2'] : nil)
+ else
+ claim_class.new(form: filtered_params[:form])
+ end
end
def short_name
@@ -59,12 +68,6 @@ def claim_class
private
- def determine_submission_attempt
- claim = claim_class.find_by(guid: params[:id])
- form_submission = claim&.form_submissions&.last
- form_submission&.form_submission_attempts&.last
- end
-
def central_mail_submission
CentralMailSubmission.joins(:central_mail_claim).find_by(saved_claims: { guid: params[:id] })
end
@@ -72,5 +75,39 @@ def central_mail_submission
def in_progress_form
current_user ? InProgressForm.form_for_user(claim.form_id, current_user) : nil
end
+
+ def track_claim_save_failure(claim)
+ StatsD.increment("#{stats_key}.failure")
+ Sentry.set_tags(team: 'benefits-memorial-1') # tag sentry logs with team name
+ Rails.logger.error('Burial claim was not saved', { error_messages: claim.errors,
+ user_uuid: current_user&.uuid,
+ in_progress_form_id: in_progress_form&.id })
+ log_validation_error_to_metadata(in_progress_form, claim)
+ raise Common::Exceptions::ValidationErrors, claim
+ end
+
+ ##
+ # include validation error on in_progress_form metadata.
+ # `noop` if in_progress_form is `blank?`
+ #
+ # @param in_progress_form [InProgressForm]
+ # @param claim [Pensions::SavedClaim]
+ #
+ def log_validation_error_to_metadata(in_progress_form, claim)
+ return if in_progress_form.blank?
+
+ metadata = in_progress_form.metadata
+ metadata['submission']['error_message'] = claim&.errors&.errors&.to_s
+ in_progress_form.update(metadata:)
+ end
+
+ ##
+ # retreive a monitor for tracking
+ #
+ # @return [Burials::Monitor]
+ #
+ def monitor
+ @monitor ||= Burials::Monitor.new
+ end
end
end
diff --git a/app/models/backend_status.rb b/app/models/backend_status.rb
index 25e176f53f4..cfca2b7c2b9 100644
--- a/app/models/backend_status.rb
+++ b/app/models/backend_status.rb
@@ -5,14 +5,29 @@
class BackendStatus
include ActiveModel::Serialization
include ActiveModel::Validations
- include Virtus.model(nullify_blank: true)
- attribute :name, String
- attribute :service_id, String
- attribute :is_available, Boolean
- attribute :uptime_remaining, Integer
+ attr_reader :name, :service_id
validates :name, presence: true
validates :is_available, presence: true
validates :uptime_remaining, presence: true
+
+ def initialize(name:, service_id: nil)
+ @name = name
+ @service_id = service_id
+ end
+
+ def available?
+ gibs_service? ? BenefitsEducation::Service.within_scheduled_uptime? : true
+ end
+
+ def uptime_remaining
+ gibs_service? ? BenefitsEducation::Service.seconds_until_downtime.to_i : 0
+ end
+
+ private
+
+ def gibs_service?
+ @name == BackendServices::GI_BILL_STATUS
+ end
end
diff --git a/app/models/form_submission_attempt.rb b/app/models/form_submission_attempt.rb
index b670547ab92..b6319b5d3aa 100644
--- a/app/models/form_submission_attempt.rb
+++ b/app/models/form_submission_attempt.rb
@@ -74,7 +74,7 @@ def log_status_change
def enqueue_result_email(notification_type)
config = {
- form_data: form_submission.form_data,
+ form_data: JSON.parse(form_submission.form_data),
form_number: form_submission.form_type,
confirmation_number: form_submission.benefits_intake_uuid,
date_submitted: created_at.strftime('%B %d, %Y'),
diff --git a/app/serializers/backend_status_serializer.rb b/app/serializers/backend_status_serializer.rb
index 2e3cce21318..6f714574d29 100644
--- a/app/serializers/backend_status_serializer.rb
+++ b/app/serializers/backend_status_serializer.rb
@@ -7,6 +7,6 @@ class BackendStatusSerializer
attribute :name
attribute :service_id
- attribute :is_available
+ attribute :is_available, &:available?
attribute :uptime_remaining
end
diff --git a/app/services/mhv/user_account/creator.rb b/app/services/mhv/user_account/creator.rb
index 4ae03b32ac4..6e18cc43ea6 100644
--- a/app/services/mhv/user_account/creator.rb
+++ b/app/services/mhv/user_account/creator.rb
@@ -5,11 +5,11 @@
module MHV
module UserAccount
class Creator
- attr_reader :user_verification, :cached
+ attr_reader :user_verification, :break_cache
- def initialize(user_verification:, cached: true)
+ def initialize(user_verification:, break_cache: false)
@user_verification = user_verification
- @cached = cached
+ @break_cache = break_cache
end
def perform
@@ -33,8 +33,9 @@ def create_mhv_user_account!
end
def mhv_account_creation_response
- MHV::AccountCreation::Service.new
- .create_account(icn:, email:, tou_occurred_at: current_tou_agreement.created_at)
+ tou_occurred_at = current_tou_agreement.created_at
+
+ mhv_client.create_account(icn:, email:, tou_occurred_at:, break_cache:)
end
def icn
@@ -53,6 +54,10 @@ def user_account
@user_account ||= user_verification.user_account
end
+ def mhv_client
+ MHV::AccountCreation::Service.new
+ end
+
def validate!
errors = [
('ICN must be present' if icn.blank?),
diff --git a/app/sidekiq/benefits_intake_remediation_status_job.rb b/app/sidekiq/benefits_intake_remediation_status_job.rb
index 3d7c304ab54..f92a317b628 100644
--- a/app/sidekiq/benefits_intake_remediation_status_job.rb
+++ b/app/sidekiq/benefits_intake_remediation_status_job.rb
@@ -24,12 +24,15 @@ def initialize(batch_size: BATCH_SIZE)
# search all submissions for outstanding failures
# poll LH endpoint to see if status has changed (case if endpoint had an error initially)
# report stats on submissions, grouped by form-type
- def perform
+ def perform(form_id = nil)
Rails.logger.info('BenefitsIntakeRemediationStatusJob started')
form_submissions = FormSubmission.includes(:form_submission_attempts)
failures = outstanding_failures(form_submissions.all)
+ @form_id = form_id
+ failures.select! { |f| f.form_type == form_id } if form_id
+
batch_process(failures) unless failures.empty?
submission_audit
@@ -39,7 +42,7 @@ def perform
private
- attr_reader :batch_size, :total_handled
+ attr_reader :batch_size, :total_handled, :form_id
# determine if a claim has an outstanding failure
# each claim can have multiple FormSubmission, which can have multiple FormSubmissionAttempt
@@ -118,10 +121,12 @@ def submission_audit
form_submissions = FormSubmission.includes(:form_submission_attempts)
form_submission_groups = form_submissions.all.group_by(&:form_type)
- form_submission_groups.each do |form_id, submissions|
+ form_submission_groups.each do |form_type, submissions|
+ next if form_id && form_id != form_type
+
fs_saved_claim_ids = submissions.map(&:saved_claim_id).uniq
- claims = SavedClaim.where(form_id:).where('id >= ?', fs_saved_claim_ids.min)
+ claims = SavedClaim.where(form_id: form_type).where('id >= ?', fs_saved_claim_ids.min)
claim_ids = claims.map(&:id).uniq
unsubmitted = claim_ids - fs_saved_claim_ids
@@ -133,9 +138,9 @@ def submission_audit
{ claim_id: fs.saved_claim_id, uuid: fs.benefits_intake_uuid, error_message: last_attempt.error_message }
end
- StatsD.set("#{STATS_KEY}.#{form_id}.unsubmitted_claims", unsubmitted.length)
- StatsD.set("#{STATS_KEY}.#{form_id}.orphaned_submissions", orphaned.length)
- StatsD.set("#{STATS_KEY}.#{form_id}.outstanding_failures", failures.length)
+ StatsD.gauge("#{STATS_KEY}.unsubmitted_claims", unsubmitted.length, tags: ["form_id:#{form_type}"])
+ StatsD.gauge("#{STATS_KEY}.orphaned_submissions", orphaned.length, tags: ["form_id:#{form_type}"])
+ StatsD.gauge("#{STATS_KEY}.outstanding_failures", failures.length, tags: ["form_id:#{form_type}"])
Rails.logger.info("BenefitsIntakeRemediationStatusJob submission audit #{form_id}", form_id:, unsubmitted:,
orphaned:, failures:)
end
diff --git a/app/sidekiq/form526_failure_state_snapshot_job.rb b/app/sidekiq/form526_failure_state_snapshot_job.rb
new file mode 100644
index 00000000000..ed895299abd
--- /dev/null
+++ b/app/sidekiq/form526_failure_state_snapshot_job.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# Log information about Form526Submission state to populate an admin facing Datadog dashboard
+class Form526FailureStateSnapshotJob
+ include Sidekiq::Job
+ sidekiq_options retry: false
+
+ STATSD_PREFIX = 'form526.state.snapshot'
+
+ def perform
+ write_failure_snapshot
+ rescue => e
+ Rails.logger.error('Error logging 526 state snapshot',
+ class: self.class.name,
+ message: e.try(:message))
+ end
+
+ def write_failure_snapshot
+ state_as_counts.each do |description, count|
+ StatsD.gauge("#{STATSD_PREFIX}.#{description}", count)
+ end
+ end
+
+ def state_as_counts
+ @state_as_counts ||= {}.tap do |abbreviation|
+ snapshot_state.each do |dp, ids|
+ abbreviation[:"#{dp}_count"] = ids.count
+ end
+ end
+ end
+
+ def snapshot_state
+ @snapshot_state ||= load_snapshot_state
+ end
+
+ def load_snapshot_state
+ {
+ total_awaiting_backup_status: Form526Submission.pending_backup.pluck(:id).sort,
+ total_incomplete_type: Form526Submission.incomplete_type.pluck(:id).sort,
+ total_failure_type: Form526Submission.failure_type.pluck(:id).sort
+ }
+ end
+end
diff --git a/app/sidekiq/form526_state_logging_job.rb b/app/sidekiq/form526_submission_processing_report_job.rb
similarity index 66%
rename from app/sidekiq/form526_state_logging_job.rb
rename to app/sidekiq/form526_submission_processing_report_job.rb
index 6de183f9372..a53623d357e 100644
--- a/app/sidekiq/form526_state_logging_job.rb
+++ b/app/sidekiq/form526_submission_processing_report_job.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Log information about Form526Submission state to populate an admin facing Datadog dashboard
-class Form526StateLoggingJob
+class Form526SubmissionProcessingReportJob
include Sidekiq::Job
sidekiq_options retry: false
@@ -9,7 +9,6 @@ class Form526StateLoggingJob
END_DATE = Time.zone.today.beginning_of_day
START_DATE = END_DATE - 1.week
- STATSD_PREFIX = 'form526.state'
def initialize(start_date: START_DATE, end_date: END_DATE)
@start_date = start_date
@@ -17,7 +16,6 @@ def initialize(start_date: START_DATE, end_date: END_DATE)
end
def perform
- write_as_gauges
write_as_log
rescue => e
Rails.logger.error('Error logging 526 state data',
@@ -29,43 +27,23 @@ def perform
def write_as_log
Rails.logger.info('Form 526 State Data',
- state_log: counts_with_failures,
+ state_log: state_as_counts,
start_date:,
end_date:)
end
- def counts_with_failures
- counts = state_as_counts
- counts[:total_failure_type_ids] = total_failure_type
- counts
- end
-
- def base_state
- @base_state ||= timeboxed_state.merge(all_time_state)
- end
-
def state_as_counts
@state_as_counts ||= {}.tap do |abbreviation|
- base_state.each do |dp, ids|
+ timeboxed_state.each do |dp, ids|
abbreviation[:"#{dp}_count"] = ids.count
end
end
end
- def write_as_gauges
- state_as_counts.each do |description, count|
- StatsD.gauge("#{STATSD_PREFIX}.#{description}", count)
- end
- end
-
def timeboxed_state
@timeboxed_state ||= load_timeboxed_state
end
- def all_time_state
- @all_time_state ||= load_all_time_state
- end
-
def load_timeboxed_state
{
timeboxed: timeboxed_submissions.pluck(:id).sort,
@@ -76,27 +54,10 @@ def load_timeboxed_state
}
end
- def load_all_time_state
- {
- total_awaiting_backup_status: Form526Submission.pending_backup.pluck(:id).sort,
- total_incomplete_type: Form526Submission.incomplete_type.pluck(:id).sort,
- total_failure_type:
- }
- end
-
- def total_failure_type
- @total_failure_type ||= Form526Submission.failure_type.pluck(:id).sort
- end
-
def sub_arel
@sub_arel ||= Form526Submission.arel_table
end
- def combined_pending_types_for(submissions)
- submissions.incomplete.pluck(:id) +
- submissions.in_process.pluck(:id)
- end
-
def backup_submissions
@backup_submissions ||= timeboxed_submissions
.joins(:form526_job_statuses)
diff --git a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
index 34c35f76142..b14e09424f1 100644
--- a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
+++ b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
@@ -29,7 +29,7 @@ class BenefitsIntakeClaimError < StandardError; end
StatsD.increment("#{STATSD_KEY_PREFIX}.exhausted")
end
- def perform(saved_claim_id)
+ def perform(saved_claim_id) # rubocop:disable Metrics/MethodLength
init(saved_claim_id)
@pdf_path = if @claim.form_id == '21P-530V2'
@@ -46,7 +46,10 @@ def perform(saved_claim_id)
Rails.logger.info('Lighthouse::SubmitBenefitsIntakeClaim succeeded', generate_log_details)
StatsD.increment("#{STATSD_KEY_PREFIX}.success")
- @claim.send_confirmation_email if @claim.respond_to?(:send_confirmation_email)
+
+ send_confirmation_email
+
+ @lighthouse_service.uuid
rescue => e
Rails.logger.warn('Lighthouse::SubmitBenefitsIntakeClaim failed, retrying...', generate_log_details(e))
StatsD.increment("#{STATSD_KEY_PREFIX}.failure")
@@ -165,5 +168,13 @@ def cleanup_file_paths
def check_zipcode(address)
address['country'].upcase.in?(%w[USA US])
end
+
+ def send_confirmation_email
+ @claim.respond_to?(:send_confirmation_email) && @claim.send_confirmation_email
+ rescue => e
+ Rails.logger.warn('Lighthouse::SubmitBenefitsIntakeClaim send_confirmation_email failed',
+ generate_log_details(e))
+ StatsD.increment("#{STATSD_KEY_PREFIX}.send_confirmation_email.failure")
+ end
end
end
diff --git a/app/swagger/swagger/requests/my_va/submission_statuses.rb b/app/swagger/swagger/requests/my_va/submission_statuses.rb
new file mode 100644
index 00000000000..dff3632dd59
--- /dev/null
+++ b/app/swagger/swagger/requests/my_va/submission_statuses.rb
@@ -0,0 +1,122 @@
+# app/controllers/v0/my_va/submission_statuses_controller.rb
+
+# frozen_string_literal: true
+
+module Swagger
+ module Requests
+ module MyVA
+ class SubmissionStatuses
+ include Swagger::Blocks
+
+ swagger_path '/v0/my_va/submission_statuses' do
+ operation :get do
+ key :description, 'Get list of submitted forms for the current session'
+ key :operationId, 'getSubmissionStatuses'
+ key :tags, %w[
+ my_va
+ ]
+
+ parameter :authorization
+
+ key :produces, ['application/json']
+
+ response 200 do
+ key :description, 'submitted forms and statuses'
+
+ schema do
+ key :type, :object
+
+ property :data do
+ key :type, :array
+ items do
+ key :required, %i[
+ id
+ type
+ attributes
+ ]
+ property :id, type: :string, example: '3b03b5a0-3ad9-4207-b61e-3a13ed1c8b80',
+ description: 'Form submission UID'
+ property :type, type: :string, example: 'submission_status', description: 'type of request'
+ property :attributes do
+ key :$ref, :SubmissionStatusAttrs
+ end
+ end
+ end
+ end
+ end
+
+ response 296 do
+ key :description, 'submitted forms but errors occured looking up statuses from lighthouse'
+
+ schema do
+ key :type, :object
+ key :required, %i[data errors]
+ property :data do
+ key :type, :array
+ items do
+ key :required, %i[
+ id
+ type
+ attributes
+ ]
+ property :id, type: :string, example: '3b03b5a0-3ad9-4207-b61e-3a13ed1c8b80',
+ description: 'Form submission UID'
+ property :type, type: :string, example: 'submission_status', description: 'type of request'
+ property :attributes do
+ key :$ref, :SubmissionStatusAttrs
+ end
+ end
+ end
+
+ property :errors do
+ key :type, :array
+ items do
+ key :required, %i[
+ status
+ source
+ title
+ detail
+ ]
+ property :status, type: :integer, example: 429, description: 'Error code'
+ property :source, type: :string, example: 'Lighthouse - Benefits Intake API',
+ description: 'Error source'
+ property :title, type: :string, example: 'Form Submission Status: Too Many Requests',
+ description: 'Error description'
+ property :detail, type: :string, example: 'API rate limit exceeded', description: 'Error details'
+ end
+ end
+ end
+ end
+ end
+ end
+
+ swagger_schema :SubmissionStatusAttrs do
+ key :type, :object
+ key :description, 'submitted form attributes'
+
+ property :id, type: :string, example: '3b03b5a0-3ad9-4207-b61e-3a13ed1c8b80',
+ description: 'Submitted form UID from lighthouse'
+ property :detail, type: [:string, 'null'], example: '',
+ description: 'Error details (only when errors are present)'
+ property :form_type, type: :string, example: '21-0845', description: 'The type of form'
+ property :message, type: [:string, 'null']
+ property :status, type: [:string, 'null'], enum: [
+ nil,
+ 'pending',
+ 'uploaded',
+ 'received',
+ 'processing',
+ 'success',
+ 'vbms',
+ 'error',
+ 'expired'
+ ], example: 'received', description: 'The current status of the submission'
+ property :created_at, type: :string, example: '2023-12-15T20:40:47.583Z',
+ description: 'The submission record created in VA.gov'
+ property :updated_at, type: [:string, 'null'], example: '2023-12-15T20:40:54.474Z',
+ description: 'The last time the submission status was updated'
+ end
+ end
+ end
+ end
+end
diff --git a/config/betamocks/services_config.yml b/config/betamocks/services_config.yml
index 08ee0647473..b4bd3a93623 100644
--- a/config/betamocks/services_config.yml
+++ b/config/betamocks/services_config.yml
@@ -595,6 +595,15 @@
:path: "/api/v2/search/i14y"
:file_path: "search/default"
+# Search GSA
+- :name: 'Search GSA'
+ :base_uri: <%= "#{URI(Settings.search.gsa_url).host}:#{URI(Settings.search.gsa_url).port}" %>
+ :endpoints:
+ # Search results
+ - :method: :get
+ :path: "/technology/searchgov/v2/results/i14y"
+ :file_path: "search/default"
+
#GIS
- :name: 'GIS'
:base_uri: <%= "#{URI(Settings.locators.gis_base_path).host}:#{URI(Settings.locators.gis_base_path).port}" %>
diff --git a/config/features.yml b/config/features.yml
index dbff51ecca9..8eff7f67da5 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -66,6 +66,9 @@ features:
caregiver_browser_monitoring_enabled:
actor_type: user
description: Enables Datadog Real Time User Monitoring
+ caregiver_carma_submitted_at:
+ actor_type: user
+ description: Enables sending CARMA the creation timestamp of a claim as a metadata submitted_at value
hca_browser_monitoring_enabled:
actor_type: user
description: Enables browser monitoring for the health care application.
@@ -830,6 +833,9 @@ features:
mhv_secure_messaging_edit_contact_list:
actor_type: user
description: Disables/Enables Secure Messaging edit contact list page
+ mhv_secure_messaging_triage_group_plain_language:
+ actor_type: user
+ description: Disables/Enables Secure Messaging recipients group plain language design
enable_in_development: true
mhv_medical_records_allow_txt_downloads:
actor_type: user
@@ -989,18 +995,6 @@ features:
profile_show_credential_retirement_messaging:
actor_type: user
description: Show/hide MHV and DS Logon credential retirement messaging in profile
- profile_show_direct_deposit_single_form:
- actor_type: user
- description: Show/hide the single direct deposit form in profile for all users
- profile_show_direct_deposit_single_form_uat:
- actor_type: user
- description: Show/hide the single direct deposit form just for users during UAT only
- profile_show_direct_deposit_single_form_alert:
- actor_type: user
- description: Show/hide an alert with information around migrating to a single direct deposit form in profile
- profile_show_direct_deposit_single_form_edu_downtime:
- actor_type: user
- description: Show/hide the edu direct deposit form and instead display a downtime message in its place
profile_show_payments_notification_setting:
actor_type: user
description: Show/Hide the payments section of notifications in profile
@@ -1703,3 +1697,7 @@ features:
mgib_verifications_maintenance:
actor_type: user
description: Used to show maintenance alert for MGIB Verifications
+ search_use_v2_gsa:
+ actor_type: cookie_id
+ description: Swaps the Search Service's configuration url with an updated api.gsa.gov address
+ enabled_in_development: true
diff --git a/config/locales/exceptions.en.yml b/config/locales/exceptions.en.yml
index e339cf41181..2c17524a937 100644
--- a/config/locales/exceptions.en.yml
+++ b/config/locales/exceptions.en.yml
@@ -798,6 +798,30 @@ en:
code: 'SEARCH_504'
detail: 'Did not receive a timely response from Search.gov'
status: 504
+ SEARCH_GSA_400:
+ <<: *external_defaults
+ title: Bad Request
+ code: 'SEARCH_GSA_400'
+ detail: 'api.gsa.gov service responded with a Bad Request'
+ status: 400
+ SEARCH_GSA_429:
+ <<: *external_defaults
+ title: Exceeded rate limit
+ code: 'SEARCH_GSA_429'
+ detail: 'Exceeded api.gsa.gov rate limit'
+ status: 429
+ SEARCH_GSA_503:
+ <<: *external_defaults
+ title: Service Unavailable
+ code: 'SEARCH_GSA_503'
+ detail: 'api.gsa.gov service is currently unavailable'
+ status: 503
+ SEARCH_GSA_504:
+ <<: *external_defaults
+ title: Gateway Timeout
+ code: 'SEARCH_GSA_504'
+ detail: 'Did not receive a timely response from api.gsa.gov'
+ status: 504
SEARCH_TYPEAHEAD_400:
<<: *external_defaults
title: Bad Request
diff --git a/config/settings.yml b/config/settings.yml
index 1cc10b7c9b2..001202f4978 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -900,7 +900,8 @@ search:
access_key: SEARCH_GOV_ACCESS_KEY
affiliate: va
mock_search: false
- url: https://search.usa.gov/api/v2
+ gsa_url: https://api.gsa.gov/technology/searchgov/v2/results/i14y
+ url: https://search.usa.gov/api/v2/search/i14y
# Settings for search-typeahead
search_typeahead:
@@ -908,7 +909,7 @@ search_typeahead:
name: va
url: https://api.gsa.gov/technology/searchgov/v1
- # Settings for search-click-tracking
+# Settings for search-click-tracking
search_click_tracking:
access_key: SEARCH_GOV_ACCESS_KEY
affiliate: va
diff --git a/config/settings/test.yml b/config/settings/test.yml
index 36d2843c03d..8bfb03d4867 100644
--- a/config/settings/test.yml
+++ b/config/settings/test.yml
@@ -72,7 +72,8 @@ directory:
search:
access_key: TESTKEY
affiliate: va
- url: https://search.usa.gov/api/v2
+ gsa_url: https://api.gsa.gov/technology/searchgov/v2/results/i14y
+ url: https://search.usa.gov/api/v2/search/i14y
search_typeahead:
api_key: TEST_KEY
diff --git a/helmCharts/vets-api/templates/postman-test.yaml b/helmCharts/vets-api/templates/postman-test.yaml
new file mode 100644
index 00000000000..cad4b6ebaed
--- /dev/null
+++ b/helmCharts/vets-api/templates/postman-test.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "vets-api-postman-test"
+ annotations:
+ "helm.sh/hook": post-install,post-upgrade,test
+spec:
+ containers:
+ - name: postman
+ image: "{{ .Values.image.testRepository }}:{{ .Values.image.tag }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ env:
+ - name: "API_URL"
+ value: "http://vets-api-service"
+ restartPolicy: Never
+
\ No newline at end of file
diff --git a/helmCharts/vets-api/values.yaml b/helmCharts/vets-api/values.yaml
index 0a15f726682..c4e5e9c4484 100644
--- a/helmCharts/vets-api/values.yaml
+++ b/helmCharts/vets-api/values.yaml
@@ -3,5 +3,6 @@
replicaCount: 1
image:
repository: 008577686731.dkr.ecr.us-gov-west-1.amazonaws.com/dsva/preview-environment/vets-api
+ testRepository: 008577686731.dkr.ecr.us-gov-west-1.amazonaws.com/dsva/vets-api-postman
pullPolicy: IfNotPresent
tag: "0.0.1"
diff --git a/lib/burials/monitor.rb b/lib/burials/monitor.rb
new file mode 100644
index 00000000000..66d1436b735
--- /dev/null
+++ b/lib/burials/monitor.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+module Burials
+ ##
+ # Monitor functions for Rails logging and StatsD
+ #
+ class Monitor
+ CLAIM_STATS_KEY = 'api.burial_claim'
+
+ ##
+ # log GET 404 from controller
+ # @see BurialClaimsController
+ #
+ # @param confirmation_number [UUID] saved_claim guid
+ # @param current_user [User]
+ # @param e [ActiveRecord::RecordNotFound]
+ #
+ def track_show404(confirmation_number, current_user, e)
+ Rails.logger.error('21P-530EZ submission not found',
+ { confirmation_number:, user_uuid: current_user&.uuid, message: e&.message })
+ end
+
+ ##
+ # log GET 500 from controller
+ # @see BurialClaimsController
+ #
+ # @param confirmation_number [UUID] saved_claim guid
+ # @param current_user [User]
+ # @param e [Error]
+ #
+ def track_show_error(confirmation_number, current_user, e)
+ Rails.logger.error('21P-530EZ fetching submission failed',
+ { confirmation_number:, user_uuid: current_user&.uuid, message: e&.message })
+ end
+
+ ##
+ # log POST processing started
+ # @see BurialClaimsController
+ #
+ # @param claim [Pension::SavedClaim]
+ # @param current_user [User]
+ #
+ def track_create_attempt(claim, current_user)
+ StatsD.increment("#{CLAIM_STATS_KEY}.attempt")
+ Rails.logger.info('21P-530EZ submission to Sidekiq begun',
+ { confirmation_number: claim&.confirmation_number, user_uuid: current_user&.uuid })
+ end
+
+ ##
+ # log POST processing failure
+ # @see BurialClaimsController
+ #
+ # @param in_progress_form [InProgressForm]
+ # @param claim [SavedClaim::Burial]
+ # @param current_user [User]
+ # @param e [Error]
+ #
+ def track_create_error(in_progress_form, claim, current_user, e = nil)
+ StatsD.increment("#{CLAIM_STATS_KEY}.failure")
+ Rails.logger.error('21P-530EZ submission to Sidekiq failed',
+ { confirmation_number: claim&.confirmation_number, user_uuid: current_user&.uuid,
+ in_progress_form_id: in_progress_form&.id, errors: claim&.errors&.errors,
+ message: e&.message })
+ end
+
+ ##
+ # log POST processing success
+ # @see BurialClaimsController
+ #
+ # @param in_progress_form [InProgressForm]
+ # @param claim [SavedClaim::Burial]
+ # @param current_user [User]
+ #
+ def track_create_success(in_progress_form, claim, current_user)
+ StatsD.increment("#{CLAIM_STATS_KEY}.success")
+ if claim.form_start_date
+ claim_duration = claim.created_at - claim.form_start_date
+ tags = ["form_id:#{claim.form_id}"]
+ StatsD.measure('saved_claim.time-to-file', claim_duration, tags:)
+ end
+ context = {
+ confirmation_number: claim&.confirmation_number,
+ user_uuid: current_user&.uuid,
+ in_progress_form_id: in_progress_form&.id
+ }
+ Rails.logger.info('21P-530EZ submission to Sidekiq success', context)
+ end
+ end
+end
diff --git a/lib/carma/models/metadata.rb b/lib/carma/models/metadata.rb
index 09cb870de3c..296188a822a 100644
--- a/lib/carma/models/metadata.rb
+++ b/lib/carma/models/metadata.rb
@@ -9,13 +9,15 @@ module Models
class Metadata < Base
request_payload_key :claim_id,
:claim_guid,
+ :submitted_at,
:veteran,
:primary_caregiver,
:secondary_caregiver_one,
:secondary_caregiver_two
attr_accessor :claim_id,
- :claim_guid
+ :claim_guid,
+ :submitted_at
attr_reader :veteran,
:primary_caregiver,
@@ -25,6 +27,7 @@ class Metadata < Base
def initialize(args = {})
@claim_id = args[:claim_id]
@claim_guid = args[:claim_guid]
+ @submitted_at = args[:submitted_at]
self.veteran = args[:veteran] || {}
self.primary_caregiver = args[:primary_caregiver]
diff --git a/lib/carma/models/submission.rb b/lib/carma/models/submission.rb
index 9b2c3525ee9..c95b88d290b 100644
--- a/lib/carma/models/submission.rb
+++ b/lib/carma/models/submission.rb
@@ -19,6 +19,17 @@ class Submission < Base
# @return [CARMA::Models::Submission] A CARMA Submission model object
#
def self.from_claim(claim, metadata = {})
+ if Flipper.enabled?(:caregiver_carma_submitted_at)
+ return new(
+ data: claim.parsed_form,
+ metadata: metadata.merge(
+ claim_id: claim.id,
+ claim_guid: claim.guid,
+ submitted_at: claim.created_at&.iso8601
+ )
+ )
+ end
+
new(
data: claim.parsed_form,
metadata: metadata.merge(claim_id: claim.id, claim_guid: claim.guid)
diff --git a/lib/evss/disability_compensation_form/form526_to_lighthouse_transform.rb b/lib/evss/disability_compensation_form/form526_to_lighthouse_transform.rb
index 2952d774100..222924e28cb 100644
--- a/lib/evss/disability_compensation_form/form526_to_lighthouse_transform.rb
+++ b/lib/evss/disability_compensation_form/form526_to_lighthouse_transform.rb
@@ -291,6 +291,17 @@ def transform_toxic_exposure(toxic_exposure_source) # rubocop:disable Metrics/Me
MULTIPLE_EXPOSURES_TYPE[:hazard])
end
+ # multiple exposures could have repeated values that LH will not accept in the primary path.
+ # remove them!
+ multiple_exposures.uniq! do |exposure|
+ [
+ exposure.exposure_dates.begin_date,
+ exposure.exposure_dates.end_date,
+ exposure.exposure_location,
+ exposure.hazard_exposed_to
+ ]
+ end
+
toxic_exposure_target.multiple_exposures = multiple_exposures
toxic_exposure_target
diff --git a/lib/mhv/account_creation/service.rb b/lib/mhv/account_creation/service.rb
index 4251b1f0deb..6128714188e 100644
--- a/lib/mhv/account_creation/service.rb
+++ b/lib/mhv/account_creation/service.rb
@@ -7,13 +7,13 @@ module AccountCreation
class Service < Common::Client::Base
configuration Configuration
- def create_account(icn:, email:, tou_occurred_at:)
+ def create_account(icn:, email:, tou_occurred_at:, break_cache: false)
params = build_create_account_params(icn:, email:, tou_occurred_at:)
- response = perform(:post, config.account_creation_path, params, authenticated_header(icn:))
- Rails.logger.info("#{config.logging_prefix} create_account success", icn:)
-
- normalize_response_body(response.body)
+ create_account_with_cache(icn:, force: break_cache, expires_in: 1.day) do
+ response = perform(:post, config.account_creation_path, params, authenticated_header(icn:))
+ normalize_response_body(response.body)
+ end
rescue Common::Client::Errors::ParsingError, Common::Client::Errors::ClientError => e
Rails.logger.error("#{config.logging_prefix} create_account #{e.class.name.demodulize.underscore}",
{ error_message: e.message, body: e.body, icn: })
@@ -22,6 +22,17 @@ def create_account(icn:, email:, tou_occurred_at:)
private
+ def create_account_with_cache(icn:, force:, expires_in:, &request)
+ cache_hit = true
+ account = Rails.cache.fetch("#{config.service_name}_#{icn}", force:, expires_in:) do
+ cache_hit = false
+ request.call
+ end
+ Rails.logger.info("#{config.logging_prefix} create_account success", { icn:, account:, from_cache: cache_hit })
+
+ account
+ end
+
def build_create_account_params(icn:, email:, tou_occurred_at:)
{
icn:,
diff --git a/lib/periodic_jobs.rb b/lib/periodic_jobs.rb
index 828491aa91c..e090cb8bf0e 100644
--- a/lib/periodic_jobs.rb
+++ b/lib/periodic_jobs.rb
@@ -66,8 +66,11 @@
# Checks all 'success' type submissions in LH to ensure they haven't changed
mgr.register('0 2 * * 0', 'Form526ParanoidSuccessPollingJob')
- # Log the state of Form 526 submissions to hydrate Datadog monitor
- mgr.register('0 3 * * *', 'Form526StateLoggingJob')
+ # Log a report of 526 submission processing for a given timebox
+ mgr.register('5 4 * * 7', 'Form526SubmissionProcessingReportJob')
+
+ # Log a snapshot of everything in a full failure type state
+ mgr.register('5 * * * *', 'Form526FailureStateSnapshotJob')
# Clear out processed 22-1990 applications that are older than 1 month
mgr.register('0 0 * * *', 'EducationForm::DeleteOldApplications')
diff --git a/lib/scopes/form526_submission_state.rb b/lib/scopes/form526_submission_state.rb
index a0b4373ed84..141261c06d9 100644
--- a/lib/scopes/form526_submission_state.rb
+++ b/lib/scopes/form526_submission_state.rb
@@ -112,7 +112,7 @@ module Form526SubmissionState
scope :failure_type, lambda {
# filtering in stages avoids timeouts. see doc for more info
- allids = all.pluck(:id)
+ allids = where(submitted_claim_id: nil).pluck(:id)
filter1 = where(id: allids - accepted_to_primary_path.pluck(:id)).pluck(:id)
filter2 = where(id: filter1 - accepted_to_backup_path.pluck(:id)).pluck(:id)
filter3 = where(id: filter2 - remediated.pluck(:id)).pluck(:id)
diff --git a/lib/search/configuration.rb b/lib/search/configuration.rb
index ea802406877..e56178d8df6 100644
--- a/lib/search/configuration.rb
+++ b/lib/search/configuration.rb
@@ -23,7 +23,16 @@ def mock_enabled?
end
def base_path
- "#{Settings.search.url}/search/i14y"
+ flipper_enabled? ? Settings.search.gsa_url : Settings.search.url
+ end
+
+ # Breakers initialization requires this configuration which means the #base_path
+ # is required when building the DBs in CI that flipper uses for checking toggles.
+ # The NoDatabaseError rescue handles times we're building new DBs.
+ def flipper_enabled?
+ Flipper.enabled?(:search_use_v2_gsa)
+ rescue ActiveRecord::NoDatabaseError
+ false
end
def service_name
diff --git a/lib/search/service.rb b/lib/search/service.rb
index 1b8d4ac018d..876f6bae028 100644
--- a/lib/search/service.rb
+++ b/lib/search/service.rb
@@ -7,10 +7,11 @@
require 'search/configuration'
module Search
- # This class builds a wrapper around Search.gov web results API. Creating a new instance of class
+ # This class builds a wrapper around Search.gov or api.gsa.gov web results API. Creating a new instance of class
# will and calling #results will return a ResultsResponse upon success or an exception upon failure.
#
# @see https://search.usa.gov/sites/7378/api_instructions
+ # @see https://open.gsa.gov/api/searchgov-results/
#
class Service < Common::Client::Base
include Common::Client::Concerns::Monitoring
@@ -48,6 +49,7 @@ def results_url
# Optional params [enable_highlighting, limit, offset, sort_by]
#
# @see https://search.usa.gov/sites/7378/api_instructions
+ # @see https://open.gsa.gov/api/searchgov-results/
#
def query_params
{
@@ -92,7 +94,7 @@ def handle_error(error)
message = parse_messages(error).first
save_error_details(message)
handle_429!(error)
- raise_backend_exception('SEARCH_400', self.class, error) if error.status >= 400
+ raise_backend_exception(error_code_name(400), self.class, error) if error.status >= 400
else
raise error
end
@@ -114,20 +116,22 @@ def handle_429!(error)
return unless error.status == 429
StatsD.increment("#{Search::Service::STATSD_KEY_PREFIX}.exceptions", tags: ['exception:429'])
- raise_backend_exception('SEARCH_429', self.class, error)
+ raise_backend_exception(error_code_name(error.status), self.class, error)
end
def handle_server_error!(error)
return unless [503, 504].include?(error.status)
- exceptions = {
- 503 => 'SEARCH_503',
- 504 => 'SEARCH_504'
- }
# Catch when the error's structure doesn't match what's usually expected.
- message = error.body.is_a?(Hash) ? parse_messages(error).first : 'Search.gov is down'
+ message = error.body.is_a?(Hash) ? parse_messages(error).first : 'Search API is down'
save_error_details(message)
- raise_backend_exception(exceptions[error.status], self.class, error)
+ raise_backend_exception(error_code_name(error.status), self.class, error)
+ end
+
+ def error_code_name(error_status)
+ error_code_prefix = self.class.configuration.flipper_enabled? ? 'SEARCH_GSA' : 'SEARCH'
+
+ "#{error_code_prefix}_#{error_status}"
end
end
end
diff --git a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/application_controller.rb b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/application_controller.rb
index 4faeb258113..4f5072b7075 100644
--- a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/application_controller.rb
+++ b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/application_controller.rb
@@ -9,9 +9,17 @@ class ApplicationController < SignIn::ApplicationController
validates_access_token_audience Settings.sign_in.arp_client_id
before_action :verify_pilot_enabled_for_user
+ around_action :handle_exceptions
private
+ def handle_exceptions
+ yield
+ rescue => e
+ Rails.logger.error("ARP: Unexpected error occurred for user with user_uuid=#{@current_user&.uuid} - #{e.message}")
+ raise e
+ end
+
def verify_pilot_enabled_for_user
unless Flipper.enabled?(:accredited_representative_portal_pilot, @current_user)
message = 'The accredited_representative_portal_pilot feature flag is disabled ' \
diff --git a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/form21a_controller.rb b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/form21a_controller.rb
index a1f4618aec1..7c9905816fd 100644
--- a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/form21a_controller.rb
+++ b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/form21a_controller.rb
@@ -12,13 +12,10 @@ class Form21aController < ApplicationController
# Parses the request body and submits the form.
# Renders the appropriate response based on the service's outcome.
def submit
- response = AccreditationService.submit_form21a(parsed_request_body)
+ response = AccreditationService.submit_form21a(parsed_request_body, @current_user&.uuid)
InProgressForm.form_for_user(FORM_ID, @current_user)&.destroy if response.success?
render_ogc_service_response(response)
- rescue => e
- Rails.logger.error("Form21aController: Unexpected error occurred - #{e.message}")
- render json: { errors: 'Unexpected error' }, status: :internal_server_error
end
private
@@ -30,19 +27,30 @@ def submit
def parse_request_body
@parsed_request_body = JSON.parse(request.raw_post)
rescue JSON::ParserError
- Rails.logger.error('Form21aController: Invalid JSON in request body')
+ Rails.logger.error(
+ "Form21aController: Invalid JSON in request body for user with user_uuid=#{@current_user&.uuid}"
+ )
render json: { errors: 'Invalid JSON' }, status: :bad_request
end
# Renders the response based on the service call's success or failure.
def render_ogc_service_response(response)
if response.success?
+ Rails.logger.info(
+ 'Form21aController: Form 21a successfully submitted to OGC service ' \
+ "by user with user_uuid=#{@current_user&.uuid} - Response: #{response.body}"
+ )
render json: response.body, status: response.status
elsif response.body.blank?
- Rails.logger.info('Form21aController: Blank response from OGC service')
+ Rails.logger.info(
+ "Form21aController: Blank response from OGC service for user with user_uuid=#{@current_user&.uuid}"
+ )
render status: :no_content
else
- Rails.logger.error('Form21aController: Failed to parse response from external OGC service')
+ Rails.logger.error(
+ 'Form21aController: Failed to parse response from external OGC service ' \
+ "for user with user_uuid=#{@current_user&.uuid}"
+ )
render json: { errors: 'Failed to parse response' }, status: :bad_gateway
end
end
diff --git a/modules/accredited_representative_portal/app/services/accreditation_service.rb b/modules/accredited_representative_portal/app/services/accreditation_service.rb
index fd0a2a7a4a3..bf0b8356793 100644
--- a/modules/accredited_representative_portal/app/services/accreditation_service.rb
+++ b/modules/accredited_representative_portal/app/services/accreditation_service.rb
@@ -8,16 +8,20 @@ class AccreditationService
# self.submit_form21a(parsed_body): Submits the given parsed body as JSON to the accreditation service.
# - Parameters:
# - parsed_body: A Hash representing the parsed form data.
+ # - user_uuid: A String representing the user's UUID, which is also stored in the in_progress_forms DB entry.
# - Returns: A Faraday::Response object containing the service response.
- def self.submit_form21a(parsed_body)
+ def self.submit_form21a(parsed_body, user_uuid)
+ Rails.logger.info("Accreditation Service attempting submit_form21a with service_url: #{service_url}")
connection.post do |req|
req.body = parsed_body.to_json
end
rescue Faraday::ConnectionFailed => e
- Rails.logger.error("Accreditation Service connection failed: #{e.message}, URL: #{service_url}")
+ Rails.logger.error(
+ "Accreditation Service connection failed for user with user_uuid=#{user_uuid}: #{e.message}, URL: #{service_url}"
+ )
Faraday::Response.new(status: :service_unavailable, body: { errors: 'Accreditation Service unavailable' }.to_json)
rescue Faraday::TimeoutError => e
- Rails.logger.error("Accreditation Service request timed out: #{e.message}")
+ Rails.logger.error("Accreditation Service request timed out for user with user_uuid=#{user_uuid}: #{e.message}")
Faraday::Response.new(status: :request_timeout, body: { errors: 'Accreditation Service request timed out' }.to_json)
end
@@ -39,7 +43,7 @@ def self.service_url
case Rails.env
when 'development', 'test'
# NOTE: the below is a temporary URL for development purposes only.
- # TODO: Update this once ESECC request goes through. See: https://github.com/department-of-veterans-affairs/va.gov-team/
+ # TODO: Update this once ESECC request goes through. See: https://github.com/department-of-veterans-affairs/va.gov-team/issues/88288
'http://localhost:5000/api/v1/accreditation/applications/form21a'
when 'production'
# TODO: Update this once MOU has been signed and the ESECC request has gone through. See:
diff --git a/modules/accredited_representative_portal/config/routes.rb b/modules/accredited_representative_portal/config/routes.rb
index 55381a28919..c5a94e8365e 100644
--- a/modules/accredited_representative_portal/config/routes.rb
+++ b/modules/accredited_representative_portal/config/routes.rb
@@ -2,13 +2,6 @@
AccreditedRepresentativePortal::Engine.routes.draw do
namespace :v0, defaults: { format: :json } do
- resources :power_of_attorney_requests, only: [:index] do
- member do
- post :accept
- post :decline
- end
- end
-
get 'user', to: 'representative_users#show'
post 'form21a', to: 'form21a#submit'
diff --git a/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/form21a_spec.rb b/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/form21a_spec.rb
index 8c53592bdd0..bb190cdb8c5 100644
--- a/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/form21a_spec.rb
+++ b/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/form21a_spec.rb
@@ -16,7 +16,7 @@
context 'with valid JSON' do
let!(:in_progress_form) { create(:in_progress_form, form_id: '21a', user_uuid: representative_user.uuid) }
- it 'returns a successful response from the service and destroys in progress form' do
+ it 'logs a successful submission and destroys in-progress form' do
get('/accredited_representative_portal/v0/in_progress_forms/21a')
expect(response).to have_http_status(:ok)
expect(parsed_response.keys).to contain_exactly('formData', 'metadata')
@@ -25,6 +25,11 @@
instance_double(Faraday::Response, success?: true, body: { result: 'success' }.to_json, status: 200)
)
+ expect(Rails.logger).to receive(:info).with(
+ 'Form21aController: Form 21a successfully submitted to OGC service ' \
+ "by user with user_uuid=#{representative_user.uuid} - Response: {\"result\":\"success\"}"
+ )
+
headers = { 'Content-Type' => 'application/json' }
post('/accredited_representative_portal/v0/form21a', params: valid_json, headers:)
@@ -38,7 +43,11 @@
end
context 'with invalid JSON' do
- it 'returns a bad request status' do
+ it 'logs the error and returns a bad request status' do
+ expect(Rails.logger).to receive(:error).with(
+ "Form21aController: Invalid JSON in request body for user with user_uuid=#{representative_user.uuid}"
+ )
+
headers = { 'Content-Type' => 'application/json' }
post('/accredited_representative_portal/v0/form21a', params: invalid_json, headers:)
@@ -48,11 +57,15 @@
end
context 'when service returns a blank response' do
- it 'returns no content status' do
+ it 'logs the error and returns no content status' do
allow(AccreditationService).to receive(:submit_form21a).and_return(
instance_double(Faraday::Response, success?: false, body: nil, status: 204)
)
+ expect(Rails.logger).to receive(:info).with(
+ "Form21aController: Blank response from OGC service for user with user_uuid=#{representative_user.uuid}"
+ )
+
headers = { 'Content-Type' => 'application/json' }
post('/accredited_representative_portal/v0/form21a', params: valid_json, headers:)
@@ -61,12 +74,17 @@
end
context 'when service fails to parse response' do
- it 'returns a bad gateway status' do
+ it 'logs the error and returns a bad gateway status' do
allow(AccreditationService).to receive(:submit_form21a).and_return(
instance_double(Faraday::Response, success?: false, body: { errors: 'Failed to parse response' }.to_json,
status: 502)
)
+ expect(Rails.logger).to receive(:error).with(
+ 'Form21aController: Failed to parse response from external OGC service ' \
+ "for user with user_uuid=#{representative_user.uuid}"
+ )
+
headers = { 'Content-Type' => 'application/json' }
post('/accredited_representative_portal/v0/form21a', params: valid_json, headers:)
@@ -76,12 +94,18 @@
end
context 'when an unexpected error occurs' do
- it 'returns an internal server error status' do
+ it 'logs the error and returns an internal server error status' do
allow_any_instance_of(AccreditedRepresentativePortal::V0::Form21aController)
.to receive(:parse_request_body).and_raise(StandardError, 'Unexpected error')
+ allow(Rails.logger).to receive(:error).and_call_original
+
post '/accredited_representative_portal/v0/form21a'
+ expect(Rails.logger).to have_received(:error).with(
+ "ARP: Unexpected error occurred for user with user_uuid=#{representative_user.uuid} - Unexpected error"
+ )
+
expect(response).to have_http_status(:internal_server_error)
expect(parsed_response).to match(
'errors' => [
diff --git a/modules/accredited_representative_portal/spec/services/accreditation_service_spec.rb b/modules/accredited_representative_portal/spec/services/accreditation_service_spec.rb
index 9d0f7df10d8..591074e8ad3 100644
--- a/modules/accredited_representative_portal/spec/services/accreditation_service_spec.rb
+++ b/modules/accredited_representative_portal/spec/services/accreditation_service_spec.rb
@@ -6,6 +6,7 @@
RSpec.describe AccreditationService do
let(:parsed_body) { { field: 'value' } }
+ let(:user_uuid) { 'test-user-uuid' }
describe '#submit_form21a' do
context 'when the request is successful' do
@@ -13,7 +14,7 @@
stub_request(:post, 'http://localhost:5000/api/v1/accreditation/applications/form21a')
.to_return(status: 200, body: parsed_body.to_json, headers: { 'Content-Type' => 'application/json' })
- response = described_class.submit_form21a(parsed_body)
+ response = described_class.submit_form21a(parsed_body, user_uuid)
expect(response.status).to eq(200)
expect(response.body).to eq(parsed_body.stringify_keys)
@@ -21,11 +22,16 @@
end
context 'when the connection fails' do
- it 'returns a service unavailable status' do
+ it 'logs the error and returns a service unavailable status' do
stub_request(:post, 'http://localhost:5000/api/v1/accreditation/applications/form21a')
.to_raise(Faraday::ConnectionFailed.new('Accreditation Service connection failed'))
- response = described_class.submit_form21a(parsed_body)
+ expect(Rails.logger).to receive(:error).with(
+ "Accreditation Service connection failed for user with user_uuid=#{user_uuid}: " \
+ 'Accreditation Service connection failed, URL: http://localhost:5000/api/v1/accreditation/applications/form21a'
+ )
+
+ response = described_class.submit_form21a(parsed_body, user_uuid)
expect(response.status).to eq(:service_unavailable)
expect(JSON.parse(response.body)['errors']).to eq('Accreditation Service unavailable')
@@ -33,11 +39,15 @@
end
context 'when the request times out' do
- it 'returns a request timeout status' do
+ it 'logs the error and returns a request timeout status' do
stub_request(:post, 'http://localhost:5000/api/v1/accreditation/applications/form21a')
.to_raise(Faraday::TimeoutError.new('Request timed out'))
- response = described_class.submit_form21a(parsed_body)
+ expect(Rails.logger).to receive(:error).with(
+ "Accreditation Service request timed out for user with user_uuid=#{user_uuid}: Request timed out"
+ )
+
+ response = described_class.submit_form21a(parsed_body, user_uuid)
expect(response.status).to eq(:request_timeout)
expect(JSON.parse(response.body)['errors']).to eq('Accreditation Service request timed out')
diff --git a/modules/claims_api/app/controllers/claims_api/v1/forms/power_of_attorney_controller.rb b/modules/claims_api/app/controllers/claims_api/v1/forms/power_of_attorney_controller.rb
index 62cd19ad72c..2651623c496 100644
--- a/modules/claims_api/app/controllers/claims_api/v1/forms/power_of_attorney_controller.rb
+++ b/modules/claims_api/app/controllers/claims_api/v1/forms/power_of_attorney_controller.rb
@@ -141,10 +141,13 @@ def validate
validate_poa_code!(poa_code)
validate_poa_code_for_current_user!(poa_code) if header_request? && !token.client_credentials_token?
if Flipper.enabled?(:lighthouse_claims_api_poa_dependent_claimants) && form_attributes['claimant'].present?
- service = ClaimsApi::DependentClaimantVerificationService.new(target_veteran.participant_id,
- form_attributes.dig('claimant', 'firstName'),
- form_attributes.dig('claimant', 'lastName'),
- poa_code)
+ veteran_participant_id = target_veteran.participant_id
+ claimant_first_name = form_attributes.dig('claimant', 'firstName')
+ claimant_last_name = form_attributes.dig('claimant', 'lastName')
+ service = ClaimsApi::DependentClaimantVerificationService.new(veteran_participant_id:,
+ claimant_first_name:,
+ claimant_last_name:,
+ poa_code:)
service.validate_poa_code_exists!
service.validate_dependent_by_participant_id!
end
diff --git a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
index a5bb909ecea..db5d33537c1 100644
--- a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
+++ b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
@@ -42,12 +42,16 @@ def status
private
def shared_form_validation(form_number)
- target_veteran
+ base = form_number == '2122' ? 'serviceOrganization' : 'representative'
+ poa_code = form_attributes.dig(base, 'poaCode')
+
# Custom validations for POA submission, we must check this first
- @claims_api_forms_validation_errors = validate_form_2122_and_2122a_submission_values(user_profile)
+ @claims_api_forms_validation_errors = validate_form_2122_and_2122a_submission_values(
+ target_veteran.participant_id, user_profile, poa_code
+ )
# JSON validations for POA submission, will combine with previously captured errors and raise
validate_json_schema(form_number.upcase)
- @rep_id = validate_registration_number!(form_number)
+ @rep_id = validate_registration_number!(base, poa_code)
add_claimant_data_to_form if user_profile
# if we get here there were only validations file errors
@@ -57,10 +61,8 @@ def shared_form_validation(form_number)
end
end
- def validate_registration_number!(form_number)
- base = form_number == '2122' ? 'serviceOrganization' : 'representative'
+ def validate_registration_number!(base, poa_code)
rn = form_attributes.dig(base, 'registrationNumber')
- poa_code = form_attributes.dig(base, 'poaCode')
rep = ::Veteran::Service::Representative.where('? = ANY(poa_codes) AND representative_id = ?',
poa_code,
rn).order(created_at: :desc).first
diff --git a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
index 40725d8bd2f..092ecb7d5d3 100644
--- a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
+++ b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
@@ -10,12 +10,15 @@ class PowerOfAttorney::RequestController < ClaimsApi::V2::Veterans::PowerOfAttor
FORM_NUMBER = 'POA_REQUEST'
def request_representative
- target_veteran
- @claims_api_forms_validation_errors = validate_form_2122_and_2122a_submission_values(user_profile)
+ poa_code = form_attributes.dig('poa', 'poaCode')
+ @claims_api_forms_validation_errors = validate_form_2122_and_2122a_submission_values(
+ target_veteran.participant_id, user_profile, poa_code
+ )
+
validate_json_schema(FORM_NUMBER)
validate_accredited_representative(form_attributes.dig('poa', 'registrationNumber'),
- form_attributes.dig('poa', 'poaCode'))
- validate_accredited_organization(form_attributes.dig('poa', 'poaCode'))
+ poa_code)
+ validate_accredited_organization(poa_code)
# if we get here, the only errors not raised are form value validation errors
if @claims_api_forms_validation_errors
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb b/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
index 140664f0d87..4fdd2eb80c6 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
@@ -21,6 +21,8 @@ def validate_form_526_submission_values!
validate_form_526_service_information_confinements!
# ensure conflicting homelessness values are not provided
validate_form_526_veteran_homelessness!
+ # ensure that the active duty start date is not prior to the claimants 13th birthday
+ validate_service_after_13th_birthday!
# ensure 'militaryRetiredPay.receiving' and 'militaryRetiredPay.willReceiveInFuture' are not same non-null values
validate_form_526_service_pay!
# ensure 'title10ActivationDate' if provided, is after the earliest servicePeriod.activeDutyBeginDate and on or before the current date # rubocop:disable Layout/LineLength
@@ -232,6 +234,23 @@ def validate_form_526_veteran_homelessness!
end
end
+ def validate_service_after_13th_birthday!
+ service_periods = form_attributes&.dig('serviceInformation', 'servicePeriods')
+ age_thirteen = auth_headers['va_eauth_birthdate'].to_datetime.next_year(13).to_date
+
+ return if age_thirteen.nil? || service_periods.nil?
+
+ started_before_age_thirteen = service_periods.any? do |period|
+ Date.parse(period['activeDutyBeginDate']) < age_thirteen
+ end
+ if started_before_age_thirteen
+ raise ::Common::Exceptions::UnprocessableEntity.new(
+ detail: "If any 'serviceInformation.servicePeriods.activeDutyBeginDate' is "\
+ "before the Veteran's 13th birthdate: #{age_thirteen}, the claim can not be processed."
+ )
+ end
+ end
+
def validate_form_526_service_pay!
validate_form_526_military_retired_pay!
validate_form_526_separation_pay!
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb b/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
index 9d006adca09..c4e66bd16ac 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/v2/disability_compensation_validation.rb
@@ -982,13 +982,25 @@ def validate_federal_activation_values(service_information)
form_obj_desc = '/serviceInformation/federalActivation'
+ # For a valid BDD EP code to be assigned we need these values
+ validate_required_values_for_federal_activation(federal_activation_date, anticipated_separation_date)
+
+ validate_federal_activation_date(federal_activation_date, form_obj_desc)
+
+ validate_federal_activation_date_order(federal_activation_date) if federal_activation_date.present?
+ if anticipated_separation_date.present?
+ validate_anticipated_separation_date_in_past(anticipated_separation_date)
+ end
+ end
+
+ def validate_federal_activation_date(federal_activation_date, form_obj_desc)
if federal_activation_date.blank?
collect_error_if_value_not_present('federal activation date',
form_obj_desc)
end
+ end
- return if anticipated_separation_date.blank?
-
+ def validate_federal_activation_date_order(federal_activation_date)
# we know the dates are present
if activation_date_not_after_duty_begin_date?(federal_activation_date)
collect_error_messages(
@@ -996,9 +1008,41 @@ def validate_federal_activation_values(service_information)
detail: 'The federalActivation date must be after the earliest service period active duty begin date.'
)
end
+ end
- validate_anticipated_separation_date_in_past(anticipated_separation_date)
+ # rubocop:disable Metrics/MethodLength
+ def validate_required_values_for_federal_activation(activation_date, separation_date)
+ activation_form_obj_desc = 'serviceInformation/federalActivation/'
+ reserves_dates_form_obj_desc = 'serviceInformation/reservesNationalGuardServce/obligationTermsOfService/'
+ reserves_unit_form_obj_desc = 'serviceInformation/reservesNationalGuardServce/'
+
+ reserves = form_attributes.dig('serviceInformation', 'reservesNationalGuardService')
+ tos_start_date = reserves&.dig('obligationTermsOfService', 'beginDate')
+ tos_end_date = reserves&.dig('obligationTermsOfService', 'endDate')
+ unit_name = reserves&.dig('unitName')
+
+ if activation_date.blank?
+ collect_error_messages(detail: 'activationDate is missing or blank',
+ source: activation_form_obj_desc)
+ end
+ if separation_date.blank?
+ collect_error_messages(detail: 'anticipatedSeparationDate is missing or blank',
+ source: activation_form_obj_desc)
+ end
+ if tos_start_date.blank?
+ collect_error_messages(detail: 'beginDate is missing or blank',
+ source: reserves_dates_form_obj_desc)
+ end
+ if tos_end_date.blank?
+ collect_error_messages(detail: 'endDate is missing or blank',
+ source: reserves_dates_form_obj_desc)
+ end
+ if unit_name.blank?
+ collect_error_messages(detail: 'unitName is missing or blank',
+ source: reserves_unit_form_obj_desc)
+ end
end
+ # rubocop:enable Metrics/MethodLength
def activation_date_not_after_duty_begin_date?(activation_date)
service_information = form_attributes['serviceInformation']
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/v2/power_of_attorney_validation.rb b/modules/claims_api/app/controllers/concerns/claims_api/v2/power_of_attorney_validation.rb
index c1001eb50b0..9defb298c25 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/v2/power_of_attorney_validation.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/v2/power_of_attorney_validation.rb
@@ -5,20 +5,21 @@
module ClaimsApi
module V2
module PowerOfAttorneyValidation
- def validate_form_2122_and_2122a_submission_values(user_profile)
- validate_claimant(user_profile)
+ def validate_form_2122_and_2122a_submission_values(veteran_participant_id, user_profile, poa_code)
+ validate_claimant(veteran_participant_id, user_profile, poa_code)
# collect errors and pass back to the controller
raise_error_collection if @errors
end
private
- def validate_claimant(user_profile)
+ def validate_claimant(veteran_participant_id, user_profile, poa_code)
return if form_attributes['claimant'].blank?
validate_claimant_id_included(user_profile)
validate_address
validate_relationship
+ validate_dependent_claimant(veteran_participant_id, user_profile, poa_code)
end
def validate_address
@@ -95,6 +96,38 @@ def validate_relationship
end
end
+ # rubocop:disable Metrics/MethodLength
+ def validate_dependent_claimant(veteran_participant_id, user_profile, poa_code)
+ unless Flipper.enabled?(:lighthouse_claims_api_poa_dependent_claimants) && form_attributes['claimant'].present?
+ return
+ end
+
+ claimant = user_profile.profile
+ service = ClaimsApi::DependentClaimantVerificationService.new(veteran_participant_id:,
+ claimant_first_name: claimant.given_names.first,
+ claimant_last_name: claimant.family_name,
+ claimant_participant_id: claimant.participant_id,
+ poa_code:)
+ begin
+ service.validate_poa_code_exists!
+ rescue ::Common::Exceptions::UnprocessableEntity
+ collect_error_messages(
+ source: '/poaCode',
+ detail: ClaimsApi::DependentClaimantVerificationService::POA_CODE_NOT_FOUND_ERROR_MESSAGE
+ )
+ end
+
+ begin
+ service.validate_dependent_by_participant_id!
+ rescue ::Common::Exceptions::UnprocessableEntity
+ collect_error_messages(
+ source: '/claimant/claimantId',
+ detail: ClaimsApi::DependentClaimantVerificationService::CLAIMANT_NOT_A_DEPENDENT_ERROR_MESSAGE
+ )
+ end
+ end
+ # rubocop:enable Metrics/MethodLength
+
def validate_claimant_id_included(user_profile)
claimant_icn = form_attributes.dig('claimant', 'claimantId')
if (user_profile.blank? || user_profile&.status == :not_found) && claimant_icn
diff --git a/modules/claims_api/app/services/claims_api/dependent_claimant_verification_service.rb b/modules/claims_api/app/services/claims_api/dependent_claimant_verification_service.rb
index 40c4dd6610b..db4c41612a3 100644
--- a/modules/claims_api/app/services/claims_api/dependent_claimant_verification_service.rb
+++ b/modules/claims_api/app/services/claims_api/dependent_claimant_verification_service.rb
@@ -5,25 +5,28 @@
module ClaimsApi
class DependentClaimantVerificationService
- def initialize(participant_id, dependent_first_name, dependent_last_name, poa_code)
- @participant_id = participant_id
- @dependent_first_name = dependent_first_name
- @dependent_last_name = dependent_last_name
- @poa_code = poa_code
+ CLAIMANT_NOT_A_DEPENDENT_ERROR_MESSAGE = 'The claimant is not listed as a dependent for the specified Veteran. ' \
+ 'Please submit VA Form 21-686c to add this dependent.'
+ POA_CODE_NOT_FOUND_ERROR_MESSAGE = 'The requested POA code could not be found.'
+
+ def initialize(options = {})
+ @veteran_participant_id = options[:veteran_participant_id]
+ @claimant_first_name = options[:claimant_first_name]
+ @claimant_last_name = options[:claimant_last_name]
+ @claimant_participant_id = options[:claimant_participant_id]
+ @poa_code = options[:poa_code]
end
def validate_dependent_by_participant_id!
return if valid_participant_dependent_combo?
- detail = 'The claimant is not listed as a dependent for the specified Veteran. Please submit VA Form 21-686c ' \
- 'to add this dependent.'
- raise ::Common::Exceptions::UnprocessableEntity.new(detail:)
+ raise ::Common::Exceptions::UnprocessableEntity.new(detail: CLAIMANT_NOT_A_DEPENDENT_ERROR_MESSAGE)
end
def validate_poa_code_exists!
return if poa_code_exists?
- raise ::Common::Exceptions::UnprocessableEntity.new(detail: 'The requested POA code could not be found.')
+ raise ::Common::Exceptions::UnprocessableEntity.new(detail: POA_CODE_NOT_FOUND_ERROR_MESSAGE)
end
private
@@ -33,27 +36,33 @@ def normalize(item)
end
def valid_participant_dependent_combo?
- return false if @participant_id.blank?
+ return false if @veteran_participant_id.blank?
person_web_service = PersonWebService.new(external_uid: 'dependent_claimant_verification_uid',
external_key: 'dependent_claimant_verification_key')
- response = person_web_service.find_dependents_by_ptcpnt_id(@participant_id)
+ response = person_web_service.find_dependents_by_ptcpnt_id(@veteran_participant_id)
return false if response.nil? || response.fetch(:number_of_records, 0).to_i.zero?
dependents = response[:dependent]
Array.wrap(dependents).any? do |dependent|
- normalized_first_name_to_verify = normalize(@dependent_first_name)
- normalized_last_name_to_verify = normalize(@dependent_last_name)
- normalized_first_name_service = normalize(dependent[:first_nm])
- normalized_last_name_service = normalize(dependent[:last_nm])
-
- return false if [normalized_first_name_to_verify, normalized_last_name_to_verify, normalized_first_name_service,
- normalized_last_name_service].any?(&:blank?)
-
- normalized_first_name_to_verify == normalized_first_name_service &&
- normalized_last_name_to_verify == normalized_last_name_service
+ # If the claimant_participant_id is present (most v2), use it to verify the dependent
+ if @claimant_participant_id.present?
+ return normalize(@claimant_participant_id) == normalize(dependent[:ptcpnt_id])
+ end
+
+ # Otherwise, we need to verify the dependent by first and last name (all v1 and some v2 without participant_ids)
+ normalized_claimant_first_name = normalize(@claimant_first_name)
+ normalized_claimant_last_name = normalize(@claimant_last_name)
+ normalized_dependent_first_name = normalize(dependent[:first_nm])
+ normalized_dependent_last_name = normalize(dependent[:last_nm])
+
+ return false if [normalized_claimant_first_name, normalized_claimant_last_name,
+ normalized_dependent_first_name, normalized_dependent_last_name].any?(&:blank?)
+
+ normalized_claimant_first_name == normalized_dependent_first_name &&
+ normalized_claimant_last_name == normalized_dependent_last_name
end
end
diff --git a/modules/claims_api/app/services/claims_api/disability_compensation/pdf_generation_service.rb b/modules/claims_api/app/services/claims_api/disability_compensation/pdf_generation_service.rb
index 3970e8d0a47..94bc2f29977 100644
--- a/modules/claims_api/app/services/claims_api/disability_compensation/pdf_generation_service.rb
+++ b/modules/claims_api/app/services/claims_api/disability_compensation/pdf_generation_service.rb
@@ -28,6 +28,13 @@ def generate(claim_id, middle_initial) # rubocop:disable Metrics/MethodLength
file_name = "#{SecureRandom.hex}.pdf"
path = ::Common::FileHelpers.generate_clamav_temp_file(pdf_string, file_name)
+ # temporary debugging of clam av
+ log_job_progress(
+ auto_claim.id,
+ "526EZ PDF generator PDF existence check: #{File.exist?(path)}, file size : #{File.size?(path)}",
+ auto_claim.transaction_id
+ )
+
upload = ActionDispatch::Http::UploadedFile.new({
filename: file_name,
type: 'application/pdf',
diff --git a/modules/claims_api/app/sidekiq/claims_api/service_base.rb b/modules/claims_api/app/sidekiq/claims_api/service_base.rb
index 397ca0ce012..89b136e3d93 100644
--- a/modules/claims_api/app/sidekiq/claims_api/service_base.rb
+++ b/modules/claims_api/app/sidekiq/claims_api/service_base.rb
@@ -34,6 +34,10 @@ class ServiceBase
end
end
+ def retry_limits_for_notification
+ [11]
+ end
+
protected
def preserve_original_form_data(form_data)
diff --git a/modules/claims_api/app/swagger/claims_api/v1/swagger.json b/modules/claims_api/app/swagger/claims_api/v1/swagger.json
index db27c9362af..39778110f32 100644
--- a/modules/claims_api/app/swagger/claims_api/v1/swagger.json
+++ b/modules/claims_api/app/swagger/claims_api/v1/swagger.json
@@ -979,6 +979,71 @@
}
}
}
+ },
+ "422": {
+ "description": "Unprocessable Entity",
+ "content": {
+ "application/json": {
+ "example": {
+ "errors": [
+ {
+ "title": "Unprocessable Entity",
+ "detail": "Unknown EVSS Async Error",
+ "code": "422",
+ "status": "422"
+ }
+ ]
+ },
+ "schema": {
+ "required": [
+ "errors"
+ ],
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "additionalProperties": false,
+ "required": [
+ "title",
+ "detail",
+ "code",
+ "status"
+ ],
+ "properties": {
+ "title": {
+ "type": "string",
+ "description": "HTTP error title"
+ },
+ "detail": {
+ "type": "string",
+ "description": "HTTP error detail"
+ },
+ "code": {
+ "type": "string",
+ "description": "HTTP error code"
+ },
+ "status": {
+ "type": "string",
+ "description": "HTTP error code"
+ },
+ "source": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "Source of error",
+ "properties": {
+ "pointer": {
+ "type": "string",
+ "description": "Pointer to source of error"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
}
diff --git a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
index ab775f33925..bb73cfb51dd 100644
--- a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
+++ b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json
@@ -2947,6 +2947,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -4330,6 +4331,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -5075,7 +5077,7 @@
"202 without a transactionId": {
"value": {
"data": {
- "id": "a51ca7e0-180e-4792-b5a4-148a10a9bdae",
+ "id": "74d7a3b6-8164-49c2-9dfb-fdb3fa019477",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -5260,7 +5262,7 @@
},
"federalActivation": {
"activationDate": "2023-10-01",
- "anticipatedSeparationDate": "2024-09-18"
+ "anticipatedSeparationDate": "2024-09-21"
},
"confinements": [
{
@@ -5306,7 +5308,7 @@
"202 with a transactionId": {
"value": {
"data": {
- "id": "3b5398d2-5851-4f40-9b81-9f03717acbff",
+ "id": "3356c8ae-350b-4a23-aea1-9bb00de306e1",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -6488,6 +6490,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -7871,6 +7874,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -10031,6 +10035,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -10521,7 +10526,7 @@
"application/json": {
"example": {
"data": {
- "id": "9195a402-a9df-43ea-a62d-715e59db90c9",
+ "id": "e788a384-ee89-4e9e-9055-b81fa61b166d",
"type": "forms/526",
"attributes": {
"claimProcessType": "STANDARD_CLAIM_PROCESS",
@@ -14181,8 +14186,8 @@
"id": "1",
"type": "intent_to_file",
"attributes": {
- "creationDate": "2024-09-16",
- "expirationDate": "2025-09-16",
+ "creationDate": "2024-09-19",
+ "expirationDate": "2025-09-19",
"type": "compensation",
"status": "active"
}
@@ -15078,7 +15083,7 @@
"application/json": {
"example": {
"data": {
- "id": "c231aa7f-e16b-4616-9f79-c5c5b4d1f1e3",
+ "id": "c97b8c40-558a-4d35-af92-57362329173a",
"type": "individual",
"attributes": {
"code": "067",
@@ -15771,7 +15776,7 @@
"application/json": {
"example": {
"data": {
- "id": "1b2bcb3f-8af7-46d1-ba2f-cecbcd87a451",
+ "id": "8d551fa6-0088-4910-bbd7-08492f1f1dfc",
"type": "organization",
"attributes": {
"code": "083",
@@ -17722,10 +17727,10 @@
"application/json": {
"example": {
"data": {
- "id": "4636c375-2259-4a3c-afe0-79bf01664c7d",
+ "id": "17c1d357-0b69-4ce1-a7e2-deb13931a6c6",
"type": "claimsApiPowerOfAttorneys",
"attributes": {
- "dateRequestAccepted": "2024-09-16",
+ "dateRequestAccepted": "2024-09-19",
"previousPoa": null,
"representative": {
"serviceOrganization": {
diff --git a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
index 0ee1c313d3d..9fe4dd2c154 100644
--- a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
+++ b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json
@@ -1560,6 +1560,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -2943,6 +2944,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -3688,7 +3690,7 @@
"202 without a transactionId": {
"value": {
"data": {
- "id": "b636aeb7-88c0-4451-bd9b-73fd63697834",
+ "id": "c57050e5-558e-4f9f-9a6b-68ea79752541",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -3873,7 +3875,7 @@
},
"federalActivation": {
"activationDate": "2023-10-01",
- "anticipatedSeparationDate": "2024-09-18"
+ "anticipatedSeparationDate": "2024-09-21"
},
"confinements": [
{
@@ -3919,7 +3921,7 @@
"202 with a transactionId": {
"value": {
"data": {
- "id": "bf5dc2c7-c4f6-497c-b263-c9cf2ef097d0",
+ "id": "e981c089-c851-4c90-8a76-87a956381c2e",
"type": "forms/526",
"attributes": {
"claimId": "600442191",
@@ -5101,6 +5103,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -6484,6 +6487,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -8644,6 +8648,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": "object",
"nullable": true,
"additionalProperties": false,
@@ -9134,7 +9139,7 @@
"application/json": {
"example": {
"data": {
- "id": "cc98ad06-eee9-4a56-b089-f891410097a5",
+ "id": "18853a53-9cc4-479a-8443-0f3c9bdea0af",
"type": "forms/526",
"attributes": {
"claimProcessType": "STANDARD_CLAIM_PROCESS",
@@ -12794,8 +12799,8 @@
"id": "1",
"type": "intent_to_file",
"attributes": {
- "creationDate": "2024-09-16",
- "expirationDate": "2025-09-16",
+ "creationDate": "2024-09-19",
+ "expirationDate": "2025-09-19",
"type": "compensation",
"status": "active"
}
@@ -13691,7 +13696,7 @@
"application/json": {
"example": {
"data": {
- "id": "46dd81ce-150a-46af-ab26-fd631d7a8c30",
+ "id": "b06d7ca0-bb23-4481-b9e1-6a15351e0d63",
"type": "individual",
"attributes": {
"code": "067",
@@ -14384,7 +14389,7 @@
"application/json": {
"example": {
"data": {
- "id": "25afc8c7-aeba-47c9-aaec-31654a9c954e",
+ "id": "4d21ac8b-52eb-48ea-930e-6277a1a272be",
"type": "organization",
"attributes": {
"code": "083",
@@ -16335,10 +16340,10 @@
"application/json": {
"example": {
"data": {
- "id": "0d361496-a409-41d3-9946-5c796ba0febe",
+ "id": "42002b9e-354f-40b4-a7c2-5b816a934c6b",
"type": "claimsApiPowerOfAttorneys",
"attributes": {
- "dateRequestAccepted": "2024-09-16",
+ "dateRequestAccepted": "2024-09-19",
"previousPoa": null,
"representative": {
"serviceOrganization": {
diff --git a/modules/claims_api/config/schemas/v2/526.json b/modules/claims_api/config/schemas/v2/526.json
index b587dfc94e2..4c98b55e3c1 100644
--- a/modules/claims_api/config/schemas/v2/526.json
+++ b/modules/claims_api/config/schemas/v2/526.json
@@ -872,6 +872,7 @@
}
},
"federalActivation": {
+ "description": "If federalActivation is present then reservesNationalGuardService.obligationTermsOfService.beginDate, reservesNationalGuardService.obligationTermsOfService.endDate and reservesNationalGuardService.unitName are required",
"type": ["object", "null"],
"nullable": true,
"additionalProperties": false,
diff --git a/modules/claims_api/lib/bd/bd.rb b/modules/claims_api/lib/bd/bd.rb
index 0d42c6bfc2f..501acc771e4 100644
--- a/modules/claims_api/lib/bd/bd.rb
+++ b/modules/claims_api/lib/bd/bd.rb
@@ -122,9 +122,9 @@ def generate_upload_body(claim:, doc_type:, pdf_path:, action:, original_filenam
end
def determine_birls_file_number(doc_type, auth_headers)
- if %w[L122 L705].include?(doc_type)
+ if %w[L122].include?(doc_type)
birls_file_num = auth_headers['va_eauth_birlsfilenumber']
- elsif %w[L075 L190].include?(doc_type)
+ elsif %w[L075 L190 L705].include?(doc_type)
birls_file_num = nil
end
birls_file_num
diff --git a/modules/claims_api/lib/bgs_service/local_bgs.rb b/modules/claims_api/lib/bgs_service/local_bgs.rb
index ed982050e53..b29ea73d914 100644
--- a/modules/claims_api/lib/bgs_service/local_bgs.rb
+++ b/modules/claims_api/lib/bgs_service/local_bgs.rb
@@ -437,6 +437,10 @@ def jrn
private
+ def builder_to_xml(builder)
+ builder.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
+ end
+
def transform_keys(hash_or_array)
transformer = lambda do |object|
case object
diff --git a/modules/claims_api/lib/bgs_service/person_web_service.rb b/modules/claims_api/lib/bgs_service/person_web_service.rb
index 1fde82af939..b108d4c70c9 100644
--- a/modules/claims_api/lib/bgs_service/person_web_service.rb
+++ b/modules/claims_api/lib/bgs_service/person_web_service.rb
@@ -7,15 +7,33 @@ def bean_name
end
def find_dependents_by_ptcpnt_id(id)
- body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
-
{- "environment": "dev",
- "buildNumber": "10",
- "os": "android"
}
{- "data": {
- "id": "1.0",
- "type": "discovery",
- "attributes": {
- "endpoints": {
- "appointments": {
- "url": "/v0/appointments",
- "method": "GET"
}, - "uploadClaimDocuments": {
- "url": "/claim/:id/documents",
- "method": "POST"
}, - "createUserPhone": {
- "url": "/user/phones",
- "method": "POST"
}, - "updateUserPhone": {
- "url": "/user/phones",
- "method": "PUT"
}
}, - "webviews": {
}, - "appAccess": true,
- "displayMessage": "Please update the app to continue"
}
}
}
{- "environment": "dev",
- "buildNumber": "10",
- "os": "android"
}
{- "data": {
- "id": "1.0",
- "type": "discovery",
- "attributes": {
- "endpoints": {
- "appointments": {
- "url": "/v0/appointments",
- "method": "GET"
}, - "uploadClaimDocuments": {
- "url": "/claim/:id/documents",
- "method": "POST"
}, - "createUserPhone": {
- "url": "/user/phones",
- "method": "POST"
}, - "updateUserPhone": {
- "url": "/user/phones",
- "method": "PUT"
}
}, - "webviews": {
}, - "appAccess": true,
- "displayMessage": "Please update the app to continue"
}
}
}
Returns info on all user's claims and appeals for mobile overview page. Should be identical to the following docs https://developer.va.gov/explore/api/appeals-status/docs?version=current
id required | string Appeal Id |
X-Key-Inflection | string Default: snake Enum: "camel" "snake" Allows the API to return camelCase keys rather than snake_case |
X-Key-Inflection | string Default: snake Enum: "camel" "snake" Allows the API to return camelCase keys rather than snake_case |
{- "data": [
- {
- "type": "immunization",
- "id": "I2-3JYDMXC6RXTU4H25KRVXATSEJQ000000",
- "attributes": {
- "cvxCode": 140,
- "date": "2009-03-19T12:24:55Z",
- "doseNumber": "Booster",
- "doseSeries": 1,
- "groupName": "FLU",
- "manufacturer": "nil",
- "note": "Dose #45 of 101 of Influenza seasonal injectable preservative free vaccine administered.",
- "shortDescription": "Influenza seasonal injectable preservative free",
- "reaction": "Swelling"
}, - "relationships": {
- "location": {
- "data": {
- "id": "I2-4KG3N5YUSPTWD3DAFMLMRL5V5U000000",
- "type": "location"
}, - "links": {
- "related": "staging-api.va.gov/mobile/v0/health/locations/I2-2FPCKUIXVR7RJLLG34XVWGZERM000000"
}
}
}
}
], - "meta": {
- "pagination": {
- "currentPage": 1,
- "perPage": 10,
- "totalPages": 2,
- "totalEntries": 15
}
}
}