Skip to content

VFMP - Added the ability for CST providers to upload supporting docs to existing cases#27798

Merged
ksantiagoBAH merged 9 commits intomasterfrom
vfmp-champva-fileuploader
Apr 23, 2026
Merged

VFMP - Added the ability for CST providers to upload supporting docs to existing cases#27798
ksantiagoBAH merged 9 commits intomasterfrom
vfmp-champva-fileuploader

Conversation

@ksantiagoBAH
Copy link
Copy Markdown
Contributor

@ksantiagoBAH ksantiagoBAH commented Apr 16, 2026

Here’s a PR description you can paste for the vets-api changes:


Summary

This work is behind a feature toggle (flipper): YES

Primary flipper for docs-only flow:

  • form1010d_enhanced_flow_enabled

Additional optional behavior flipper used in this flow:

  • champva_convert_to_pdf_on_upload (non-PDF -> PDF conversion at upload)

What changed

This PR adds/updates backend support for CST CHAMPVA supporting-document uploads using a data-driven destination model and fixes file status progression for docs-only resubmissions.

Changes include:

  1. CST upload metadata for provider-driven routing
  • Added uploadMetadata to claim payloads in V0::BenefitsClaimsController.
  • For CHAMPVA provider claims, metadata includes:
    • uploadDestinationKey
    • formId
    • acceptedFileTypes
    • documentTypeOptions
    • finalizeDestinationKey + submissionType (for 10-10D-EXTENDED docs-only finalize path)
  1. Docs-only resubmission endpoint flow
  • POST /ivc_champva/v1/forms/docs_only_resubmission validates payload, hydrates required veteran/contact fields from existing CHAMPVA claim record, and submits supporting docs via existing uploader path.
  • Flow is gated by form1010d_enhanced_flow_enabled.
  1. VES bypass for docs-only resubmission
  • Docs-only path explicitly bypasses VES submission and uses file upload path directly.
  1. Fix for “stuck in File submissions in progress”
  • After successful docs-only finalize response, matching EvidenceSubmission rows for the claim are moved from pending statuses (CREATED/QUEUED/PENDING) to SUCCESS so CST can show files in “Files received.”

Bug / repro

Prior behavior:

  • User uploads CHAMPVA supporting doc
  • Upload + docs-only finalize returns success
  • EvidenceSubmission remained CREATED, so CST continued showing item under “File submissions in progress” instead of “Files received.”

Why this solution

  • Reuses existing upload pipeline and CHAMPVA infrastructure.
  • Keeps endpoint/destination selection provider-driven via metadata.
  • Ensures CST status UX matches backend submission lifecycle.

Team ownership

  • Team: CST / Benefits (Cross Benefits Crew)
  • Maintenance ownership: CST upload/status integration + CHAMPVA backend components touched in this PR.

Flipper success criteria

For form1010d_enhanced_flow_enabled:

  • Docs-only uploads for CHAMPVA 10-10D-EXTENDED succeed without VES.
  • New supporting docs transition to SUCCESS evidence status after successful finalize.
  • Files appear in CST “Files received” and not indefinitely in “in progress.”

Related issue(s)

department-of-veterans-affairs/vets-website#44126
department-of-veterans-affairs/va.gov-team#139480


Testing done

  • ✅ New code is covered by unit/request specs.
  • ✅ Flipper on/off behavior tested for docs-only endpoint.
  • ✅ Manual local verification of end-to-end behavior performed.

Automated

  • bundle exec rspec modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb:857
    • verifies docs-only flow success and status transition
    • verifies flipper-off returns expected error path
  • bundle exec rubocop --parallel --format github (clean)

Manual verification steps

  1. Start local vets-api and vets-website.
  2. Open CHAMPVA claim files tab in CST flow.
  3. Upload supporting doc via CHAMPVA destination (submit_supporting_documents).
  4. Finalize docs-only submission (docs_only_resubmission) with submission_type=existing.
  5. Confirm:
    • endpoint response success
    • matching EvidenceSubmission transitions to SUCCESS
    • file moves to “Files received.”

Flipper rollout test plan

  • Enable form1010d_enhanced_flow_enabled for lower env/internal users first.
  • Validate docs-only upload + finalize + status transition for multiple document types.
  • Confirm no VES invocation for docs-only payloads.
  • Monitor CHAMPVA submission/error logs and evidence status distribution before wider rollout.

