Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

96561 Update signer flow to automatically copy data + notify user - Form 10-10d #33032

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';

/**
* Displays a custom alert based on the current `certifierRole` value.
* Used to alert the user when we automatically fill in some portions
* of the form based on their previous answers, e.g., adding an applicant
* with the certifier's information if the certifier specified that they
* were applying for themselves.
* @param {object} formData Standard formdata object
* @param {string} role Value we expect certifierRole to have if we are to display this message
* @param {string} msg Optional custom text to display in our alert
* @returns
*/
export default function CustomPrefillMessage(formData, role, msg) {
const text =
msg ||
'We’ve prefilled some details based on information you provided earlier in this form. If you need to make changes, you can edit on this screen.';
const certifierRole =
formData.certifierRole ?? formData['view:certifierRole'];
if (certifierRole && certifierRole === role)
return (
<va-alert status="info">
<p className="vads-u-margin-y--0">
<strong>Note:</strong> {text}
</p>
</va-alert>
);
return <></>;
}
158 changes: 100 additions & 58 deletions src/applications/ivc-champva/10-10D/config/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import {
yesNoSchema,
yesNoUI,
radioSchema,
radioUI,
titleSchema,
titleUI,
} from 'platform/forms-system/src/js/web-component-patterns';
import VaTextInputField from 'platform/forms-system/src/js/web-component-fields/VaTextInputField';
import get from '@department-of-veterans-affairs/platform-forms-system/get';
import { blankSchema } from 'platform/forms-system/src/js/utilities/data/profile';
import SubmissionError from '../../shared/components/SubmissionError';
import CustomPrefillMessage from '../components/CustomPrefillAlert';
// import { fileUploadUi as fileUploadUI } from '../components/File/upload';

import { ssnOrVaFileNumberCustomUI } from '../components/CustomSsnPattern';
Expand All @@ -42,9 +44,12 @@ import {
onReviewPage,
applicantListSchema,
sponsorWording,
populateFirstApplicant,
page15aDepends,
} from '../helpers/utilities';
import {
certifierNameValidation,
certifierAddressValidation,
} from '../helpers/validations';
import { MAX_APPLICANTS, ADDITIONAL_FILES_HINT } from './constants';
import { applicantWording, getAgeInYears } from '../../shared/utilities';
import { sponsorNameDobConfig } from '../pages/Sponsor/sponsorInfoConfig';
Expand Down Expand Up @@ -107,16 +112,17 @@ import {
depends18f3,
} from '../pages/ApplicantSponsorMarriageDetailsPage';
import { ApplicantAddressCopyPage } from '../../shared/components/applicantLists/ApplicantAddressPage';
import {
signerContactInfoPage,
SignerContactInfoPage,
} from '../pages/SignerContactInfoPage';

import { hasReq } from '../../shared/components/fileUploads/MissingFileOverview';
import { fileWithMetadataSchema } from '../../shared/components/fileUploads/attachments';

// import mockData from '../tests/e2e/fixtures/data/test-data.json';
import FileFieldWrapped from '../components/FileUploadWrapper';

// Used by populateFirstApplicant fn:
const SIGNER_REL_PATH = 'signer-relationship';

