Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 137 additions & 14 deletions app/controllers/v0/benefits_claims_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require 'lighthouse/benefits_documents/update_documents_status_service'

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!

include InboundRequestLogging
include V0::Concerns::MultiProviderSupport
Expand All @@ -28,19 +29,63 @@ class BenefitsClaimsController < ApplicationController

FEATURE_USE_TITLE_GENERATOR_WEB = 'cst_use_claim_title_generator_web'
FEATURE_MULTI_CLAIM_PROVIDER = 'cst_multi_claim_provider'
DEFAULT_UPLOAD_DESTINATION_KEY = 'benefits_claims'
IVC_CHAMPVA_UPLOAD_DESTINATION_KEY = 'ivc_champva_supporting_documents'
IVC_CHAMPVA_FINALIZE_DESTINATION_KEY = 'ivc_champva_docs_only_resubmission'

UPLOAD_DESTINATION_KEY_BY_PROVIDER = {
'lighthouse' => DEFAULT_UPLOAD_DESTINATION_KEY,
'ivc_champva' => IVC_CHAMPVA_UPLOAD_DESTINATION_KEY
}.freeze

IVC_CHAMPVA_FORM_ID_BY_CLAIM_TYPE = {
'CHAMPVA application' => '10-10D-EXTENDED',
'Other Health Insurance' => '10-7959C',
'Foreign Medical Program registration' => '10-7959F-1',
'Foreign Medical Program claim' => '10-7959F-2',
'CHAMPVA claim' => '10-7959A'
}.freeze

IVC_CHAMPVA_10_10D_EXTENDED_DOCUMENT_TYPE_OPTIONS = [
'Court ordered adoption papers',
'Birth certificate',
'Certificate of civil union',
'Divorce decree',
'Marriage certificate',
'Front of Medicare Parts A or B card',
'Back of Medicare Parts A or B card',
'Front of Medicare Part C card',
'Back of Medicare Part C card',
'Front of Medicare Part D card',
'Back of Medicare Part D card',
'Front of health insurance card',
'Back of health insurance card',
'Other document',
'School enrollment certification form',
'Enrollment letter',
'Letter from the SSA'
].map { |option| { 'value' => option, 'label' => option } }.freeze

IVC_CHAMPVA_DOCUMENT_TYPE_OPTIONS_BY_FORM_ID = {
'10-10D-EXTENDED' => IVC_CHAMPVA_10_10D_EXTENDED_DOCUMENT_TYPE_OPTIONS
}.freeze

IVC_CHAMPVA_ACCEPTED_FILE_TYPES = %w[pdf jpg jpeg png].freeze

def index
claims = if Flipper.enabled?(FEATURE_MULTI_CLAIM_PROVIDER, @current_user)
get_claims_from_providers
else
service.get_claims
end
champva_enhanced_flow_enabled = Flipper.enabled?(:form1010d_enhanced_flow_enabled, @current_user)

check_for_birls_id
check_for_file_number

claims['data'].each do |claim|
update_claim_type_language(claim)
add_upload_metadata(claim, champva_enhanced_flow_enabled:)
end

claim_ids = claims['data'].map { |claim| claim['id'] }
Expand All @@ -63,7 +108,9 @@ def show
# Legacy single-provider path: Apply Lighthouse-specific transforms here
get_legacy_claim(params[:id])
end
champva_enhanced_flow_enabled = Flipper.enabled?(:form1010d_enhanced_flow_enabled, @current_user)
update_claim_type_language(claim['data'])
add_upload_metadata(claim['data'], champva_enhanced_flow_enabled:)

# Document uploads to EVSS require a birls_id; This restriction should
# be removed when we move to Lighthouse Benefits Documents for document uploads
Expand Down Expand Up @@ -180,6 +227,37 @@ def update_claim_type_language(claim)
end
end

def add_upload_metadata(claim, champva_enhanced_flow_enabled: false)
metadata = build_upload_metadata_for_claim(claim, champva_enhanced_flow_enabled:)
return if metadata.blank?

claim['attributes'] ||= {}
claim['attributes']['uploadMetadata'] = metadata
end

def build_upload_metadata_for_claim(claim, champva_enhanced_flow_enabled: false)
claim_attributes = claim['attributes'] || {}
provider = claim_attributes['provider'].presence
destination_key = UPLOAD_DESTINATION_KEY_BY_PROVIDER.fetch(provider, DEFAULT_UPLOAD_DESTINATION_KEY)

metadata = { 'uploadDestinationKey' => destination_key }

if destination_key == IVC_CHAMPVA_UPLOAD_DESTINATION_KEY
form_id = IVC_CHAMPVA_FORM_ID_BY_CLAIM_TYPE[claim_attributes['claimType']]
metadata['formId'] = form_id if form_id.present?
metadata['acceptedFileTypes'] = IVC_CHAMPVA_ACCEPTED_FILE_TYPES
if form_id == '10-10D-EXTENDED' && champva_enhanced_flow_enabled
metadata['finalizeDestinationKey'] = IVC_CHAMPVA_FINALIZE_DESTINATION_KEY
metadata['submissionType'] = 'existing'
end