Screenshots

(Optional)

  • [ADD SCREENSHOTS IF DESIRED]

What areas of the site does it impact?

Backend/API impact:

  • v0/benefits_claims response shape (adds uploadMetadata for provider-aware uploads)
  • ivc_champva uploads controller docs-only flow and evidence status updates
  • CHAMPVA-specific supporting document submission behavior in CST claims files UX

No direct non-CHAMPVA upload behavior changes intended; default upload destination remains unchanged for non-CHAMPVA providers.


Acceptance criteria

  • I fixed/updated/added unit tests and integration tests for each feature (if applicable).
  • No error nor warning in the console (for changed backend paths/tests).
  • Events/logging are sent to existing logging/monitoring patterns (Rails logger/Datadog traces used in touched paths).
  • Documentation has been updated (link to documentation) [ADD IF APPLICABLE]
  • No sensitive information (PII/credentials/internal URLs/etc.) is captured in logging, hardcoded, or specs.
  • Feature/bug has a monitor built into Datadog (if applicable) [ADD/UPDATE IF CREATED]
  • If app impacted requires authentication, logged in locally and verified authenticated routes involved in this change.
  • I added a screenshot of the developed feature (optional)

Requested Feedback

Please focus review on:

  1. Docs-only CHAMPVA flow gating and validation.
  2. Evidence submission status transition logic (CREATED/QUEUED/PENDING -> SUCCESS) and matching strategy.
  3. Any concerns around provider metadata contract shape for CST consumers.

@github-actions github-actions Bot added the missing-service-tag With a service tag, you can filter Datadog APM on a per-service level. label Apr 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

1 Warning
⚠️ This PR changes 447 lines of code (not counting whitespace/newlines, comments, or test files).

In order to ensure each PR receives the proper attention it deserves, we recommend not exceeding
200. Expect some delays getting reviews.

File Summary

Files

  • app/controllers/v0/benefits_claims_controller.rb (+114/-10)

  • config/features.yml (+3/-0)

  • lib/benefits_claims/providers/ivc_champva/claim_builder.rb (+9/-1)

  • lib/forms/submission_statuses/formatters/ivc_champva_formatter.rb (+9/-2)

  • modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb (+291/-7)

  • modules/ivc_champva/config/routes.rb (+1/-0)

    Note: We exclude files matching the following when considering PR size:

    *.csv, *.json, *.tsv, *.txt, *.md, Gemfile.lock, app/swagger, modules/mobile/docs, spec/fixtures/, spec/support/vcr_cassettes/, modules/mobile/spec/support/vcr_cassettes/, db/seeds, modules/vaos/app/docs, modules/meb_api/app/docs, modules/appeals_api/app/swagger/, *.bru, *.pdf, modules/*/spec/fixtures/*, modules/*/spec/factories/*, modules/*/spec/**/*.rb, spec/**/*.rb, modules/*/docs/**/*.yaml, modules/*/docs/**/*.yml, modules/*/app/docs/**/*.yaml, modules/*/app/docs/**/*.yml
    

Big PRs are difficult to review, often become stale, and cause delays.

Generated by 🚫 Danger

@github-actions github-actions Bot removed missing-service-tag With a service tag, you can filter Datadog APM on a per-service level. lint-failure labels Apr 16, 2026
breedbah
breedbah previously approved these changes Apr 17, 2026
Copy link
Copy Markdown
Contributor

@breedbah breedbah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIce!

@ksantiagoBAH ksantiagoBAH marked this pull request as ready for review April 17, 2026 19:05
@ksantiagoBAH ksantiagoBAH requested review from a team as code owners April 17, 2026 19:05
Copilot AI review requested due to automatic review settings April 17, 2026 19:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CHAMPVA/CST support for uploading supporting documents to existing cases by exposing upload-routing metadata on benefits claims, introducing a docs-only resubmission endpoint, and correcting evidence-submission status progression after finalize.

