Skip to content

Commit

Permalink
Merge branch 'master' of github.com:department-of-veterans-affairs/ve…
Browse files Browse the repository at this point in the history
…ts-api into ndbex/69209-ch36-to-benefits-intake
  • Loading branch information
micahaspyr committed Apr 16, 2024
2 parents 1c15c40 + 251a5d6 commit 2f9c9b0
Show file tree
Hide file tree
Showing 155 changed files with 4,939 additions and 1,750 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,7 @@ spec/fixtures/pdf_fill/21-674 @department-of-veterans-affairs/Benefits-Team-1 @d
spec/fixtures/pdf_fill/21-8940 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/21P-527EZ @department-of-veterans-affairs/pensions @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/21P-530 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/21P-530V2 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/26-1880 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/28-1900 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/fixtures/pdf_fill/28-8832 @department-of-veterans-affairs/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ GEM
multipart-post (~> 2)
faraday-net_http (3.1.0)
net-http
faraday-retry (2.2.0)
faraday-retry (2.2.1)
faraday (~> 2.0)
faraday-typhoeus (1.1.0)
faraday (~> 2.0)
Expand Down Expand Up @@ -890,7 +890,7 @@ GEM
rswag-ui (2.13.0)
actionpack (>= 3.1, < 7.2)
railties (>= 3.1, < 7.2)
rubocop (1.63.1)
rubocop (1.63.2)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
Expand Down
4 changes: 4 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,10 @@ features:
actor_type: user
description: Enables sleep apnea supplies to be ordered in the supply reorder tool / MDOT.
enable_in_development: true
toe_dup_contact_info_call:
actor_type: user
description: Flag to use contact info call and modal
enable_in_development: true
toe_short_circuit_bgs_failure:
actor_type: user
description: Flag to use begin rescue block for BGS call
Expand Down
40 changes: 40 additions & 0 deletions db/migrate/20240411153910_create_accredited_individuals.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

class CreateAccreditedIndividuals < ActiveRecord::Migration[7.1]
def change
create_table :accredited_individuals, id: :uuid do |t|
t.uuid :ogc_id, null: false
t.string :registration_number, null: false
t.string :poa_code, limit: 3, index: true
t.string :individual_type, null: false
t.string :first_name
t.string :middle_initial
t.string :last_name
t.string :full_name, index: true
t.string :email
t.string :phone
t.string :address_type
t.string :address_line1
t.string :address_line2
t.string :address_line3
t.string :city
t.string :country_code_iso3
t.string :country_name
t.string :county_name
t.string :county_code
t.string :international_postal_code
t.string :province
t.string :state_code
t.string :zip_code
t.string :zip_suffix
t.jsonb :raw_address
t.float :lat
t.float :long
t.geography :location, limit: { srid: 4326, type: 'st_point', geographic: true }
t.timestamps

t.index :location, using: :gist
t.index %i[ registration_number individual_type ], name: 'index_on_reg_num_and_type_for_accredited_individuals', unique: true
end
end
end
37 changes: 37 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 132 additions & 0 deletions lib/lighthouse/benefits_intake/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

module BenefitsIntake
##
# Validate the required metadata which must accompany an upload:
#
# {
# 'veteranFirstName': String,
# 'veteranLastName': String,
# 'fileNumber': String, # 8-9 digits
# 'zipCode': String, # 5 or 9 digits
# 'source': String,
# 'docType': String,
# 'businessLine': String, # optional; enum in BUSINESS_LINE
# }
#
# https://developer.va.gov/explore/api/benefits-intake/docs
#
class Metadata
BUSINESS_LINE = {
CMP: 'Compensation requests such as those related to disability, unemployment, and pandemic claims',
PMC: 'Pension requests including survivor’s pension',
INS: 'Insurance such as life insurance, disability insurance, and other health insurance',
EDU: 'Education benefits, programs, and affiliations',
VRE: 'Veteran Readiness & Employment such as employment questionnaires, ' \
'employment discrimination, employment verification',
BVA: 'Board of Veteran Appeals',
FID: 'Fiduciary / financial appointee, including family member benefits',
NCA: 'National Cemetery Administration',
OTH: 'Other (this value if used, will be treated as CMP)'
}.freeze