document_type_options = IVC_CHAMPVA_DOCUMENT_TYPE_OPTIONS_BY_FORM_ID[form_id]
metadata['documentTypeOptions'] = document_type_options if document_type_options.present?
end

metadata
end

def add_evidence_submissions(claim, evidence_submissions)
non_duplicate_submissions = filter_duplicate_evidence_submissions(evidence_submissions, claim)
tracked_items = claim['attributes']['trackedItems']
Expand Down Expand Up @@ -328,7 +406,10 @@ def report_evidence_submission_metrics(endpoint, evidence_submissions)
end

def fetch_evidence_submissions(claim_ids, endpoint)
EvidenceSubmission.where(claim_id: claim_ids)
query_ids = resolve_evidence_submission_claim_ids(claim_ids)
return EvidenceSubmission.none if query_ids.empty?

EvidenceSubmission.where(claim_id: query_ids)
rescue => e
::Rails.logger.error(
"BenefitsClaimsController##{endpoint} Error fetching evidence submissions",
Expand All @@ -342,6 +423,17 @@ def fetch_evidence_submissions(claim_ids, endpoint)
EvidenceSubmission.none
end

def resolve_evidence_submission_claim_ids(claim_ids)
identifiers = Array(claim_ids).compact.map(&:to_s)
return [] if identifiers.empty?

numeric_ids = identifiers.grep(/\A\d+\z/).map(&:to_i)
uuid_ids = identifiers.grep_v(/\A\d+\z/)
numeric_ids += IvcChampvaForm.where(form_uuid: uuid_ids).pluck(:id) if uuid_ids.any?

numeric_ids.uniq
end

def update_evidence_submissions_for_claim(claim_id, evidence_submissions)
# Get pending evidence submissions as an ActiveRecord relation
# PENDING = successfully sent to Lighthouse with request_id, awaiting final status
Expand Down Expand Up @@ -441,30 +533,60 @@ def handle_error(claim_id, response, lighthouse_document_request_ids, error_sour
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

return if claims.empty?

# Group evidence submissions by claim_id for efficient lookup
evidence_submissions_by_claim = all_evidence_submissions.group_by(&:claim_id)
evidence_submissions_by_claim_id = all_evidence_submissions.group_by(&:claim_id)
ivc_form_ids_by_uuid = {}

# Add evidence submissions to each claim
claims.each do |claim|
claim_id = claim['id'].to_i
evidence_submissions = evidence_submissions_by_claim[claim_id] || []
assign_evidence_submissions_to_claims(
claims,
evidence_submissions_by_claim_id,
ivc_form_ids_by_uuid
)
rescue ArgumentError
ensure_claims_have_evidence_submissions(claims)
rescue => e
log_add_evidence_submissions_error(claims, endpoint, e)
end

def assign_evidence_submissions_to_claims(claims, evidence_submissions_by_claim_id, ivc_form_ids_by_uuid)
claims.each do |claim|
evidence_submissions = evidence_submissions_for_claim(
claim,
evidence_submissions_by_claim_id,
ivc_form_ids_by_uuid
)
claim['attributes']['evidenceSubmissions'] =
add_evidence_submissions(claim, evidence_submissions)
end
rescue => e
# Log error but don't fail the request - graceful degradation
# Frontend already handles missing evidenceSubmissions attribute
end

def ensure_claims_have_evidence_submissions(claims)
claims.each do |claim|
claim['attributes']['evidenceSubmissions'] ||= []
end
end

def log_add_evidence_submissions_error(claims, endpoint, error)
claim_ids = claims.map { |claim| claim['id'] }
::Rails.logger.error(
"BenefitsClaimsController##{endpoint} Error adding evidence submissions",
{
claim_ids:,
error_class: e.class.name
}
{ claim_ids:, error_class: error.class.name }
)
end

def evidence_submissions_for_claim(claim, evidence_submissions_by_claim_id, ivc_form_ids_by_uuid)
provider = claim.dig('attributes', 'provider')
claim_id = claim['id'].to_s
return non_champva_evidence_submissions(claim_id, evidence_submissions_by_claim_id) if provider != 'ivc_champva'

ivc_form_ids_by_uuid[claim_id] ||= IvcChampvaForm.where(form_uuid: claim_id).pluck(:id)
ivc_form_ids_by_uuid[claim_id].flat_map { |form_id| evidence_submissions_by_claim_id[form_id] || [] }
end

def non_champva_evidence_submissions(claim_id, evidence_submissions_by_claim_id)
numeric_claim_id = Integer(claim_id, 10)
evidence_submissions_by_claim_id[numeric_claim_id] || []
end

def recently_polled_request_ids?(claim_id, request_ids)
cache_record = EvidenceSubmissionPollStore.find(claim_id.to_s)
return false if cache_record.nil?
Expand Down Expand Up @@ -500,4 +622,5 @@ def cache_polled_request_ids(claim_id, request_ids)
)
end
end
# rubocop:enable Metrics/ClassLength
end
25 changes: 25 additions & 0 deletions app/swagger/swagger/schemas/benefits_claims.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ class BenefitsClaims
key :description, 'Base claim type used for title generation'
key :example, 'Compensation'
end
property :uploadMetadata do
key :type, :object
key :description, 'Upload routing metadata used by clients to choose upload destination and payload'
property :uploadDestinationKey, type: :string, example: 'benefits_claims'
property :formId, type: %i[string null], example: '10-10D-EXTENDED'
property :finalizeDestinationKey, type: %i[string null], example: 'ivc_champva_docs_only_resubmission'
property :submissionType, type: %i[string null], example: 'existing'
property :acceptedFileTypes do
key :type, :array
key :description, 'Optional list of accepted file extensions for this upload destination'
items do
key :type, :string
key :example, 'pdf'
end
end
property :documentTypeOptions do
key :type, :array
key :description, 'Optional list of provider-specific document type choices for uploader dropdowns'
items do
key :type, :object
property :value, type: :string, example: 'Birth certificate'
property :label, type: :string, example: 'Birth certificate'
end
end
end

property :supportingDocuments do
key :type, :array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"emptyState": "You don't need to do anything right now. If there's an update, we'll mail you a letter."
},
"files": {
"simpleLayout": true,
"simpleLayout": false,
"headerTitle": "Application files",
"description": "If you need to add or update your supporting documents, you can submit them online, by mail, or by fax. This could be your personal information, health insurance, or school status.",
"sectionTitle": "Application files",
Expand Down
3 changes: 3 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ features:
champva_convert_to_pdf_on_upload:
actor_type: user
description: Converts supporting documents to PDF at upload time instead of final submission to improve submit latency
form1010d_enhanced_flow_enabled:
actor_type: user
description: Enables enhanced docs-only resubmission flow for CHAMPVA 10-10D-EXTENDED submissions
champva_stamper_logging:
actor_type: user
description: Enables logging of the desired stamp text
Expand Down
12 changes: 11 additions & 1 deletion lib/benefits_claims/providers/ivc_champva/claim_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module ClaimBuilder
'vha_10_10d_2027' => 'CHAMPVA application',
'10-10d' => 'CHAMPVA application',
'10-10d-extended' => 'CHAMPVA application',
'10-10d-extended-existing' => 'CHAMPVA application',
'10-10d-extended-enrollment' => 'CHAMPVA application',
'10-7959c' => 'Other Health Insurance',
'10-7959f-1' => 'Foreign Medical Program registration',
'10-7959f-2' => 'Foreign Medical Program claim',
Expand All @@ -23,6 +25,7 @@ module ClaimBuilder

PROCESSED_STATUSES = ['processed', 'manually processed'].freeze
ERROR_STATUSES = ['error', 'failed', 'rejected', 'submission failed'].freeze
INTERNAL_DOCS_ONLY_1010D_FILE_NAME_PATTERN = /_vha_10_10d(?:_supporting_doc-\d+)?\.pdf\z/i

def self.build_claim_response(records, user = nil)
records = Array(records)
Expand Down Expand Up @@ -79,7 +82,7 @@ def self.claim_phase_dates_for(representative, status)
end

def self.build_supporting_documents(records)
records.map do |record|
records.reject { |record| internal_docs_only_artifact?(record) }.map do |record|
BenefitsClaims::Responses::SupportingDocument.new(
document_id: record.id.to_s,
document_type_label: nil,
Expand All @@ -90,6 +93,13 @@ def self.build_supporting_documents(records)
end
end

def self.internal_docs_only_artifact?(record)
form_number = record.form_number.to_s
file_name = record.file_name.to_s

form_number.start_with?('10-10D-EXTENDED-') && file_name.match?(INTERNAL_DOCS_ONLY_1010D_FILE_NAME_PATTERN)
end

def self.format_date(value)
value&.to_date&.iso8601
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ module SubmissionStatuses
module Formatters
class IvcChampvaFormatter < BaseFormatter
FORM_TYPE_MAP = {
'10-10d-extended' => '10-10D'
'10-10d-extended' => '10-10D',
'10-10d-extended-existing' => '10-10D',
'10-10d-extended-enrollment' => '10-10D'
}.freeze
DOCS_ONLY_FORM_NUMBER_PATTERN = /\A10-10d-extended-(existing|enrollment)\z/i

STATUS_MAP = {
# PEGA statuses
Expand Down Expand Up @@ -43,7 +46,9 @@ def merge_record(_submission_map, _status)
end

def build_submissions_map(submissions)
submissions.each_with_object({}) do |submission, hash|
filtered_submissions = submissions.reject { |submission| docs_only_submission?(submission) }

filtered_submissions.each_with_object({}) do |submission, hash|
hash[submission.form_uuid.to_s] = OpenStruct.new(
id: submission.form_uuid.to_s,
detail: submission.case_id,
Expand All @@ -57,6 +62,10 @@ def build_submissions_map(submissions)
end
end

def docs_only_submission?(submission)
submission.form_number.to_s.match?(DOCS_ONLY_FORM_NUMBER_PATTERN)
end

def normalize_status(submission)
[submission.pega_status, submission.ves_status, submission.s3_status].each do |raw_status|
next if raw_status.blank?
Expand Down
Loading
Loading