// Control whether we show the file overview page by calling `hasReq` to
// determine if any required files have not been uploaded
function showFileOverviewPage(formData) {
Expand All @@ -134,7 +140,7 @@ const formConfig = {
rootUrl: manifest.rootUrl,
urlPrefix: '/',
transformForSubmit,
showReviewErrors: !environment.isProduction(),
showReviewErrors: true, // May want to hide in prod later, but for now keeping in due to complexity of this form
submitUrl: `${environment.API_URL}/ivc_champva/v1/forms`,
footerContent: GetFormHelp,
// submit: () =>
Expand Down Expand Up @@ -184,13 +190,44 @@ const formConfig = {
certifierInformation: {
title: 'Signer information',
pages: {
page1: {
// initialData: mockData.data,
path: 'signer-type',
title: 'Which of these best describes you?',
uiSchema: {
...titleUI('Your information'),
certifierRole: radioUI({
title: 'Which of these best describes you?',
required: () => true,
labels: {
applicant: 'I’m applying for benefits for myself',
sponsor:
'I’m a Veteran applying for benefits for my spouse or dependents',
other:
'I’m a representative applying for benefits on behalf of someone else',
},
// Changing this data on review messes up the ad hoc prefill
// mapping of certifier -> applicant|sponsor:
hideOnReview: true,
}),
},
schema: {
type: 'object',
required: ['certifierRole'],
properties: {
titleSchema,
certifierRole: radioSchema(['applicant', 'sponsor', 'other']),
},
},
},
Comment on lines +193 to +222
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This page collects the signer's certifierRole, which is the key to determining which data to duplicate + which extra screens to show.

page2: {
// initialData: mockData.data,
path: 'signer-info',
title: 'Certification',
uiSchema: {
...titleUI('Your name'),
certifierName: fullNameUI(),
'ui:validations': [certifierNameValidation],
},
schema: {
type: 'object',
Expand All @@ -210,6 +247,7 @@ const formConfig = {
'We’ll send any important information about this application to your address',
),
certifierAddress: addressUI(),
'ui:validations': [certifierAddressValidation],
},
schema: {
type: 'object',
Expand All @@ -223,27 +261,14 @@ const formConfig = {
page4: {
path: 'signer-contact-info',
title: 'Certification',
uiSchema: {
...titleUI(
'Your contact information',
'We use this information to contact you and verify other details.',
),
certifierPhone: phoneUI(),
certifierEmail: emailUI(),
},
schema: {
type: 'object',
required: ['certifierPhone', 'certifierEmail'],
properties: {
titleSchema,
certifierPhone: phoneSchema,
certifierEmail: emailSchema,
},
},
CustomPage: SignerContactInfoPage,
CustomPageReview: null,
...signerContactInfoPage,
Comment on lines +264 to +266
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to a custom page here so that we can do a custom onGoForward function. That custom function is what copies the certifier information to the appropriate other locations in the form.

},
page5: {
path: SIGNER_REL_PATH,
path: 'signer-relationship',
title: 'Certification',
depends: formData => get('certifierRole', formData) === 'other',
uiSchema: {
...titleUI(
'Your relationship to the applicant',
Expand All @@ -255,7 +280,6 @@ const formConfig = {
hint: 'Select all that apply',
required: () => true,
labels: {
applicant: 'I’m applying for benefits for myself',
spouse: 'I’m an applicant’s spouse',
child: 'I’m an applicant’s child',
parent: 'I’m an applicant’s parent',
Expand All @@ -280,22 +304,6 @@ const formConfig = {
'ui:options': {
updateSchema: (formData, formSchema) => {
const fs = formSchema;
if (
get(
'certifierRelationship.relationshipToVeteran.applicant',
formData,
)
) {
// If the certifier is also an applicant, pre-populate first app slot with certifier info:
populateFirstApplicant(
formData,
SIGNER_REL_PATH, // Used to verify we only ever apply this fn in one location
formData.certifierName,
formData.certifierEmail,
formData.certifierPhone,
formData.certifierAddress,
);
}
Comment on lines -283 to -298
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropping this updateSchema functionality - the data replication has been moved into the SignerContactInfoPage's custom onGoForward function.

// If 'other', open the text field to specify:
if (
get(
Expand Down Expand Up @@ -334,7 +342,6 @@ const formConfig = {
type: 'object',
properties: {
relationshipToVeteran: checkboxGroupSchema([
'applicant',
Copy link
Contributor Author

@michaelclement michaelclement Nov 15, 2024

Choose a reason for hiding this comment

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

No longer need this option because we get this info from the new page1.

'spouse',
'child',
'parent',
Expand Down Expand Up @@ -384,6 +391,7 @@ const formConfig = {
page8: {
path: 'sponsor-status',
title: 'Sponsor status',
depends: formData => get('certifierRole', formData) !== 'sponsor',
uiSchema: {
sponsorInfoTitle: titleUI(
'Sponsor status',
Expand All @@ -409,7 +417,9 @@ const formConfig = {
page9: {
path: 'sponsor-status-date',
title: 'Sponsor status details',
depends: formData => get('sponsorIsDeceased', formData),
depends: formData =>
get('certifierRole', formData) !== 'sponsor' &&
get('sponsorIsDeceased', formData),
uiSchema: {
sponsorInfoTitle: titleUI('Sponsor status details'),
sponsorDOD: dateOfDeathUI('When did the sponsor die?'),
Expand Down Expand Up @@ -440,7 +450,16 @@ const formConfig = {
uiSchema: {
...titleUI(
({ formData }) => `${sponsorWording(formData)} mailing address`,
'We’ll send any important information about this application to this address.',
({ formData }) => (
// Prefill message conditionally displays based on `certifierRole`
<>
<p>
We’ll send any important information about this application
to this address.
</p>
{CustomPrefillMessage(formData, 'sponsor')}
</>
),
Comment on lines +453 to +462
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the signer is also the sponsor, we duplicate some of the signer's information over to the sponsor fields and add this alert to the top of the page.

),
sponsorAddress: {
...addressUI({
Expand Down Expand Up @@ -500,15 +519,14 @@ const formConfig = {
title: 'Applicant information',
path: 'applicant-info',
uiSchema: {
...titleUI('Applicant name and date of birth', () => (
...titleUI('Applicant name and date of birth', ({ formData }) => (
// Prefill message conditionally displays based on `certifierRole`
<>
Enter the information for any applicants you want to enroll in
CHAMPVA benefits.
<br />
<br />
{`You can add up to ${MAX_APPLICANTS} applicants in a single application. If you
need to add more than ${MAX_APPLICANTS} applicants, you'll need to submit a
separate application for them.`}
<p>
Enter the information for any applicants you want to enroll in
CHAMPVA benefits.
</p>
{CustomPrefillMessage(formData, 'applicant')}
Comment on lines -503 to +529
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updating copy + adding the custom prefill message for when the signer is also an applicant. This lets the users know when we've duplicated the signer info for them so they don't have to re-type.

</>
)),
applicants: {
Expand Down Expand Up @@ -624,11 +642,20 @@ const formConfig = {
...titleUI(
({ formData }) =>
`${applicantWording(formData)} mailing address`,
({ formData, formContext }) => {
const txt =
'We’ll send any important information about your application to this address';
// Prefill message conditionally displays based on `certifierRole`
return formContext.pagePerItemIndex === '0' ? (
<>
<p>{txt}</p>
{CustomPrefillMessage(formData, 'applicant')}
</>
) : (
<p>{txt}</p>
);
},
Comment on lines +645 to +657
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as all the other uses of CustomPrefillMessage - if the signer is also an applicant, we copy their address over to the first applicant slot and show this blue alert.

),
'view:description': {
'ui:description':
'We’ll send any important information about your application to this address.',
},
applicantAddress: {
...addressUI({
labels: {
Expand All @@ -642,7 +669,6 @@ const formConfig = {
},
schema: applicantListSchema([], {
titleSchema,
'view:description': blankSchema,
applicantAddress: addressSchema(),
}),
},
Expand All @@ -657,8 +683,24 @@ const formConfig = {
items: {
...titleUI(
({ formData }) =>
`${applicantWording(formData)} contact information`,
'This information helps us contact you faster if we need to follow up with you about your application',
`${applicantWording(formData)} mailing address`,
({ formData, formContext }) => {
const txt = `We'll use this information to contact ${applicantWording(
formData,
false,
false,
true,
)} if we need to follow up about this application.`;
// Prefill message conditionally displays based on `certifierRole`
return formContext.pagePerItemIndex === '0' ? (
<>
<p>{txt}</p>
{CustomPrefillMessage(formData, 'applicant')}
</>
) : (
<p>{txt}</p>
);
},
Comment on lines +686 to +703
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same prefill alert functionality as referenced above.

),
applicantPhone: phoneUI(),
applicantEmailAddress: emailUI(),
Expand Down
23 changes: 12 additions & 11 deletions src/applications/ivc-champva/10-10D/config/submitTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ function fmtDate(date) {
: date;
}

// Returns list of object keys where obj[key] === true. Used
// to get the list of relationships a certifier has w/ the applicants:
function trueKeysOnly(obj) {
return Object.keys(obj ?? {}).filter(k => obj[k] === true);
}

Comment on lines +16 to +21
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This lets us take an object like:

let obj = { applicant: true, spouse: false, child: true };

and produce a list like:

// ['applicant', 'child']

// Simplify a relationship object from (potentially) multiple nested objects to a single string
function transformRelationship(obj) {
if (typeof obj === 'string') return obj;
Expand Down Expand Up @@ -107,18 +113,18 @@ export default function transformForSubmit(formConfig, form) {
// If certifier is also applicant, we don't need to fill out the
// bottom "Certification" section of the PDF:
certification:
transformedData?.certifierRelationship?.relationshipToVeteran
?.applicant === true
transformedData?.certifierRole === 'applicant'
Comment on lines -110 to +116
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that we have the certifierRole property, we no longer use the certifierRelationship object for this logic.

? { date: currentDate }
: {
date: currentDate,
lastName: transformedData?.certifierName?.last || '',
middleInitial: transformedData?.certifierName?.middle || '',
firstName: transformedData?.certifierName?.first || '',
phoneNumber: transformedData?.certifierPhone || '',
relationship: transformRelationship(
transformedData?.certifierRelationship,
),
// will produce string list of checkboxgroup keys (e.g., "spouse; child")
relationship: trueKeysOnly(
transformedData?.certifierRelationship?.relationshipToVeteran,
).join('; '),
Comment on lines -119 to +127
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, a certifier could have multiple relationships (e.g., spouse AND parent if there were two applicants listed). However, when we filled the PDF we were only showing the first of those values.

Now, we're producing a list like this:

relationship: trueKeysOnly(
  // { spouse: true, parent: true, child: false }
).join('; ')

Produces:

"spouse; parent"

which is then stamped in the PDF.

streetAddress:
transformedData?.certifierAddress?.streetCombined || '',
city: transformedData?.certifierAddress?.city || '',
Expand Down Expand Up @@ -156,12 +162,7 @@ export default function transformForSubmit(formConfig, form) {
dataPostTransform.supportingDocs = dataPostTransform.supportingDocs
.flat()
.concat(supDocs);
// TODO - remove usage of `certifierRole` in vets-api
dataPostTransform.certifierRole =
dataPostTransform?.certifierRelationship?.relationshipToVeteran
?.applicant === true
? 'applicant'
: 'other';
dataPostTransform.certifierRole = transformedData.certifierRole;
Comment on lines -159 to +165
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that we have certifierRole, this logic can be simplified - the BE has expected a certifierRole property all along.

dataPostTransform.statementOfTruthSignature =
transformedData.statementOfTruthSignature;
// `primaryContactInfo` is who BE callback API emails if there's a notification event
Expand Down
Loading
Loading