# rubocop:disable Metrics/ParameterLists
def self.generate(first_name, last_name, file_number, zip_code, source, doc_type, business_line = nil)
validate({
'veteranFirstName' => first_name,
'veteranLastName' => last_name,
'fileNumber' => file_number,
'zipCode' => zip_code,
'source' => source,
'docType' => doc_type,
'businessLine' => business_line
})
end
# rubocop:enable Metrics/ParameterLists

def self.validate(metadata)
validate_first_name(metadata)
.then { |m| validate_last_name(m) }
.then { |m| validate_file_number(m) }
.then { |m| validate_zip_code(m) }
.then { |m| validate_source(m) }
.then { |m| validate_doc_type(m) }
.then { |m| validate_business_line(m) }
end

def self.validate_first_name(metadata)
validate_presence_and_stringiness(metadata['veteranFirstName'], 'veteran first name')

first_name = I18n.transliterate(metadata['veteranFirstName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50)
validate_nonblank(first_name, 'veteran first name')

metadata['veteranFirstName'] = first_name
metadata
end

def self.validate_last_name(metadata)
validate_presence_and_stringiness(metadata['veteranLastName'], 'veteran last name')

last_name = I18n.transliterate(metadata['veteranLastName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50)
validate_nonblank(last_name, 'veteran last name')

metadata['veteranLastName'] = last_name
metadata
end

def self.validate_file_number(metadata)
validate_presence_and_stringiness(metadata['fileNumber'], 'file number')
unless metadata['fileNumber'].match?(/^\d{8,9}$/)
raise ArgumentError, 'file number is invalid. It must be 8 or 9 digits'
end

metadata
end

def self.validate_zip_code(metadata)
validate_presence_and_stringiness(metadata['zipCode'], 'zip code')

zip_code = metadata['zipCode'].dup.gsub(/[^0-9]/, '')
zip_code.insert(5, '-') if zip_code.match?(/\A[0-9]{9}\z/)
zip_code = '00000' unless zip_code.match?(/\A[0-9]{5}(-[0-9]{4})?\z/)

metadata['zipCode'] = zip_code

metadata
end

def self.validate_source(metadata)
validate_presence_and_stringiness(metadata['source'], 'source')

metadata
end

def self.validate_doc_type(metadata)
validate_presence_and_stringiness(metadata['docType'], 'doc type')

metadata
end

def self.validate_business_line(metadata)
bl = metadata['businessLine']
if bl
bl = bl.dup.to_s.upcase.to_sym
bl = :OTH unless BUSINESS_LINE.key?(bl)
metadata['businessLine'] = bl.to_s
else
metadata.delete('businessLine')
end

metadata
end

def self.validate_presence_and_stringiness(value, error_label)
raise ArgumentError, "#{error_label} is missing" unless value
raise ArgumentError, "#{error_label} is not a string" if value.class != String
end

def self.validate_nonblank(value, error_label)
raise ArgumentError, "#{error_label} is blank" if value.blank?
end
end
end
Binary file modified lib/pdf_fill/forms/pdfs/21P-530V2.pdf
Binary file not shown.
2 changes: 0 additions & 2 deletions lib/pdf_fill/forms/va21p530v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -725,12 +725,10 @@ def merge_fields(_options = {})
# special case: these fields were built as checkboxes instead of radios, so usual radio logic can't be used.
burial_expense_responsibility = @form_data['burialExpenseResponsibility']
@form_data['hasBurialExpenseResponsibility'] = burial_expense_responsibility ? 'On' : nil
@form_data['noBurialExpenseResponsibility'] = burial_expense_responsibility ? nil : 'On'

# special case: these fields were built as checkboxes instead of radios, so usual radio logic can't be used.
plot_expense_responsibility = @form_data['plotExpenseResponsibility']
@form_data['hasPlotExpenseResponsibility'] = plot_expense_responsibility ? 'On' : nil
@form_data['noPlotExpenseResponsibility'] = plot_expense_responsibility ? nil : 'On'

# special case: these fields were built as checkboxes instead of radios, so usual radio logic can't be used.
process_option = @form_data['processOption']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

module CheckIn
module VAOS
class AppointmentsService < CheckIn::VAOS::BaseService
class AppointmentService < CheckIn::VAOS::BaseService
def get_appointments(start_date, end_date, statuses = nil)
params = date_params(start_date, end_date)
.merge(status_params(statuses))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def connection
# conn.request(:curl, ::Logger.new(STDOUT), :warn) unless Rails.env.production?
# conn.response(:logger, ::Logger.new(STDOUT), bodies: true) unless Rails.env.production?

conn.response :raise_error, error_prefix: service_name
conn.response :betamocks if mock_enabled?
# conn.response :snakecase
conn.response :json, content_type: /\bjson$/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require 'rails_helper'

describe CheckIn::VAOS::AppointmentService do
subject { described_class.new(patient_icn:) }

let(:patient_icn) { '123' }
let(:token) { 'test_token' }
let(:request_id) { SecureRandom.uuid }

describe '#initialize' do
it 'returns an instance of service' do
service_obj = subject
expect(service_obj).to be_an_instance_of(CheckIn::VAOS::AppointmentService)
expect(service_obj.token_service).to be_an_instance_of(CheckIn::Map::TokenService)
end
end

describe '#perform' do
let(:token) { 'test-token-123' }
let(:start_date) { '2023-11-10T17:12:30Z' }
let(:end_date) { '2023-12-12T17:12:30Z' }
let(:statuses) { 'confirmed' }
let(:appointments_response) do
{
data: [
{
id: '180765',
kind: 'clinic',
status: 'booked',
patientIcn: 'icn',
locationId: '983GC',
clinic: '1081',
start: '2023-11-02T17:12:30.174Z',
end: '2023-12-12T17:12:30.174Z',
minutesDuration: 30
}
]
}.with_indifferent_access
end
let(:faraday_response) { double('Faraday::Response') }
let(:faraday_env) { double('Faraday::Env', status: 200, body: appointments_response.to_json) }

context 'when vaos returns successful response' do
before do
allow_any_instance_of(CheckIn::Map::TokenService).to receive(:token)
.and_return(token)
allow_any_instance_of(Faraday::Connection).to receive(:get).with('/vaos/v1/patients/123/appointments',
{ start: start_date, end: end_date,
statuses: })
.and_return(faraday_response)
allow(faraday_response).to receive(:env).and_return(faraday_env)
end

it 'returns appointments' do
response = subject.get_appointments(DateTime.parse(start_date).in_time_zone,
DateTime.parse(end_date).in_time_zone,
statuses)
expect(response).to eq(appointments_response)
end
end

context 'when vaos returns server error' do
let(:resp) { Faraday::Response.new(body: { error: 'Internal server error' }, status: 500) }
let(:exception) { Common::Exceptions::BackendServiceException.new(nil, {}, resp.status, resp.body) }

before do
allow_any_instance_of(CheckIn::Map::TokenService).to receive(:token)
.and_return(token)
allow_any_instance_of(Faraday::Connection).to receive(:get).with('/vaos/v1/patients/123/appointments',
{ start: start_date, end: end_date,
statuses: })
.and_raise(exception)
end

it 'throws exception' do
expect do
subject.get_appointments(DateTime.parse(start_date).in_time_zone,
DateTime.parse(end_date).in_time_zone,
statuses)
end.to(raise_error do |error|
expect(error).to be_a(Common::Exceptions::BackendServiceException)
end)
end
end
end
end
Loading

0 comments on commit 2f9c9b0

Please sign in to comment.