Changes:

  • Add uploadMetadata to v0/benefits_claims claim payloads (provider-aware destination + CHAMPVA doc-type options; docs-only finalize metadata gated by form1010d_enhanced_flow_enabled).
  • Introduce POST /ivc_champva/v1/forms/docs_only_resubmission to submit supporting docs to existing CHAMPVA cases (bypasses VES) and mark matching EvidenceSubmission rows as SUCCESS.
  • Persist EvidenceSubmission rows for CHAMPVA supporting-document uploads when claim_id is provided.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
app/controllers/v0/benefits_claims_controller.rb Adds upload routing metadata to claim responses and improves evidence-submission lookup for CHAMPVA UUID claim IDs.
app/swagger/swagger/schemas/benefits_claims.rb Documents the new uploadMetadata shape in Swagger.
config/features.yml Adds form1010d_enhanced_flow_enabled flipper for enhanced docs-only flow gating.
config/benefits_claims/claim_status_meta/ivc_champva/default.json Adjusts CHAMPVA claim status meta UI config (simpleLayout).
modules/ivc_champva/config/routes.rb Adds the docs-only resubmission route.
modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb Implements docs-only resubmission flow, bypasses VES for that path, persists evidence submissions, and updates statuses post-finalize.
spec/controllers/v0/benefits_claims_controller_spec.rb Updates evidence-submission query expectations and adds unit coverage for upload metadata building.
modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb Adds request coverage for evidence-submission persistence and the docs-only resubmission endpoint/status transition.

validate_docs_only_resubmission!(parsed_form_data)
hydrate_docs_only_resubmission_data!(parsed_form_data)

form_id = get_form_id
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In submit_docs_only_resubmission, parsed_form_data['form_number'] ||= '10-10D-EXTENDED' won’t prevent get_form_id from raising because get_form_id reads params[:form_number] (not parsed_form_data). If form_number is omitted from the request body, this will 500 despite the default. Consider deriving form_id from parsed_form_data['form_number'] (or setting params[:form_number] when defaulting) so the default actually works.

Suggested change
form_id = get_form_id
form_number = parsed_form_data['form_number']
form_id = FORM_NUMBER_MAP[form_number]
raise ArgumentError, "Unsupported form number: #{form_number}" if form_id.blank?

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is worth addressing. The spec masks the bug by always passing form_number. The default assignment on line 104 (parsed_form_data['form_number'] ||= '10-10D-EXTENDED') is dead code because get_form_id reads from params[:form_number], not parsed_form_data. If a client omits form_number, the raise on line 1326 gets caught by the generic rescue => e and returns a 500.

Comment on lines +1233 to +1235
if doc['confirmation_code'].blank?
raise ArgumentError, "supporting_docs[#{index}] is missing confirmation_code"
end
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_docs_only_resubmission! only checks supporting_docs[*].confirmation_code, but later code assumes additional fields exist and are valid: supporting_document_ids will raise if the referenced PersistentAttachments::MilitaryRecords record can’t be found, and mark_docs_only_evidence_submissions_received! relies on supporting_docs[*].name to match EvidenceSubmissions. Consider validating supporting_docs[*].name presence and that each confirmation_code resolves to an existing persistent attachment (returning a 422 with a clear message instead of a 500).

Suggested change
if doc['confirmation_code'].blank?
raise ArgumentError, "supporting_docs[#{index}] is missing confirmation_code"
end
unless doc.respond_to?(:[])
raise ArgumentError, "supporting_docs[#{index}] must be an object"
end
if doc['name'].blank?
raise ArgumentError, "supporting_docs[#{index}] is missing name"
end
confirmation_code = doc['confirmation_code']
if confirmation_code.blank?
raise ArgumentError, "supporting_docs[#{index}] is missing confirmation_code"
end
next if PersistentAttachments::MilitaryRecords.exists?(guid: confirmation_code)
raise ArgumentError,
"supporting_docs[#{index}] confirmation_code could not be resolved to an existing attachment"

Copilot uses AI. Check for mistakes.
Comment on lines +618 to +633
claim_id = resolve_claim_record_id(params[:claim_id])
if claim_id.blank?
Rails.logger.warn('Skipping CHAMPVA evidence submission persistence: missing/unresolvable claim_id')
return
end

user_account = current_user_account_for_evidence_submission
if user_account.blank?
Rails.logger.warn('Skipping CHAMPVA evidence submission persistence: missing user_account')
return
end

