Class: SavedClaim::CoeClaim

Inherits:
SavedClaim show all
Defined in:
app/models/saved_claim/coe_claim.rb

Constant Summary collapse

FORM =
'26-1880'

Instance Method Summary collapse

Methods inherited from SavedClaim

add_form_and_validation, #after_create_metrics, #after_destroy_metrics, #attachment_keys, #business_line, #confirmation_number, #email, #form_is_string, #form_matches_schema, #form_must_be_string, #insert_notification, #open_struct_form, #parsed_form, #submitted_at, #to_pdf, #update_form, #va_notification?, #validate_form, #validate_schema

Methods inherited from ApplicationRecord

descendants_using_encryption, lockbox_options, #timestamp_attributes_for_update_in_model, #valid?

Instance Method Details

#lgy_serviceObject (private)

rubocop:enable Metrics/MethodLength



85
86
87
# File 'app/models/saved_claim/coe_claim.rb', line 85

def lgy_service
  @lgy_service ||= LGY::Service.new(edipi: @edipi, icn: @icn)
end

#periods_of_service(form_copy) ⇒ Object (private)

rubocop:enable Metrics/MethodLength



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'app/models/saved_claim/coe_claim.rb', line 128

def periods_of_service(form_copy)
  parsed_form['periodsOfService'].each do |service_info|
    # values from the FE for military_branch are:
    # ["Air Force", "Air Force Reserve", "Air National Guard", "Army", "Army National Guard", "Army Reserve",
    # "Coast Guard", "Coast Guard Reserve", "Marine Corps", "Marine Corps Reserve", "Navy", "Navy Reserve"]
    # these need to be formatted because LGY only accepts [ARMY, NAVY, MARINES, AIR_FORCE, COAST_GUARD, OTHER]
    # and then we have to pass in ACTIVE_DUTY or RESERVE_NATIONAL_GUARD for service_type
    military_branch = service_info['serviceBranch'].parameterize.underscore.upcase
    service_type = 'ACTIVE_DUTY'

    # "Marine Corps" must be converted to "Marines" here, so that the `.any`
    # block below can convert "Marine Corps" and "Marine Corps Reserve" to
    # "MARINES", to meet LGY's requirements.
    military_branch = military_branch.gsub('MARINE_CORPS', 'MARINES')

    %w[RESERVE NATIONAL_GUARD].any? do |service_branch|
      next unless military_branch.include?(service_branch)

      index = military_branch.index('_NATIONAL_GUARD') || military_branch.index('_RESERVE')
      military_branch = military_branch[0, index]
      # "Air National Guard", unlike "Air Force Reserve", needs to be manually
      # transformed to AIR_FORCE here, to meet LGY's requirements.
      military_branch = 'AIR_FORCE' if military_branch == 'AIR'
      service_type = 'RESERVE_NATIONAL_GUARD'
    end

    form_copy['periodsOfService'] << {
      'enteredOnDuty' => service_info['dateRange']['from'],
      'releasedActiveDuty' => service_info['dateRange']['to'],
      'militaryBranch' => military_branch,
      'serviceType' => service_type,
      'disabilityIndicator' => false
    }
  end
end

#prepare_document_dataObject (private)



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/saved_claim/coe_claim.rb', line 174

def prepare_document_data
  persistent_attachments.each do |attachment|
    file_extension = File.extname(URI.parse(attachment.file.url).path)
    claim_file_data =
      parsed_form['files'].find { |f| f['confirmationCode'] == attachment['guid'] } ||
      { 'attachmentType' => '', 'attachmentDescription' => '' }

    if %w[.jpg .jpeg .png .pdf].include? file_extension.downcase
      file_path = Common::FileHelpers.generate_clamav_temp_file(attachment.file.read)

      File.rename(file_path, "#{file_path}#{file_extension}")
      file_path = "#{file_path}#{file_extension}"

      document_data = {
        # This is one of the options in the "Document Type" dropdown on the
        # "Your supporting documents" step of the COE form. E.g. "Discharge or
        # separation papers (DD214)"
        'documentType' => claim_file_data['attachmentType'],
        # This is the vet's own description of a document, after selecting
        # "other" as the `attachmentType`.
        'description' => claim_file_data['attachmentDescription'],
        'contentsBase64' => Base64.encode64(File.read(file_path)),
        'fileName' => attachment.file.['filename']
      }

      lgy_service.post_document(payload: document_data)
      Common::FileHelpers.delete_file_if_exists(file_path)
    end
  end
end

#prepare_form_dataObject (private)

rubocop:disable Metrics/MethodLength



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'app/models/saved_claim/coe_claim.rb', line 44

