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

MCR-3600 & MCR-3605: 438 Attestation #2050

Merged
merged 22 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a8fccae
Add statutoryRegulatoryAttestation field to DB.
JasonLin0991 Nov 13, 2023
02c0ba7
Add statutoryRegulatoryAttestation to protobuf and domain types.
JasonLin0991 Nov 13, 2023
41a5aea
Add statutoryRegulatoryAttestation to front-end zod and missed one mock.
JasonLin0991 Nov 13, 2023
fe0c22c
Add feature flag.
JasonLin0991 Nov 13, 2023
fcaa07e
Add attestation question to Contract Details page.
JasonLin0991 Nov 13, 2023
97253e0
Add attestation question to summary page.
JasonLin0991 Nov 13, 2023
a79fbde
Fix unit test warnings and add tests for attestation question.
JasonLin0991 Nov 13, 2023
4af78e7
Add statutoryRegulatoryAttestation to postgres updateDraftContractWit…
JasonLin0991 Nov 13, 2023
d625f0d
Update cypress test commands.
JasonLin0991 Nov 13, 2023
6ac919b
Missed some more mock data.
JasonLin0991 Nov 13, 2023
c237159
Validate statutoryRegulatoryAttestation on submit.
JasonLin0991 Nov 13, 2023
ac97588
Missed handler.
JasonLin0991 Nov 14, 2023
56f7200
Mock `allFlags` method.
JasonLin0991 Nov 14, 2023
0b24aa2
Add 438 attestation tests to ContractDetails
JasonLin0991 Nov 14, 2023
d5051fb
Fix test name.
JasonLin0991 Nov 14, 2023
2e86e0a
Update one more cypress command
JasonLin0991 Nov 14, 2023
4837f2f
Add links to guides.
JasonLin0991 Nov 14, 2023
a0d18e2
Update services/app-web/src/constants/statutoryRegulatoryAttestation.ts
JasonLin0991 Nov 14, 2023
414ad9b
Move attestation question error to the top.
JasonLin0991 Nov 14, 2023
0d20159
Fix test for text change.
JasonLin0991 Nov 14, 2023
7ad9b86
Fix test for text change.
JasonLin0991 Nov 14, 2023
46ac5fc
cypress re-run
JasonLin0991 Nov 14, 2023
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,6 @@
BEGIN;

-- AlterTable
ALTER TABLE "ContractRevisionTable" ADD COLUMN "statutoryRegulatoryAttestation" BOOLEAN;

COMMIT;
1 change: 1 addition & 0 deletions services/app-api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ model ContractRevisionTable {
modifiedLengthOfContract Boolean?
modifiedNonRiskPaymentArrangements Boolean?
inLieuServicesAndSettings Boolean?
statutoryRegulatoryAttestation Boolean?
}

model RateRevisionTable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ function convertContractWithRatesToFormData(
contractRev.formData.modifiedNonRiskPaymentArrangements,
},
},
statutoryRegulatoryAttestation:
contractRev.formData.statutoryRegulatoryAttestation,
rateInfos,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const contractFormDataSchema = z.object({
modifiedNetworkAdequacyStandards: z.boolean().optional(),
modifiedLengthOfContract: z.boolean().optional(),
modifiedNonRiskPaymentArrangements: z.boolean().optional(),
statutoryRegulatoryAttestation: z.boolean().optional(),
})

const rateFormDataSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ function contractFormDataToDomainModel(
contractRevision.modifiedNonRiskPaymentArrangements ?? undefined,
inLieuServicesAndSettings:
contractRevision.inLieuServicesAndSettings ?? undefined,
statutoryRegulatoryAttestation:
contractRevision.statutoryRegulatoryAttestation ?? undefined,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ async function unlockContract(
currentRev.modifiedLengthOfContract,
modifiedNonRiskPaymentArrangements:
currentRev.modifiedNonRiskPaymentArrangements,
statutoryRegulatoryAttestation:
currentRev.statutoryRegulatoryAttestation,

contractDocuments: {
create: currentRev.contractDocuments.map((d) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ async function updateDraftContractWithRates(
modifiedLengthOfContract,
modifiedNonRiskPaymentArrangements,
inLieuServicesAndSettings,
statutoryRegulatoryAttestation,
} = formData

try {
Expand Down Expand Up @@ -504,6 +505,9 @@ async function updateDraftContractWithRates(
modifiedNonRiskPaymentArrangements: nullify(
modifiedNonRiskPaymentArrangements
),
statutoryRegulatoryAttestation: nullify(
statutoryRegulatoryAttestation
),
draftRates: {
disconnect: updateRates?.disconnectRates
? updateRates.disconnectRates.map((rate) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ describe.each(flagValueTestParameters)(
`Tests $testName`,
({ flagName, flagValue }) => {
const cmsUser = testCMSUser()
const mockLDService = testLDService({ [flagName]: flagValue })
const mockLDService = testLDService({
[flagName]: flagValue,
})

afterEach(() => {
jest.clearAllMocks()
jest.restoreAllMocks()
})
it('returns a StateSubmission if complete', async () => {
const server = await constructTestPostgresServer({
Expand Down Expand Up @@ -1252,5 +1254,74 @@ describe.each(flagValueTestParameters)(
)
}, 20000)
})

describe('Feature flagged 4348 attestation question test', () => {
const ldService = testLDService({
...mockLDService,
'438-attestation': true,
})

it('errors when contract 4348 attestation question is undefined', async () => {
const server = await constructTestPostgresServer({
ldService: ldService,
})

// setup
const initialPkg = await createAndUpdateTestHealthPlanPackage(
server,
{
statutoryRegulatoryAttestation: undefined,
}
)
const draft = latestFormData(initialPkg)
const draftID = draft.id

await new Promise((resolve) => setTimeout(resolve, 2000))

// submit
const submitResult = await server.executeOperation({
query: SUBMIT_HEALTH_PLAN_PACKAGE,
variables: {
input: {
pkgID: draftID,
},
},
})

expect(submitResult.errors).toBeDefined()
expect(submitResult.errors?.[0].extensions?.message).toBe(
'formData is missing required contract fields'
)
}, 20000)
it('successfully submits when contract 4348 attestation question is valid', async () => {
const server = await constructTestPostgresServer({
ldService: ldService,
})

// setup
const initialPkg = await createAndUpdateTestHealthPlanPackage(
server,
{
statutoryRegulatoryAttestation: false,
}
)
const draft = latestFormData(initialPkg)
const draftID = draft.id

await new Promise((resolve) => setTimeout(resolve, 2000))

// submit
const submitResult = await server.executeOperation({
query: SUBMIT_HEALTH_PLAN_PACKAGE,
variables: {
input: {
pkgID: draftID,
},
},
})

expect(submitResult.errors).toBeUndefined()
}, 20000)
})
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import type {
HealthPlanFormDataType,
LockedHealthPlanFormDataType,
} from '../../../../app-web/src/common-code/healthPlanFormDataType'
import type { LDService } from '../../launchDarkly/launchDarkly'
import type {
FeatureFlagSettings,
LDService,
} from '../../launchDarkly/launchDarkly'
import {
convertContractWithRatesToFormData,
convertContractWithRatesToUnlockedHPP,
Expand Down Expand Up @@ -106,7 +109,8 @@ const validateStatusAndUpdateInfo = (
// This strategy (returning a different type from validation) is taken from the
// "parse, don't validate" article: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
export function parseAndSubmit(
draft: HealthPlanFormDataType
draft: HealthPlanFormDataType,
featureFlag: FeatureFlagSettings
): LockedHealthPlanFormDataType | SubmissionError {
// Remove fields from edits on irrelevant logic branches
// - CONTRACT_ONLY submission type should not contain any CONTRACT_AND_RATE rates data.
Expand All @@ -125,10 +129,22 @@ export function parseAndSubmit(
submittedAt: new Date(),
}

if (isValidAndCurrentLockedHealthPlanFormData(maybeStateSubmission))
// Check for valid attestation. Returns true if flag is off.
const isValid438Attestation = !featureFlag['438-attestation']
? true
: Boolean(featureFlag['438-attestation']) &&
draft.statutoryRegulatoryAttestation !== undefined

if (
isValidAndCurrentLockedHealthPlanFormData(maybeStateSubmission) &&
isValid438Attestation
)
return maybeStateSubmission
else if (
!hasValidContract(maybeStateSubmission as LockedHealthPlanFormDataType)
!hasValidContract(
maybeStateSubmission as LockedHealthPlanFormDataType
) ||
!isValid438Attestation
) {
return {
code: 'INCOMPLETE',
Expand Down Expand Up @@ -184,6 +200,10 @@ export function submitHealthPlanPackageResolver(
context,
'rates-db-refactor'
)
const contract438Attestation = await launchDarkly.getFeatureFlag(
context,
'438-attestation'
)
const { user, span } = context
const { submittedReason, pkgID } = input
setResolverDetailsOnActiveSpan('submitHealthPlanPackage', user, span)
Expand Down Expand Up @@ -307,7 +327,9 @@ export function submitHealthPlanPackageResolver(
contractRevisionID = contractWithHistory.draftRevision.id

// Final clean + check of data before submit - parse to state submission
const maybeLocked = parseAndSubmit(initialFormData)
const maybeLocked = parseAndSubmit(initialFormData, {
'438-attestation': contract438Attestation,
})

if (isSubmissionError(maybeLocked)) {
const errMessage = maybeLocked.message
Expand Down Expand Up @@ -530,7 +552,9 @@ export function submitHealthPlanPackageResolver(
contractRevisionID = initialPackage.revisions[0].id

// Final clean + check of data before submit - parse to state submission
const maybeLocked = parseAndSubmit(initialFormData)
const maybeLocked = parseAndSubmit(initialFormData, {
'438-attestation': contract438Attestation,
})

if (isSubmissionError(maybeLocked)) {
const errMessage = maybeLocked.message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ describe.each(flagValueTestParameters)(
modifiedNonRiskPaymentArrangements: undefined,
},
},
statutoryRegulatoryAttestation: true,
rateInfos: [],
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ const createContractRevision = (
inLieuServicesAndSettings: null,
rateRevisions: [],
draftRates: [],
statutoryRegulatoryAttestation: null,
...revision,
}
}
Expand Down
3 changes: 3 additions & 0 deletions services/app-api/src/testHelpers/emailerHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ const mockContractAndRatesFormData = (
],
addtlActuaryCommunicationPreference: 'OACT_TO_ACTUARY',
...submissionPartial,
statutoryRegulatoryAttestation: false,
}
}

Expand Down Expand Up @@ -520,6 +521,7 @@ const mockContractOnlyFormData = (
},
],
addtlActuaryContacts: [],
statutoryRegulatoryAttestation: false,
...submissionPartial,
}
}
Expand Down Expand Up @@ -606,6 +608,7 @@ const mockContractAmendmentFormData = (
},
],
addtlActuaryCommunicationPreference: 'OACT_TO_ACTUARY',
statutoryRegulatoryAttestation: false,
...submissionPartial,
}
}
Expand Down
1 change: 1 addition & 0 deletions services/app-api/src/testHelpers/gqlHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ const createAndUpdateTestHealthPlanPackage = async (
modifiedNonRiskPaymentArrangements: true,
},
}
draft.statutoryRegulatoryAttestation = true

Object.assign(draft, partialUpdates)

Expand Down
1 change: 1 addition & 0 deletions services/app-proto/src/health_plan_form_data.proto
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ message ContractInfo {
repeated FederalAuthority federal_authorities = 5;
repeated Document contract_documents = 6;
optional ContractExecutionStatus contract_execution_status = 7;
optional bool statutory_regulatory_attestation = 8;

// Rates Refactor: No need for nested contract amendment info
optional ContractAmendmentInfo contract_amendment_info = 50;
Expand Down
4 changes: 4 additions & 0 deletions services/app-web/src/common-code/featureFlags/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const featureFlags = {
flag: 'rate-filters',
defaultValue: false,
},
CONTRACT_438_ATTESTATION: {
flag: '438-attestation',
defaultValue: false,
},
/**
* Used in testing to simulate errors in fetching flag value.
* This flag does not exist in LaunchDarkly dashboard so fetching this will return the defaultValue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ function basicLockedHealthPlanFormData(): LockedHealthPlanFormDataType {
programIDs: [mockMNState().programs[0].id],
submissionType: 'CONTRACT_ONLY',
riskBasedContract: false,
statutoryRegulatoryAttestation: false,
submissionDescription: 'A real submission',
documents: [],
contractType: 'BASE',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export type LockedHealthPlanFormDataType = {
stateContacts: StateContact[]
addtlActuaryContacts: ActuaryContact[]
addtlActuaryCommunicationPreference?: ActuaryCommunicationType
statutoryRegulatoryAttestation: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type UnlockedHealthPlanFormDataType = {
federalAuthorities: FederalAuthority[]
contractAmendmentInfo?: UnlockedContractAmendmentInfo
rateInfos: RateInfoType[]
statutoryRegulatoryAttestation?: boolean
}

export type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,8 @@ const toDomain = (
stateContacts: cleanedStateContacts,
addtlActuaryContacts: parseActuaryContacts(addtlActuaryContacts),
documents: parseProtoDocuments(formDataMessage.documents),
statutoryRegulatoryAttestation:
contractInfo?.statutoryRegulatoryAttestation ?? undefined,
}

// Now that we've gotten things into our combined draft & state domain format.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ function domainEnumStringToProtoString(
return protoEnumString
}

const domainDocsToProtoDocs = (domainDocs:SubmissionDocument[]): mcreviewproto.IDocument[] | null | undefined => {
const domainDocsToProtoDocs = (
domainDocs: SubmissionDocument[]
): mcreviewproto.IDocument[] | null | undefined => {
return domainDocs.map((doc) => ({
s3Url: doc.s3URL,
name: doc.name,
Expand Down Expand Up @@ -190,8 +192,12 @@ const toProtoBuffer = (
mcreviewproto.FederalAuthority,
domainData.federalAuthorities
),
contractDocuments: domainDocsToProtoDocs(domainData.contractDocuments),
contractDocuments: domainDocsToProtoDocs(
domainData.contractDocuments
),
contractAmendmentInfo: contractAmendmentInfo,
statutoryRegulatoryAttestation:
domainData.statutoryRegulatoryAttestation,
},
rateInfos:
domainData.rateInfos && domainData.rateInfos.length
Expand All @@ -215,8 +221,12 @@ const toProtoBuffer = (
rateDateCertified: domainDateToProtoDate(
rateInfo.rateDateCertified
),
rateDocuments: domainDocsToProtoDocs(rateInfo.rateDocuments),
supportingDocuments: domainDocsToProtoDocs(rateInfo.supportingDocuments),
rateDocuments: domainDocsToProtoDocs(
rateInfo.rateDocuments
),
supportingDocuments: domainDocsToProtoDocs(
rateInfo.supportingDocuments
),
rateCertificationName: generateRateName(
domainData,
rateInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const submissionDocumentSchema = z.object({
.optional()
),
sha256: z.string().optional(),
id: z.string().optional() // doesn't exist for newly created
id: z.string().optional(), // doesn't exist for newly created
})

const contractAmendmentInfoSchema = z.object({
Expand Down Expand Up @@ -168,6 +168,7 @@ const unlockedHealthPlanFormDataZodSchema = z.object({
federalAuthorities: z.array(federalAuthoritySchema),
contractAmendmentInfo: contractAmendmentInfoSchema.optional(),
rateInfos: z.array(rateInfosTypeSchema),
statutoryRegulatoryAttestation: z.boolean().optional(),
})

/*
Expand Down
Loading
Loading