diff --git a/Gemfile b/Gemfile index 056e8a031aa..3d0de296b6e 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ path 'modules' do gem 'facilities_api' gem 'health_quest' gem 'income_limits' + gem 'ivc_champva' gem 'meb_api' gem 'mobile' gem 'mocked_authentication' diff --git a/Gemfile.lock b/Gemfile.lock index 508023b6094..2a2b6c08ca1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -104,6 +104,7 @@ PATH facilities_api (0.1.0) health_quest (0.1.0) income_limits (0.1.0) + ivc_champva (0.1.0) meb_api (0.1.0) mobile (0.1.0) dry-validation @@ -1157,6 +1158,7 @@ DEPENDENCIES ice_nine income_limits! iso_country_codes + ivc_champva! json json-schema json_schemer diff --git a/config/routes.rb b/config/routes.rb index 79870956adb..f4bd4aea31d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -463,6 +463,7 @@ mount DebtsApi::Engine, at: '/debts_api' mount DhpConnectedDevices::Engine, at: '/dhp_connected_devices' mount FacilitiesApi::Engine, at: '/facilities_api' + mount IvcChampva::Engine, at: '/ivc_champva' mount RepresentationManagement::Engine, at: '/representation_management' mount SimpleFormsApi::Engine, at: '/simple_forms_api' mount HealthQuest::Engine, at: '/health_quest' diff --git a/modules/ivc_champva/Gemfile b/modules/ivc_champva/Gemfile new file mode 100644 index 00000000000..d9266b3d7af --- /dev/null +++ b/modules/ivc_champva/Gemfile @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# Declare your gem's dependencies in ivcchampva.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use a debugger +# gem 'byebug', group: [:development, :test] diff --git a/modules/ivc_champva/README.rdoc b/modules/ivc_champva/README.rdoc new file mode 100644 index 00000000000..189a7a8e2e4 --- /dev/null +++ b/modules/ivc_champva/README.rdoc @@ -0,0 +1,19 @@ += IvcChampva +This module allows you to generate form_mappings based on a PDF file. +With this in place, you can submit a form payload from the vets-website +and have this module map that payload to the associated PDF and submit it +to PEGA via S3. + +To generate files: +rails ivc_champva:generate\['path to PDF file'\] + +Submission endpoint: +/ivc_champva/v1/forms + +== Installation +Ensure the following line is in the root project's Gemfile: + + gem 'ivcchampva', path: 'modules/ivcchampva' + +== License +This module is open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). \ No newline at end of file diff --git a/modules/ivc_champva/Rakefile b/modules/ivc_champva/Rakefile new file mode 100644 index 00000000000..887d143b182 --- /dev/null +++ b/modules/ivc_champva/Rakefile @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'IvcChampva' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.md') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +load 'rails/tasks/statistics.rake' + +require 'bundler/gem_tasks' + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + +task default: :test diff --git a/modules/ivc_champva/app/controllers/ivc_champva/v1/application_controller.rb b/modules/ivc_champva/app/controllers/ivc_champva/v1/application_controller.rb new file mode 100644 index 00000000000..d7c1c0563b8 --- /dev/null +++ b/modules/ivc_champva/app/controllers/ivc_champva/v1/application_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module IvcChampva + module V1 + class ApplicationController < ::ApplicationController + service_tag 'veteran-ivc-champva-forms' + end + end +end diff --git a/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb new file mode 100644 index 00000000000..1b2b90bca66 --- /dev/null +++ b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'ddtrace' + +module IvcChampva + module V1 + class UploadsController < ApplicationController + skip_before_action :authenticate + before_action :authenticate, if: :should_authenticate + skip_after_action :set_csrf_header + + FORM_NUMBER_MAP = { + '10-10D' => 'vha_10_10d', + '10-7959F-1' => 'vha_10_7959f_1', + '10-7959F-2' => 'vha_10_7959f_2' + }.freeze + + UNAUTHENTICATED_FORMS = %w[].freeze + + def submit + Datadog::Tracing.active_trace&.set_tag('form_id', params[:form_number]) + form_id = get_form_id + parsed_form_data = JSON.parse(params.to_json) + file_paths, metadata = get_file_paths_and_metadata(parsed_form_data) + status, error_message = handle_uploads(form_id, metadata, file_paths) + + render json: { + error_message:, + status: + } + rescue + puts 'A default error occurred while uploading a document.' + end + + def submit_supporting_documents + if %w[10-10D].include?(params[:form_id]) + attachment = PersistentAttachments::MilitaryRecords.new(form_id: params[:form_id]) + attachment.file = params['file'] + raise Common::Exceptions::ValidationErrors, attachment unless attachment.valid? + + attachment.save + render json: attachment + end + end + + def authenticate + super + rescue Common::Exceptions::Unauthorized + Rails.logger.info( + 'IVC Champva - unauthenticated user submitting form', + { form_number: params[:form_number] } + ) + end + + private + + def get_file_paths_and_metadata(parsed_form_data) + form_id = get_form_id + form = "IvcChampva::#{form_id.titleize.gsub(' ', '')}".constantize.new(parsed_form_data) + filler = IvcChampva::PdfFiller.new(form_number: form_id, form:) + + file_path = if @current_user + filler.generate(@current_user.loa[:current]) + else + filler.generate + end + + metadata = IvcChampva::MetadataValidator.validate(form.metadata) + file_paths = form.handle_attachments(file_path) + + [file_paths, metadata] + end + + def handle_uploads(form_id, metadata, pdf_file_paths) + meta_file_name = "#{form_id}_metadata.json" + meta_file_path = "tmp/#{meta_file_name}" + + pdf_results = + pdf_file_paths.map do |pdf_file_path| + pdf_file_name = pdf_file_path.gsub('tmp/', '').gsub('-tmp', '') + upload_to_ivc_s3(pdf_file_name, pdf_file_path, metadata) + end + + all_pdf_success = pdf_results.all? { |(status, _)| status == 200 } + + if all_pdf_success + File.write(meta_file_path, metadata) + meta_upload_status, meta_upload_error_message = upload_to_ivc_s3(meta_file_name, meta_file_path) + + if meta_upload_status == 200 + FileUtils.rm_f(meta_file_path) + [meta_upload_status, nil] + else + [meta_upload_status, meta_upload_error_message] + end + else + [pdf_results] + end + end + + def upload_to_ivc_s3(file_name, file_path, metadata = {}) + case ivc_s3_client.put_object(file_name, file_path, metadata) + in { success: true } + [200] + in { success: false, error_message: error_message } + [400, error_message] + else + [500, 'Unexpected response from S3 upload'] + end + end + + def get_form_id + form_number = params[:form_number] + raise 'missing form_number in params' unless form_number + + FORM_NUMBER_MAP[form_number] + end + + def should_authenticate + true unless UNAUTHENTICATED_FORMS.include? params[:form_number] + end + + def ivc_s3_client + @ivc_s3_client ||= IvcChampva::S3.new( + region: Settings.ivc_forms.s3.region, + access_key_id: Settings.ivc_forms.s3.aws_access_key_id, + secret_access_key: Settings.ivc_forms.s3.aws_secret_access_key, + bucket_name: Settings.ivc_forms.s3.bucket + ) + end + end + end +end diff --git a/modules/ivc_champva/app/form_mappings/vha_10_10d.json.erb b/modules/ivc_champva/app/form_mappings/vha_10_10d.json.erb new file mode 100644 index 00000000000..9f3ecb0a88f --- /dev/null +++ b/modules/ivc_champva/app/form_mappings/vha_10_10d.json.erb @@ -0,0 +1,74 @@ +{ + "form1[0].#subform[0].VeteransLastName[0]": "<%= form.data.dig('veteran', 'full_name', 'last') %>", + "form1[0].#subform[0].VeteransFirstName[0]": "<%= form.data.dig('veteran', 'full_name', 'first') %>", + "form1[0].#subform[0].VeteransMI[0]": "<%= form.data.dig('veteran', 'full_name', 'middle') %>", + "form1[0].#subform[0].VeteransSSN[0]": "<%= form.data.dig('veteran', 'ssn_or_tin') %>", + "form1[0].#subform[0].VAFileNumber[0]": "<%= form.data.dig('veteran', 'va_claim_number') %>", + "form1[0].#subform[0].VeteransStreetAddress[0]": "<%= form.data.dig('veteran', 'address', 'street') %>", + "form1[0].#subform[0].VeteransState[0]": "<%= form.data.dig('veteran', 'address', 'state') %>", + "form1[0].#subform[0].VeteransCity[0]": "<%= form.data.dig('veteran', 'address', 'city') %>", + "form1[0].#subform[0].VeteransZipCode[0]": "<%= form.data.dig('veteran', 'address', 'postal_code') %>", + "form1[0].#subform[0].VeteransPhoneNumber[0]": "<%= form.data.dig('veteran', 'phone_number') %>", + "form1[0].#subform[0].VeteransDateOfBirth[0]": "<%= form.data.dig('veteran', 'date_of_birth') %>", + "form1[0].#subform[0].VeteransDateOfMarriage[0]": "<%= form.data.dig('veteran', 'date_of_marriage') %>", + "form1[0].#subform[0].IsTheVeteranDeceased[0]": "<%= form.data.dig('veteran', 'is_deceased') ? 1 : 0 %>", + "form1[0].#subform[0].VeteransDateOfDeath[0]": "<%= form.data.dig('veteran', 'date_of_death') %>", + "form1[0].#subform[0].DieOnActiveMilitaryService[0]": "<%= form.data.dig('veteran', 'is_active_service_death') ? 1 : 0 %>", + "form1[0].#subform[0].LastName1[0]": "<%= form.data['applicants'][0]&.dig('full_name', 'last') %>", + "form1[0].#subform[0].FirstName1[0]": "<%= form.data['applicants'][0]&.dig('full_name', 'first') %>", + "form1[0].#subform[0].MI1[0]": "<%= form.data['applicants'][0]&.dig('full_name', 'middle') %>", + "form1[0].#subform[0].SSN1[0]": "<%= form.data['applicants'][0]&.dig('ssn_or_tin') %>", + "form1[0].#subform[0].StreetAddress1[0]": "<%= form.data['applicants'][0]&.dig('address', 'street') %>", + "form1[0].#subform[0].City1[0]": "<%= form.data['applicants'][0]&.dig('address', 'city') %>", + "form1[0].#subform[0].ZipCode1[0]": "<%= form.data['applicants'][0]&.dig('address', 'postal_code') %>", + "form1[0].#subform[0].DateOfBirth1[0]": "<%= form.data['applicants'][0]&.dig('date_of_birth') %>", + "form1[0].#subform[0].EmailAddress1[0]": "<%= form.data['applicants'][0]&.dig('email') %>", + "form1[0].#subform[0].PhoneNumber1[0]": "<%= form.data['applicants'][0]&.dig('phone_number') %>", + "form1[0].#subform[0].Gender1[0]": "<%= form.data['applicants'][0]&.dig('gender') ? 1 : 0 %>", + "form1[0].#subform[0].EnrolledMedicare[0]": "<%= form.data['applicants'][0]&.dig('is_enrolled_in_medicare') ? 1 : 0 %>", + "form1[0].#subform[0].HasOtherInsurance[0]": "<%= form.data['applicants'][0]&.dig('has_other_health_insurance') ? 1 : 0 %>", + "form1[0].#subform[0].RelationshipToVeteran1[0]": "<%= form.data['applicants'][0]&.dig('vet_relationship') %>", + "form1[0].#subform[0].State1[0]": "<%= form.data['applicants'][0]&.dig('address', 'state') %>", + "form1[0].#subform[0].LastName2[0]": "<%= form.data['applicants'][1]&.dig('full_name', 'last') %>", + "form1[0].#subform[0].FirstName2[0]": "<%= form.data['applicants'][1]&.dig('full_name', 'first') %>", + "form1[0].#subform[0].MI2[0]": "<%= form.data['applicants'][1]&.dig('full_name', 'middle') %>", + "form1[0].#subform[0].SSN2[0]": "<%= form.data['applicants'][1]&.dig('ssn_or_tin') %>", + "form1[0].#subform[0].StreetAddress2[0]": "<%= form.data['applicants'][1]&.dig('address', 'street') %>", + "form1[0].#subform[0].City2[0]": "<%= form.data['applicants'][1]&.dig('address', 'city') %>", + "form1[0].#subform[0].ZipCode2[0]": "<%= form.data['applicants'][1]&.dig('address', 'postal_code') %>", + "form1[0].#subform[0].DateOfBirth2[0]": "<%= form.data['applicants'][1]&.dig('date_of_birth') %>", + "form1[0].#subform[0].EmailAddress2[0]": "<%= form.data['applicants'][1]&.dig('email') %>", + "form1[0].#subform[0].PhoneNumber2[0]": "<%= form.data['applicants'][1]&.dig('phone_number') %>", + "form1[0].#subform[0].Gender2[0]": "<%= form.data['applicants'][1]&.dig('gender') ? 1 : 0 %>", + "form1[0].#subform[0].EnrolledMedicare[1]": "<%= form.data['applicants'][1]&.dig('is_enrolled_in_medicare') ? 1 : 0 %>", + "form1[0].#subform[0].HasOtherInsurance[1]": "<%= form.data['applicants'][1]&.dig('has_other_health_insurance') ? 1 : 0 %>", + "form1[0].#subform[0].RelationshipToVeteran2[0]": "<%= form.data['applicants'][1]&.dig('vet_relationship') %>", + "form1[0].#subform[0].State2[0]": "<%= form.data['applicants'][1]&.dig('address', 'state') %>", + "form1[0].#subform[0].LastName3[0]": "<%= form.data['applicants'][2]&.dig('full_name', 'last') %>", + "form1[0].#subform[0].FirstName3[0]": "<%= form.data['applicants'][2]&.dig('full_name', 'first') %>", + "form1[0].#subform[0].MI3[0]": "<%= form.data['applicants'][2]&.dig('full_name', 'middle') %>", + "form1[0].#subform[0].SSN3[0]": "<%= form.data['applicants'][2]&.dig('ssn_or_tin') %>", + "form1[0].#subform[0].StreetAddress3[0]": "<%= form.data['applicants'][2]&.dig('address', 'street') %>", + "form1[0].#subform[0].City3[0]": "<%= form.data['applicants'][2]&.dig('address', 'city') %>", + "form1[0].#subform[0].ZipCode3[0]": "<%= form.data['applicants'][2]&.dig('address', 'postal_code') %>", + "form1[0].#subform[0].DateOfBirth3[0]": "<%= form.data['applicants'][2]&.dig('date_of_birth') %>", + "form1[0].#subform[0].EmailAddress3[0]": "<%= form.data['applicants'][2]&.dig('email') %>", + "form1[0].#subform[0].PhoneNumber3[0]": "<%= form.data['applicants'][2]&.dig('phone_number') %>", + "form1[0].#subform[0].Gender3[0]": "<%= form.data['applicants'][2]&.dig('gender') ? 1 : 0 %>", + "form1[0].#subform[0].EnrolledMedicare[2]": "<%= form.data['applicants'][2]&.dig('is_enrolled_in_medicare') ? 1 : 0 %>", + "form1[0].#subform[0].HasOtherInsurance[2]": "<%= form.data['applicants'][2]&.dig('has_other_health_insurance') ? 1 : 0 %>", + "form1[0].#subform[0].RelationshipToVeteran3[0]": "<%= form.data['applicants'][2]&.dig('vet_relationship') %>", + "form1[0].#subform[0].State3[0]": "<%= form.data['applicants'][2]&.dig('address', 'state') %>", + "form1[0].#subform[0].DateSigned[0]": "<%= form.data.dig('certification', 'date') %>", + "form1[0].#subform[0].SignatureField11[0]": "<%= form.data['statement_of_truth_signature'] %>", + "form1[0].#subform[0].LastName4[0]": "<%= form.data.dig('certification', 'lastName') %>", + "form1[0].#subform[0].FirstName4[0]": "<%= form.data.dig('certification', 'firstName') %>", + "form1[0].#subform[0].MI4[0]": "<%= form.data.dig('certification', 'middleInitial') %>", + "form1[0].#subform[0].StreetAddress4[0]": "<%= form.data.dig('certification', 'streetAddress') %>", + "form1[0].#subform[0].City4[0]": "<%= form.data.dig('certification', 'city') %>", + "form1[0].#subform[0].ZipCode4[0]": "<%= form.data.dig('certification', 'postal_code') %>", + "form1[0].#subform[0].State4[0]": "<%= form.data.dig('certification', 'state') %>", + "form1[0].#subform[0].RelationshipToApplicants[0]": "<%= form.data.dig('certification', 'relationship') %>", + "form1[0].#subform[0].PhoneNumber4[0]": "<%= form.data.dig('certification', 'phone_number') %>", + "form1[0]": "<%= form.data.dig('form1') %>" +} diff --git a/modules/ivc_champva/app/form_mappings/vha_10_7959c.json.erb b/modules/ivc_champva/app/form_mappings/vha_10_7959c.json.erb new file mode 100644 index 00000000000..da2e998ad6a --- /dev/null +++ b/modules/ivc_champva/app/form_mappings/vha_10_7959c.json.erb @@ -0,0 +1,63 @@ +{ + "form1[0].#subform[0].#area[0].Doyouhaveotherinsyesno[0]": "<%= form.data.dig('has_other_health_insurance') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].#area[0]": "<%= form.data.dig('') %>", + "form1[0].#subform[0].NewAddressBox-1[0]": "<%= form.data.dig('is_new_address') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].PhoneNumb-1[0]": "<%= form.data.dig('applicants', 'home_phone') %>", + "form1[0].#subform[0].PartABDBkgrnd[0].PartARadioButtonList[0]": "<%= form.data.dig('part_a', 'has_part_a') == 'F' ? 1 : 0 %>", + "form1[0].#subform[0].PartABDBkgrnd[0].PartBRadioButtonList[0]": "<%= form.data.dig('part_b','has_part_b') == 'F' ? 1 : 0 %>", + "form1[0].#subform[0].PartABDBkgrnd[0].PartDRadioButtonList[0]": "<%= form.data.dig('part_d','has_part_d') == 'F' ? 1 : 0 %>", + "form1[0].#subform[0].PartABDBkgrnd[0]": "<%= form.data.dig('') %>", + "form1[0].#subform[0].PartA_CarrierName[0]": "<%= form.data.dig('part_a','part_a_carrier') %>", + "form1[0].#subform[0].PartB_CarrierName[0]": "<%= form.data.dig('part_b','part_b_carrier') %>", + "form1[0].#subform[0].PartD_CarrierName[0]": "<%= form.data.dig('part_d','part_d_carrier') %>", + "form1[0].#subform[0].PharmacyBenefitsRadioButtonList[0]": "<%= form.data.dig('has_pharmacy_benefits') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].MedicareAdvantageRadioButtonList[0]": "<%= form.data.dig('has_medicare_advantage') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].CheckBox-HMO-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_hmo') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].CheckBox-PPO-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_ppo') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].CheckBox1-MedicaidSA-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_medicaid') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].CheckBox-RxDiscount-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_rx_discount') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].#area[3].CheckBox-Medigap-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_medigap') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].#area[3].DropDownList-Medigap-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_medigap_type') ? 1 : 0 %>", + "form1[0].#subform[0].#area[2].CheckBox-Other-1[0]": "<%= form.data.dig('other_health_insurance1' , 'is_other_type') ? 1 : 0 %>", + "form1[0].#subform[0].Comments-Ins-1[0]": "<%= form.data.dig('other_health_insurance1', 'comments' ) %>", + "form1[0].#subform[0].DateTimeField1[0]": "<%= form.data.dig('DateTimeField1') %>", + "form1[0].#subform[0].NameofInsurance-1[0]": "<%= form.data.dig('other_health_insurance1' , 'name_of_health_insurance') %>", + "form1[0].#subform[0].Signature[0]": "<%= form.data['statement_of_truth_signature'] %>", + "form1[0].#subform[0].Date-PartA[0]": "<%= form.data.dig('part_a','part_a_effective_date') %>", + "form1[0].#subform[0].Date-PartB[0]": "<%= form.data.dig('part_b','part_b_effective_date') %>", + "form1[0].#subform[0].Date-PartD[0]": "<%= form.data.dig('part_d','part_d_effective_date') %>", + "form1[0].#subform[0].Date-NameInsurance-1[0]": "<%= form.data.dig('other_health_insurance1' , 'date_health_insurance') %>", + "form1[0].#subform[0].Date-TermnNameInsurance-1[0]": "<%= form.data.dig('other_health_insurance1' , 'terminate_date_health_insurance') %>", + "form1[0].#subform[0].#area[4].applicantSexRadioButtons2[0]": "<%= form.data.dig('male_or_female') == 'M' ? 1 : 0 %>", + "form1[0].#subform[0].#area[4]": "<%= form.data.dig('') %>", + "form1[0].#subform[0].applicantZipCode2[0]": "<%= form.data.dig('applicants', 'address', 'postal_code') %>", + "form1[0].#subform[0].applicantState2[0]": "<%= form.data.dig('applicants', 'address', 'state') %>", + "form1[0].#subform[0].applicantCity2[0]": "<%= form.data.dig('applicants', 'address', 'city') %>", + "form1[0].#subform[0].applicantStreetAddress2[0]": "<%= form.data.dig('applicants', 'address', 'street') %>", + "form1[0].#subform[0].applicantMiddleInitial2[0]": "<%= form.data.dig('applicants', 'full_name', 'middle') %>", + "form1[0].#subform[0].applicantFirstName2[0]": "<%= form.data.dig('applicants', 'full_name', 'first') %>", + "form1[0].#subform[0].applicantLastName2[0]": "<%= form.data.dig('applicants', 'full_name', 'last') %>", + "form1[0].#subform[0].applicantSocialSecurityNumber2[0]": "<%= form.data.dig('applicants', 'ssn_or_tin') %>", + "form1[0].#subform[0].RadioButtonList[0]": "<%= form.data.dig('other_health_insurance1' , 'does_insurance') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].#area[6].#area[7].RadioButtonList[1]": "<%= form.data.dig('other_health_insurance1' , 'does_explain') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].#area[6].#area[7]": "<%= form.data.dig('#area') %>", + "form1[0].#subform[0].#area[6]": "<%= form.data.dig('#area') %>", + "form1[0].#subform[0].RadioButtonList[2]": "<%= form.data.dig('other_health_insurance1' , 'does_prescription') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].CheckBox-HMO-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_hmo') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].CheckBox-PPO-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_ppo') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].CheckBox1-MedicaidSA-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_medicaid') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].CheckBox-RxDiscount-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_rx_discount') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].#area[9].CheckBox-Medigap-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_medigap') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].#area[9].DropDownList-Medigap-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_medigap_type') ? 1 : 0 %>", + "form1[0].#subform[0].#area[8].CheckBox-Other-2[0]": "<%= form.data.dig('other_health_insurance2' , 'is_other_type') ? 1 : 0 %>", + "form1[0].#subform[0].Comments-Ins-2[0]": "<%= form.data.dig('other_health_insurance2', 'comments') %>", + "form1[0].#subform[0].NameofInsurance-2[0]": "<%= form.data.dig('other_health_insurance2', 'name_of_health_insurance') %>", + "form1[0].#subform[0].Date-NameInsurance-2[0]": "<%= form.data.dig('other_health_insurance2', 'date_health_insurance') %>", + "form1[0].#subform[0].Date-TermnNameInsurance-2[0]": "<%= form.data.dig('other_health_insurance2', 'terminate_date_health_insurance') %>", + "form1[0].#subform[0].RadioButtonList[3]": "<%= form.data.dig('other_health_insurance2' , 'does_insurance') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].RadioButtonList[4]": "<%= form.data.dig('other_health_insurance2' , 'does_explain') == 'T' ? 1 : 0 %>", + "form1[0].#subform[0].RadioButtonList[5]": "<%= form.data.dig('other_health_insurance2' , 'does_prescription') == 'T' ? 1 : 0 %>", + "form1[0]": "<%= form.data.dig('form1') %>" +} + + diff --git a/modules/ivc_champva/app/form_mappings/vha_10_7959f_1.json.erb b/modules/ivc_champva/app/form_mappings/vha_10_7959f_1.json.erb new file mode 100644 index 00000000000..ed90786035f --- /dev/null +++ b/modules/ivc_champva/app/form_mappings/vha_10_7959f_1.json.erb @@ -0,0 +1,16 @@ +{ + "form1[0].#subform[0].VetLastName[0]": "<%= form.data.dig('veteran', 'full_name', 'last') %>", + "form1[0].#subform[0].VetFirstName[0]": "<%= form.data.dig('veteran', 'full_name', 'first') %>", + "form1[0].#subform[0].MiddleInitials[0]": "<%= form.data.dig('veteran', 'full_name', 'middle') %>", + "form1[0].#subform[0].SocialSecurityNumber[0]": "<%= form.data.dig('veteran', 'ssn') %>", + "form1[0].#subform[0].VAClaimFileNumber[0]": "<%= form.data.dig('veteran', 'va_claim_number') %>", + "form1[0].#subform[0].DateofBirth[0]": "<%= form.data.dig('veteran', 'date_of_birth') %>", + "form1[0].#subform[0].PhysicalAddress[0]": "<%= form.data.dig('veteran', 'physical_address', 'street') + '\n' + form.data.dig('veteran', 'physical_address', 'city') + ', ' + form.data.dig('veteran', 'physical_address', 'state') + '\n' + form.data.dig('veteran', 'physical_address', 'postal_code') %>", + "form1[0].#subform[0].Country[0]": "<%= form.data.dig('veteran', 'physical_address', 'country') %>", + "form1[0].#subform[0].MailingAddress[0]": "<%= form.data.dig('veteran', 'mailing_address', 'street') + '\n' + form.data.dig('veteran', 'mailing_address', 'city') + ', ' + form.data.dig('veteran', 'mailing_address', 'state') + '\n' + form.data.dig('veteran', 'mailing_address', 'postal_code') %>", + "form1[0].#subform[0].Country[1]": "<%= form.data.dig('veteran', 'mailing_address', 'country') %>", + "form1[0].#subform[0].TelephoneNumber[0]": "<%= form.data.dig('veteran', 'phone_number') %>", + "form1[0].#subform[0].EmailAddress[0]": "<%= form.data.dig('veteran', 'email_address') %>", + "form1[0].#subform[0].VeteranSignature[0]": "<%= form.data['statement_of_truth_signature'] %>", + "form1[0].#subform[0].Date[0]": "<%= form.data['current_date'] %>" +} diff --git a/modules/ivc_champva/app/form_mappings/vha_10_7959f_2.json.erb b/modules/ivc_champva/app/form_mappings/vha_10_7959f_2.json.erb new file mode 100644 index 00000000000..6d5a307c362 --- /dev/null +++ b/modules/ivc_champva/app/form_mappings/vha_10_7959f_2.json.erb @@ -0,0 +1,17 @@ +{ + "vha107959fform[0].#subform[0].RadioButtonList[0]": "<%= form.data['payment_to_be_sent_type'] == 'Veteran' ? 0 : 1 %>", + "vha107959fform[0].#subform[0].LastName-1[0]": "<%= form.data.dig('veteran', 'full_name', 'last') %>", + "vha107959fform[0].#subform[0].FirstName-1[0]": "<%= form.data.dig('veteran', 'full_name', 'first') %>", + "vha107959fform[0].#subform[0].MiddleInitial-1[0]": "<%= form.data.dig('veteran', 'full_name', 'middle') %>", + "vha107959fform[0].#subform[0].SSN-1[0]": "<%= form.data.dig('veteran', 'ssn') %>", + "vha107959fform[0].#subform[0].VAClaimNumber-1[0]": "<%= form.data.dig('veteran', 'va_claim_number') %>", + "vha107959fform[0].#subform[0].DateofBirth-1[0]": "<%= form.data.dig('veteran', 'va_claim_number') %>", + "vha107959fform[0].#subform[0].PhysicalAddress1-1[0]": "<%= form.data.dig('veteran', 'physical_address', 'street') + '\n' + form.data.dig('veteran', 'physical_address', 'city') + ', ' + form.data.dig('veteran', 'physical_address', 'state') + '\n' + form.data.dig('veteran', 'physical_address', 'postal_code') %>", + "vha107959fform[0].#subform[0].PhysicalAddressCountry-1[0]": "<%= form.data.dig('veteran', 'physical_address', 'country') %>", + "vha107959fform[0].#subform[0].MailingAddress1-2[0]": "<%= form.data.dig('veteran', 'mailing_address', 'street') + '\n' + form.data.dig('veteran', 'mailing_address', 'city') + ', ' + form.data.dig('veteran', 'mailing_address', 'state') + '\n' + form.data.dig('veteran', 'mailing_address', 'postal_code') %>", + "vha107959fform[0].#subform[0].MailingAddressCountry[0]": "<%= form.data.dig('veteran', 'mailing_address', 'country') %>", + "vha107959fform[0].#subform[0].Telephone-1[0]": "<%= form.data.dig('veteran', 'phone_number') %>", + "vha107959fform[0].#subform[0].EmailAddress[0]": "<%= form.data.dig('veteran', 'email_address') %>", + "vha107959fform[0].#subform[0].SignatureDate-1[0]": "<%= form.data['current_date'] %>", + "vha107959fform[0].#subform[0].VeteranFiduciarySignature-1[0]": "<%= form.data['statement_of_truth_signature'] %>" +} diff --git a/modules/ivc_champva/app/json/cemeteries.json b/modules/ivc_champva/app/json/cemeteries.json new file mode 100644 index 00000000000..455dc70d7c8 --- /dev/null +++ b/modules/ivc_champva/app/json/cemeteries.json @@ -0,0 +1,3304 @@ +{ + "data": [ + { + "id": "915", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "915", + "name": "ABRAHAM LINCOLN NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "915" + } + }, + { + "id": "944", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "944", + "name": "ACADIA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "944" + } + }, + { + "id": "927", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "927", + "name": "ALABAMA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "927" + } + }, + { + "id": "400", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "400", + "name": "ALABAMA STATE VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "400" + } + }, + { + "id": "946", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "946", + "name": "ALAMEDA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "946" + } + }, + { + "id": "088", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "088", + "name": "ALBANY RURAL CEMETERY", + "cemetery_type": "N", + "num": "088" + } + }, + { + "id": "109", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "109", + "name": "ALBERT G. HORTON, JR. MEMORIAL VETERANS CEMETERY", + "cemetery_type": "S", + "num": "109" + } + }, + { + "id": "825", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "825", + "name": "ALEXANDRIA NATIONAL CEMETERY, LA", + "cemetery_type": "N", + "num": "825" + } + }, + { + "id": "826", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "826", + "name": "ALEXANDRIA NATIONAL CEMETERY, VA", + "cemetery_type": "N", + "num": "826" + } + }, + { + "id": "417", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "417", + "name": "ALL NATIONS VETERANS CEMETERY", + "cemetery_type": "S", + "num": "417" + } + }, + { + "id": "120", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "120", + "name": "ALLEGHENY CEMETERY", + "cemetery_type": "N", + "num": "120" + } + }, + { + "id": "800", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "800", + "name": "ALTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "800" + } + }, + { + "id": "409", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "409", + "name": "ANDERSONVILLE NATIONAL CEMETERY", + "cemetery_type": "I", + "num": "409" + } + }, + { + "id": "410", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "410", + "name": "ANDREW JOHNSON NATIONAL CEMETERY", + "cemetery_type": "I", + "num": "410" + } + }, + { + "id": "136", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "136", + "name": "ANGEL FIRE NM STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "136" + } + }, + { + "id": "801", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "801", + "name": "ANNAPOLIS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "801" + } + }, + { + "id": "152", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "152", + "name": "APSAALOOKE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "152" + } + }, + { + "id": "412", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "412", + "name": "ARIZONA VETERANS MEMORIAL CEMETERY AT CAMP NAVAJO", + "cemetery_type": "S", + "num": "412" + } + }, + { + "id": "413", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "413", + "name": "ARIZONA VETERANS MEMORIAL CEMETERY AT MARANA", + "cemetery_type": "S", + "num": "413" + } + }, + { + "id": "091", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "091", + "name": "ARKANSAS STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "091" + } + }, + { + "id": "396", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "396", + "name": "ARKANSAS STATE VETERANS CEMETERY-BIRDEYE", + "cemetery_type": "S", + "num": "396" + } + }, + { + "id": "411", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "411", + "name": "ARLINGTON NATIONAL CEMETERY", + "cemetery_type": "A", + "num": "411" + } + }, + { + "id": "121", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "121", + "name": "ASHLAND CEMETERY", + "cemetery_type": "N", + "num": "121" + } + }, + { + "id": "406", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "406", + "name": "ATLANTIC GARDEN VETERANS CEMETERY", + "cemetery_type": "S", + "num": "406" + } + }, + { + "id": "929", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "929", + "name": "BAKERSFIELD NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "929" + } + }, + { + "id": "827", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "827", + "name": "BALLS BLUFF NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "827" + } + }, + { + "id": "802", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "802", + "name": "BALTIMORE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "802" + } + }, + { + "id": "828", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "828", + "name": "BARRANCAS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "828" + } + }, + { + "id": "803", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "803", + "name": "BATH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "803" + } + }, + { + "id": "829", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "829", + "name": "BATON ROUGE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "829" + } + }, + { + "id": "042", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "042", + "name": "BAXTER SPRINGS", + "cemetery_type": "N", + "num": "042" + } + }, + { + "id": "830", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "830", + "name": "BAY PINES NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "830" + } + }, + { + "id": "831", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "831", + "name": "BEAUFORT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "831" + } + }, + { + "id": "961", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "961", + "name": "BENICIA ARSENAL POST CEMETERY", + "cemetery_type": "N", + "num": "961" + } + }, + { + "id": "804", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "804", + "name": "BEVERLY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "804" + } + }, + { + "id": "025", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "025", + "name": "BG WILLIAM C DOYLE VET'S MEM CEM", + "cemetery_type": "S", + "num": "025" + } + }, + { + "id": "117", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "117", + "name": "BIG SANDY RANCHERIA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "117" + } + }, + { + "id": "832", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "832", + "name": "BILOXI NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "832" + } + }, + { + "id": "884", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "884", + "name": "BLACK HILLS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "884" + } + }, + { + "id": "414", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "414", + "name": "CALIFORNIA CENTRAL COAST VETERANS CEMETERY", + "cemetery_type": "S", + "num": "414" + } + }, + { + "id": "805", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "805", + "name": "CALVERTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "805" + } + }, + { + "id": "806", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "806", + "name": "CAMP BUTLER NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "806" + } + }, + { + "id": "075", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "075", + "name": "CAMP CHASE CONFEDERATE CEMETERY", + "cemetery_type": "N", + "num": "075" + } + }, + { + "id": "833", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "833", + "name": "CAMP NELSON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "833" + } + }, + { + "id": "934", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "934", + "name": "CAPE CANAVERAL NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "934" + } + }, + { + "id": "834", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "834", + "name": "CAVE HILL NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "834" + } + }, + { + "id": "942", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "942", + "name": "CEDAR CITY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "942" + } + }, + { + "id": "397", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "397", + "name": "CENTRAL LOUISIANA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "397" + } + }, + { + "id": "127", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "127", + "name": "CENTRAL TEXAS STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "127" + } + }, + { + "id": "015", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "015", + "name": "CENTRAL WISCONSIN VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "015" + } + }, + { + "id": "835", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "835", + "name": "CHATTANOOGA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "835" + } + }, + { + "id": "001", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "001", + "name": "CHELTENHAM VETERANS CEMETERY", + "cemetery_type": "S", + "num": "001" + } + }, + { + "id": "945", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "945", + "name": "CHEYENNE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "945" + } + }, + { + "id": "950", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "950", + "name": "CHICAGO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "950" + } + }, + { + "id": "836", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "836", + "name": "CITY POINT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "836" + } + }, + { + "id": "132", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "132", + "name": "CNMI VETERANS CEMETERY", + "cemetery_type": "S", + "num": "132" + } + }, + { + "id": "395", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "395", + "name": "COASTAL BEND VETERANS CEMETERY", + "cemetery_type": "S", + "num": "395" + } + }, + { + "id": "052", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "052", + "name": "COASTAL CAROLINA STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "052" + } + }, + { + "id": "837", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "837", + "name": "COLD HARBOR NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "837" + } + }, + { + "id": "010", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "010", + "name": "COLONEL RAYMOND F. GATES CEMETERY", + "cemetery_type": "S", + "num": "010" + } + }, + { + "id": "073", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "073", + "name": "CONFEDERATE MOUND", + "cemetery_type": "N", + "num": "073" + } + }, + { + "id": "076", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "076", + "name": "CONFEDERATE STOCKADE CEMETERY", + "cemetery_type": "N", + "num": "076" + } + }, + { + "id": "054", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "054", + "name": "CONGRESSIONAL CEMETERY", + "cemetery_type": "N", + "num": "054" + } + }, + { + "id": "030", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "030", + "name": "CONNECTICUT STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "030" + } + }, + { + "id": "838", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "838", + "name": "CORINTH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "838" + } + }, + { + "id": "074", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "074", + "name": "CROWN HILL CONFEDERATE PLOT", + "cemetery_type": "N", + "num": "074" + } + }, + { + "id": "807", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "807", + "name": "CROWN HILL NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "807" + } + }, + { + "id": "002", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "002", + "name": "CROWNSVILLE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "002" + } + }, + { + "id": "839", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "839", + "name": "CULPEPER NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "839" + } + }, + { + "id": "808", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "808", + "name": "CYPRESS HILLS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "808" + } + }, + { + "id": "916", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "916", + "name": "DALLAS - FT. WORTH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "916" + } + }, + { + "id": "809", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "809", + "name": "DANVILLE NATIONAL CEMETERY, IL", + "cemetery_type": "N", + "num": "809" + } + }, + { + "id": "840", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "840", + "name": "DANVILLE NATIONAL CEMETERY, KY", + "cemetery_type": "N", + "num": "840" + } + }, + { + "id": "841", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "841", + "name": "DANVILLE NATIONAL CEMETERY, VA", + "cemetery_type": "N", + "num": "841" + } + }, + { + "id": "810", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "810", + "name": "DAYTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "810" + } + }, + { + "id": "036", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "036", + "name": "DELAWARE VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "036" + } + }, + { + "id": "093", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "093", + "name": "DELAWARE VETERANS MEMORIAL CEMETERY-SUSSEX CO.", + "cemetery_type": "S", + "num": "093" + } + }, + { + "id": "398", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "398", + "name": "DONEL KINNARD MEMORIAL STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "398" + } + }, + { + "id": "906", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "906", + "name": "EAGLE POINT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "906" + } + }, + { + "id": "394", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "394", + "name": "EAST TENNESSEE STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "394" + } + }, + { + "id": "416", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "416", + "name": "EASTERN CAROLINA STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "416" + } + }, + { + "id": "090", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "090", + "name": "EASTERN MONTANA STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "090" + } + }, + { + "id": "004", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "004", + "name": "EASTERN SHORE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "004" + } + }, + { + "id": "943", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "943", + "name": "ELKO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "943" + } + }, + { + "id": "055", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "055", + "name": "EVERGREEN CEMETERY", + "cemetery_type": "N", + "num": "055" + } + }, + { + "id": "940", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "940", + "name": "FARGO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "940" + } + }, + { + "id": "842", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "842", + "name": "FAYETTEVILLE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "842" + } + }, + { + "id": "811", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "811", + "name": "FINN'S POINT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "811" + } + }, + { + "id": "843", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "843", + "name": "FLORENCE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "843" + } + }, + { + "id": "911", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "911", + "name": "FLORIDA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "911" + } + }, + { + "id": "123", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "123", + "name": "FOREST HILL CEMETERY", + "cemetery_type": "N", + "num": "123" + } + }, + { + "id": "122", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "122", + "name": "FOREST HOME CEMETERY", + "cemetery_type": "N", + "num": "122" + } + }, + { + "id": "058", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "058", + "name": "FOREST LAWN CEMETERY", + "cemetery_type": "N", + "num": "058" + } + }, + { + "id": "140", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "140", + "name": "FORT CAMPBELL POW POST CEMETERY", + "cemetery_type": "M", + "num": "140" + } + }, + { + "id": "960", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "960", + "name": "FORT DEVENS POST CEMETERY", + "cemetery_type": "N", + "num": "960" + } + }, + { + "id": "957", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "957", + "name": "FORT DOUGLAS POST CEMETERY", + "cemetery_type": "N", + "num": "957" + } + }, + { + "id": "141", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "141", + "name": "FORT DRUM POW POST CEMETERY", + "cemetery_type": "M", + "num": "141" + } + }, + { + "id": "142", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "142", + "name": "FORT GORDON GERMAN AND ITALIAN POW CEMETERY", + "cemetery_type": "M", + "num": "142" + } + }, + { + "id": "930", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "930", + "name": "FORT JACKSON VA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "930" + } + }, + { + "id": "143", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "143", + "name": "FORT LEAVENWORTH POST CEMETERY", + "cemetery_type": "M", + "num": "143" + } + }, + { + "id": "082", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "082", + "name": "FORT MACKINAC POST CEMETERY", + "cemetery_type": "N", + "num": "082" + } + }, + { + "id": "089", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "089", + "name": "FORT MCCLELLAN EPW CEMETERY", + "cemetery_type": "N", + "num": "089" + } + }, + { + "id": "955", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "955", + "name": "FORT MCCLELLAN POST CEMETERY", + "cemetery_type": "N", + "num": "955" + } + }, + { + "id": "144", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "144", + "name": "FORT MEADE POST CEMETERY", + "cemetery_type": "M", + "num": "144" + } + }, + { + "id": "952", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "952", + "name": "FORT MISSOULA POST CEMETERY", + "cemetery_type": "N", + "num": "952" + } + }, + { + "id": "953", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "953", + "name": "FORT SHERIDAN NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "953" + } + }, + { + "id": "113", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "113", + "name": "FORT STANTON STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "113" + } + }, + { + "id": "959", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "959", + "name": "FORT STEVENS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "959" + } + }, + { + "id": "958", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "958", + "name": "FORT WORDEN POST CEMETERY", + "cemetery_type": "N", + "num": "958" + } + }, + { + "id": "391", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "391", + "name": "FT LEONARD WOOD STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "391" + } + }, + { + "id": "885", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "885", + "name": "FT. BAYARD NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "885" + } + }, + { + "id": "886", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "886", + "name": "FT. BLISS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "886" + } + }, + { + "id": "059", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "059", + "name": "FT. CRAWFORD CEMETERY", + "cemetery_type": "N", + "num": "059" + } + }, + { + "id": "909", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "909", + "name": "FT. CUSTER NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "909" + } + }, + { + "id": "844", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "844", + "name": "FT. GIBSON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "844" + } + }, + { + "id": "845", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "845", + "name": "FT. HARRISON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "845" + } + }, + { + "id": "951", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "951", + "name": "FT. LAWTON POST CEMETERY", + "cemetery_type": "N", + "num": "951" + } + }, + { + "id": "887", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "887", + "name": "FT. LEAVENWORTH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "887" + } + }, + { + "id": "888", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "888", + "name": "FT. LOGAN NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "888" + } + }, + { + "id": "889", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "889", + "name": "FT. LYON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "889" + } + }, + { + "id": "890", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "890", + "name": "FT. MCPHERSON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "890" + } + }, + { + "id": "891", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "891", + "name": "FT. MEADE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "891" + } + }, + { + "id": "908", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "908", + "name": "FT. MITCHELL NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "908" + } + }, + { + "id": "910", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "910", + "name": "FT. RICHARDSON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "910" + } + }, + { + "id": "407", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "407", + "name": "FT. RILEY POST CEMETERY", + "cemetery_type": "M", + "num": "407" + } + }, + { + "id": "892", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "892", + "name": "FT. ROSECRANS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "892" + } + }, + { + "id": "846", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "846", + "name": "FT. SAM HOUSTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "846" + } + }, + { + "id": "893", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "893", + "name": "FT. SCOTT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "893" + } + }, + { + "id": "920", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "920", + "name": "FT. SILL NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "920" + } + }, + { + "id": "847", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "847", + "name": "FT. SMITH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "847" + } + }, + { + "id": "894", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "894", + "name": "FT. SNELLING NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "894" + } + }, + { + "id": "083", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "083", + "name": "FT. WINNEBAGO CEMETERY", + "cemetery_type": "N", + "num": "083" + } + }, + { + "id": "118", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "118", + "name": "GALLUP STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "118" + } + }, + { + "id": "003", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "003", + "name": "GARRISON FOREST VETERANS CEMETERY", + "cemetery_type": "S", + "num": "003" + } + }, + { + "id": "922", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "922", + "name": "GEORGIA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "922" + } + }, + { + "id": "096", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "096", + "name": "GEORGIA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "096" + } + }, + { + "id": "303", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "303", + "name": "GEORGIA VETERANS MEMORIAL CEMETERY - GLENNVILLE", + "cemetery_type": "S", + "num": "303" + } + }, + { + "id": "917", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "917", + "name": "GERALD B.H. SOLOMON SARATOGA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "917" + } + }, + { + "id": "848", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "848", + "name": "GLENDALE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "848" + } + }, + { + "id": "895", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "895", + "name": "GOLDEN GATE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "895" + } + }, + { + "id": "812", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "812", + "name": "GRAFTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "812" + } + }, + { + "id": "923", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "923", + "name": "GREAT LAKES NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "923" + } + }, + { + "id": "061", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "061", + "name": "GREEN MOUNT CEMETERY", + "cemetery_type": "N", + "num": "061" + } + }, + { + "id": "151", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "151", + "name": "GUAM VETERANS CEMETERY", + "cemetery_type": "S", + "num": "151" + } + }, + { + "id": "849", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "849", + "name": "HAMPTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "849" + } + }, + { + "id": "850", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "850", + "name": "HAMPTON VA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "850" + } + }, + { + "id": "043", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "043", + "name": "HAWAII STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "043" + } + }, + { + "id": "896", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "896", + "name": "HOT SPRINGS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "896" + } + }, + { + "id": "124", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "124", + "name": "HOULTON BAND OF MALISEET INDIANS VETERANS CEMETERY", + "cemetery_type": "S", + "num": "124" + } + }, + { + "id": "851", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "851", + "name": "HOUSTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "851" + } + }, + { + "id": "107", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "107", + "name": "IDAHO STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "107" + } + }, + { + "id": "139", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "139", + "name": "IDAHO STATE VETERANS CEMETERY AT BLACKFOOT", + "cemetery_type": "S", + "num": "139" + } + }, + { + "id": "006", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "006", + "name": "INDIANA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "006" + } + }, + { + "id": "948", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "948", + "name": "INDIANAPOLIS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "948" + } + }, + { + "id": "813", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "813", + "name": "INDIANTOWN GAP NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "813" + } + }, + { + "id": "301", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "301", + "name": "IOWA VETERANS CEMETERY AT VAN METER", + "cemetery_type": "S", + "num": "301" + } + }, + { + "id": "928", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "928", + "name": "JACKSONVILLE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "928" + } + }, + { + "id": "852", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "852", + "name": "JEFFERSON BARRACKS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "852" + } + }, + { + "id": "853", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "853", + "name": "JEFFERSON CITY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "853" + } + }, + { + "id": "094", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "094", + "name": "KANSAS VETERANS CEMETERY AT FORT DODGE", + "cemetery_type": "S", + "num": "094" + } + }, + { + "id": "277", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "277", + "name": "KANSAS VETERANS CEMETERY AT FORT RILEY", + "cemetery_type": "S", + "num": "277" + } + }, + { + "id": "110", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "110", + "name": "KANSAS VETERANS CEMETERY AT WAKEENEY", + "cemetery_type": "S", + "num": "110" + } + }, + { + "id": "128", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "128", + "name": "KANSAS VETERANS CEMETERY AT WINFIELD", + "cemetery_type": "S", + "num": "128" + } + }, + { + "id": "112", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "112", + "name": "KENTUCKY VETERAN CEMETERY SOUTHEAST", + "cemetery_type": "S", + "num": "112" + } + }, + { + "id": "114", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "114", + "name": "KENTUCKY VETERANS CEMETERY - NORTHEAST", + "cemetery_type": "S", + "num": "114" + } + }, + { + "id": "134", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "134", + "name": "KENTUCKY VETERANS CEMETERY CENTRAL", + "cemetery_type": "S", + "num": "134" + } + }, + { + "id": "135", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "135", + "name": "KENTUCKY VETERANS CEMETERY NORTH", + "cemetery_type": "S", + "num": "135" + } + }, + { + "id": "105", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "105", + "name": "KENTUCKY VETERANS CEMETERY-WEST", + "cemetery_type": "S", + "num": "105" + } + }, + { + "id": "814", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "814", + "name": "KEOKUK NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "814" + } + }, + { + "id": "854", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "854", + "name": "KERRVILLE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "854" + } + }, + { + "id": "855", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "855", + "name": "KNOXVILLE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "855" + } + }, + { + "id": "062", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "062", + "name": "LAKESIDE CEMETERY", + "cemetery_type": "N", + "num": "062" + } + }, + { + "id": "403", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "403", + "name": "LAKOTA FREEDOM VETERANS CEMETERY", + "cemetery_type": "S", + "num": "403" + } + }, + { + "id": "897", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "897", + "name": "LEAVENWORTH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "897" + } + }, + { + "id": "856", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "856", + "name": "LEBANON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "856" + } + }, + { + "id": "418", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "418", + "name": "LEECH LAKE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "418" + } + }, + { + "id": "857", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "857", + "name": "LEXINGTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "857" + } + }, + { + "id": "858", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "858", + "name": "LITTLE ROCK NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "858" + } + }, + { + "id": "815", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "815", + "name": "LONG ISLAND NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "815" + } + }, + { + "id": "898", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "898", + "name": "LOS ANGELES NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "898" + } + }, + { + "id": "816", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "816", + "name": "LOUDON PARK NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "816" + } + }, + { + "id": "970", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "970", + "name": "LOUISIANA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "970" + } + }, + { + "id": "302", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "302", + "name": "M.J. DOLLY COOPER VETERANS CEMETERY", + "cemetery_type": "S", + "num": "302" + } + }, + { + "id": "012", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "012", + "name": "MAINE VETERANS' MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "012" + } + }, + { + "id": "095", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "095", + "name": "MAINE VETERANS' MEMORIAL CEMETERY - MT VERNON RD", + "cemetery_type": "S", + "num": "095" + } + }, + { + "id": "859", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "859", + "name": "MARIETTA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "859" + } + }, + { + "id": "817", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "817", + "name": "MARION NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "817" + } + }, + { + "id": "818", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "818", + "name": "MASSACHUSETTS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "818" + } + }, + { + "id": "108", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "108", + "name": "MASSACHUSETTS VETERAN MEMORIAL CEMETERY/WINCHENDON", + "cemetery_type": "S", + "num": "108" + } + }, + { + "id": "034", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "034", + "name": "MASSACHUSETTS VETERANS' MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "034" + } + }, + { + "id": "999", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "999", + "name": "MBMS TEST AUTOMATION CEM ONE", + "cemetery_type": "N", + "num": "999" + } + }, + { + "id": "997", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "997", + "name": "MBMS TEST AUTOMATION CEM THREE", + "cemetery_type": "N", + "num": "997" + } + }, + { + "id": "998", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "998", + "name": "MBMS TEST AUTOMATION CEM TWO", + "cemetery_type": "N", + "num": "998" + } + }, + { + "id": "860", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "860", + "name": "MEMPHIS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "860" + } + }, + { + "id": "137", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "137", + "name": "METLAKATLA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "137" + } + }, + { + "id": "050", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "050", + "name": "MIDDLE TENNESSEE STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "050" + } + }, + { + "id": "861", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "861", + "name": "MILL SPRINGS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "861" + } + }, + { + "id": "116", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "116", + "name": "MINNESOTA STATE VETERANS CEMETERY - DULUTH", + "cemetery_type": "S", + "num": "116" + } + }, + { + "id": "053", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "053", + "name": "MINNESOTA STATE VETERANS CEMETERY - LITTLE FALLS", + "cemetery_type": "S", + "num": "053" + } + }, + { + "id": "415", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "415", + "name": "MINNESOTA STATE VETERANS CEMETERY - PRESTON", + "cemetery_type": "S", + "num": "415" + } + }, + { + "id": "154", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "154", + "name": "MINNESOTA STATE VETERANS CEMETERY - REDWOOD FALLS", + "cemetery_type": "S", + "num": "154" + } + }, + { + "id": "992", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "992", + "name": "MIRAMAR NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "992" + } + }, + { + "id": "278", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "278", + "name": "MISSISSIPPI STATE VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "278" + } + }, + { + "id": "104", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "104", + "name": "MISSOURI STATE VETERANS CEMETERY/JACKSONVILLE", + "cemetery_type": "S", + "num": "104" + } + }, + { + "id": "056", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "056", + "name": "MISSOURI VETERANS CEMETERY / SPRINGFIELD", + "cemetery_type": "S", + "num": "056" + } + }, + { + "id": "103", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "103", + "name": "MISSOURI VETERANS CEMETERY AT BLOOMFIELD", + "cemetery_type": "S", + "num": "103" + } + }, + { + "id": "057", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "057", + "name": "MISSOURI VETERANS CEMETERY/HIGGINSVILLE", + "cemetery_type": "S", + "num": "057" + } + }, + { + "id": "862", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "862", + "name": "MOBILE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "862" + } + }, + { + "id": "040", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "040", + "name": "MONTANA STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "040" + } + }, + { + "id": "147", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "147", + "name": "MONTE CALVARIO CEMETERY", + "cemetery_type": "S", + "num": "147" + } + }, + { + "id": "971", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "971", + "name": "MOROVIS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "971" + } + }, + { + "id": "063", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "063", + "name": "MOUND CEMETERY", + "cemetery_type": "N", + "num": "063" + } + }, + { + "id": "064", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "064", + "name": "MOUND CITY", + "cemetery_type": "N", + "num": "064" + } + }, + { + "id": "863", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "863", + "name": "MOUND CITY NATIONAL CEMETERY - IL", + "cemetery_type": "N", + "num": "863" + } + }, + { + "id": "864", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "864", + "name": "MOUNTAIN HOME NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "864" + } + }, + { + "id": "065", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "065", + "name": "MT MORIAH SOLDIERS LOT", + "cemetery_type": "N", + "num": "065" + } + }, + { + "id": "066", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "066", + "name": "MT. MORIAH NAVAL PLOT", + "cemetery_type": "N", + "num": "066" + } + }, + { + "id": "067", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "067", + "name": "MT. PLEASANT CEMETERY", + "cemetery_type": "N", + "num": "067" + } + }, + { + "id": "865", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "865", + "name": "NASHVILLE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "865" + } + }, + { + "id": "866", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "866", + "name": "NATCHEZ NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "866" + } + }, + { + "id": "925", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "925", + "name": "NATIONAL CEMETERY OF THE ALLEGHENIES", + "cemetery_type": "N", + "num": "925" + } + }, + { + "id": "914", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "914", + "name": "NATIONAL MEMORIAL CEMETERY OF ARIZONA", + "cemetery_type": "N", + "num": "914" + } + }, + { + "id": "899", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "899", + "name": "NATIONAL MEMORIAL CEMETERY OF THE PACIFIC", + "cemetery_type": "N", + "num": "899" + } + }, + { + "id": "267", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "267", + "name": "NEBRASKA VETERANS CEMETERY AT ALLIANCE", + "cemetery_type": "S", + "num": "267" + } + }, + { + "id": "867", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "867", + "name": "NEW ALBANY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "867" + } + }, + { + "id": "868", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "868", + "name": "NEW BERN NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "868" + } + }, + { + "id": "087", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "087", + "name": "NEW HAMPSHIRE STATE CEMETERY", + "cemetery_type": "S", + "num": "087" + } + }, + { + "id": "155", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "155", + "name": "NEW YORK STATE VETERANS CEMETERY - FINGER LAKES", + "cemetery_type": "S", + "num": "155" + } + }, + { + "id": "077", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "077", + "name": "NORTH ALTON CONFEDERATE CEMETERY", + "cemetery_type": "N", + "num": "077" + } + }, + { + "id": "049", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "049", + "name": "NORTH DAKOTA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "049" + } + }, + { + "id": "035", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "035", + "name": "NORTH MISSISSIPPI VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "035" + } + }, + { + "id": "392", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "392", + "name": "NORTHEAST LOUISIANA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "392" + } + }, + { + "id": "130", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "130", + "name": "NORTHERN CALIFORNIA VETERANS CEMETERY AT REDDING", + "cemetery_type": "S", + "num": "130" + } + }, + { + "id": "099", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "099", + "name": "NORTHERN MAINE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "099" + } + }, + { + "id": "046", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "046", + "name": "NORTHERN NEVADA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "046" + } + }, + { + "id": "102", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "102", + "name": "NORTHERN WISCONSIN VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "102" + } + }, + { + "id": "131", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "131", + "name": "NORTHWEST LOUISIANA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "131" + } + }, + { + "id": "939", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "939", + "name": "NORTHWOODS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "939" + } + }, + { + "id": "068", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "068", + "name": "OAKDALE CEMETERY", + "cemetery_type": "N", + "num": "068" + } + }, + { + "id": "092", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "092", + "name": "OHIO VETERANS HOME CEMETERY", + "cemetery_type": "S", + "num": "092" + } + }, + { + "id": "918", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "918", + "name": "OHIO WESTERN RESERVE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "918" + } + }, + { + "id": "935", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "935", + "name": "OMAHA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "935" + } + }, + { + "id": "016", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "016", + "name": "OREGON TRAIL STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "016" + } + }, + { + "id": "148", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "148", + "name": "PENNSYLVANIA SOLDIERS AND SAILORS HOME CEMETERY", + "cemetery_type": "S", + "num": "148" + } + }, + { + "id": "819", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "819", + "name": "PHILADELPHIA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "819" + } + }, + { + "id": "933", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "933", + "name": "PIKES PEAK NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "933" + } + }, + { + "id": "078", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "078", + "name": "POINT LOOKOUT CONFEDERATE CEMETERY", + "cemetery_type": "N", + "num": "078" + } + }, + { + "id": "870", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "870", + "name": "PORT HUDSON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "870" + } + }, + { + "id": "900", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "900", + "name": "PRESCOTT NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "900" + } + }, + { + "id": "069", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "069", + "name": "PROSPECT HILL CEMETERY, PA", + "cemetery_type": "N", + "num": "069" + } + }, + { + "id": "070", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "070", + "name": "PROSPECT HILL CEMETERY, VT", + "cemetery_type": "N", + "num": "070" + } + }, + { + "id": "871", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "871", + "name": "PUERTO RICO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "871" + } + }, + { + "id": "872", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "872", + "name": "QUANTICO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "872" + } + }, + { + "id": "820", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "820", + "name": "QUINCY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "820" + } + }, + { + "id": "873", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "873", + "name": "RALEIGH NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "873" + } + }, + { + "id": "013", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "013", + "name": "RHODE ISLAND VETERAN MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "013" + } + }, + { + "id": "874", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "874", + "name": "RICHMOND NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "874" + } + }, + { + "id": "133", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "133", + "name": "RIO GRANDE VALLEY STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "133" + } + }, + { + "id": "901", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "901", + "name": "RIVERSIDE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "901" + } + }, + { + "id": "079", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "079", + "name": "ROCK ISLAND CONFEDERATE CEMETERY", + "cemetery_type": "N", + "num": "079" + } + }, + { + "id": "821", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "821", + "name": "ROCK ISLAND NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "821" + } + }, + { + "id": "005", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "005", + "name": "ROCKY GAP VETERANS CEMETERY", + "cemetery_type": "S", + "num": "005" + } + }, + { + "id": "902", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "902", + "name": "ROSEBURG NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "902" + } + }, + { + "id": "921", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "921", + "name": "SACRAMENTO VALLEY VA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "921" + } + }, + { + "id": "875", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "875", + "name": "SAINT AUGUSTINE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "875" + } + }, + { + "id": "876", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "876", + "name": "SALISBURY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "876" + } + }, + { + "id": "877", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "877", + "name": "SAN ANTONIO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "877" + } + }, + { + "id": "119", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "119", + "name": "SAN CARLOS APACHE TRIBE", + "cemetery_type": "S", + "num": "119" + } + }, + { + "id": "903", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "903", + "name": "SAN FRANCISCO NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "903" + } + }, + { + "id": "913", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "913", + "name": "SAN JOAQUIN VALLEY NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "913" + } + }, + { + "id": "085", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "085", + "name": "SANDHILLS STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "085" + } + }, + { + "id": "904", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "904", + "name": "SANTA FE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "904" + } + }, + { + "id": "931", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "931", + "name": "SARASOTA VA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "931" + } + }, + { + "id": "153", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "153", + "name": "SEAMAN FIRST CLASS BILLY TURNER VETERANS CEMETERY", + "cemetery_type": "S", + "num": "153" + } + }, + { + "id": "149", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "149", + "name": "SEMINOLE NATION AND VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "149" + } + }, + { + "id": "878", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "878", + "name": "SEVEN PINES NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "878" + } + }, + { + "id": "401", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "401", + "name": "SICANGU AKICITA OWICAHE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "401" + } + }, + { + "id": "125", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "125", + "name": "SISSETON WAHPETON OYATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "125" + } + }, + { + "id": "905", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "905", + "name": "SITKA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "905" + } + }, + { + "id": "941", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "941", + "name": "SNAKE RIVER CANYON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "941" + } + }, + { + "id": "138", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "138", + "name": "SOUTH DAKOTA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "138" + } + }, + { + "id": "924", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "924", + "name": "SOUTH FLORIDA VA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "924" + } + }, + { + "id": "404", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "404", + "name": "SOUTHEAST LOUISIANA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "404" + } + }, + { + "id": "098", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "098", + "name": "SOUTHERN ARIZONA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "098" + } + }, + { + "id": "389", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "389", + "name": "SOUTHERN MAINE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "389" + } + }, + { + "id": "045", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "045", + "name": "SOUTHERN NEVADA VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "045" + } + }, + { + "id": "060", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "060", + "name": "SOUTHERN WISCONSIN VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "060" + } + }, + { + "id": "126", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "126", + "name": "SOUTHWEST LOUISIANA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "126" + } + }, + { + "id": "186", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "186", + "name": "SOUTHWEST VIRGINIA VETERANS CEMETERY", + "cemetery_type": "S", + "num": "186" + } + }, + { + "id": "009", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "009", + "name": "SPRING GROVE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "009" + } + }, + { + "id": "879", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "879", + "name": "SPRINGFIELD NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "879" + } + }, + { + "id": "947", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "947", + "name": "ST. ALBANS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "947" + } + }, + { + "id": "880", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "880", + "name": "STAUNTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "880" + } + }, + { + "id": "150", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "150", + "name": "SUNSET ILLINOIS VETERAN HOME CEMETERY", + "cemetery_type": "S", + "num": "150" + } + }, + { + "id": "919", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "919", + "name": "TAHOMA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "919" + } + }, + { + "id": "937", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "937", + "name": "TALLAHASSEE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "937" + } + }, + { + "id": "044", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "044", + "name": "TENNESSEE STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "044" + } + }, + { + "id": "273", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "273", + "name": "TEXAS STATE VETERANS CEMETERY AT ABILENE", + "cemetery_type": "S", + "num": "273" + } + }, + { + "id": "115", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "115", + "name": "TN STATE VETERANS CEMETERY AT PARKERS CROSSROADS", + "cemetery_type": "S", + "num": "115" + } + }, + { + "id": "822", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "822", + "name": "TOGUS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "822" + } + }, + { + "id": "080", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "080", + "name": "UNION CONFEDERATE MONUMENT SITE", + "cemetery_type": "N", + "num": "080" + } + }, + { + "id": "408", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "408", + "name": "US MILITARY ACADEMY CEMETERY", + "cemetery_type": "M", + "num": "408" + } + }, + { + "id": "170", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "170", + "name": "US SOLDIERS' & AIRMEN'S HOME NATIONAL CEMETERY", + "cemetery_type": "A", + "num": "170" + } + }, + { + "id": "038", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "038", + "name": "UTAH STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "038" + } + }, + { + "id": "954", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "954", + "name": "VANCOUVER BARRACKS NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "954" + } + }, + { + "id": "399", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "399", + "name": "VERMONT VETERANS MEMORIAL CEMETERY", + "cemetery_type": "S", + "num": "399" + } + }, + { + "id": "097", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "097", + "name": "VETERANS MEMORIAL CEMETERY OF WESTERN COLORADO", + "cemetery_type": "S", + "num": "097" + } + }, + { + "id": "084", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "084", + "name": "VIRGINIA VETERANS CEMETERY AT AMELIA", + "cemetery_type": "S", + "num": "084" + } + }, + { + "id": "926", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "926", + "name": "WASHINGTON CROSSING NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "926" + } + }, + { + "id": "390", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "390", + "name": "WASHINGTON STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "390" + } + }, + { + "id": "145", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "145", + "name": "WATERVLIET ARSENAL POST CEMETERY", + "cemetery_type": "M", + "num": "145" + } + }, + { + "id": "048", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "048", + "name": "WEST TENNESSEE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "048" + } + }, + { + "id": "171", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "171", + "name": "WEST TEXAS STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "171" + } + }, + { + "id": "912", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "912", + "name": "WEST VIRGINIA NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "912" + } + }, + { + "id": "051", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "051", + "name": "WESTERN CAROLINA STATE VETERAN CEMETERY", + "cemetery_type": "S", + "num": "051" + } + }, + { + "id": "388", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "388", + "name": "WESTERN MONTANA STATE VETERANS CEMETERY", + "cemetery_type": "S", + "num": "388" + } + }, + { + "id": "936", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "936", + "name": "WESTERN NEW YORK NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "936" + } + }, + { + "id": "146", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "146", + "name": "WHITE EAGLE CEMETERY", + "cemetery_type": "S", + "num": "146" + } + }, + { + "id": "907", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "907", + "name": "WILLAMETTE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "907" + } + }, + { + "id": "881", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "881", + "name": "WILMINGTON NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "881" + } + }, + { + "id": "882", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "882", + "name": "WINCHESTER NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "882" + } + }, + { + "id": "823", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "823", + "name": "WOOD NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "823" + } + }, + { + "id": "071", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "071", + "name": "WOODLAND CEMETERY", + "cemetery_type": "N", + "num": "071" + } + }, + { + "id": "072", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "072", + "name": "WOODLAWN CEMETERY", + "cemetery_type": "N", + "num": "072" + } + }, + { + "id": "081", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "081", + "name": "WOODLAWN MONUMENT SITE", + "cemetery_type": "N", + "num": "081" + } + }, + { + "id": "824", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "824", + "name": "WOODLAWN NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "824" + } + }, + { + "id": "305", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "305", + "name": "YELLOWSTONE COUNTY VETERAN'S CEMETERY", + "cemetery_type": "S", + "num": "305" + } + }, + { + "id": "938", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "938", + "name": "YELLOWSTONE NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "938" + } + }, + { + "id": "405", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "405", + "name": "YUROK VETERANS CEMETERY", + "cemetery_type": "S", + "num": "405" + } + }, + { + "id": "883", + "type": "preneeds_cemeteries", + "attributes": { + "cemetery_id": "883", + "name": "ZACHARY TAYLOR NATIONAL CEMETERY", + "cemetery_type": "N", + "num": "883" + } + } + ] +} diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb new file mode 100644 index 00000000000..4df2629fcc4 --- /dev/null +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'securerandom' + +module IvcChampva + class VHA1010d + include Virtus.model(nullify_blank: true) + + attribute :data + + def initialize(data) + @data = data + @uuid = SecureRandom.uuid + end + + def metadata + { + 'veteranFirstName' => @data.dig('veteran', 'full_name', 'first'), + 'veteranMiddleName' => @data.dig('veteran', 'full_name', 'middle'), + 'veteranLastName' => @data.dig('veteran', 'full_name', 'last'), + 'sponsorFirstName' => @data.fetch('applicants', [])&.first&.dig('full_name', 'first'), + 'sponsorMiddleName' => @data.fetch('applicants', [])&.first&.dig('full_name', 'middle'), + 'sponsorLastName' => @data.fetch('applicants', [])&.first&.dig('full_name', 'last'), + 'fileNumber' => @data.dig('veteran', 'va_claim_number').presence || @data.dig('veteran', 'ssn_or_tin'), + 'zipCode' => @data.dig('veteran', 'address', 'postal_code') || '00000', + 'source' => 'VA Platform Digital Forms', + 'docType' => @data['form_number'], + 'businessLine' => 'CMP', + 'ssn_or_tin' => @data.dig('veteran', 'ssn_or_tin'), + 'uuid' => @uuid + } + end + + def handle_attachments(file_path) + uuid = @uuid # Generate the UUID as an instance variable + file_path_uuid = file_path.gsub('vha_10_10d-tmp', "#{uuid}_vha_10_10d-tmp") + File.rename(file_path, file_path_uuid) + attachments = get_attachments + file_paths = [file_path_uuid] + + if attachments.count.positive? + attachments.each_with_index do |attachment, index| + new_file_name = "#{uuid}_vha_10_10d-tmp#{index + 1}.pdf" + new_file_path = File.join(File.dirname(attachment), new_file_name) + File.rename(attachment, new_file_path) + file_paths << new_file_path + end + end + + file_paths + end + + def submission_date_config + { should_stamp_date?: false } + end + + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end + + private + + def get_attachments + attachments = [] + + # TODO: We need to look into generatings individual PDFs for each + # attachment based on what PEGA needs + supporting_documents = @data['supporting_docs'] + if supporting_documents + confirmation_codes = [] + supporting_documents&.map { |doc| confirmation_codes << doc['confirmation_code'] } + PersistentAttachment.where(guid: confirmation_codes).map { |attachment| attachments << attachment.to_pdf } + end + + attachments + end + end +end diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb new file mode 100644 index 00000000000..7a06cedb8c5 --- /dev/null +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module IvcChampva + class VHA107959c + include Virtus.model(nullify_blank: true) + + attribute :data + + def initialize(data) + @data = data + end + + def metadata + { + 'veteranFirstName' => @data.dig('applicants', 'full_name', 'first'), + 'veteranMiddleName' => @data.dig('applicants', 'full_name', 'middle'), + 'veteranLastName' => @data.dig('applicants', 'full_name', 'last'), + 'fileNumber' => @data.dig('applicants', 'ssn_or_tin'), + 'zipCode' => @data.dig('applicants', 'address', 'postal_code') || '00000', + 'source' => 'VA Platform Digital Forms', + 'docType' => @data['form_number'], + 'businessLine' => 'CMP' + } + end + + def submission_date_config + { + should_stamp_date?: false, + page_number: 1, + title_coords: [440, 690], + text_coords: [440, 670] + } + end + + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end + end +end diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb new file mode 100644 index 00000000000..e05845a492f --- /dev/null +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module IvcChampva + class VHA107959f1 + include Virtus.model(nullify_blank: true) + + attribute :data + + def initialize(data) + @data = data + end + + def metadata + { + 'veteranFirstName' => @data.dig('veteran', 'full_name', 'first'), + 'veteranMiddleName' => @data.dig('veteran', 'full_name', 'middle'), + 'veteranLastName' => @data.dig('veteran', 'full_name', 'last'), + 'fileNumber' => @data.dig('veteran', 'va_claim_number').presence || @data.dig('veteran', 'ssn'), + 'zipCode' => @data.dig('veteran', 'mailing_address', 'postal_code') || '00000', + 'source' => 'VA Platform Digital Forms', + 'docType' => @data['form_number'], + 'businessLine' => 'CMP' + } + end + + def submission_date_config + { should_stamp_date?: false } + end + + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end + end +end diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb new file mode 100644 index 00000000000..48671478e5f --- /dev/null +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module IvcChampva + class VHA107959f2 + include Virtus.model(nullify_blank: true) + + attribute :data + + def initialize(data) + @data = data + end + + def metadata + { + 'veteranFirstName' => @data.dig('veteran', 'full_name', 'first'), + 'veteranMiddleName' => @data.dig('veteran', 'full_name', 'middle'), + 'veteranLastName' => @data.dig('veteran', 'full_name', 'last'), + 'fileNumber' => @data.dig('veteran', 'va_claim_number').presence || @data.dig('veteran', 'ssn'), + 'zipCode' => @data.dig('veteran', 'mailing_address', 'postal_code') || '00000', + 'source' => 'VA Platform Digital Forms', + 'docType' => @data['form_number'], + 'businessLine' => 'CMP' + } + end + + def submission_date_config + { should_stamp_date?: false } + end + + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end + end +end diff --git a/modules/ivc_champva/app/services/ivc_champva/metadata_validator.rb b/modules/ivc_champva/app/services/ivc_champva/metadata_validator.rb new file mode 100644 index 00000000000..01fba1c9fbf --- /dev/null +++ b/modules/ivc_champva/app/services/ivc_champva/metadata_validator.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module IvcChampva + class MetadataValidator + def self.validate(metadata, zip_code_is_us_based: true) + validate_first_name(metadata) + .then { |m| validate_last_name(m) } + .then { |m| validate_file_number(m) } + .then { |m| validate_zip_code(m, zip_code_is_us_based) } + .then { |m| validate_source(m) } + .then { |m| validate_doc_type(m) } + end + + def self.validate_first_name(metadata) + validate_presence_and_stringiness(metadata['veteranFirstName'], 'veteran first name') + metadata['veteranFirstName'] = + I18n.transliterate(metadata['veteranFirstName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50) + + metadata + end + + def self.validate_last_name(metadata) + validate_presence_and_stringiness(metadata['veteranLastName'], 'veteran last name') + metadata['veteranLastName'] = + I18n.transliterate(metadata['veteranLastName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50) + + 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, zip_code_is_us_based) + zip_code = metadata['zipCode'] + if zip_code_is_us_based + validate_presence_and_stringiness(zip_code, 'zip code') + zip_code = zip_code.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/) + else + zip_code = '00000' + end + + 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_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 + end +end diff --git a/modules/ivc_champva/app/services/ivc_champva/pdf_filler.rb b/modules/ivc_champva/app/services/ivc_champva/pdf_filler.rb new file mode 100644 index 00000000000..db0ad19ac2b --- /dev/null +++ b/modules/ivc_champva/app/services/ivc_champva/pdf_filler.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'common/file_helpers' + +module IvcChampva + class PdfFiller + attr_accessor :form, :form_number, :name + + TEMPLATE_BASE = Rails.root.join('modules', 'simple_forms_api', 'templates') + + def initialize(form_number:, form:, name: nil) + @form = form + @form_number = form_number + @name = name || form_number + end + + def generate(current_loa = nil) + template_form_path = "#{TEMPLATE_BASE}/#{form_number}.pdf" + generated_form_path = "tmp/#{name}-tmp.pdf" + stamped_template_path = "tmp/#{name}-stamped.pdf" + pdftk = PdfForms.new(Settings.binaries.pdftk) + FileUtils.copy(template_form_path, stamped_template_path) + PdfStamper.stamp_pdf(stamped_template_path, form, current_loa) + if File.exist? stamped_template_path + pdftk.fill_form(stamped_template_path, generated_form_path, mapped_data, flatten: true) + generated_form_path + else + raise "stamped template file does not exist: #{stamped_template_path}" + end + ensure + Common::FileHelpers.delete_file_if_exists(stamped_template_path) if defined?(stamped_template_path) + end + + def mapped_data + template = Rails.root.join('modules', 'simple_forms_api', 'app', 'form_mappings', "#{form_number}.json.erb").read + b = binding + b.local_variable_set(:data, form) + result = ERB.new(template).result(b) + JSON.parse(escape_json_string(result)) + end + + def escape_json_string(str) + # remove characters that will break the json parser + # \u0000-\u001f: control characters in the ASCII table, + # characters such as null, tab, line feed, and carriage return + # \u0080-\u009f: control characters in the Latin-1 Supplement block of Unicode + # \u2000-\u201f: various punctuation and other non-printable characters in Unicode, + # including various types of spaces, dashes, and quotation marks. + str.gsub(/[\u0000-\u001f\u0080-\u009f\u2000-\u201f]/, ' ') + end + end +end diff --git a/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb b/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb new file mode 100644 index 00000000000..3b370d68793 --- /dev/null +++ b/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'central_mail/datestamp_pdf' + +module IvcChampva + class PdfStamper + FORM_REQUIRES_STAMP = %w[10-7959F-1].freeze + SUBMISSION_TEXT = 'Signed electronically and submitted via VA.gov at ' + SUBMISSION_DATE_TITLE = 'Application Submitted:' + + def self.stamp_pdf(stamped_template_path, form, current_loa) + form_number = form.data['form_number'] + if FORM_REQUIRES_STAMP.include? form_number + stamp_method = "stamp#{form_number.gsub('-', '')}".downcase + send(stamp_method, stamped_template_path, form) + end + + current_time = "#{Time.current.in_time_zone('America/Chicago').strftime('%H:%M:%S')} " + auth_text = case current_loa + when 3 + 'Signee signed with an identity-verified account.' + when 2 + 'Signee signed in but hasn’t verified their identity.' + else + 'Signee not signed in.' + end + stamp_text = SUBMISSION_TEXT + current_time + desired_stamps = [[10, 10, stamp_text]] + stamp(desired_stamps, stamped_template_path, auth_text, text_only: false) + + stamp_submission_date(stamped_template_path, form.submission_date_config) + end + + def self.stamp107959f1(stamped_template_path, form) + desired_stamps = [[26, 82.5, form.data['statement_of_truth_signature']]] + append_to_stamp = false + stamp(desired_stamps, stamped_template_path, append_to_stamp) + end + + def self.multistamp(stamped_template_path, signature_text, page_configuration, font_size = 16) + stamp_path = Common::FileHelpers.random_file_path + Prawn::Document.generate(stamp_path, margin: [0, 0]) do |pdf| + page_configuration.each do |config| + case config[:type] + when :text + pdf.draw_text signature_text, at: config[:position], size: font_size + when :new_page + pdf.start_new_page + end + end + end + + perform_multistamp(stamped_template_path, stamp_path) + rescue => e + Rails.logger.error 'Simple forms api - Failed to generate stamped file', message: e.message + raise + ensure + Common::FileHelpers.delete_file_if_exists(stamp_path) if defined?(stamp_path) + end + + def self.stamp(desired_stamps, stamped_template_path, append_to_stamp, text_only: true) + current_file_path = stamped_template_path + desired_stamps.each do |x, y, text| + out_path = CentralMail::DatestampPdf.new(current_file_path, append_to_stamp:).run(text:, x:, y:, text_only:, + size: 9) + current_file_path = out_path + end + File.rename(current_file_path, stamped_template_path) + end + + def self.perform_multistamp(stamped_template_path, stamp_path) + out_path = "#{Common::FileHelpers.random_file_path}.pdf" + pdftk = PdfFill::Filler::PDF_FORMS + pdftk.multistamp(stamped_template_path, stamp_path, out_path) + File.delete(stamped_template_path) + File.rename(out_path, stamped_template_path) + rescue + Common::FileHelpers.delete_file_if_exists(out_path) + raise + end + + def self.stamp_submission_date(stamped_template_path, config) + if config[:should_stamp_date?] + date_title_stamp_position = config[:title_coords] + date_text_stamp_position = config[:text_coords] + page_configuration = default_page_configuration + page_configuration[config[:page_number]] = { type: :text, position: date_title_stamp_position } + + multistamp(stamped_template_path, SUBMISSION_DATE_TITLE, page_configuration, 12) + + page_configuration = default_page_configuration + page_configuration[config[:page_number]] = { type: :text, position: date_text_stamp_position } + + multistamp(stamped_template_path, Time.current.in_time_zone('UTC').strftime('%H:%M %Z %D'), page_configuration, + 12) + end + end + + def self.default_page_configuration + [ + { type: :new_page }, + { type: :new_page }, + { type: :new_page }, + { type: :new_page } + ] + end + end +end diff --git a/modules/ivc_champva/app/services/ivc_champva/s3.rb b/modules/ivc_champva/app/services/ivc_champva/s3.rb new file mode 100644 index 00000000000..4d59c660f7a --- /dev/null +++ b/modules/ivc_champva/app/services/ivc_champva/s3.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# S3 Module for ivc form submission +# Return +# { success: Boolean, [error_message: String] } +module IvcChampva + class S3 + attr_reader :region, :access_key_id, :secret_access_key, :bucket_name + + def initialize(region:, access_key_id:, secret_access_key:, bucket_name:) + @region = region + @access_key_id = access_key_id + @secret_access_key = secret_access_key + @bucket_name = bucket_name + end + + def put_object(key, file, metadata = {}) + Datadog::Tracing.trace('S3 Put File(s)') do + # Convert nil values to empty strings in the metadata + metadata&.transform_values! { |value| value || '' } + + client.put_object({ + bucket: Settings.ivc_forms.s3.bucket, + key:, + body: File.read(file), + metadata:, + acl: 'public-read' + }) + { success: true } + rescue => e + { success: false, error_message: "S3 PutObject failure for #{file}: #{e.message}" } + end + end + + def upload_file(key, file) + obj = resource.bucket(bucket_name).object(key) + obj.upload_file(file) + + { success: true } + rescue => e + { success: false, error_message: "S3 Upload failure for #{file}: #{e.message}" } + end + + private + + def client + @client ||= Aws::S3::Client.new( + region: Settings.ivc_forms.s3.region, + access_key_id: Settings.ivc_forms.s3.aws_access_key_id, + secret_access_key: Settings.ivc_forms.s3.aws_secret_access_key + ) + end + + def resource + @resource ||= Aws::S3::Resource.new(client:) + end + end +end diff --git a/modules/ivc_champva/bin/rails b/modules/ivc_champva/bin/rails new file mode 100755 index 00000000000..9b6185dcb07 --- /dev/null +++ b/modules/ivc_champva/bin/rails @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +ENGINE_ROOT = File.expand_path('../..', __dir__) +ENGINE_PATH = File.expand_path('../../lib/ivcchampva/engine', __dir__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/modules/ivc_champva/config/routes.rb b/modules/ivc_champva/config/routes.rb new file mode 100644 index 00000000000..6bdddf4ae08 --- /dev/null +++ b/modules/ivc_champva/config/routes.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +IvcChampva::Engine.routes.draw do + namespace :v1, defaults: { format: 'json' } do + post '/forms', to: 'uploads#submit' + post '/forms/submit_supporting_documents', to: 'uploads#submit_supporting_documents' + end +end diff --git a/modules/ivc_champva/ivc_champva.gemspec b/modules/ivc_champva/ivc_champva.gemspec new file mode 100644 index 00000000000..29e05d6cb81 --- /dev/null +++ b/modules/ivc_champva/ivc_champva.gemspec @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +$LOAD_PATH.push File.expand_path('lib', __dir__) + +# Maintain your gem's version: +require 'ivc_champva/version' + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |spec| + spec.name = 'ivc_champva' + spec.version = IvcChampva::VERSION + spec.authors = ['Bryan Alexander', 'Don Shin'] + spec.email = ['bryan.alexander@adhocteam.us', 'donald.shin@agile6.com'] + spec.homepage = 'https://api.va.gov' + spec.summary = 'An api.va.gov module' + spec.description = 'This module is responsible for parsing and filling IVC CHAMPVA forms' + spec.license = 'CC0-1.0' + + spec.files = Dir['{app,config,db,lib}/**/*', 'Rakefile', 'README.md'] +end diff --git a/modules/ivc_champva/lib/ivc_champva.rb b/modules/ivc_champva/lib/ivc_champva.rb new file mode 100644 index 00000000000..9f795f59644 --- /dev/null +++ b/modules/ivc_champva/lib/ivc_champva.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'ivc_champva/engine' + +module IvcChampva + # Your code goes here... +end diff --git a/modules/ivc_champva/lib/ivc_champva/engine.rb b/modules/ivc_champva/lib/ivc_champva/engine.rb new file mode 100644 index 00000000000..e5e34440026 --- /dev/null +++ b/modules/ivc_champva/lib/ivc_champva/engine.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module IvcChampva + class Engine < ::Rails::Engine + isolate_namespace IvcChampva + config.generators.api_only = true + + initializer 'model_core.factories', after: 'factory_bot.set_factory_paths' do + FactoryBot.definition_file_paths << File.expand_path('../../spec/factories', __dir__) if defined?(FactoryBot) + end + end +end diff --git a/modules/ivc_champva/lib/ivc_champva/version.rb b/modules/ivc_champva/lib/ivc_champva/version.rb new file mode 100644 index 00000000000..d5ff6d86809 --- /dev/null +++ b/modules/ivc_champva/lib/ivc_champva/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module IvcChampva + VERSION = '0.1.0' +end diff --git a/modules/ivc_champva/lib/tasks/forms.rake b/modules/ivc_champva/lib/tasks/forms.rake new file mode 100644 index 00000000000..cdf0a7293df --- /dev/null +++ b/modules/ivc_champva/lib/tasks/forms.rake @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +PDFTK_HOMEBREW_PATH = '/opt/homebrew/bin/pdftk' +PDFTK_LOCAL_PATH = '/usr/local/bin/pdftk' +MODELS_PATH = 'modules/ivc_champva/app/models/ivc_champva' +MAPPINGS_PATH = 'modules/ivc_champva/app/form_mappings' + +# rubocop:disable Metrics/BlockLength +namespace :ivc_champva do + task :generate, [:form_path] => :environment do |_, args| + file_path = args[:form_path] + + reader = if File.exist?(PDFTK_HOMEBREW_PATH) + PdfForms.new(PDFTK_HOMEBREW_PATH) + else + PdfForms.new(PDFTK_LOCAL_PATH) + end + + form_name = file_path.split('/').last.split('.').first + + new_model_file = Rails.root.join(MODELS_PATH, "#{form_name}.rb") + + meta_data = reader.get_field_names(file_path).map do |field| + { pdf_field: field, data_type: 'String', attribute: field.split('.').last.split('[').first } + end + + metadata_method = <<-METADATA + def metadata + { + 'veteranFirstName' => @data.dig('veteran', 'full_name', 'first'), + 'veteranLastName' => @data.dig('veteran', 'full_name', 'last'), + 'fileNumber' => @data.dig('veteran', 'va_file_number').presence || @data.dig('veteran', 'ssn'), + 'zipCode' => @data.dig('veteran', 'address', 'postal_code'), + 'source' => 'VA Platform Digital Forms', + 'docType' => @data['form_number'], + 'businessLine' => 'CMP' + } + end + METADATA + + submission_date_config_method = <<-SUB_DATE_CONFIG + def submission_date_config + { should_stamp_date?: false } + end + SUB_DATE_CONFIG + + method_missing_method = <<-METHOD_MISSING + def method_missing(_, *args, _) + args + end + METHOD_MISSING + + respond_to_missing_method = <<-RESPOND_METHOD_MISSING + def respond_to_missing?(method) + method == :handle_attachments || super + end + RESPOND_METHOD_MISSING + + File.open(new_model_file, 'w') do |f| + f.puts '# frozen_string_literal: true' + f.puts '' + f.puts 'module IvcChampva' + f.puts " class #{form_name.upcase.gsub('_', '')}" + f.puts ' include Virtus.model(nullify_blank: true)' + f.puts '' + f.puts ' attribute :data' + + # Attributes are not yet needed. Their advantage is that they provide datatypes that can manipulated such as + # formatting dates. This is also a bit overkill for only central mail + # meta_data.each do |field| + # f.puts " attribute :#{field[:attribute].underscore}" + # end + + f.puts '' + f.puts ' def initialize(data)' + f.puts ' @data = data' + f.puts ' end' + + f.puts '' + + f.puts metadata_method + + f.puts submission_date_config_method + + f.puts method_missing_method + + f.puts respond_to_missing_method + + f.puts ' end' + f.puts 'end' + end + + puts "Created #{new_model_file}" + + # create the form mapping file + mapping_file = Rails.root.join(MAPPINGS_PATH, "#{form_name}.json.erb") + File.open(mapping_file, 'w') do |f| + f.puts '{' + meta_data.each_with_index do |field, index| + puts field.inspect + f.print " \"#{field[:pdf_field]}\": \"<%= data.dig('#{field[:attribute]}') %>" + f.puts "\"#{index + 1 == meta_data.size ? '' : ','}" + end + f.puts '}' + end + + puts "Created #{mapping_file}" + end +end +# rubocop:enable Metrics/BlockLength diff --git a/modules/ivc_champva/spec/fixtures/form_json/vha_10_10d.json b/modules/ivc_champva/spec/fixtures/form_json/vha_10_10d.json new file mode 100644 index 00000000000..5a81f0a6e50 --- /dev/null +++ b/modules/ivc_champva/spec/fixtures/form_json/vha_10_10d.json @@ -0,0 +1,106 @@ +{ + "form_number": "10-10D", + "veteran": { + "date_of_birth": "1987-02-02", + "date_of_marriage": "2005-04-06", + "is_deceased": true, + "date_of_death": "2021-01-08", + "is_active_service_death": true, + "address": { + "country": "USA", + "street": "1 First Ln", + "city": "Place", + "state": "AL", + "postal_code": "12345" + }, + "full_name": { + "first": "Veteran", + "middle": "B", + "last": "Surname" + }, + "ssn_or_tin": "222554444", + "va_claim_number": "123456789", + "phone_number": "9876543213" + }, + "applicants": [ + { + "address": { + "country": "USA", + "street": "2 Second St", + "city": "Town", + "state": "LA", + "postal_code": "16542" + }, + "full_name": { + "first": "Applicant", + "middle": "C", + "last": "Onceler" + }, + "ssn_or_tin": "123456644", + "gender": "F", + "email": "email@address.com", + "phone_number": "6543219877", + "date_of_birth": "1978-03-04", + "is_enrolled_in_medicare": true, + "has_other_health_insurance": true, + "vet_relationship": "Relative - Other" + }, + { + "address": { + "country": "USA", + "street": "3 Third Ave", + "city": "Ville", + "state": "AR", + "postal_code": "65478" + }, + "full_name": { + "first": "Appy", + "middle": "D", + "last": "Twos" + }, + "ssn_or_tin": "123664444", + "gender": "M", + "email": "mailme@domain.com", + "phone_number": "2345698777", + "date_of_birth": "1985-03-10", + "is_enrolled_in_medicare": true, + "has_other_health_insurance": true, + "vet_relationship": "Relative - Other" + }, + { + "address": { + "country": "USA", + "street": "4 Third Ave", + "city": "Mark", + "state": "AR", + "postal_code": "65478" + }, + "full_name": { + "first": "Homer", + "middle": "D", + "last": "Simpson" + }, + "ssn_or_tin": "123664444", + "gender": "M", + "email": "mailme@homer.com", + "phone_number": "2345698777", + "date_of_birth": "1985-03-10", + "is_enrolled_in_medicare": true, + "has_other_health_insurance": true, + "vet_relationship": "Relative - Other" + } + ], + "certification": { + "lastName": "Joe", + "firstName": "GI", + "middleInitial": "Canceled", + "streetAddress": "Hasbro", + "city": "Burbank", + "postal_code": "90041", + "state": "CA", + "relationship": "Agent", + "date": "2021-01-08", + "phone_number": "2345698777" + }, + "statement_of_truth_signature": "GI Joe" +} diff --git a/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c.json b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c.json new file mode 100644 index 00000000000..c5b194959ec --- /dev/null +++ b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c.json @@ -0,0 +1,75 @@ +{ + "form_number": "10-7959C", + "applicants": { + "address": { + "country": "USA", + "street": "2 Second St", + "city": "Town", + "state": "LA", + "postal_code": "16542" + }, + "full_name": { + "first": "Applicant", + "middle": "C", + "last": "Onceler" + }, + "ssn_or_tin": "123456644", + "home_phone": "6543219877" + }, + + "is_new_address": "T", + "male_or_female": "M", + + "part_a": { + "has_part_a": "T", + "part_a_effective_date": "2010-05-05", + "part_a_carrier": "United Health" + }, + "part_b": { + "has_part_b": "F", + "part_b_effective_date": "2010-05-05", + "part_d_carrier": "United Health" + }, + "part_d": { + "has_part_d": "F", + "part_b_effective_date": "2010-05-05", + "part_d_carrier": "United Health" + }, + "has_pharmacy_benefits": "F", + "has_medicare_advantage": "F", + "has_other_health_insurance": "F", + + "other_health_insurance1": { + "name_of_health_insurance": "Blue Cross", + "date_health_insurance": "2010-02-02", + "terminate_date_health_insurance": "2012-09-09", + "does_insurance": "F", + "does_prescription": "F", + "does_explain": "F", + "is_hmo": true, + "is_ppo": false, + "is_medicaid": false, + "is_rx_discount": false, + "is_medigap": false, + "is_medigap_type": "A", + "is_other_type": false, + "comments": "many stuff to say. Mary had a little lamb. Gingerbread man ate my muffins" + }, + + "other_health_insurance2": { + "name_of_health_insurance": "Aetna", + "date_health_insurance": "2010-02-02", + "terminate_date_health_insurance": "2012-09-09", + "does_insurance": "F", + "does_prescription": "F", + "does_explain": "F", + "is_hmo": true, + "is_ppo": false, + "is_medicaid": false, + "is_rx_discount": false, + "is_medigap": false, + "is_medigap_type": "A", + "is_other_type": false, + "comments": "Row row row your boat gently down the stream. Oh McDonald had a farm eee yaaa" + } +} diff --git a/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_1.json b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_1.json new file mode 100644 index 00000000000..b7e43026ac2 --- /dev/null +++ b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_1.json @@ -0,0 +1,31 @@ +{ + "form_number": "10-7959F-1", + "veteran": { + "date_of_birth": "02/02/1987", + "full_name": { + "first": "Veteran", + "middle": "B", + "last": "Surname" + }, + "physical_address": { + "country": "USA", + "street": "1 Physical Ln", + "city": "Place", + "state": "AL", + "postal_code": "12345" + }, + "mailing_address": { + "country": "USA", + "street": "1 Mail Ln", + "city": "Place", + "state": "PA", + "postal_code": "12345" + }, + "ssn": "222554444", + "va_claim_number": "123456789", + "phone_number": "9876543213", + "email_address": "veteran@mail.com" + }, + "statement_of_truth_signature": "Veteran B Surname", + "current_date": "01/01/2024" +} \ No newline at end of file diff --git a/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_2.json b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_2.json new file mode 100644 index 00000000000..308f182b3cc --- /dev/null +++ b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959f_2.json @@ -0,0 +1,32 @@ +{ + "form_number": "10-7959F-2", + "veteran": { + "date_of_birth": "02/02/1987", + "full_name": { + "first": "Veteran", + "middle": "B", + "last": "Surname" + }, + "physical_address": { + "country": "USA", + "street": "1 Physical Ln", + "city": "Place", + "state": "AL", + "postal_code": "12345" + }, + "mailing_address": { + "country": "USA", + "street": "1 Mail Ln", + "city": "Place", + "state": "PA", + "postal_code": "12345" + }, + "ssn": "222554444", + "va_claim_number": "123456789", + "phone_number": "9876543213", + "email_address": "veteran@mail.com" + }, + "statement_of_truth_signature": "Veteran B Surname", + "payment_to_be_sent_type": "Veteran", + "current_date": "01/01/2024" +} \ No newline at end of file diff --git a/modules/ivc_champva/spec/fixtures/test_file/test_file.pdf b/modules/ivc_champva/spec/fixtures/test_file/test_file.pdf new file mode 100644 index 00000000000..c72e23f5a84 Binary files /dev/null and b/modules/ivc_champva/spec/fixtures/test_file/test_file.pdf differ diff --git a/modules/ivc_champva/spec/models/vha_1010d_spec.rb b/modules/ivc_champva/spec/models/vha_1010d_spec.rb new file mode 100644 index 00000000000..8dd14c1488d --- /dev/null +++ b/modules/ivc_champva/spec/models/vha_1010d_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rails_helper' + +folder_path = 'modules/ivc_champva/spec/fixtures/test_file/' +file_name = 'test_file.pdf' +file_path = File.join(folder_path, file_name) + +RSpec.describe IvcChampva::VHA1010d do + let(:data) do + { + 'veteran' => { + 'full_name' => { 'first' => 'John', 'last' => 'Doe' }, + 'va_claim_number' => '123456789', + 'address' => { 'postal_code' => '12345' } + }, + 'form_number' => 'VHA1010d', + 'veteran_supporting_documents' => [ + { 'confirmation_code' => 'abc123' }, + { 'confirmation_code' => 'def456' } + ] + } + end + let(:vha1010d) { described_class.new(data) } + + describe '#metadata' do + it 'returns metadata for the form' do + metadata = vha1010d.metadata + + expect(metadata).to include( + 'veteranFirstName' => 'John', + 'veteranLastName' => 'Doe', + 'fileNumber' => '123456789', + 'zipCode' => '12345', + 'source' => 'VA Platform Digital Forms', + 'docType' => 'VHA1010d', + 'businessLine' => 'CMP' + ) + end + end + + describe '#handle_attachments' do + it 'calls CombinePDF.new' do + # Stub the CombinePDF.new method to return a double that does not perform any actions + allow(CombinePDF).to receive(:new).and_return(double('combined_pdf', save: nil)) + combined_pdf = CombinePDF.new + p combined_pdf # Output to console using `p` for inspection + + # Stub the file operation + allow(File).to receive(:exist?).with(file_path).and_return(true) + allow(File).to receive(:open).with(file_path, 'rb') + # Call the method under test + vha1010d.handle_attachments(file_path) + + # Verify that CombinePDF.new was called + expect(CombinePDF).to have_received(:new) + end + end +end diff --git a/modules/ivc_champva/spec/requests/v1/uploads_spec.rb b/modules/ivc_champva/spec/requests/v1/uploads_spec.rb new file mode 100644 index 00000000000..4e991a4ba99 --- /dev/null +++ b/modules/ivc_champva/spec/requests/v1/uploads_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Forms uploader', type: :request do + forms = [ + 'vha_10_10d.json', + 'vha_10_7959f_1.json', + 'vha_10_7959f_2.json' + ] + + describe '#submit' do + forms.each do |form| + fixture_path = Rails.root.join('modules', 'ivc_champva', 'spec', 'fixtures', 'form_json', form) + data = JSON.parse(fixture_path.read) + + it 'uploads a PDF file to S3' do + allow(IvcChampva::MetadataValidator).to receive(:validate) + allow_any_instance_of(Aws::S3::Client).to receive(:put_object).and_return(true) + + post '/ivc_champva/v1/forms', params: data + + expect(response).to have_http_status(:ok) + end + end + end + + describe '#submit_supporting_documents' do + it 'renders the attachment as json' do + clamscan = double(safe?: true) + allow(Common::VirusScan).to receive(:scan).and_return(clamscan) + file = fixture_file_upload('doctors-note.gif') + + data_sets = [ + { form_id: '10-10D', file: } + ] + + data_sets.each do |data| + expect do + post '/ivc_champva/v1/forms/submit_supporting_documents', params: data + end.to change(PersistentAttachment, :count).by(1) + + expect(response).to have_http_status(:ok) + resp = JSON.parse(response.body) + expect(resp['data']['attributes'].keys.sort).to eq(%w[confirmation_code name size]) + expect(PersistentAttachment.last).to be_a(PersistentAttachments::MilitaryRecords) + end + end + end +end diff --git a/modules/ivc_champva/spec/services/pdf_filler_spec.rb b/modules/ivc_champva/spec/services/pdf_filler_spec.rb new file mode 100644 index 00000000000..ff73e328f96 --- /dev/null +++ b/modules/ivc_champva/spec/services/pdf_filler_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'rails_helper' +require IvcChampva::Engine.root.join('spec', 'spec_helper.rb') + +describe IvcChampva::PdfFiller do + def self.test_pdf_fill(form_number, test_payload = form_number) + it 'fills out a PDF from a templated JSON file' do + expected_pdf_path = "tmp/#{form_number}-tmp.pdf" + + # remove the pdf if it already exists + FileUtils.rm_f(expected_pdf_path) + + # fill the PDF + data = JSON.parse(File.read("modules/ivc_champva/spec/fixtures/form_json/#{test_payload}.json")) + form = "IvcChampva::#{form_number.titleize.gsub(' ', '')}".constantize.new(data) + filler = IvcChampva::PdfFiller.new(form_number:, form:) + filler.generate + expect(File.exist?(expected_pdf_path)).to eq(true) + end + end + + test_pdf_fill 'vha_10_10d' + test_pdf_fill 'vha_10_7959f_1' + test_pdf_fill 'vha_10_7959f_2' + + def self.test_json_valid(mapping_file) + it 'validates json is parseable' do + expect do + JSON.parse(File.read("modules/simple_forms_api/app/form_mappings/#{mapping_file}")) + end.not_to raise_error + end + end + + test_json_valid 'vha_10_10d.json.erb' + test_json_valid 'vha_10_7959f_1.json.erb' + test_json_valid 'vha_10_7959f_2.json.erb' +end diff --git a/modules/ivc_champva/spec/services/pdf_stamper_spec.rb b/modules/ivc_champva/spec/services/pdf_stamper_spec.rb new file mode 100644 index 00000000000..cc2dc2732d9 --- /dev/null +++ b/modules/ivc_champva/spec/services/pdf_stamper_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' +require IvcChampva::Engine.root.join('spec', 'spec_helper.rb') + +describe IvcChampva::PdfStamper do + def self.test_pdf_stamp_error(stamp_method, test_payload) + it 'raises an error when generating stamped file' do + allow(Common::FileHelpers).to receive(:random_file_path).and_return('fake/stamp_path') + allow(Common::FileHelpers).to receive(:delete_file_if_exists) + allow(Prawn::Document).to receive(:generate).and_raise('Error generating stamped file') + + generated_form_path = 'fake/generated_form_path' + data = JSON.parse(File.read("modules/ivc_champva/spec/fixtures/form_json/#{test_payload}.json")) + form = "IvcChampva::#{test_payload.titleize.gsub(' ', '')}".constantize.new(data) + + expect do + IvcChampva::PdfStamper.send(stamp_method, generated_form_path, form) + end.to raise_error(RuntimeError, 'Error generating stamped file') + + expect(Common::FileHelpers).to have_received(:delete_file_if_exists).with('fake/stamp_path') + end + end + + test_pdf_stamp_error 'stamp107959f1', 'vha_10_7959f_1' +end diff --git a/modules/ivc_champva/spec/services/s3_spec.rb b/modules/ivc_champva/spec/services/s3_spec.rb new file mode 100644 index 00000000000..ace56c10427 --- /dev/null +++ b/modules/ivc_champva/spec/services/s3_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'common/file_helpers' + +describe IvcChampva::S3 do + let(:region) { 'test-region' } + let(:access_key_id) { 'test-access-key' } + let(:secret_access_key) { 'test-secret-key' } + let(:bucket_name) { 'test-bucket' } + let(:bucket) { instance_double(Aws::S3::Bucket) } + let(:object) { instance_double(Aws::S3::Object) } + + # rubocop:disable Style/HashSyntax + let(:s3_instance) do + IvcChampva::S3.new( + region: region, + access_key_id: access_key_id, + secret_access_key: secret_access_key, + bucket_name: bucket_name + ) + end + # rubocop:enable Style/HashSyntax + + describe '#put_object' do + let(:key) { 'test_file.pdf' } + let(:file_path) { 'spec/fixtures/files/doctors-note.pdf' } + + context 'when upload is successful' do + before do + allow_any_instance_of(Aws::S3::Client).to receive(:put_object).and_return(true) + end + + it 'returns success response' do + expect(s3_instance.put_object(key, file_path)).to eq({ success: true }) + end + end + + context 'when upload fails' do + before do + allow_any_instance_of(Aws::S3::Client).to receive(:put_object) + .and_raise(Aws::S3::Errors::ServiceError.new(nil, 'upload failed')) + end + + it 'returns error response' do + expect(s3_instance.put_object(key, file_path)) + .to eq({ success: false, error_message: "S3 PutObject failure for #{file_path}: upload failed" }) + end + end + end + + describe '#upload_file' do + let(:key) { 'test_form.pdf' } + let(:file_path) { 'test_form.pdf' } + + context 'when upload is successful' do + before do + allow_any_instance_of(Aws::S3::Resource).to receive(:bucket).and_return(bucket) + allow(bucket).to receive(:object).with(key).and_return(object) + allow(object).to receive(:upload_file).and_return(true) + end + + it 'returns success response' do + expect(s3_instance.upload_file(key, file_path)).to eq({ success: true }) + end + end + + context 'when upload fails' do + before do + allow_any_instance_of(Aws::S3::Resource).to receive(:bucket).and_return(bucket) + allow(bucket).to receive(:object).with(key).and_return(object) + allow(object).to receive(:upload_file).and_raise(Aws::S3::Errors::ServiceError.new(nil, 'upload failed')) + end + + it 'returns error response' do + expect(s3_instance.upload_file(key, file_path)) + .to eq({ success: false, error_message: "S3 Upload failure for #{file_path}: upload failed" }) + end + end + end +end diff --git a/modules/ivc_champva/spec/spec_helper.rb b/modules/ivc_champva/spec/spec_helper.rb new file mode 100644 index 00000000000..9b94ee05d3c --- /dev/null +++ b/modules/ivc_champva/spec/spec_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Configure Rails Envinronment +ENV['RAILS_ENV'] = 'test' + +require 'rspec/rails' + +RSpec.configure { |config| config.use_transactional_fixtures = true } diff --git a/modules/ivc_champva/templates/vha_10_10d.pdf b/modules/ivc_champva/templates/vha_10_10d.pdf new file mode 100644 index 00000000000..6ff50ae7030 Binary files /dev/null and b/modules/ivc_champva/templates/vha_10_10d.pdf differ diff --git a/modules/ivc_champva/templates/vha_10_7959c.pdf b/modules/ivc_champva/templates/vha_10_7959c.pdf new file mode 100644 index 00000000000..84248cdc042 Binary files /dev/null and b/modules/ivc_champva/templates/vha_10_7959c.pdf differ diff --git a/modules/ivc_champva/templates/vha_10_7959f_1.pdf b/modules/ivc_champva/templates/vha_10_7959f_1.pdf new file mode 100644 index 00000000000..d41a9d53eab Binary files /dev/null and b/modules/ivc_champva/templates/vha_10_7959f_1.pdf differ diff --git a/modules/ivc_champva/templates/vha_10_7959f_2.pdf b/modules/ivc_champva/templates/vha_10_7959f_2.pdf new file mode 100644 index 00000000000..26f6b7ab601 Binary files /dev/null and b/modules/ivc_champva/templates/vha_10_7959f_2.pdf differ diff --git a/spec/simplecov_helper.rb b/spec/simplecov_helper.rb index 7ded952eb15..cb82fd49798 100644 --- a/spec/simplecov_helper.rb +++ b/spec/simplecov_helper.rb @@ -69,6 +69,7 @@ def self.add_modules add_group 'DebtsApi', 'modules/debts_api/' add_group 'DhpConnectedDevices', 'modules/dhp_connected_devices/' add_group 'FacilitiesApi', 'modules/facilities_api/' + add_group 'IvcChampva', 'modules/ivc_champva/' add_group 'RepresentationManagement', 'modules/representation_management/' add_group 'SimpleFormsApi', 'modules/simple_forms_api/' add_group 'HealthQuest', 'modules/health_quest' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8b036e3f589..593140521e4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -62,6 +62,7 @@ add_group 'DebtsApi', 'modules/debts_api/' add_group 'DhpConnectedDevices', 'modules/dhp_connected_devices/' add_group 'FacilitiesApi', 'modules/facilities_api/' + add_group 'IvcChampva', 'modules/ivc_champva/' add_group 'RepresentationManagement', 'modules/representation_management/' add_group 'SimpleFormsApi', 'modules/simple_forms_api/' add_group 'HealthQuest', 'modules/health_quest/'