def prepare_form_data
  postal_code, postal_code_suffix = parsed_form['applicantAddress']['postalCode'].split('-', 2)
  form_copy = {
    'status' => 'SUBMITTED',
    'veteran' => {
      'firstName' => parsed_form['fullName']['first'],
      'middleName' => parsed_form['fullName']['middle'] || '',
      'lastName' => parsed_form['fullName']['last'],
      'suffixName' => parsed_form['fullName']['suffix'] || '',
      'dateOfBirth' => parsed_form['dateOfBirth'],
      'vetAddress1' => parsed_form['applicantAddress']['street'],
      'vetAddress2' => parsed_form['applicantAddress']['street2'] || '',
      'vetCity' => parsed_form['applicantAddress']['city'],
      'vetState' => parsed_form['applicantAddress']['state'],
      'vetZip' => postal_code,
      'vetZipSuffix' => postal_code_suffix,
      'mailingAddress1' => parsed_form['applicantAddress']['street'],
      'mailingAddress2' => parsed_form['applicantAddress']['street2'] || '',
      'mailingCity' => parsed_form['applicantAddress']['city'],
      'mailingState' => parsed_form['applicantAddress']['state'],
      'mailingZip' => postal_code,
      'mailingZipSuffix' => postal_code_suffix || '',
      'contactPhone' => parsed_form['contactPhone'],
      'contactEmail' => parsed_form['contactEmail'],
      'vaLoanIndicator' => parsed_form['vaLoanIndicator'],
      'vaHomeOwnIndicator' => (parsed_form['relevantPriorLoans'] || []).any? { |obj| obj['propertyOwned'] },
      # parsed_form['identity'] can be: 'VETERAN', 'ADSM', 'NADNA', 'DNANA', or 'DRNA'.
      'activeDutyIndicator' => parsed_form['identity'] == 'ADSM',
      'disabilityIndicator' => false
    },
    'relevantPriorLoans' => [],
    'periodsOfService' => []
  }
  relevant_prior_loans(form_copy) if parsed_form.key?('relevantPriorLoans')
  periods_of_service(form_copy) if parsed_form.key?('periodsOfService')

  update(form: form_copy.to_json)
  form_copy
end

#process_attachments!Object (private)



164
165
166
167
168
169
170
171
172
# File 'app/models/saved_claim/coe_claim.rb', line 164

def process_attachments!
  supporting_documents = parsed_form['files']
  if supporting_documents.present?
    files = PersistentAttachment.where(guid: supporting_documents.map { |doc| doc['confirmationCode'] })
    files.find_each { |f| f.update(saved_claim_id: id) }

    prepare_document_data
  end
end

#regional_officeObject



37
38
39
# File 'app/models/saved_claim/coe_claim.rb', line 37

def regional_office
  []
end

#relevant_prior_loans(form_copy) ⇒ Object (private)

rubocop:disable Metrics/MethodLength



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/models/saved_claim/coe_claim.rb', line 90

def relevant_prior_loans(form_copy)
  parsed_form['relevantPriorLoans'].each do |loan_info|
    property_zip, property_zip_suffix = loan_info['propertyAddress']['propertyZip'].split('-', 2)
    form_copy['relevantPriorLoans'] << {
      'vaLoanNumber' => loan_info['vaLoanNumber'].to_s,
      'startDate' => loan_info['dateRange']['from'],
      'paidOffDate' => loan_info['dateRange']['to'],
      'loanAmount' => loan_info['loanAmount'],
      'loanEntitlementCharged' => loan_info['loanEntitlementCharged'],
      # propertyOwned also maps to the the stillOwn indicator on the LGY side
      'propertyOwned' => loan_info['propertyOwned'] || false,
      # In UI: "A one-time restoration of entitlement"
      # In LGY: "One Time Resto"
      'oneTimeRestorationRequested' => loan_info['intent'] == 'ONETIMERESTORATION',
      # In UI: "An Interest Rate Reduction Refinance Loan (IRRRL) to refinance the balance of a current VA home loan"
      # In LGY: "IRRRL Ind"
      'irrrlRequested' => loan_info['intent'] == 'IRRRL',
      # In UI: "A regular cash-out refinance of a current VA home loan"
      # In LGY: "Cash Out Refi"
      'cashoutRefinaceRequested' => loan_info['intent'] == 'REFI',
      # In UI: "An entitlement inquiry only"
      # In LGY: "Entitlement Inquiry Only"
      'noRestorationEntitlementIndicator' => loan_info['intent'] == 'INQUIRY',
      # LGY has requested `homeSellIndicator` always be null
      'homeSellIndicator' => nil,
      'propertyAddress1' => loan_info['propertyAddress']['propertyAddress1'],
      'propertyAddress2' => loan_info['propertyAddress']['propertyAddress2'] || '',
      'propertyCity' => loan_info['propertyAddress']['propertyCity'],
      'propertyState' => loan_info['propertyAddress']['propertyState'],
      # confirmed OK to omit propertyCounty, but LGY still requires a string
      'propertyCounty' => '',
      'propertyZip' => property_zip,
      'propertyZipSuffix' => property_zip_suffix || ''
    }
  end
end

#send_to_lgy(edipi:, icn:) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/models/saved_claim/coe_claim.rb', line 6

def send_to_lgy(edipi:, icn:)
  @edipi = edipi
  @icn = icn

  # If the EDIPI is blank, throw an error
  if @edipi.blank?
    Rails.logger.error('COE application cannot be submitted without an edipi!')
  # Otherwise, submit the claim to the LGY API
  else
    Rails.logger.info('Begin COE claim submission to LGY API', guid:)
    response = lgy_service.put_application(payload: prepare_form_data)
    Rails.logger.info('COE claim submitted to LGY API', guid:)

    process_attachments!
    response['reference_number']
  end
rescue Common::Client::Errors::ClientError => e
  # 502-503 errors happen frequently from LGY endpoint at the time of implementation
  # and have not been corrected yet. We would like to seperate these from our monitoring for now
  # See https://github.com/department-of-veterans-affairs/va.gov-team/issues/90411
  # and https://github.com/department-of-veterans-affairs/va.gov-team/issues/91111
  if [503, 504].include?(e.status)
    Rails.logger.info('LGY server unavailable or unresponsive',
                      { status: e.status, messsage: e.message, body: e.body })
  else
    Rails.logger.error('LGY API returned error', { status: e.status, messsage: e.message, body: e.body })
  end

  raise e
end