file_name = uploaded_file_name(params['file'], attachment)
if file_name.blank?
Rails.logger.warn('Skipping CHAMPVA evidence submission persistence: missing file_name')
return
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

persist_claim_evidence_submission logs warnings when claim_id/user_account/file_name are missing, but claim_id is explicitly optional for legacy upload paths. As written, calls to submit_supporting_documents that don’t include claim_id will emit a warn on every upload, which is likely to create noisy logs. Consider only warning when a claim_id param is present but unresolvable (or downgrade to debug/info for the expected legacy case).

Copilot uses AI. Check for mistakes.
DOCS_ONLY_RESUBMISSION_SUBMISSION_TYPES.include?(submission_type)
end

def docs_only_resubmission_flow_enabled?(parsed_form_data)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This controller’s authenticate method rescues Common::Exceptions::Unauthorized and allows the request to continue unauthenticated, which means POST /forms/docs_only_resubmission can be called without a logged-in user. Since this endpoint hydrates data from an existing claim and can update EvidenceSubmission statuses, consider requiring @current_user (or other auth/authorization) as part of docs_only_resubmission_flow_enabled? / submit_docs_only_resubmission so the docs-only finalize path can’t be exercised anonymously when the flipper is enabled broadly.

Suggested change
def docs_only_resubmission_flow_enabled?(parsed_form_data)
def docs_only_resubmission_flow_enabled?(parsed_form_data)
return false unless @current_user

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@rmtolmach rmtolmach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksantiagoBAH Can you do the following?

  1. Resolve the Reek DuplicatesMethodCall warnings? There's some room for improvement there.
  2. Refactor so you're not disabling rubocop? I see you're disabling it in 6 places in this PR. We try to avoid that if at all possible.
  3. Take a look at Copilot's comments and see if any of them are relevant? (I haven't read them, but sometimes they're helpful, sometimes not 😄)

swillisdev
swillisdev previously approved these changes Apr 20, 2026
Copy link
Copy Markdown
Contributor

@swillisdev swillisdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

end

def hydrate_docs_only_resubmission_data(parsed_form_data)
source_form = IvcChampvaForm.where(form_uuid: parsed_form_data['claim_id'].to_s).order(updated_at: :desc).first
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ownership enforcement isn't occurring elsewhere we might want to add a check to ensure the UUID belongs to the requesting user.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally fair callout. I wanted to add ownership checks too, but IvcChampvaForm doesn’t currently have a reliable user ownership field in this path, so I didn’t want to add a fake/partial check.

I kept this PR scoped to the docs-only flow fix behind the flag, and I’ll open a follow-up to add real ownership enforcement once we lock down the right ownership signal for claim_id -> IvcChampvaForm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Worth capturing that as a ticket so it doesn't slip.

Comment thread modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb Outdated
end

# rubocop:disable Metrics/MethodLength
def add_evidence_submissions_to_claims(claims, all_evidence_submissions, endpoint)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksantiagoBAH it looks like you can tighten up this method a little bit to avoid disabling this rubocop. See what I did here:

Image


module V0
# rubocop:disable Metrics/ClassLength
class BenefitsClaimsController < ApplicationController
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has your team considered a plan to refactor this class so it's not so long?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm going to refactor these real quick

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored the method level ones, but I will bring this to the team as some tech debt for us to refactor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

Comment thread app/controllers/v0/benefits_claims_controller.rb Outdated
Copy link
Copy Markdown
Contributor

@nathangthomas nathangthomas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments. Some you've already addressed or commented on. Getting close!

@ksantiagoBAH ksantiagoBAH requested a review from a team as a code owner April 22, 2026 13:04
Copy link
Copy Markdown
Contributor

@rmtolmach rmtolmach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀
You'll need nathan's approval, or to dismiss his requested review.

@ksantiagoBAH ksantiagoBAH removed the request for review from nathangthomas April 23, 2026 13:29
@ksantiagoBAH ksantiagoBAH merged commit 1f54e38 into master Apr 23, 2026
42 of 44 checks passed
@ksantiagoBAH ksantiagoBAH deleted the vfmp-champva-fileuploader branch April 23, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants