From 36e528dbbe0c239bb144ae79605c06d689bf228f Mon Sep 17 00:00:00 2001
From: pearl-truss <67110378+pearl-truss@users.noreply.github.com>
Date: Thu, 7 Mar 2024 13:26:47 -0500
Subject: [PATCH] MCR-3944: ReviewSubmit updated to use contract and rates
(#2275)
* wip: reviewSubmit added to V2 and contractAndRate mock created for using in storybook
* fix tests for reviewsubmit -> RateDetailsSummarySection
* add V2 version of component, storybook and test for SubmissionTypeSummarySection
* wip push of contractdetailssummarysection and common code helpers
* fixes 99% of the ContractDetailsSummarySectionV2 test
* Add storybook file for contractdetails
* fix contractdetails storybook
* created component, test and storybook for ContactsSummarySection, updated graphql schema to match stateContacts for hpp with titleRole field
* add uploads tables back to rateDetailsSummary add functionality for draft and submitted rates
* wip integrating page
* update file structure so reviewsubmitV2 related files are under reviewsubmit
* code clean up in tests
* fixed issue with fetchContract not being passed the createdAt and updatedAt fields from prisma and the resolver
* remove unneeded console logs
* resolve local test error
* code cleanup, pr fixes
* fix test
* fix ui bug
* additional PR fixes
---
.../contractAndRates/contractTypes.ts | 2 +
.../src/mutations/submitRate.graphql | 2 +-
.../src/mutations/unlockRate.graphql | 8 +-
.../src/queries/fetchContract.graphql | 118 +--
.../app-graphql/src/queries/fetchRate.graphql | 10 +-
.../src/queries/indexRates.graphql | 2 +-
services/app-graphql/src/schema.graphql | 8 +-
.../app-web/src/common-code/ContractType.ts | 50 +
.../src/common-code/ContractTypeProvisions.ts | 168 +++
.../DataDetailContactField.tsx | 4 +-
.../UploadedDocumentsTable.tsx | 8 +-
services/app-web/src/index.tsx | 23 +-
.../ContactsSummarySectionV2.stories.tsx | 35 +
.../ContactsSummarySectionV2.test.tsx | 141 +++
.../ReviewSubmit/ContactsSummarySectionV2.tsx | 145 +++
...ontractDetailsSummarySectionV2.stories.tsx | 43 +
.../ContractDetailsSummarySectionV2.test.tsx | 977 ++++++++++++++++++
.../ContractDetailsSummarySectionV2.tsx | 336 ++++++
.../RateDetailsSummarySectionV2.stories.tsx | 46 +
.../RateDetailsSummarySectionV2.test.tsx | 786 ++++++++++++++
.../RateDetailsSummarySectionV2.tsx | 320 ++++++
.../V2/ReviewSubmit/ReviewSubmitV2.test.tsx | 148 +++
.../V2/ReviewSubmit/ReviewSubmitV2.tsx | 187 ++++
...SubmissionTypeSummarySectionV2.stories.tsx | 38 +
.../SubmissionTypeSummarySectionV2.test.tsx | 211 ++++
.../SubmissionTypeSummarySectionV2.tsx | 142 +++
.../StateSubmission/StateSubmissionForm.tsx | 5 +-
.../apolloMocks/contractGQLMock.ts | 34 +
.../apolloMocks/contractPackageDataMock.ts | 167 ++-
.../apolloMocks/healthPlanFormDataMock.ts | 2 +-
.../src/testHelpers/apolloMocks/index.ts | 7 +-
.../testHelpers/apolloMocks/rateDataMock.ts | 2 +-
32 files changed, 4038 insertions(+), 137 deletions(-)
create mode 100644 services/app-web/src/common-code/ContractType.ts
create mode 100644 services/app-web/src/common-code/ContractTypeProvisions.ts
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.stories.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.test.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.stories.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.test.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.stories.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.test.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.stories.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.test.tsx
create mode 100644 services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.tsx
create mode 100644 services/app-web/src/testHelpers/apolloMocks/contractGQLMock.ts
diff --git a/services/app-api/src/domain-models/contractAndRates/contractTypes.ts b/services/app-api/src/domain-models/contractAndRates/contractTypes.ts
index 1d0196b732..98708bee9a 100644
--- a/services/app-api/src/domain-models/contractAndRates/contractTypes.ts
+++ b/services/app-api/src/domain-models/contractAndRates/contractTypes.ts
@@ -12,6 +12,8 @@ const contractSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
status: statusSchema,
+ createdAt: z.date(),
+ updatedAt: z.date(),
stateCode: z.string(),
mccrsID: z.string().optional(),
stateNumber: z.number().min(1),
diff --git a/services/app-graphql/src/mutations/submitRate.graphql b/services/app-graphql/src/mutations/submitRate.graphql
index 96c3de8365..821b611bc6 100644
--- a/services/app-graphql/src/mutations/submitRate.graphql
+++ b/services/app-graphql/src/mutations/submitRate.graphql
@@ -101,7 +101,7 @@ mutation submitRate($input: SubmitRateInput!) {
submissionDescription
stateContacts {
name,
- title
+ titleRole
email
}
supportingDocuments {
diff --git a/services/app-graphql/src/mutations/unlockRate.graphql b/services/app-graphql/src/mutations/unlockRate.graphql
index e03faeb404..01472d638a 100644
--- a/services/app-graphql/src/mutations/unlockRate.graphql
+++ b/services/app-graphql/src/mutations/unlockRate.graphql
@@ -1,4 +1,4 @@
-fragment rateRevisionFragment on RateRevision {
+fragment rateRevisionFragmentForUnlock on RateRevision {
id
createdAt
updatedAt
@@ -82,7 +82,7 @@ fragment rateRevisionFragment on RateRevision {
submissionDescription
stateContacts {
name,
- title
+ titleRole
email
}
supportingDocuments {
@@ -143,11 +143,11 @@ mutation unlockRate($input: UnlockRateInput!) {
initiallySubmittedAt
draftRevision {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForUnlock
}
revisions {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForUnlock
}
}
}
diff --git a/services/app-graphql/src/queries/fetchContract.graphql b/services/app-graphql/src/queries/fetchContract.graphql
index 2afbd48ef6..cd9e5bc1bd 100644
--- a/services/app-graphql/src/queries/fetchContract.graphql
+++ b/services/app-graphql/src/queries/fetchContract.graphql
@@ -1,21 +1,7 @@
query fetchContract($input: FetchContractInput!) {
fetchContract(input: $input) {
contract {
- id
- status
- initiallySubmittedAt
- stateCode
- state {
- code
- name
- programs {
- id
- name
- fullName
- }
- }
-
- stateNumber
+ ...contractFields
draftRevision {
...contractRevisionFragment
@@ -25,11 +11,11 @@ query fetchContract($input: FetchContractInput!) {
...rateFields
draftRevision {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForFetchContract
}
revisions {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForFetchContract
}
}
@@ -40,6 +26,26 @@ query fetchContract($input: FetchContractInput!) {
}
}
+fragment contractFields on Contract {
+ id
+ status
+ createdAt
+ updatedAt
+ initiallySubmittedAt
+ stateCode
+ state {
+ code
+ name
+ programs {
+ id
+ name
+ fullName
+ }
+ }
+
+ stateNumber
+}
+
fragment rateFields on Rate {
id
createdAt
@@ -59,7 +65,7 @@ fragment rateFields on Rate {
initiallySubmittedAt
}
-fragment rateRevisionFragment on RateRevision {
+fragment rateRevisionFragmentForFetchContract on RateRevision {
id
createdAt
updatedAt
@@ -143,7 +149,7 @@ fragment rateRevisionFragment on RateRevision {
submissionDescription
stateContacts {
name
- title
+ titleRole
email
}
supportingDocuments {
@@ -194,7 +200,7 @@ fragment contractFormDataFragment on ContractFormData {
stateContacts {
name
- title
+ titleRole
email
}
@@ -236,50 +242,6 @@ fragment contractFormDataFragment on ContractFormData {
modifiedNonRiskPaymentArrangements
}
-fragment rateFormDataFragment on RateFormData {
- rateType
- rateCapitationType
- rateDocuments {
- name
- s3URL
- sha256
- }
- supportingDocuments {
- name
- s3URL
- sha256
- }
- rateDateStart
- rateDateEnd
- rateDateCertified
- amendmentEffectiveDateStart
- amendmentEffectiveDateEnd
- rateProgramIDs
- rateCertificationName
- certifyingActuaryContacts {
- id
- name
- titleRole
- email
- actuarialFirm
- actuarialFirmOther
- }
- addtlActuaryContacts {
- id
- name
- titleRole
- email
- actuarialFirm
- actuarialFirmOther
- }
- actuaryCommunicationPreference
- packagesWithSharedRateCerts {
- packageName
- packageId
- packageStatus
- }
-}
-
fragment contractRevisionFragment on ContractRevision {
id
createdAt
@@ -316,38 +278,16 @@ fragment packageSubmissionsFragment on ContractPackageSubmission {
...contractRevisionFragment
}
rateRevisions {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForFetchContract
}
}
fragment submittableRevisionsFields on SubmittableRevision {
... on ContractRevision {
- id
- createdAt
- updatedAt
- submitInfo {
- ...updateInformationFields
- }
- unlockInfo {
- ...updateInformationFields
- }
- formData {
- ...contractFormDataFragment
- }
+ ...contractRevisionFragment
}
... on RateRevision {
- id
- createdAt
- updatedAt
- unlockInfo {
- ...updateInformationFields
- }
- submitInfo {
- ...updateInformationFields
- }
- formData {
- ...rateFormDataFragment
- }
+ ...rateRevisionFragmentForFetchContract
}
}
diff --git a/services/app-graphql/src/queries/fetchRate.graphql b/services/app-graphql/src/queries/fetchRate.graphql
index 135daf1127..d14fcee1e3 100644
--- a/services/app-graphql/src/queries/fetchRate.graphql
+++ b/services/app-graphql/src/queries/fetchRate.graphql
@@ -1,4 +1,4 @@
-fragment rateRevisionFragment on RateRevision {
+fragment rateRevisionFragmentForFetchRate on RateRevision {
id
createdAt
updatedAt
@@ -81,8 +81,8 @@ fragment rateRevisionFragment on RateRevision {
riskBasedContract
submissionDescription
stateContacts {
- name
- title
+ name,
+ titleRole
email
}
supportingDocuments {
@@ -147,11 +147,11 @@ query fetchRate($input: FetchRateInput!) {
...rateFields
draftRevision {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForFetchRate
}
revisions {
- ...rateRevisionFragment
+ ...rateRevisionFragmentForFetchRate
}
}
}
diff --git a/services/app-graphql/src/queries/indexRates.graphql b/services/app-graphql/src/queries/indexRates.graphql
index a708d93831..bbea7e77cc 100644
--- a/services/app-graphql/src/queries/indexRates.graphql
+++ b/services/app-graphql/src/queries/indexRates.graphql
@@ -159,7 +159,7 @@ query indexRates {
submissionDescription
stateContacts {
name,
- title
+ titleRole
email
}
supportingDocuments {
diff --git a/services/app-graphql/src/schema.graphql b/services/app-graphql/src/schema.graphql
index 024fe9b0ea..31d5b24c64 100644
--- a/services/app-graphql/src/schema.graphql
+++ b/services/app-graphql/src/schema.graphql
@@ -876,7 +876,7 @@ enum FederalAuthority {
"Contact information for contacting states regarding their submission"
type StateContact {
name: String
- title: String
+ titleRole: String
email: String
}
@@ -993,7 +993,11 @@ type ContractFormData {
"If contract includes modifications to the length of the contract period"
modifiedLengthOfContract: Boolean
"If contract includes modifications to the non-risk payment arrangements"
- modifiedNonRiskPaymentArrangements: Boolean
+ modifiedNonRiskPaymentArrangements: Boolean,
+ "If contract has statutory regulatory attestation"
+ statutoryRegulatoryAttestation: Boolean,
+ "Description provided for if contract has statutory regulatory attestation"
+ statutoryRegulatoryAttestationDescription: String
}
"Either new capitation rates (NEW) or updates to previously certified capitation rates (AMENDMENT)"
diff --git a/services/app-web/src/common-code/ContractType.ts b/services/app-web/src/common-code/ContractType.ts
new file mode 100644
index 0000000000..9c797a4952
--- /dev/null
+++ b/services/app-web/src/common-code/ContractType.ts
@@ -0,0 +1,50 @@
+import { Contract, ContractRevision } from '../gen/gqlClient'
+
+const getContractRev = (contract: Contract): ContractRevision => {
+ if (contract.draftRevision) {
+ return contract.draftRevision
+ } else {
+ return contract.packageSubmissions[0].contractRevision
+ }
+}
+const isContractOnly = (contract: Contract): boolean => {
+ const contractRev = getContractRev(contract)
+ return contractRev.formData.submissionType === 'CONTRACT_ONLY'
+}
+
+
+const isBaseContract = (contract: Contract): boolean => {
+ const contractRev = getContractRev(contract)
+ return contractRev.formData.contractType === 'BASE'
+}
+
+const isContractAmendment = (contract: Contract): boolean => {
+ const contractRev = getContractRev(contract)
+ return contractRev.formData.contractType === 'AMENDMENT'
+}
+
+const isCHIPOnly = (contract: Contract): boolean => {
+ const contractRev = getContractRev(contract)
+ return contractRev.formData.populationCovered === 'CHIP'
+}
+
+const isContractAndRates = (contract: Contract): boolean => {
+ const contractRev = getContractRev(contract)
+ return contractRev.formData.submissionType === 'CONTRACT_AND_RATES'
+}
+
+const isContractWithProvisions = (contract: Contract): boolean =>
+ isContractAmendment(contract) || (isBaseContract(contract) && !isCHIPOnly(contract))
+
+const isSubmitted = (contract: Contract): boolean =>
+ contract.status === 'SUBMITTED'
+
+export {
+ isContractWithProvisions,
+ isBaseContract,
+ isContractAmendment,
+ isCHIPOnly,
+ isContractOnly,
+ isContractAndRates,
+ isSubmitted,
+}
diff --git a/services/app-web/src/common-code/ContractTypeProvisions.ts b/services/app-web/src/common-code/ContractTypeProvisions.ts
new file mode 100644
index 0000000000..da16b3a56e
--- /dev/null
+++ b/services/app-web/src/common-code/ContractTypeProvisions.ts
@@ -0,0 +1,168 @@
+import {
+ ModifiedProvisionsAmendmentRecord,
+ ModifiedProvisionsBaseContractRecord,
+ ModifiedProvisionsCHIPRecord,
+} from '../constants/modifiedProvisions'
+import { Contract } from '../gen/gqlClient'
+import {
+ CHIPProvisionType,
+ MedicaidBaseProvisionType,
+ MedicaidAmendmentProvisionType,
+ provisionCHIPKeys,
+ modifiedProvisionMedicaidBaseKeys,
+ modifiedProvisionMedicaidAmendmentKeys,
+ GeneralizedProvisionType,
+ isCHIPProvision,
+ isMedicaidAmendmentProvision,
+ isMedicaidBaseProvision,
+} from './healthPlanFormDataType/ModifiedProvisions'
+import {
+ isBaseContract,
+ isCHIPOnly,
+ isContractAmendment,
+ isContractWithProvisions,
+} from './ContractType'
+
+/*
+ Each provision key represents a Yes/No question asked on Contract Details.
+ This is a set of helper functions that each take in a submission and return provisions related data.
+
+ There are currently three distrinct variants of the provisions:
+ 1. For CHIP amendment
+ 2. For non CHIP base contract
+ 3. For non CHIP contract amendment
+
+ See also ModifiedProvisions.ts
+*/
+
+// Returns the list of provision keys that apply for given submission variant
+const generateApplicableProvisionsList = (
+ draftSubmission: Contract
+):
+ | CHIPProvisionType[]
+ | MedicaidBaseProvisionType[]
+ | MedicaidAmendmentProvisionType[] => {
+ if (isCHIPOnly(draftSubmission)) {
+ return isContractAmendment(draftSubmission)
+ ? (provisionCHIPKeys as unknown as CHIPProvisionType[])
+ : [] // there are no applicable provisions for CHIP base contract
+ } else if (isBaseContract(draftSubmission)) {
+ return modifiedProvisionMedicaidBaseKeys as unknown as MedicaidBaseProvisionType[]
+ } else {
+ return modifiedProvisionMedicaidAmendmentKeys as unknown as MedicaidAmendmentProvisionType[]
+ }
+}
+
+// Returns user-friendly label text for the provision based on the given submission variant
+const generateProvisionLabel = (
+ draftSubmission: Contract,
+ provision: GeneralizedProvisionType
+): string => {
+ if (isCHIPOnly(draftSubmission) && isCHIPProvision(provision)) {
+ return ModifiedProvisionsCHIPRecord[provision]
+ } else if (
+ isBaseContract(draftSubmission) &&
+ isMedicaidBaseProvision(provision)
+ ) {
+ return ModifiedProvisionsBaseContractRecord[provision]
+ } else if (
+ isContractAmendment(draftSubmission) &&
+ isMedicaidAmendmentProvision(provision)
+ ) {
+ return ModifiedProvisionsAmendmentRecord[provision]
+ } else {
+ console.warn('Coding Error: This is a fallback case and is unexpected.')
+ return 'Invalid Provision'
+ }
+}
+
+/*
+ Returns two lists of provisions keys sorted by whether they are set true/false
+ This function also quietly discard keys from the submission's own provisions list that are not valid for the current variant.
+ That functionality needed for unlocked contracts which can be edited in a non-linear fashion)
+*/
+const sortModifiedProvisions = (
+ contract: Contract
+): [GeneralizedProvisionType[], GeneralizedProvisionType[]] => {
+ const contractFormData = contract.draftRevision?.formData || contract.packageSubmissions[0].contractRevision.formData
+ const initialProvisions = {
+ inLieuServicesAndSettings: contractFormData.inLieuServicesAndSettings,
+ modifiedBenefitsProvided: contractFormData.modifiedBenefitsProvided,
+ modifiedGeoAreaServed: contractFormData.modifiedGeoAreaServed,
+ modifiedMedicaidBeneficiaries: contractFormData.modifiedMedicaidBeneficiaries,
+ modifiedRiskSharingStrategy: contractFormData.modifiedRiskSharingStrategy,
+ modifiedIncentiveArrangements: contractFormData.modifiedIncentiveArrangements,
+ modifiedWitholdAgreements: contractFormData.modifiedWitholdAgreements,
+ modifiedStateDirectedPayments: contractFormData.modifiedStateDirectedPayments,
+ modifiedPassThroughPayments: contractFormData.modifiedPassThroughPayments,
+ modifiedPaymentsForMentalDiseaseInstitutions: contractFormData.modifiedPaymentsForMentalDiseaseInstitutions,
+ modifiedMedicalLossRatioStandards: contractFormData.modifiedMedicalLossRatioStandards,
+ modifiedOtherFinancialPaymentIncentive: contractFormData.modifiedOtherFinancialPaymentIncentive,
+ modifiedEnrollmentProcess: contractFormData.modifiedEnrollmentProcess,
+ modifiedGrevienceAndAppeal: contractFormData.modifiedGrevienceAndAppeal,
+ modifiedNetworkAdequacyStandards: contractFormData.modifiedNetworkAdequacyStandards,
+ modifiedLengthOfContract: contractFormData.modifiedLengthOfContract,
+ modifiedNonRiskPaymentArrangements: contractFormData.modifiedNonRiskPaymentArrangements,
+ statutoryRegulatoryAttestation: contractFormData.statutoryRegulatoryAttestation,
+ statutoryRegulatoryAttestationDescription: contractFormData.statutoryRegulatoryAttestationDescription
+ }
+ const hasInitialProvisions = Object.values(initialProvisions).some((val) => val !== undefined)
+ const modifiedProvisions: GeneralizedProvisionType[] = []
+ const unmodifiedProvisions: GeneralizedProvisionType[] = []
+
+ if (hasInitialProvisions && isContractWithProvisions(contract)) {
+ const applicableProvisions =
+ generateApplicableProvisionsList(contract)
+
+ for (const provisionKey of applicableProvisions) {
+ const value = initialProvisions[provisionKey]
+ if (value === true) {
+ modifiedProvisions.push(provisionKey)
+ } else if (value === false) {
+ unmodifiedProvisions.push(provisionKey)
+ }
+ }
+ }
+
+ return [modifiedProvisions, unmodifiedProvisions]
+}
+
+/*
+ Returns boolean for weher a submission variant is missing required provisions
+ This is used to determine if we display the missing data warning on review and submit
+*/
+const isMissingProvisions = (contract: Contract): boolean => {
+ const requiredProvisions = generateApplicableProvisionsList(contract)
+ const [modifiedProvisions, unmodifiedProvisions] =
+ sortModifiedProvisions(contract)
+
+ return (
+ modifiedProvisions.length + unmodifiedProvisions.length <
+ requiredProvisions.length
+ )
+}
+
+/*
+ Returns lang string dictionary for variant
+*/
+const getProvisionDictionary = (
+ contract: Contract
+):
+ | typeof ModifiedProvisionsCHIPRecord
+ | typeof ModifiedProvisionsBaseContractRecord
+ | typeof ModifiedProvisionsAmendmentRecord => {
+ if (isCHIPOnly(contract)) {
+ return ModifiedProvisionsCHIPRecord
+ } else if (isBaseContract(contract)) {
+ return ModifiedProvisionsBaseContractRecord
+ } else {
+ return ModifiedProvisionsAmendmentRecord
+ }
+}
+export {
+ getProvisionDictionary,
+ sortModifiedProvisions,
+ generateApplicableProvisionsList,
+ generateProvisionLabel,
+ isMissingProvisions,
+}
diff --git a/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx b/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx
index 3b524904c5..9ce5d1bc5e 100644
--- a/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx
+++ b/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx
@@ -5,9 +5,9 @@ import {
} from '../../../common-code/healthPlanFormDataType'
import { getActuaryFirm } from '../../SubmissionSummarySection'
import { DataDetailMissingField } from '../DataDetailMissingField'
-import { ActuaryContact as GQLActuaryContact } from '../../../gen/gqlClient'
+import { ActuaryContact as GQLActuaryContact, StateContact as GQLStateContact } from '../../../gen/gqlClient'
-type Contact = ActuaryContact | StateContact | GQLActuaryContact
+type Contact = ActuaryContact | GQLActuaryContact | StateContact | GQLStateContact
function isCertainActuaryContact(contact: Contact): contact is ActuaryContact {
return (contact as ActuaryContact).actuarialFirm !== undefined
}
diff --git a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.tsx b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.tsx
index 9fda3b2252..072eed956c 100644
--- a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.tsx
+++ b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.tsx
@@ -92,7 +92,7 @@ export const UploadedDocumentsTable = ({
Edit {caption}
@@ -119,7 +119,7 @@ export const UploadedDocumentsTable = ({
if (refreshedDocs.length === 0) {
return (
-
{tableCaptionJSX}
+
{tableCaptionJSX}
@@ -137,7 +137,9 @@ export const UploadedDocumentsTable = ({
className={`${borderTopGradientStyles} ${supportingDocsTopMarginStyles}`}
>
- {tableCaptionJSX}
+
+ {tableCaptionJSX}
+
{!multipleDocumentsAllowed &&
documents.length > 1 &&
!isSubmitted && (
diff --git a/services/app-web/src/index.tsx b/services/app-web/src/index.tsx
index 33c55cd844..d4b4fbfeeb 100644
--- a/services/app-web/src/index.tsx
+++ b/services/app-web/src/index.tsx
@@ -1,6 +1,11 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
-import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
+import {
+ ApolloClient,
+ InMemoryCache,
+ HttpLink,
+ DefaultOptions,
+} from '@apollo/client'
import { Amplify } from 'aws-amplify'
import { loader } from 'graphql.macro'
@@ -57,17 +62,23 @@ Amplify.configure({
const authMode = process.env.REACT_APP_AUTH_MODE
assertIsAuthMode(authMode)
+const cache = new InMemoryCache()
+const defaultOptions: DefaultOptions = {
+ watchQuery: {
+ fetchPolicy: 'network-only',
+ },
+ query: {
+ fetchPolicy: 'network-only',
+ },
+}
const apolloClient = new ApolloClient({
link: new HttpLink({
uri: '/graphql',
fetch: authMode === 'LOCAL' ? localGQLFetch : fakeAmplifyFetch,
}),
- cache: new InMemoryCache({
- possibleTypes: {
- Submission: ['DraftSubmission', 'StateSubmission'],
- },
- }),
+ cache,
+ defaultOptions,
typeDefs: gqlSchema,
})
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.stories.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.stories.tsx
new file mode 100644
index 0000000000..c4e532cb2e
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.stories.tsx
@@ -0,0 +1,35 @@
+import { Story } from '@storybook/react'
+import ProvidersDecorator from '../../../../../../.storybook/providersDecorator'
+import {
+ ContactsSummarySectionProps,
+ ContactsSummarySection,
+} from './ContactsSummarySectionV2'
+import { mockContractPackageDraft } from '../../../../../testHelpers/apolloMocks'
+
+export default {
+ title: 'Components/SubmissionSummary/ContactsSummarySection/V2',
+ component: ContactsSummarySection,
+ parameters: {
+ componentSubtitle:
+ 'ContactsSummarySection displays the Contacts data for a Draft or State Submission',
+ },
+}
+
+const Template: Story = (args) => (
+
+)
+
+export const WithAction = Template.bind({})
+WithAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithAction.args = {
+ contract: mockContractPackageDraft(),
+ editNavigateTo: 'contract-details',
+}
+
+export const WithoutAction = Template.bind({})
+WithoutAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithoutAction.args = {
+ contract: mockContractPackageDraft(),
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.test.tsx
new file mode 100644
index 0000000000..75b7d178f2
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.test.tsx
@@ -0,0 +1,141 @@
+import { screen } from '@testing-library/react'
+import { renderWithProviders } from '../../../../../testHelpers/jestHelpers'
+import { ContactsSummarySection } from './ContactsSummarySectionV2'
+import {
+ mockContractPackageDraft,
+ mockContractPackageSubmitted,
+} from '../../../../../testHelpers/apolloMocks'
+
+describe('ContactsSummarySection', () => {
+ const draftSubmission = mockContractPackageDraft()
+ const stateSubmission = mockContractPackageSubmitted()
+ afterEach(() => jest.clearAllMocks())
+
+ it('can render draft submission without errors', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'State contacts',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'Additional actuary contacts',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('link', { name: 'Edit State contacts' })
+ ).toHaveAttribute('href', '/contacts')
+ })
+
+ it('can render state submission without errors', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'State contacts',
+ })
+ ).toBeInTheDocument()
+ expect(screen.queryByText('Edit')).not.toBeInTheDocument()
+ })
+
+ it('can render all state and actuary contact fields', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'State contacts',
+ })
+ ).toBeInTheDocument()
+ expect(screen.queryByText('Contact 1')).toBeInTheDocument()
+ // expect(screen.queryByText('State Contact 1')).toBeInTheDocument()
+ // expect(screen.queryByText('Test State Contact 1')).toBeInTheDocument()
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'Additional actuary contacts',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByText('Additional actuary contact')
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('link', {
+ name: 'additionalactuarycontact1@test.com',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByText('Actuaries’ communication preference')
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByText(
+ 'OACT can communicate directly with the state’s actuaries but should copy the state on all written communication and all appointments for verbal discussions.'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('can render only state contacts for contract only submission', () => {
+ const stateSubmission = mockContractPackageSubmitted()
+ stateSubmission.packageSubmissions[0].contractRevision.formData = {
+ ...stateSubmission.packageSubmissions[0].contractRevision.formData,
+ submissionType: 'CONTRACT_ONLY',
+ }
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'State contacts',
+ })
+ ).toBeInTheDocument()
+
+ expect(screen.queryByText('Actuary contacts')).not.toBeInTheDocument()
+ })
+
+ it('renders submitted package without errors', () => {
+ renderWithProviders(
+
+ )
+
+ // We should never display missing field text on submission summary for submitted packages
+ expect(
+ screen.queryByText(/You must provide this information/)
+ ).toBeNull()
+ })
+
+ it('does not include additional actuary contacts heading when this optional field is not provided', () => {
+ const draftSubmission = mockContractPackageDraft()
+ if (
+ draftSubmission.draftRates &&
+ draftSubmission.draftRates[0].draftRevision
+ ) {
+ draftSubmission.draftRates[0].draftRevision.formData = {
+ ...draftSubmission.draftRates[0].draftRevision.formData,
+ addtlActuaryContacts: [],
+ }
+ renderWithProviders(
+
+ )
+ }
+ expect(screen.queryByText(/Additional actuary contacts/)).toBeNull()
+ })
+})
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.tsx
new file mode 100644
index 0000000000..14503da250
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContactsSummarySectionV2.tsx
@@ -0,0 +1,145 @@
+import { Grid, GridContainer } from '@trussworks/react-uswds'
+import styles from '../../../../../components/SubmissionSummarySection/SubmissionSummarySection.module.scss'
+
+import { SectionHeader } from '../../../../../components/SectionHeader'
+import {
+ ActuaryFirmsRecord,
+ ActuaryCommunicationRecord,
+} from '../../../../../constants/healthPlanPackages'
+import { ActuaryContact } from '../../../../../common-code/healthPlanFormDataType'
+import {
+ DataDetail,
+ DataDetailContactField,
+} from '../../../../../components/DataDetail'
+import { SectionCard } from '../../../../../components/SectionCard'
+import { Contract } from '../../../../../gen/gqlClient'
+
+export type ContactsSummarySectionProps = {
+ contract: Contract
+ editNavigateTo?: string
+}
+
+export const getActuaryFirm = (actuaryContact: ActuaryContact): string => {
+ if (
+ actuaryContact.actuarialFirmOther &&
+ actuaryContact.actuarialFirm === 'OTHER'
+ ) {
+ return actuaryContact.actuarialFirmOther
+ } else if (
+ actuaryContact.actuarialFirm &&
+ ActuaryFirmsRecord[actuaryContact.actuarialFirm]
+ ) {
+ return ActuaryFirmsRecord[actuaryContact.actuarialFirm]
+ } else {
+ return ''
+ }
+}
+
+export const ContactsSummarySection = ({
+ contract,
+ editNavigateTo,
+}: ContactsSummarySectionProps): React.ReactElement => {
+ const isSubmitted = contract.status === 'SUBMITTED'
+ const contractFormData =
+ contract.draftRevision?.formData ||
+ contract.packageSubmissions[0].contractRevision.formData
+ const rateRev = contract.draftRates
+ ? contract.draftRates[0].draftRevision
+ : contract.packageSubmissions[0].rateRevisions[0]
+ return (
+
+
+
+
+
+
+ {contractFormData.stateContacts.length > 0 ? (
+ contractFormData.stateContacts.map(
+ (stateContact, index) => (
+
+ }
+ />
+ )
+ )
+ ) : (
+
+ )}
+
+
+
+
+ {contractFormData.submissionType === 'CONTRACT_AND_RATES' && (
+ <>
+ {rateRev?.formData?.addtlActuaryContacts !== undefined &&
+ rateRev.formData.addtlActuaryContacts.length > 0 && (
+
+
+
+
+ {rateRev.formData?.addtlActuaryContacts.map(
+ (actuaryContact, index) => (
+
+
+ }
+ />
+
+ )
+ )}
+
+
+
+ )}
+
+
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.stories.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.stories.tsx
new file mode 100644
index 0000000000..e6004c1ae5
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.stories.tsx
@@ -0,0 +1,43 @@
+import { Story } from '@storybook/react'
+import ProvidersDecorator from '../../../../../../.storybook/providersDecorator'
+import {
+ ContractDetailsSummarySectionV2Props,
+ ContractDetailsSummarySectionV2 as ContractDetailsSummarySection,
+} from './ContractDetailsSummarySectionV2'
+import { mockContractPackageDraft } from '../../../../../testHelpers/apolloMocks'
+
+export default {
+ title: 'Components/SubmissionSummary/ContractDetailsSummarySection/V2',
+ component: ContractDetailsSummarySection,
+ parameters: {
+ componentSubtitle:
+ 'ContractDetailsSummarySection displays the Contract Details data for a Draft or State Submission',
+ },
+}
+
+const Template: Story = (args) => (
+
+)
+
+export const WithAction = Template.bind({})
+WithAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithAction.args = {
+ contract: mockContractPackageDraft(),
+ documentDateLookupTable: {
+ fakesha: 'Fri Mar 25 2022 16:13:20 GMT-0500 (Central Daylight Time)',
+ previousSubmissionDate: '01/01/01'
+ },
+ editNavigateTo: 'contract-details',
+}
+
+export const WithoutAction = Template.bind({})
+WithoutAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithoutAction.args = {
+ contract: mockContractPackageDraft(),
+ documentDateLookupTable: {
+ fakesha: 'Fri Mar 25 2022 16:13:20 GMT-0500 (Central Daylight Time)',
+ previousSubmissionDate: '01/01/01'
+ },
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.test.tsx
new file mode 100644
index 0000000000..ff8f47d622
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.test.tsx
@@ -0,0 +1,977 @@
+import { screen, waitFor, within } from '@testing-library/react'
+import { renderWithProviders } from '../../../../../testHelpers/jestHelpers'
+import { ContractDetailsSummarySectionV2 as ContractDetailsSummarySection } from './ContractDetailsSummarySectionV2'
+import {
+ fetchCurrentUserMock,
+ mockContractPackageDraft,
+ mockContractPackageSubmitted,
+} from '../../../../../testHelpers/apolloMocks'
+import { testS3Client } from '../../../../../testHelpers/s3Helpers'
+import {
+ StatutoryRegulatoryAttestation,
+ StatutoryRegulatoryAttestationQuestion,
+} from '../../../../../constants/statutoryRegulatoryAttestation'
+
+describe('ContractDetailsSummarySection', () => {
+ const defaultApolloMocks = {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ }
+
+ it('can render draft submission without errors (review and submit behavior)', async () => {
+ const testContract = {
+ ...mockContractPackageDraft(),
+ documents: [
+ {
+ s3URL: 's3://bucketname/key/test1',
+ name: 'supporting docs test 1',
+ sha256: 'fakesha',
+ },
+ {
+ s3URL: 's3://bucketname/key/test3',
+ name: 'supporting docs test 3',
+ sha256: 'fakesha',
+ },
+ ],
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+
+ expect(
+ await screen.findByRole('heading', {
+ level: 2,
+ name: 'Contract details',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('link', { name: 'Edit Contract details' })
+ ).toHaveAttribute('href', '/contract-details')
+ expect(
+ screen.getByRole('link', {
+ name: /Edit Contract supporting documents/,
+ })
+ ).toHaveAttribute('href', '/documents')
+ expect(
+ screen.queryByRole('link', {
+ name: 'Download all contract documents',
+ })
+ ).toBeNull()
+ })
+
+ it('can render state submission on summary page without errors (submission summary behavior)', async () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'Contract details',
+ })
+ ).toBeInTheDocument()
+ expect(screen.queryByText('Edit')).not.toBeInTheDocument()
+
+ //expects loading button on component load
+ expect(screen.getByText('Loading')).toBeInTheDocument()
+
+ // expects download all button after loading has completed
+ await waitFor(() => {
+ expect(
+ screen.getByRole('link', {
+ name: 'Download all contract documents',
+ })
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('can render all contract details fields', async () => {
+ const contract = mockContractPackageDraft()
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ featureFlags: { '438-attestation': true },
+ }
+ )
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('definition', {
+ name: StatutoryRegulatoryAttestationQuestion,
+ })
+ ).toBeInTheDocument()
+ })
+
+ expect(
+ screen.getByRole('definition', { name: 'Contract status' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Contract amendment effective dates',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Managed care entities' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Active federal operating authority',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'This contract action includes new or modified provisions related to the following',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'This contract action does NOT include new or modified provisions related to the following',
+ })
+ ).toBeInTheDocument()
+ })
+
+ it('displays correct contract 438 attestation yes and no text and description', async () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ statutoryRegulatoryAttestation: false,
+ statutoryRegulatoryAttestationDescription: 'No compliance',
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ featureFlags: { '438-attestation': true },
+ }
+ )
+ }
+ await waitFor(() => {
+ expect(
+ screen.getByRole('definition', {
+ name: StatutoryRegulatoryAttestationQuestion,
+ })
+ ).toBeInTheDocument()
+ })
+
+ expect(
+ screen.getByRole('definition', {
+ name: 'Non-compliance description',
+ })
+ ).toBeInTheDocument()
+ expect(
+ await screen.findByText(StatutoryRegulatoryAttestation.NO)
+ ).toBeInTheDocument()
+ expect(await screen.findByText('No compliance')).toBeInTheDocument()
+ })
+
+ it('displays correct effective dates text for base contract', async () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ contractType: 'BASE',
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ expect(screen.getByText('Contract effective dates')).toBeInTheDocument()
+ })
+
+ it('displays correct effective dates text for contract amendment', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ expect(
+ screen.getByText('Contract amendment effective dates')
+ ).toBeInTheDocument()
+ })
+
+ it('render supporting contract docs when they exist', async () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ contractDocuments: [
+ {
+ s3URL: 's3://foo/bar/contract',
+ name: 'contract test 1',
+ sha256: 'fakesha',
+ },
+ ],
+ supportingDocuments: [
+ {
+ s3URL: 's3://bucketname/key/test1',
+ name: 'supporting docs test 1',
+ sha256: 'fakesha',
+ },
+ {
+ s3URL: 's3://bucketname/key/test2',
+ name: 'supporting docs test 2',
+ sha256: 'fakesha',
+ },
+ {
+ s3URL: 's3://bucketname/key/test3',
+ name: 'supporting docs test 3',
+ sha256: 'fakesha',
+ },
+ ],
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ await waitFor(() => {
+ const contractDocsTable = screen.getByRole('table', {
+ name: 'Contract',
+ })
+
+ const supportingDocsTable = screen.getByRole('table', {
+ name: /Contract supporting documents/,
+ })
+
+ expect(contractDocsTable).toBeInTheDocument()
+
+ expect(supportingDocsTable).toBeInTheDocument()
+
+ // check row content
+ expect(
+ within(contractDocsTable).getByRole('row', {
+ name: /contract test 1/,
+ })
+ ).toBeInTheDocument()
+ expect(
+ within(supportingDocsTable).getByText('supporting docs test 1')
+ ).toBeInTheDocument()
+ expect(
+ within(supportingDocsTable).getByText('supporting docs test 2')
+ ).toBeInTheDocument()
+ expect(
+ within(supportingDocsTable).getByText('supporting docs test 3')
+ ).toBeInTheDocument()
+
+ // check correct category on supporting docs
+ expect(
+ within(supportingDocsTable).getAllByText('Contract-supporting')
+ ).toHaveLength(3)
+ })
+ })
+
+ it('does not render supporting contract documents table when no documents exist', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+
+ expect(
+ screen.queryByRole('table', {
+ name: /Contract supporting documents/,
+ })
+ ).toBeNull()
+ })
+
+ it('does not render download all button when on previous submission', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ expect(
+ screen.queryByRole('button', {
+ name: 'Download all contract documents',
+ })
+ ).toBeNull()
+ })
+
+ it('renders federal authorities for a medicaid contract', async () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ // Add all medicaid federal authorities, as if medicaid contract being unlocked
+ federalAuthorities: [
+ 'STATE_PLAN',
+ 'WAIVER_1915B',
+ 'WAIVER_1115',
+ 'VOLUNTARY',
+ 'BENCHMARK',
+ 'TITLE_XXI',
+ ],
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ expect(
+ await screen.findByText(
+ 'Title XXI Separate CHIP State Plan Authority'
+ )
+ ).toBeInTheDocument()
+ expect(
+ await screen.findByText('1115 Waiver Authority')
+ ).toBeInTheDocument()
+ expect(
+ await screen.findByText('1932(a) State Plan Authority')
+ ).toBeInTheDocument()
+ expect(
+ await screen.findByText('1937 Benchmark Authority')
+ ).toBeInTheDocument()
+ })
+
+ it('renders federal authorities for a CHIP contract as expected, removing invalid authorities', async () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ populationCovered: 'CHIP',
+ // Add all medicaid federal authorities, as if medicaid contract being unlocked
+ federalAuthorities: [
+ 'STATE_PLAN',
+ 'WAIVER_1915B',
+ 'WAIVER_1115',
+ 'VOLUNTARY',
+ 'BENCHMARK',
+ 'TITLE_XXI',
+ ],
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ expect(
+ await screen.findByText(
+ 'Title XXI Separate CHIP State Plan Authority'
+ )
+ ).toBeInTheDocument()
+ expect(
+ await screen.findByText('1115 Waiver Authority')
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByText('1932(a) State Plan Authority')
+ ).not.toBeInTheDocument()
+ expect(
+ screen.queryByText('1937 Benchmark Authority')
+ ).not.toBeInTheDocument()
+ })
+
+ it('renders inline error when bulk URL is unavailable', async () => {
+ const s3Provider = {
+ ...testS3Client(),
+ getBulkDlURL: async (
+ _keys: string[],
+ _fileName: string
+ ): Promise => {
+ return new Error('Error: getBulkDlURL encountered an error')
+ },
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ s3Provider,
+ }
+ )
+
+ await waitFor(() => {
+ expect(
+ screen.getByText('Contract document download is unavailable')
+ ).toBeInTheDocument()
+ })
+ })
+
+ describe('contract provisions', () => {
+ it('renders provisions and MLR references for a medicaid amendment', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+
+ expect(
+ screen.getByText('Benefits provided by the managed care plans')
+ ).toBeInTheDocument()
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Benefits provided by the managed care plans'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Pass-through payments in accordance with 42 CFR § 438.6(d)'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(modifiedProvisions).getByText(/Risk-sharing strategy/)
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'State directed payments in accordance with 42 CFR § 438.6(c)'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Medical loss ratio standards in accordance with 42 CFR § 438.8'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Network adequacy standards'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Enrollment/disenrollment process'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ /Non-risk payment arrangements/
+ )
+ ).toBeInTheDocument()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Geographic areas served by the managed care plans'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Payments to MCOs and PIHPs for enrollees that are a patient in an institution for mental disease in accordance with 42 CFR § 438.6(e)'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Incentive arrangements in accordance with 42 CFR § 438.6(b)(2)'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Grievance and appeal system'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Length of the contract period'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Other financial, payment, incentive or related contractual provisions'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('renders provisions and MLR references for a medicaid base contract', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ contractType: 'BASE',
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).getByText(
+ 'In Lieu-of Services and Settings (ILOSs) in accordance with 42 CFR § 438.3(e)(2)'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Pass-through payments in accordance with 42 CFR § 438.6(d)'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(modifiedProvisions).getByText(/Risk-sharing strategy/)
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'State directed payments in accordance with 42 CFR § 438.6(c)'
+ )
+ ).toBeInTheDocument()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Payments to MCOs and PIHPs for enrollees that are a patient in an institution for mental disease in accordance with 42 CFR § 438.6(e)'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Incentive arrangements in accordance with 42 CFR § 438.6(b)(2)'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('renders provisions with correct MLR references for CHIP amendment', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ populationCovered: 'CHIP',
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+ expect(
+ screen.getByText('Benefits provided by the managed care plans')
+ ).toBeInTheDocument()
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Benefits provided by the managed care plans'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Network adequacy standards 42 CFR § 457.1218'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Enrollment/disenrollment process 42 CFR § 457.1210 and 457.1212'
+ )
+ ).toBeInTheDocument()
+ expect(
+ within(modifiedProvisions).getByText(
+ 'Non-risk payment arrangements 42 CFR 457.10 and 457.1201(c)'
+ )
+ ).toBeInTheDocument()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Grievance and appeal system 42 CFR § 457.1260'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Grievance and appeal system 42 CFR § 457.1260'
+ )
+ ).toBeInTheDocument()
+
+ expect(
+ within(unmodifiedProvisions).getByText(
+ 'Length of the contract period'
+ )
+ ).toBeInTheDocument()
+
+ // not a CHIP provision, even if saved from an unlock, it should not show up on summary page once population is CHIP
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ 'Other financial, payment, incentive or related contractual provisions'
+ )
+ ).toBeNull()
+ })
+
+ it('shows missing field error when provisions list is empty and section is in edit mode', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ inLieuServicesAndSettings: undefined,
+ modifiedBenefitsProvided: undefined,
+ modifiedGeoAreaServed: undefined,
+ modifiedMedicaidBeneficiaries: undefined,
+ modifiedRiskSharingStrategy: undefined,
+ modifiedIncentiveArrangements: undefined,
+ modifiedWitholdAgreements: undefined,
+ modifiedStateDirectedPayments: undefined,
+ modifiedPassThroughPayments: undefined,
+ modifiedPaymentsForMentalDiseaseInstitutions: undefined,
+ modifiedMedicalLossRatioStandards: undefined,
+ modifiedOtherFinancialPaymentIncentive: undefined,
+ modifiedEnrollmentProcess: undefined,
+ modifiedGrevienceAndAppeal: undefined,
+ modifiedNetworkAdequacyStandards: undefined,
+ modifiedLengthOfContract: undefined,
+ modifiedNonRiskPaymentArrangements: undefined,
+ statutoryRegulatoryAttestation: undefined,
+ statutoryRegulatoryAttestationDescription: undefined,
+ }
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeInTheDocument()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('shows missing field error when provisions list is incomplete and summary section is in edit mode', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ modifiedBenefitsProvided: false,
+ modifiedGrevienceAndAppeal: false,
+ modifiedNetworkAdequacyStandards: false,
+ modifiedLengthOfContract: null,
+ modifiedNonRiskPaymentArrangements: false,
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeInTheDocument()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('does not show missing field error when provisions list is incomplete and summary section is in view only mode', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ modifiedBenefitsProvided: false,
+ modifiedGrevienceAndAppeal: false,
+ modifiedNetworkAdequacyStandards: false,
+ modifiedLengthOfContract: true,
+ modifiedNonRiskPaymentArrangements: false,
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+ })
+
+ it('does not show missing field error for CHIP amendment when all provisions required are valid', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ modifiedBenefitsProvided: false,
+ modifiedGeoAreaServed: false,
+ modifiedMedicaidBeneficiaries: true,
+ modifiedMedicalLossRatioStandards: false,
+ modifiedOtherFinancialPaymentIncentive: false,
+ modifiedEnrollmentProcess: false,
+ modifiedGrevienceAndAppeal: false,
+ modifiedNetworkAdequacyStandards: false,
+ modifiedLengthOfContract: true,
+ modifiedNonRiskPaymentArrangements: false,
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+ })
+
+ it('does not show missing field error for Medicaid amendment when all provisions required are valid', () => {
+ const contract = mockContractPackageDraft()
+ if (contract.draftRevision) {
+ contract.draftRevision.formData = {
+ ...contract.draftRevision.formData,
+ inLieuServicesAndSettings: true,
+ modifiedBenefitsProvided: false,
+ modifiedGeoAreaServed: false,
+ modifiedMedicaidBeneficiaries: false,
+ modifiedRiskSharingStrategy: false,
+ modifiedIncentiveArrangements: false,
+ modifiedWitholdAgreements: false,
+ modifiedStateDirectedPayments: false,
+ modifiedPassThroughPayments: false,
+ modifiedPaymentsForMentalDiseaseInstitutions: false,
+ modifiedMedicalLossRatioStandards: false,
+ modifiedOtherFinancialPaymentIncentive: false,
+ modifiedEnrollmentProcess: false,
+ modifiedGrevienceAndAppeal: false,
+ modifiedNetworkAdequacyStandards: false,
+ modifiedLengthOfContract: false,
+ modifiedNonRiskPaymentArrangements: false,
+ }
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider: defaultApolloMocks,
+ }
+ )
+ }
+
+ const modifiedProvisions = screen.getByLabelText(
+ 'This contract action includes new or modified provisions related to the following'
+ )
+ expect(
+ within(modifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+
+ const unmodifiedProvisions = screen.getByLabelText(
+ 'This contract action does NOT include new or modified provisions related to the following'
+ )
+ expect(
+ within(unmodifiedProvisions).queryByText(
+ /You must provide this information/
+ )
+ ).toBeNull()
+ })
+ })
+})
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx
new file mode 100644
index 0000000000..55c195ad6c
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx
@@ -0,0 +1,336 @@
+import React, { useState } from 'react'
+import { DataDetail } from '../../../../../components/DataDetail'
+import { SectionHeader } from '../../../../../components/SectionHeader'
+import { UploadedDocumentsTable } from '../../../../../components/SubmissionSummarySection'
+import {
+ ContractExecutionStatusRecord,
+ FederalAuthorityRecord,
+ ManagedCareEntityRecord,
+} from '../../../../../constants/index'
+import { useS3 } from '../../../../../contexts/S3Context'
+import { formatCalendarDate } from '../../../../../common-code/dateHelpers'
+import { DoubleColumnGrid } from '../../../../../components/DoubleColumnGrid'
+import { DownloadButton } from '../../../../../components/DownloadButton'
+import { usePreviousSubmission } from '../../../../../hooks/usePreviousSubmission'
+import styles from '../../../../../components/SubmissionSummarySection/SubmissionSummarySection.module.scss'
+
+import {
+ sortModifiedProvisions,
+ isMissingProvisions,
+ getProvisionDictionary,
+} from '../../../../../common-code/ContractTypeProvisions'
+import { DataDetailCheckboxList } from '../../../../../components/DataDetail/DataDetailCheckboxList'
+import {
+ isBaseContract,
+ isCHIPOnly,
+ isContractWithProvisions,
+ isSubmitted,
+} from '../../../../../common-code/ContractType'
+import {
+ federalAuthorityKeysForCHIP,
+ CHIPFederalAuthority,
+} from '../../../../../common-code/healthPlanFormDataType'
+import { DocumentDateLookupTableType } from '../../../../../documentHelpers/makeDocumentDateLookupTable'
+import { recordJSException } from '../../../../../otelHelpers'
+import useDeepCompareEffect from 'use-deep-compare-effect'
+import { InlineDocumentWarning } from '../../../../../components/DocumentWarning'
+import { useLDClient } from 'launchdarkly-react-client-sdk'
+import { featureFlags } from '../../../../../common-code/featureFlags'
+import { Grid } from '@trussworks/react-uswds'
+import { booleanAsYesNoFormValue } from '../../../../../components/Form/FieldYesNo'
+import {
+ StatutoryRegulatoryAttestation,
+ StatutoryRegulatoryAttestationQuestion,
+} from '../../../../../constants/statutoryRegulatoryAttestation'
+import { SectionCard } from '../../../../../components/SectionCard'
+import { Contract } from '../../../../../gen/gqlClient'
+
+export type ContractDetailsSummarySectionV2Props = {
+ contract: Contract
+ editNavigateTo?: string
+ documentDateLookupTable: DocumentDateLookupTableType
+ isCMSUser?: boolean
+ submissionName: string
+ onDocumentError?: (error: true) => void
+}
+
+function renderDownloadButton(zippedFilesURL: string | undefined | Error) {
+ if (zippedFilesURL instanceof Error) {
+ return (
+
+ )
+ }
+ return (
+
+ )
+}
+
+export const ContractDetailsSummarySectionV2 = ({
+ contract,
+ editNavigateTo, // this is the edit link for the section. When this prop exists, summary section is loaded in edit mode
+ documentDateLookupTable,
+ submissionName,
+ onDocumentError,
+}: ContractDetailsSummarySectionV2Props): React.ReactElement => {
+ // Checks if submission is a previous submission
+ const isPreviousSubmission = usePreviousSubmission()
+ // Get the zip file for the contract
+ const { getKey, getBulkDlURL } = useS3()
+ const [zippedFilesURL, setZippedFilesURL] = useState<
+ string | undefined | Error
+ >(undefined)
+ const ldClient = useLDClient()
+
+ const contractFormData =
+ contract.draftRevision?.formData ||
+ contract.packageSubmissions[0].contractRevision.formData
+ const contract438Attestation = ldClient?.variation(
+ featureFlags.CONTRACT_438_ATTESTATION.flag,
+ featureFlags.CONTRACT_438_ATTESTATION.defaultValue
+ )
+
+ const attestationYesNo =
+ contractFormData.statutoryRegulatoryAttestation != null &&
+ booleanAsYesNoFormValue(contractFormData.statutoryRegulatoryAttestation)
+
+ const contractSupportingDocuments = contractFormData?.supportingDocuments
+ const isEditing = !isSubmitted(contract) && editNavigateTo !== undefined
+ const applicableFederalAuthorities = isCHIPOnly(contract)
+ ? contractFormData.federalAuthorities.filter((authority) =>
+ federalAuthorityKeysForCHIP.includes(
+ authority as CHIPFederalAuthority
+ )
+ )
+ : contractFormData?.federalAuthorities
+ const [modifiedProvisions, unmodifiedProvisions] =
+ sortModifiedProvisions(contract)
+ const provisionsAreInvalid = isMissingProvisions(contract) && isEditing
+
+ useDeepCompareEffect(() => {
+ // skip getting urls of this if this is a previous contract or draft
+ if (!isSubmitted(contract) || isPreviousSubmission) return
+
+ // get all the keys for the documents we want to zip
+ async function fetchZipUrl() {
+ const keysFromDocs = contractFormData.contractDocuments
+ .concat(contractSupportingDocuments)
+ .map((doc) => {
+ const key = getKey(doc.s3URL)
+ if (!key) return ''
+ return key
+ })
+ .filter((key) => key !== '')
+
+ // call the lambda to zip the files and get the url
+ const zippedURL = await getBulkDlURL(
+ keysFromDocs,
+ submissionName + '-contract-details.zip',
+ 'HEALTH_PLAN_DOCS'
+ )
+ if (zippedURL instanceof Error) {
+ const msg = `ERROR: getBulkDlURL failed to generate contract document URL. ID: ${contract.id} Message: ${zippedURL}`
+ console.info(msg)
+
+ if (onDocumentError) {
+ onDocumentError(true)
+ }
+
+ recordJSException(msg)
+ }
+
+ setZippedFilesURL(zippedURL)
+ }
+
+ void fetchZipUrl()
+ }, [
+ getKey,
+ getBulkDlURL,
+ contract,
+ contractSupportingDocuments,
+ submissionName,
+ isPreviousSubmission,
+ ])
+
+ return (
+
+
+ {isSubmitted(contract) &&
+ !isPreviousSubmission &&
+ renderDownloadButton(zippedFilesURL)}
+
+
+ {contract438Attestation && (
+
+
+ {attestationYesNo !== false &&
+ attestationYesNo !== undefined && (
+
+ )}
+
+ {attestationYesNo === 'NO' && (
+
+
+
+ )}
+
+ )}
+
+
+
+
+ )
+ }
+ />
+
+ )
+ }
+ />
+
+ {isContractWithProvisions(contract) && (
+
+
+ {provisionsAreInvalid ? null : (
+
+ )}
+
+
+
+ {provisionsAreInvalid ? null : (
+
+ )}
+
+
+ )}
+
+ {contractFormData.contractDocuments && (
+
+ )}
+ {contractSupportingDocuments && (
+
+ )}
+
+ )
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.stories.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.stories.tsx
new file mode 100644
index 0000000000..7bfa93a01f
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.stories.tsx
@@ -0,0 +1,46 @@
+import { Story } from '@storybook/react'
+import ProvidersDecorator from '../../../../../../.storybook/providersDecorator'
+import {
+ RateDetailsSummarySectionV2Props,
+ RateDetailsSummarySectionV2,
+} from './RateDetailsSummarySectionV2'
+import { mockContractPackageDraft } from '../../../../../testHelpers/apolloMocks'
+
+export default {
+ title: 'Components/SubmissionSummary/RateDetailsSummarySection/V2',
+ component: RateDetailsSummarySectionV2,
+ parameters: {
+ componentSubtitle:
+ 'RateDetailsSummarySection displays the Rate Details data for a Draft or State Submission',
+ },
+}
+
+const Template: Story = (args) => (
+
+)
+
+export const WithAction = Template.bind({})
+WithAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+const contract = mockContractPackageDraft()
+
+WithAction.args = {
+ contract: contract,
+ editNavigateTo: 'contract-details',
+ submissionName: 'StoryBook',
+ statePrograms: [],
+ documentDateLookupTable: {
+ fakesha: 'Fri Mar 25 2022 16:13:20 GMT-0500 (Central Daylight Time)',
+ previousSubmissionDate: '01/01/01'
+ },
+}
+
+export const WithoutAction = Template.bind({})
+WithoutAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+WithoutAction.args = {
+ contract: contract,
+ submissionName: 'StoryBook',
+ documentDateLookupTable: {
+ fakesha: 'Fri Mar 25 2022 16:13:20 GMT-0500 (Central Daylight Time)',
+ previousSubmissionDate: '01/01/01'
+ },
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.test.tsx
new file mode 100644
index 0000000000..e3909417b7
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.test.tsx
@@ -0,0 +1,786 @@
+import { screen, waitFor, within } from '@testing-library/react'
+import {
+ mockContractPackageDraft,
+ mockContractPackageSubmitted,
+ mockMNState,
+ fetchCurrentUserMock,
+ mockValidCMSUser,
+} from '../../../../../testHelpers/apolloMocks'
+import { renderWithProviders } from '../../../../../testHelpers/jestHelpers'
+import { RateDetailsSummarySectionV2 as RateDetailsSummarySection } from './RateDetailsSummarySectionV2'
+import { Rate } from '../../../../../gen/gqlClient'
+import { testS3Client } from '../../../../../testHelpers/s3Helpers'
+
+describe('RateDetailsSummarySection', () => {
+ const draftContract = mockContractPackageDraft()
+ const submittedContract = mockContractPackageSubmitted()
+ const statePrograms = mockMNState().programs
+ const makeMockRateInfos = (): Rate[] => {
+ return [
+ {
+ id: '1234',
+ createdAt: new Date('01/01/2021'),
+ updatedAt: new Date('01/01/2021'),
+ status: 'DRAFT',
+ state: mockMNState(),
+ stateCode: 'MN',
+ stateNumber: 5,
+ revisions: [],
+ draftRevision: {
+ id: '1234',
+ createdAt: new Date('01/01/2021'),
+ updatedAt: new Date('01/01/2021'),
+ contractRevisions: [],
+ formData: {
+ rateType: 'NEW',
+ rateCapitationType: 'RATE_CELL',
+ rateDocuments: [
+ {
+ s3URL: 's3://foo/bar/rate',
+ name: 'rate docs test 1',
+ sha256: 'fakesha',
+ },
+ ],
+ supportingDocuments: [],
+ rateDateStart: new Date('01/01/2021'),
+ rateDateEnd: new Date('12/31/2021'),
+ rateDateCertified: new Date('12/31/2020'),
+ amendmentEffectiveDateStart: new Date('01/01/2021'),
+ amendmentEffectiveDateEnd: new Date('12/31/2021'),
+ rateProgramIDs: [
+ 'abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce',
+ ],
+ certifyingActuaryContacts: [
+ {
+ actuarialFirm: 'DELOITTE',
+ name: 'Jimmy Jimerson',
+ titleRole: 'Certifying Actuary',
+ email: 'jj.actuary@test.com',
+ },
+ ],
+ addtlActuaryContacts: [],
+ packagesWithSharedRateCerts: [],
+ },
+ },
+ },
+ {
+ id: '5678',
+ createdAt: new Date('01/01/2021'),
+ updatedAt: new Date('01/01/2021'),
+ status: 'DRAFT',
+ state: mockMNState(),
+ stateCode: 'MN',
+ stateNumber: 5,
+ revisions: [],
+ draftRevision: {
+ id: '1234',
+ createdAt: new Date('01/01/2021'),
+ updatedAt: new Date('01/01/2021'),
+ contractRevisions: [],
+ formData: {
+ rateType: 'AMENDMENT',
+ rateCapitationType: 'RATE_CELL',
+ rateDocuments: [
+ {
+ s3URL: 's3://foo/bar/rate',
+ name: 'rate docs test 2',
+ sha256: 'fakesha',
+ },
+ ],
+ supportingDocuments: [],
+ rateDateStart: new Date('01/01/2021'),
+ rateDateEnd: new Date('12/31/2021'),
+ rateDateCertified: new Date('12/31/2020'),
+ amendmentEffectiveDateStart: new Date('01/01/2021'),
+ amendmentEffectiveDateEnd: new Date('12/31/2021'),
+ rateProgramIDs: [
+ 'd95394e5-44d1-45df-8151-1cc1ee66f100',
+ ],
+ certifyingActuaryContacts: [
+ {
+ actuarialFirm: 'DELOITTE',
+ name: 'Timmy Timerson',
+ titleRole: 'Certifying Actuary',
+ email: 'tt.actuary@test.com',
+ },
+ ],
+ addtlActuaryContacts: [],
+ packagesWithSharedRateCerts: [],
+ },
+ },
+ },
+ ]
+ }
+
+ const apolloProvider = {
+ mocks: [
+ fetchCurrentUserMock({
+ statusCode: 200,
+ user: mockValidCMSUser(),
+ }),
+ ],
+ }
+
+ afterEach(() => jest.clearAllMocks())
+
+ it('can render draft contract with rates without errors', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'Rate details',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('link', { name: 'Edit Rate details' })
+ ).toHaveAttribute('href', '/rate-details')
+ })
+
+ it('can render submitted contract without errors', async () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'Rate details',
+ })
+ ).toBeInTheDocument()
+ // Is this the best way to check that the link is not present?
+ expect(screen.queryByText('Edit')).not.toBeInTheDocument()
+
+ //expects loading button on component load
+ expect(screen.getByText('Loading')).toBeInTheDocument()
+
+ // expects download all button after loading has completed
+ await waitFor(() => {
+ expect(
+ screen.getByRole('link', {
+ name: 'Download all rate documents',
+ })
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('can render all rate details fields for amendment to prior rate certification submission', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ expect(
+ screen.getByRole('definition', { name: 'Rate certification type' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Does the actuary certify capitation rates specific to each rate cell or a rate range?',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Rating period of original rate certification',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Date certified for rate amendment',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: 'Rate amendment effective dates',
+ })
+ ).toBeInTheDocument()
+ })
+
+ it('can render correct rate name for new rate submission', async () => {
+ const contract = mockContractPackageSubmitted()
+ contract.packageSubmissions[0].rateRevisions[0].formData.rateCertificationName =
+ 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013'
+
+ const statePrograms = mockMNState().programs
+ await waitFor(() => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ })
+ const rateName =
+ 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013'
+ expect(screen.getByText(rateName)).toBeInTheDocument()
+ })
+
+ it('can render correct rate name for AMENDMENT rate submission', () => {
+ const submission = {
+ ...mockContractPackageDraft(),
+ rateDateStart: new Date('2022-01-25'),
+ rateDateEnd: new Date('2023-01-25'),
+ rateDateCertified: new Date('2022-01-26'),
+ amendmentEffectiveDateStart: new Date('2022-02-25'),
+ amendmentEffectiveDateEnd: new Date('2023-02-26'),
+ }
+ const draftRate = submission?.draftRates
+ if (draftRate) {
+ const draftRev = draftRate[0].draftRevision
+ if (draftRev) {
+ draftRev.formData.rateCertificationName =
+ 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013'
+
+ const statePrograms = mockMNState().programs
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ }
+ const rateName =
+ 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013'
+ expect(screen.getByText(rateName)).toBeInTheDocument()
+ })
+
+ it('can render all rate details fields for new rate certification submission', async () => {
+ const statePrograms = mockMNState().programs
+ const contract = mockContractPackageSubmitted()
+ contract.packageSubmissions[0].rateRevisions[0].formData.rateCertificationName =
+ 'MCR-MN-0005-SNBC-RATE-20221014-20221014-CERTIFICATION-20221014'
+ contract.packageSubmissions[0].rateRevisions[0].formData.rateType =
+ 'NEW'
+ contract.packageSubmissions[0].rateRevisions[0].formData.amendmentEffectiveDateStart =
+ null
+ await waitFor(() => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ })
+
+ const rateName =
+ 'MCR-MN-0005-SNBC-RATE-20221014-20221014-CERTIFICATION-20221014'
+
+ expect(screen.getByText(rateName)).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Rate certification type' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Rating period' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Date certified' })
+ ).toBeInTheDocument()
+ })
+
+ it('renders supporting rates docs when they exist', async () => {
+ const draftContract = mockContractPackageDraft()
+ if (
+ draftContract.draftRates &&
+ draftContract.draftRates[0].draftRevision
+ ) {
+ const contractWithRateDocsFormData = {
+ ...draftContract.draftRates[0].draftRevision.formData,
+ rateDocuments: [
+ {
+ s3URL: 's3://foo/bar/rate',
+ name: 'rate docs test 1',
+ sha256: 'fakesha',
+ },
+ ],
+ supportingDocuments: [
+ {
+ s3URL: 's3://foo/bar/test-2',
+ name: 'supporting docs test 2',
+ sha256: 'fakesha',
+ },
+ {
+ s3URL: 's3://foo/bar/test-3',
+ name: 'supporting docs test 3',
+ sha256: 'fakesha',
+ },
+ ],
+ }
+ const contractWithRateDocs = {
+ ...draftContract,
+ }
+ if (
+ contractWithRateDocs.draftRates &&
+ contractWithRateDocs.draftRates[0].draftRevision
+ ) {
+ contractWithRateDocs.draftRates[0].draftRevision.formData =
+ contractWithRateDocsFormData
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ }
+ await waitFor(() => {
+ const supportingDocsTable = screen.getByRole('table', {
+ name: /Rate supporting documents/,
+ })
+ const rateDocsTable = screen.getByRole('table', {
+ name: /Rate certification/,
+ })
+
+ expect(rateDocsTable).toBeInTheDocument()
+ expect(supportingDocsTable).toBeInTheDocument()
+
+ const supportingDocsTableRows =
+ within(supportingDocsTable).getAllByRole('rowgroup')
+ expect(supportingDocsTableRows).toHaveLength(2)
+
+ // check row content
+ expect(
+ within(rateDocsTable).getByRole('row', {
+ name: /rate docs test 1/,
+ })
+ ).toBeInTheDocument()
+ expect(
+ within(supportingDocsTable).getByText('supporting docs test 2')
+ ).toBeInTheDocument()
+ expect(
+ within(supportingDocsTable).getByText('supporting docs test 3')
+ ).toBeInTheDocument()
+
+ // check correct category on supporting docs
+ expect(
+ within(supportingDocsTable).getAllByText('Rate-supporting')
+ ).toHaveLength(2)
+ })
+ })
+
+ it('does not render supporting rate documents when they do not exist', () => {
+ const draftContract = mockContractPackageSubmitted()
+
+ if (
+ draftContract.draftRates &&
+ draftContract.draftRates[0].draftRevision
+ ) {
+ const contractWithRateDocsFormData = {
+ ...draftContract.draftRates[0].draftRevision.formData,
+ rateDocuments: [
+ {
+ s3URL: 's3://foo/bar/rate',
+ name: 'rate docs test 1',
+ sha256: 'fakesha',
+ },
+ ],
+ supportingDocuments: [],
+ }
+ const contractWithRateDocs = {
+ ...draftContract,
+ }
+ if (
+ contractWithRateDocs.draftRates &&
+ contractWithRateDocs.draftRates[0].draftRevision
+ ) {
+ contractWithRateDocs.draftRates[0].draftRevision.formData =
+ contractWithRateDocsFormData
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ }
+ expect(
+ screen.queryByRole('table', {
+ name: /Rate supporting documents/,
+ })
+ ).toBeNull()
+ })
+
+ it('does not render download all button when on previous submission', async () => {
+ await waitFor(() =>
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ )
+
+ expect(
+ screen.queryByRole('button', {
+ name: 'Download all rate documents',
+ })
+ ).toBeNull()
+ })
+
+ it('renders rate cell capitation type', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ expect(
+ screen.getByRole('definition', {
+ name: 'Does the actuary certify capitation rates specific to each rate cell or a rate range?',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByText(
+ 'Certification of capitation rates specific to each rate cell'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('renders rate range capitation type', () => {
+ const draftContract = mockContractPackageDraft()
+ if (
+ draftContract.draftRates &&
+ draftContract.draftRates[0].draftRevision
+ ) {
+ draftContract.draftRates[0].draftRevision.formData.rateCapitationType =
+ 'RATE_RANGE'
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ expect(
+ screen.getByRole('definition', {
+ name: 'Does the actuary certify capitation rates specific to each rate cell or a rate range?',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByText(
+ 'Certification of rate ranges of capitation rates per rate cell'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('renders programs that apply to rate certification', async () => {
+ const draftContract = mockContractPackageDraft()
+ if (
+ draftContract.draftRates &&
+ draftContract.draftRates[0].draftRevision
+ ) {
+ draftContract.draftRates[0].draftRevision.formData.rateProgramIDs =
+ [
+ 'abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce',
+ 'd95394e5-44d1-45df-8151-1cc1ee66f100',
+ ]
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ const programElement = screen.getByRole('definition', {
+ name: 'Programs this rate certification covers',
+ })
+ expect(programElement).toBeInTheDocument()
+ const programList = within(programElement).getByText('SNBC, PMAP')
+ expect(programList).toBeInTheDocument()
+ })
+
+ it('renders rate program names even when rate program ids are missing', async () => {
+ const draftContract = mockContractPackageDraft()
+ if (
+ draftContract.draftRevision &&
+ draftContract.draftRates &&
+ draftContract.draftRates[0].draftRevision
+ ) {
+ draftContract.draftRates[0].draftRevision.formData.rateProgramIDs =
+ []
+ draftContract.draftRevision.formData.programIDs = [
+ 'abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce',
+ 'd95394e5-44d1-45df-8151-1cc1ee66f100',
+ ]
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ }
+ const programElement = screen.getByRole('definition', {
+ name: 'Programs this rate certification covers',
+ })
+ expect(programElement).toBeInTheDocument()
+ const programList = within(programElement).getByText('SNBC, PMAP')
+ expect(programList).toBeInTheDocument()
+ })
+
+ it('renders multiple rate certifications with program names', async () => {
+ const draftContract = mockContractPackageDraft()
+ draftContract.draftRates = makeMockRateInfos()
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ const programList = screen.getAllByRole('definition', {
+ name: 'Programs this rate certification covers',
+ })
+ expect(programList).toHaveLength(2)
+ expect(programList[0]).toHaveTextContent('SNBC')
+ expect(programList[1]).toHaveTextContent('PMAP')
+ })
+
+ it('renders multiple rate certifications with rate type', async () => {
+ const draftContract = mockContractPackageDraft()
+ draftContract.draftRates = makeMockRateInfos()
+
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ const certType = screen.getAllByRole('definition', {
+ name: 'Rate certification type',
+ })
+ expect(certType).toHaveLength(2)
+ expect(certType[0]).toHaveTextContent('New')
+ expect(certType[1]).toHaveTextContent('Amendment')
+ })
+
+ it('renders multiple rate certifications with documents', async () => {
+ const draftSubmission = mockContractPackageDraft()
+ draftSubmission.draftRates = makeMockRateInfos()
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ await waitFor(() => {
+ const rateDocsTables = screen.getAllByRole('table', {
+ name: /Rate certification/,
+ })
+ expect(rateDocsTables).toHaveLength(2)
+ expect(
+ within(rateDocsTables[0]).getByRole('row', {
+ name: /rate docs test 1/,
+ })
+ ).toBeInTheDocument()
+ expect(
+ within(rateDocsTables[1]).getByRole('row', {
+ name: /rate docs test 2/,
+ })
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('renders multiple rate certifications with certifying actuary', async () => {
+ const draftContract = mockContractPackageDraft()
+ draftContract.draftRates = makeMockRateInfos()
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+ await waitFor(() => {
+ const certifyingActuary = screen.getAllByRole('definition', {
+ name: 'Certifying actuary',
+ })
+ expect(certifyingActuary).toHaveLength(2)
+ expect(
+ within(certifyingActuary[0]).queryByRole('link', {
+ name: 'jj.actuary@test.com',
+ })
+ ).toBeInTheDocument()
+ expect(
+ within(certifyingActuary[1]).queryByRole('link', {
+ name: 'tt.actuary@test.com',
+ })
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('renders submitted package without errors', () => {
+ renderWithProviders(
+ ,
+ {
+ apolloProvider,
+ }
+ )
+
+ expect(screen.queryByRole('link', { name: 'Edit' })).toBeNull()
+ // We should never display missing field text on submission summary for submitted packages
+ expect(
+ screen.queryByText(/You must provide this information/)
+ ).toBeNull()
+ })
+
+ it('renders inline error when bulk URL is unavailable', async () => {
+ const s3Provider = {
+ ...testS3Client(),
+ getBulkDlURL: async (
+ _keys: string[],
+ _fileName: string
+ ): Promise => {
+ return new Error('Error: getBulkDlURL encountered an error')
+ },
+ }
+ renderWithProviders(
+ ,
+ {
+ s3Provider,
+ apolloProvider,
+ }
+ )
+
+ await waitFor(() => {
+ expect(
+ screen.getByText('Rate document download is unavailable')
+ ).toBeInTheDocument()
+ })
+ })
+})
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.tsx
new file mode 100644
index 0000000000..2b9e16b167
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/RateDetailsSummarySectionV2.tsx
@@ -0,0 +1,320 @@
+import React, { useState } from 'react'
+import { DataDetail } from '../../../../../components/DataDetail'
+import { SectionHeader } from '../../../../../components/SectionHeader'
+import { useS3 } from '../../../../../contexts/S3Context'
+import { formatCalendarDate } from '../../../../../common-code/dateHelpers'
+import { DoubleColumnGrid } from '../../../../../components/DoubleColumnGrid'
+import { DownloadButton } from '../../../../../components/DownloadButton'
+import { UploadedDocumentsTable } from '../../../../../components/SubmissionSummarySection'
+import { usePreviousSubmission } from '../../../../../hooks/usePreviousSubmission'
+import styles from '../../../../../components/SubmissionSummarySection/SubmissionSummarySection.module.scss'
+
+import { recordJSException } from '../../../../../otelHelpers'
+import { DataDetailMissingField } from '../../../../../components/DataDetail/DataDetailMissingField'
+import { DataDetailContactField } from '../../../../../components/DataDetail/DataDetailContactField/DataDetailContactField'
+import { DocumentDateLookupTableType } from '../../../../../documentHelpers/makeDocumentDateLookupTable'
+import useDeepCompareEffect from 'use-deep-compare-effect'
+import { InlineDocumentWarning } from '../../../../../components/DocumentWarning'
+import { SectionCard } from '../../../../../components/SectionCard'
+import {
+ Rate,
+ Contract,
+ Program,
+ RateRevision,
+ RateFormData,
+} from '../../../../../gen/gqlClient'
+
+export type RateDetailsSummarySectionV2Props = {
+ contract: Contract
+ editNavigateTo?: string
+ documentDateLookupTable: DocumentDateLookupTableType
+ isCMSUser?: boolean
+ submissionName: string
+ statePrograms: Program[]
+ onDocumentError?: (error: true) => void
+}
+
+export function renderDownloadButton(
+ zippedFilesURL: string | undefined | Error
+) {
+ if (zippedFilesURL instanceof Error) {
+ return (
+
+ )
+ }
+ return (
+
+ )
+}
+
+export const RateDetailsSummarySectionV2 = ({
+ contract,
+ editNavigateTo,
+ documentDateLookupTable,
+ submissionName,
+ statePrograms,
+ onDocumentError,
+}: RateDetailsSummarySectionV2Props): React.ReactElement => {
+ const isSubmitted = contract.status === 'SUBMITTED'
+ const isEditing = !isSubmitted && editNavigateTo !== undefined
+ const isPreviousSubmission = usePreviousSubmission()
+ const contractFormData =
+ contract.draftRevision?.formData ||
+ contract.packageSubmissions[0].contractRevision.formData
+ const rates =
+ contract.draftRates || contract.packageSubmissions[0].rateRevisions
+ const { getKey, getBulkDlURL } = useS3()
+ const [zippedFilesURL, setZippedFilesURL] = useState<
+ string | undefined | Error
+ >(undefined)
+
+ const rateCapitationType = (rate: Rate | RateRevision) => {
+ const rateFormData = getRateFormData(rate)
+ return rateFormData.rateCapitationType
+ ? rateFormData.rateCapitationType === 'RATE_CELL'
+ ? 'Certification of capitation rates specific to each rate cell'
+ : 'Certification of rate ranges of capitation rates per rate cell'
+ : ''
+ }
+
+ const ratePrograms = (rate: Rate | RateRevision) => {
+ /* if we have rateProgramIDs, use them, otherwise use programIDs */
+ let programIDs = [] as string[]
+ const rateFormData = getRateFormData(rate)
+
+ if (
+ rateFormData.rateProgramIDs &&
+ rateFormData.rateProgramIDs.length > 0
+ ) {
+ programIDs = rateFormData.rateProgramIDs
+ } else if (
+ contractFormData?.programIDs &&
+ contractFormData?.programIDs.length > 0
+ ) {
+ programIDs = contractFormData.programIDs
+ }
+ return programIDs
+ ? statePrograms
+ .filter((p) => programIDs.includes(p.id))
+ .map((p) => p.name)
+ : undefined
+ }
+
+ const rateCertificationType = (rate: Rate | RateRevision) => {
+ const rateFormData = getRateFormData(rate)
+ if (rateFormData.rateType === 'AMENDMENT') {
+ return 'Amendment to prior rate certification'
+ }
+ if (rateFormData.rateType === 'NEW') {
+ return 'New rate certification'
+ }
+ }
+
+ const getRateFormData = (rate: Rate | RateRevision): RateFormData => {
+ const isRateRev = 'formData' in rate
+ if (!isRateRev) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return rate.draftRevision!.formData
+ } else {
+ return rate.formData
+ }
+ }
+
+ useDeepCompareEffect(() => {
+ // skip getting urls of this if this is a previous submission or draft
+ if (!isSubmitted || isPreviousSubmission) return
+
+ // get all the keys for the documents we want to zip
+ async function fetchZipUrl() {
+ const submittedRates = contract.packageSubmissions[0].rateRevisions
+ if (submittedRates !== undefined) {
+ const keysFromDocs = submittedRates
+ .flatMap((rateInfo) =>
+ rateInfo.formData.rateDocuments.concat(
+ rateInfo.formData.supportingDocuments
+ )
+ )
+ .map((doc) => {
+ const key = getKey(doc.s3URL)
+ if (!key) return ''
+ return key
+ })
+ .filter((key) => key !== '')
+
+ // call the lambda to zip the files and get the url
+ const zippedURL = await getBulkDlURL(
+ keysFromDocs,
+ submissionName + '-rate-details.zip',
+ 'HEALTH_PLAN_DOCS'
+ )
+ if (zippedURL instanceof Error) {
+ const msg = `ERROR: getBulkDlURL failed to generate supporting document URL. ID: ${contract.id} Message: ${zippedURL}`
+ console.info(msg)
+
+ if (onDocumentError) {
+ onDocumentError(true)
+ }
+
+ recordJSException(msg)
+ }
+
+ setZippedFilesURL(zippedURL)
+ }
+ }
+
+ void fetchZipUrl()
+ }, [
+ getKey,
+ getBulkDlURL,
+ contract,
+ submissionName,
+ isSubmitted,
+ isPreviousSubmission,
+ ])
+
+ return (
+
+
+ {isSubmitted &&
+ !isPreviousSubmission &&
+ renderDownloadButton(zippedFilesURL)}
+
+ {rates.length > 0 ? (
+ rates.map((rate) => {
+ const rateFormData = getRateFormData(rate)
+ return (
+
+
+ {rateFormData.rateCertificationName}
+
+
+
+ {ratePrograms && (
+
+ )}
+
+
+ )
+ }
+ />
+
+ {rateFormData?.amendmentEffectiveDateStart ? (
+
+ ) : null}
+ {rateFormData
+ .certifyingActuaryContacts[0] && (
+
+ }
+ />
+ )}
+
+
+
+ {rateFormData?.rateDocuments && (
+
+ )}
+ {rateFormData?.supportingDocuments && (
+
+ )}
+
+ )
+ })
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx
new file mode 100644
index 0000000000..175a443a23
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx
@@ -0,0 +1,148 @@
+import { screen, waitFor } from '@testing-library/react'
+import {
+ fetchCurrentUserMock,
+} from '../../../../../testHelpers/apolloMocks'
+import { renderWithProviders } from '../../../../../testHelpers/jestHelpers'
+import { ReviewSubmitV2 } from './ReviewSubmitV2'
+import { fetchContractMockSuccess } from '../../../../../testHelpers/apolloMocks'
+import { Route, Routes } from 'react-router-dom'
+import { RoutesRecord } from '../../../../../constants'
+
+// Wrap test component in some top level routes to allow getParams to be tested
+const wrapInRoutes = (children: React.ReactNode) => {
+ return (
+
+
+ }
+ />
+
+ )
+}
+
+describe.skip('ReviewSubmit', () => {
+ it.skip('renders without errors', async () => {
+ renderWithProviders(wrapInRoutes(),
+ {
+ apolloProvider: {
+ mocks: [
+ fetchCurrentUserMock({ statusCode: 200 }),
+ fetchContractMockSuccess({ contract: { id: 'test-abc-123' } }),
+ ],
+ },
+ routerProvider: {
+ route: '/submissions/test-abc-123/edit/review-and-submit',
+ }
+ })
+ await waitFor(() => {
+ expect(
+ screen.getByRole('heading', { name: 'Contract details' })
+ ).toBeInTheDocument()
+ })
+
+ })
+
+ it.skip('displays edit buttons for every section', async () => {
+ renderWithProviders(, {
+ apolloProvider: {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ },
+ })
+
+ await waitFor(() => {
+ const sectionHeadings = screen.queryAllByRole('heading', {
+ level: 2,
+ })
+ const editButtons = screen.queryAllByRole('button', {
+ name: 'Edit',
+ })
+ expect(sectionHeadings.length).toBeGreaterThanOrEqual(
+ editButtons.length
+ )
+ })
+ })
+
+ it.skip('does not display zip download buttons', async () => {
+ renderWithProviders(, {
+ apolloProvider: {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ },
+ })
+
+ await waitFor(() => {
+ const bulkDownloadButtons = screen.queryAllByRole('button', {
+ name: /documents/,
+ })
+ expect(bulkDownloadButtons).toHaveLength(0)
+ })
+ })
+
+ it.skip('renders info from a DraftSubmission', async () => {
+ renderWithProviders(, {
+ apolloProvider: {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ },
+ })
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('heading', { name: 'Contract details' })
+ ).toBeInTheDocument()
+
+ expect(
+ screen.getByRole('heading', { name: 'State contacts' })
+ ).toBeInTheDocument()
+
+ const sectionHeadings = screen.queryAllByRole('heading', {
+ level: 2,
+ })
+ const editButtons = screen.queryAllByRole('button', {
+ name: 'Edit',
+ })
+ expect(sectionHeadings.length).toBeGreaterThanOrEqual(
+ editButtons.length
+ )
+
+ const submissionDescription =
+ screen.queryByText('A real submission')
+ expect(submissionDescription).toBeInTheDocument()
+ })
+ })
+
+ it.skip('displays back and save as draft buttons', async () => {
+ renderWithProviders(, {
+ apolloProvider: {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ },
+ })
+
+ await waitFor(() =>
+ expect(
+ screen.getByRole('button', {
+ name: 'Back',
+ })
+ ).toBeDefined()
+ )
+ await waitFor(() =>
+ expect(
+ screen.getByRole('button', {
+ name: 'Save as draft',
+ })
+ ).toBeDefined()
+ )
+ })
+
+ it.skip('displays submit button', async () => {
+ renderWithProviders(, {
+ apolloProvider: {
+ mocks: [fetchCurrentUserMock({ statusCode: 200 })],
+ },
+ })
+
+ await waitFor(() =>
+ expect(screen.getByTestId('form-submit')).toBeDefined()
+ )
+ })
+})
\ No newline at end of file
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.tsx
new file mode 100644
index 0000000000..5984d75747
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.tsx
@@ -0,0 +1,187 @@
+import {
+ GridContainer,
+ ModalRef,
+ ModalToggleButton,
+} from '@trussworks/react-uswds'
+import React, { useRef, useState } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
+import { DynamicStepIndicator } from '../../../../../components'
+import { PageActionsContainer } from '../../../PageActions'
+import styles from '../../../ReviewSubmit/ReviewSubmit.module.scss'
+import { ActionButton } from '../../../../../components/ActionButton'
+import { useStatePrograms } from '../../../../../hooks/useStatePrograms'
+import {
+ RoutesRecord,
+ STATE_SUBMISSION_FORM_ROUTES,
+} from '../../../../../constants'
+import { useAuth } from '../../../../../contexts/AuthContext'
+import { RateDetailsSummarySectionV2 } from './RateDetailsSummarySectionV2'
+import { ContactsSummarySection } from './ContactsSummarySectionV2'
+import { ContractDetailsSummarySectionV2 } from './ContractDetailsSummarySectionV2'
+import { SubmissionTypeSummarySectionV2 } from './SubmissionTypeSummarySectionV2'
+import { useFetchContractQuery } from '../../../../../gen/gqlClient'
+import { ErrorForbiddenPage } from '../../../../Errors/ErrorForbiddenPage'
+import { Error404 } from '../../../../Errors/Error404Page'
+import { GenericErrorPage } from '../../../../Errors/GenericErrorPage'
+import { Loading } from '../../../../../components'
+import { PageBannerAlerts } from '../../../PageBannerAlerts'
+import { packageName } from '../../../../../common-code/healthPlanFormDataType'
+
+type RouteParams = {
+ id: string
+}
+export const ReviewSubmitV2 = (): React.ReactElement => {
+ const navigate = useNavigate()
+ const modalRef = useRef(null)
+ const [isSubmitting] = useState(false)
+ // pull the programs off the user
+ const statePrograms = useStatePrograms()
+ const { loggedInUser } = useAuth()
+
+ const { id } = useParams()
+ if (!id) {
+ throw new Error(
+ 'PROGRAMMING ERROR: id param not set in state submission form.'
+ )
+ }
+
+ const { data, loading, error } = useFetchContractQuery({
+ variables: {
+ input: {
+ contractID: id,
+ },
+ },
+ fetchPolicy: 'network-only',
+ })
+
+ const contract = data?.fetchContract.contract
+
+ if (loading) {
+ return (
+
+
+
+ )
+ } else if (error || !contract) {
+ //error handling for a state user that tries to access rates for a different state
+ if (error?.graphQLErrors[0]?.extensions?.code === 'FORBIDDEN') {
+ return (
+
+ )
+ } else if (error?.graphQLErrors[0]?.extensions?.code === 'NOT_FOUND') {
+ return
+ } else {
+ return
+ }
+ }
+
+ // TODO to be removed once makeDocumentDateTable is updated to not rely on HPP and protos
+ const documentDateLookupTable = {
+ fakesha: 'Fri Mar 25 2022 16:13:20 GMT-0500 (Central Daylight Time)',
+ previousSubmissionDate: '01/01/01',
+ }
+
+ const isContractActionAndRateCertification =
+ contract.draftRates && contract.draftRates.length > 0
+ const contractFormData =
+ contract.draftRevision?.formData ||
+ contract.packageSubmissions[0].contractRevision.formData
+ const programIDs = contractFormData.programIDs
+ const programs = statePrograms.filter((program) =>
+ programIDs.includes(program.id)
+ )
+
+ const submissionName = packageName(
+ contract.stateCode,
+ contract.stateNumber,
+ contractFormData.programIDs,
+ programs
+ )
+ return (
+ <>
+
+
+ This is the V2 version of the Review Submit Page
+
+
+
+
+ {isContractActionAndRateCertification && (
+
+ )}
+
+
+
+
+ navigate(RoutesRecord.DASHBOARD_SUBMISSIONS)
+ }
+ disabled={isSubmitting}
+ >
+ Save as draft
+
+ }
+ >
+ navigate('../documents')}
+ disabled={isSubmitting}
+ >
+ Back
+
+
+ Submit
+
+
+
+ {/* // if the session is expiring, close this modal so the countdown modal can appear
+ */}
+
+ >
+ )
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.stories.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.stories.tsx
new file mode 100644
index 0000000000..95367b781c
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.stories.tsx
@@ -0,0 +1,38 @@
+import { Story } from '@storybook/react'
+import ProvidersDecorator from '../../../../../../.storybook/providersDecorator'
+import {
+ SubmissionTypeSummarySectionV2Props,
+ SubmissionTypeSummarySectionV2,
+} from './SubmissionTypeSummarySectionV2'
+import { mockContractPackageDraft } from '../../../../../testHelpers/apolloMocks'
+
+export default {
+ title: 'Components/SubmissionSummary/SubmissionTypeSummarySection/V2',
+ component: SubmissionTypeSummarySectionV2,
+ parameters: {
+ componentSubtitle:
+ 'SubmissionTypeSummarySection displays the Submission Type data for a Draft or State Submission',
+ },
+}
+
+const Template: Story = (args) => (
+
+)
+
+export const WithAction = Template.bind({})
+WithAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithAction.args = {
+ contract: mockContractPackageDraft(),
+ //TODO: Use better mock program data
+ statePrograms: [],
+ editNavigateTo: 'submission-type',
+}
+
+export const WithoutAction = Template.bind({})
+WithoutAction.decorators = [(Story) => ProvidersDecorator(Story, {})]
+
+WithoutAction.args = {
+ contract: mockContractPackageDraft(),
+ statePrograms: [],
+}
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.test.tsx
new file mode 100644
index 0000000000..982aeea634
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.test.tsx
@@ -0,0 +1,211 @@
+import { screen } from '@testing-library/react'
+import { renderWithProviders } from '../../../../../testHelpers/jestHelpers'
+import { SubmissionTypeSummarySectionV2 as SubmissionTypeSummarySection } from './SubmissionTypeSummarySectionV2'
+import {
+ mockContractPackageDraft,
+ mockMNState,
+ mockContractPackageSubmitted,
+} from '../../../../../testHelpers/apolloMocks'
+
+describe('SubmissionTypeSummarySection', () => {
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+ const draftContract = mockContractPackageDraft()
+ const stateSubmission = mockContractPackageSubmitted()
+ const statePrograms = mockMNState().programs
+
+ it('can render draft package without errors', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'MN-PMAP-0001',
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('link', { name: 'Edit MN-PMAP-0001' })
+ ).toHaveAttribute('href', '/submission-type')
+
+ // Our mocks use the latest package data by default.
+ // Therefore we can check here that missing field is not being displayed unexpectedly
+ expect(
+ screen.queryByText(/You must provide this information/)
+ ).toBeNull()
+ })
+
+ it('can render submitted package without errors', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('heading', {
+ level: 2,
+ name: 'MN-MSHO-0003',
+ })
+ ).toBeInTheDocument()
+ expect(screen.queryByRole('link', { name: 'Edit' })).toBeNull()
+ // We should never display missing field text on submission summary for submitted packages
+ expect(
+ screen.queryByText(/You must provide this information/)
+ ).toBeNull()
+ })
+
+ it('renders expected fields for draft package on review and submit', () => {
+ renderWithProviders(
+
+ )
+
+ expect(
+ screen.getByRole('definition', { name: 'Program(s)' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: /Is this a risk based contract/,
+ })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Submission type' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Contract action type' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Submission description' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: /Which populations does this contract action cover\?/,
+ })
+ ).toBeInTheDocument()
+ })
+
+ it('renders missing field message for population coverage question when expected', () => {
+ const draftContract = mockContractPackageDraft()
+ if (draftContract.draftRevision) {
+ draftContract.draftRevision.formData.populationCovered = undefined
+
+ renderWithProviders(
+
+ )
+ }
+ expect(
+ screen.getByRole('definition', {
+ name: /Which populations does this contract action cover\?/,
+ })
+ ).toBeInTheDocument()
+ const riskBasedDefinitionParentDiv = screen.getByRole('definition', {
+ name: /Which populations does this contract action cover\?/,
+ })
+ if (!riskBasedDefinitionParentDiv) throw Error('Testing error')
+ expect(riskBasedDefinitionParentDiv).toHaveTextContent(
+ /You must provide this information/
+ )
+ })
+
+ it('renders missing field message for risk based contract when expected', () => {
+ const draftContract = mockContractPackageDraft()
+ if (draftContract.draftRevision) {
+ draftContract.draftRevision.formData.riskBasedContract = undefined
+
+ renderWithProviders(
+
+ )
+ }
+ expect(
+ screen.getByRole('definition', { name: 'Program(s)' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', {
+ name: /Is this a risk based contract/,
+ })
+ ).toBeInTheDocument()
+ const riskBasedDefinitionParentDiv = screen.getByRole('definition', {
+ name: /Is this a risk based contract/,
+ })
+ if (!riskBasedDefinitionParentDiv) throw Error('Testing error')
+ expect(riskBasedDefinitionParentDiv).toHaveTextContent(
+ /You must provide this information/
+ )
+ })
+
+ it('renders expected fields for submitted package on submission summary', () => {
+ renderWithProviders(
+
+ )
+ expect(
+ screen.getByRole('definition', { name: 'Program(s)' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Submission type' })
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole('definition', { name: 'Submission description' })
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByRole('definition', { name: 'Submitted' })
+ ).toBeInTheDocument()
+ })
+
+ it('does not render Submitted at field', () => {
+ renderWithProviders(
+
+ )
+ expect(
+ screen.queryByRole('definition', { name: 'Submitted' })
+ ).not.toBeInTheDocument()
+ })
+
+ it('renders headerChildComponent component', () => {
+ renderWithProviders(
+ Test button}
+ submissionName="MN-PMAP-0001"
+ />
+ )
+ expect(
+ screen.queryByRole('button', { name: 'Test button' })
+ ).toBeInTheDocument()
+ })
+})
diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.tsx
new file mode 100644
index 0000000000..eb31f03841
--- /dev/null
+++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/SubmissionTypeSummarySectionV2.tsx
@@ -0,0 +1,142 @@
+import { Grid } from '@trussworks/react-uswds'
+import dayjs from 'dayjs'
+import { DataDetail } from '../../../../../components/DataDetail'
+import { DoubleColumnGrid } from '../../../../../components/DoubleColumnGrid'
+import { SectionHeader } from '../../../../../components/SectionHeader'
+import {
+ SubmissionTypeRecord,
+ ContractTypeRecord,
+ PopulationCoveredRecord,
+} from '../../../../../constants/healthPlanPackages'
+import { Program, Contract } from '../../../../../gen/gqlClient'
+import { usePreviousSubmission } from '../../../../../hooks/usePreviousSubmission'
+import { booleanAsYesNoUserValue } from '../../../../../components/Form/FieldYesNo/FieldYesNo'
+import { SectionCard } from '../../../../../components/SectionCard'
+import styles from '../../../../../components/SubmissionSummarySection/SubmissionSummarySection.module.scss'
+
+export type SubmissionTypeSummarySectionV2Props = {
+ contract: Contract
+ statePrograms: Program[]
+ editNavigateTo?: string
+ headerChildComponent?: React.ReactElement
+ subHeaderComponent?: React.ReactElement
+ initiallySubmittedAt?: Date
+ submissionName: string
+}
+
+export const SubmissionTypeSummarySectionV2 = ({
+ contract,
+ statePrograms,
+ editNavigateTo,
+ subHeaderComponent,
+ headerChildComponent,
+ initiallySubmittedAt,
+ submissionName,
+}: SubmissionTypeSummarySectionV2Props): React.ReactElement => {
+ const isPreviousSubmission = usePreviousSubmission()
+ const contractFormData =
+ contract.draftRevision?.formData ||
+ contract.packageSubmissions[0].contractRevision.formData
+
+ const programNames = statePrograms
+ .filter((p) => contractFormData.programIDs.includes(p.id))
+ .map((p) => p.name)
+ const isSubmitted = contract.status === 'SUBMITTED'
+ // const isRiskBasedContract = contractFormData.riskBasedContract ===
+ return (
+
+
+ {headerChildComponent && headerChildComponent}
+
+
+
+ {isSubmitted && !isPreviousSubmission && (
+
+
+ {dayjs(initiallySubmittedAt).format(
+ 'MM/DD/YY'
+ )}
+
+ }
+ />
+ <>>
+
+ )}
+
+
+
+
+ {contractFormData.riskBasedContract !== null && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.tsx b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.tsx
index e71e0010a9..714520c2be 100644
--- a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.tsx
+++ b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.tsx
@@ -21,6 +21,7 @@ import { UnlockedHealthPlanFormDataType } from '../../common-code/healthPlanForm
import { useLDClient } from 'launchdarkly-react-client-sdk'
import { featureFlags } from '../../common-code/featureFlags'
import { RateDetailsV2 } from './RateDetails/V2/RateDetailsV2'
+import { ReviewSubmitV2 } from './ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2'
import styles from './StateSubmissionForm.module.scss'
// Can move this AppRoutes on future pass - leaving it here now to make diff clear
@@ -72,7 +73,9 @@ export const StateSubmissionForm = (): React.ReactElement => {
path={getRelativePathFromNestedRoute(
'SUBMISSIONS_REVIEW_SUBMIT'
)}
- element={}
+ element={
+ useLinkedRates ? :
+ }
/>
} />
diff --git a/services/app-web/src/testHelpers/apolloMocks/contractGQLMock.ts b/services/app-web/src/testHelpers/apolloMocks/contractGQLMock.ts
new file mode 100644
index 0000000000..deec7867f5
--- /dev/null
+++ b/services/app-web/src/testHelpers/apolloMocks/contractGQLMock.ts
@@ -0,0 +1,34 @@
+import {
+ FetchContractQuery,
+ Contract,
+ FetchContractDocument,
+} from '../../gen/gqlClient'
+import { MockedResponse } from '@apollo/client/testing'
+import { mockContractPackageDraft } from './contractPackageDataMock'
+import { GraphQLError } from 'graphql/index'
+
+const fetchContractMockSuccess = ({
+ contract,
+}: {
+ contract?: Partial
+}): MockedResponse => {
+ const contractData = mockContractPackageDraft(contract)
+
+ return {
+ request: {
+ query: FetchContractDocument,
+ variables: { input: { contractID: contractData.id } },
+ },
+ result: {
+ data: {
+ fetchContract: {
+ contract: {
+ ...contractData,
+ },
+ },
+ },
+ },
+ }
+}
+
+export { fetchContractMockSuccess }
diff --git a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts
index cb86efa4d4..7cc463ff81 100644
--- a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts
+++ b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts
@@ -2,7 +2,9 @@ import { mockMNState } from '../../common-code/healthPlanFormDataMocks/healthPla
import { Contract } from '../../gen/gqlClient'
// Assemble versions of Contract data (with or without rates) for jest testing. Intended for use with related GQL Moc file.
-function mockContractPackage(partial?: Partial): Contract {
+function mockContractPackageDraft(
+ partial?: Partial
+): Contract {
return {
status: 'DRAFT',
createdAt: new Date(),
@@ -13,27 +15,33 @@ function mockContractPackage(partial?: Partial): Contract {
stateNumber: 5,
draftRevision: {
id: '123',
- createdAt: new Date(),
- updatedAt: new Date(),
+ createdAt: new Date('01/01/2024'),
+ updatedAt: new Date('12/31/2024'),
formData: {
- programIDs: ['pmap'],
+ programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'],
populationCovered: 'MEDICAID',
submissionType: 'CONTRACT_AND_RATES',
riskBasedContract: true,
submissionDescription: 'A real submission',
supportingDocuments: [],
- stateContacts: [],
+ stateContacts: [
+ {
+ name: 'State Contact 1',
+ titleRole: 'Test State Contact 1',
+ email: 'actuarycontact1@test.com',
+ },
+ ],
contractType: 'AMENDMENT',
contractExecutionStatus: 'EXECUTED',
contractDocuments: [
{
- s3URL: 's3://bucketname/key/contract',
+ s3URL: 's3://bucketname/one-two/one-two.png',
sha256: 'fakesha',
- name: 'contract',
+ name: 'one two',
},
],
- contractDateStart: new Date(),
- contractDateEnd: new Date(),
+ contractDateStart: new Date('01/01/2023'),
+ contractDateEnd: new Date('12/31/2023'),
managedCareEntities: ['MCO'],
federalAuthorities: ['STATE_PLAN'],
inLieuServicesAndSettings: true,
@@ -53,9 +61,11 @@ function mockContractPackage(partial?: Partial): Contract {
modifiedNetworkAdequacyStandards: true,
modifiedLengthOfContract: false,
modifiedNonRiskPaymentArrangements: true,
- },
+ statutoryRegulatoryAttestation: true,
+ statutoryRegulatoryAttestationDescription: "everything meets regulatory attestation"
+ }
},
-
+
draftRates: [
{
id: '123',
@@ -74,16 +84,20 @@ function mockContractPackage(partial?: Partial): Contract {
formData: {
rateType: 'AMENDMENT',
rateCapitationType: 'RATE_CELL',
- rateDocuments: [],
+ rateDocuments: [
+ {
+ s3URL: 's3://bucketname/key/rate',
+ sha256: 'fakesha',
+ name: 'rate',
+ },
+ ],
supportingDocuments: [],
rateDateStart: new Date(),
rateDateEnd: new Date(),
rateDateCertified: new Date(),
amendmentEffectiveDateStart: new Date(),
amendmentEffectiveDateEnd: new Date(),
- rateProgramIDs: [
- 'abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce',
- ],
+ rateProgramIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'],
certifyingActuaryContacts: [
{
actuarialFirm: 'DELOITTE',
@@ -97,13 +111,14 @@ function mockContractPackage(partial?: Partial): Contract {
actuarialFirm: 'DELOITTE',
name: 'Actuary Contact 1',
titleRole: 'Test Actuary Contact 1',
- email: 'actuarycontact1@test.com',
+ email: 'additionalactuarycontact1@test.com',
},
],
actuaryCommunicationPreference: 'OACT_TO_ACTUARY',
packagesWithSharedRateCerts: [],
- },
- },
+ }
+ }
+
},
],
packageSubmissions: [],
@@ -111,4 +126,118 @@ function mockContractPackage(partial?: Partial): Contract {
}
}
-export { mockContractPackage }
+function mockContractPackageSubmitted(
+ partial?: Partial
+): Contract {
+ return {
+ status: 'SUBMITTED',
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ id: 'test-abc-123',
+ stateCode: 'MN',
+ state: mockMNState(),
+ stateNumber: 5,
+ packageSubmissions: [{
+ cause: 'CONTRACT_SUBMISSION',
+ submitInfo: {
+ updatedAt: new Date(),
+ updatedBy: 'example@state.com',
+ updatedReason: 'contract submit'
+ },
+ submittedRevisions: [],
+ contractRevision: {
+ createdAt: new Date('01/01/2024'),
+ updatedAt: new Date('12/31/2024'),
+ id: '123',
+ formData: {
+ programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'],
+ populationCovered: 'MEDICAID',
+ submissionType: 'CONTRACT_AND_RATES',
+ riskBasedContract: true,
+ submissionDescription: 'A real submission',
+ supportingDocuments: [],
+ stateContacts: [],
+ contractType: 'AMENDMENT',
+ contractExecutionStatus: 'EXECUTED',
+ contractDocuments: [
+ {
+ s3URL: 's3://bucketname/key/contract',
+ sha256: 'fakesha',
+ name: 'contract',
+ },
+ ],
+ contractDateStart: new Date(),
+ contractDateEnd: new Date(),
+ managedCareEntities: ['MCO'],
+ federalAuthorities: ['STATE_PLAN'],
+ inLieuServicesAndSettings: true,
+ modifiedBenefitsProvided: true,
+ modifiedGeoAreaServed: false,
+ modifiedMedicaidBeneficiaries: true,
+ modifiedRiskSharingStrategy: true,
+ modifiedIncentiveArrangements: false,
+ modifiedWitholdAgreements: false,
+ modifiedStateDirectedPayments: true,
+ modifiedPassThroughPayments: true,
+ modifiedPaymentsForMentalDiseaseInstitutions: false,
+ modifiedMedicalLossRatioStandards: true,
+ modifiedOtherFinancialPaymentIncentive: false,
+ modifiedEnrollmentProcess: true,
+ modifiedGrevienceAndAppeal: false,
+ modifiedNetworkAdequacyStandards: true,
+ modifiedLengthOfContract: false,
+ modifiedNonRiskPaymentArrangements: true,
+ statutoryRegulatoryAttestation: true,
+ statutoryRegulatoryAttestationDescription: "everything meets regulatory attestation"
+ }
+ },
+ rateRevisions: [
+ {
+ id: '1234',
+ createdAt: new Date('01/01/2023'),
+ updatedAt: new Date('01/01/2023'),
+ contractRevisions: [],
+ formData: {
+ rateType: 'AMENDMENT',
+ rateCapitationType: 'RATE_CELL',
+ rateDocuments: [
+ {
+ s3URL: 's3://bucketname/key/rate',
+ sha256: 'fakesha',
+ name: 'rate',
+ },
+ ],
+ supportingDocuments: [],
+ rateDateStart: new Date(),
+ rateDateEnd: new Date(),
+ rateDateCertified: new Date(),
+ amendmentEffectiveDateStart: new Date(),
+ amendmentEffectiveDateEnd: new Date(),
+ rateProgramIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'],
+ certifyingActuaryContacts: [
+ {
+ actuarialFirm: 'DELOITTE',
+ name: 'Actuary Contact 1',
+ titleRole: 'Test Actuary Contact 1',
+ email: 'actuarycontact1@test.com',
+ },
+ ],
+ addtlActuaryContacts: [
+ {
+ actuarialFirm: 'DELOITTE',
+ name: 'Actuary Contact 1',
+ titleRole: 'Test Actuary Contact 1',
+ email: 'actuarycontact1@test.com',
+ },
+ ],
+ actuaryCommunicationPreference: 'OACT_TO_ACTUARY',
+ packagesWithSharedRateCerts: []
+ }
+ },
+ ],
+ }],
+ ...partial,
+ }
+}
+
+export { mockContractPackageDraft, mockContractPackageSubmitted }
diff --git a/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts
index eedef8ff7a..41b6094d68 100644
--- a/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts
+++ b/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts
@@ -21,7 +21,7 @@ import {
domainToBase64,
protoToBase64,
} from '../../common-code/proto/healthPlanFormDataProto'
-import { HealthPlanPackage, UpdateInformation } from '../../gen/gqlClient'
+import { HealthPlanPackage, UpdateInformation, Contract, Rate } from '../../gen/gqlClient'
import { mockMNState } from './stateMock'
function mockDraft(
diff --git a/services/app-web/src/testHelpers/apolloMocks/index.ts b/services/app-web/src/testHelpers/apolloMocks/index.ts
index 9369dbeafa..8c0ef4483d 100644
--- a/services/app-web/src/testHelpers/apolloMocks/index.ts
+++ b/services/app-web/src/testHelpers/apolloMocks/index.ts
@@ -62,7 +62,10 @@ export {
} from './apiKeyGQLMocks'
// NEW APIS
-export {mockContractPackage} from './contractPackageDataMock'
+export {
+ mockContractPackageDraft,
+ mockContractPackageSubmitted,
+} from './contractPackageDataMock'
export { rateDataMock } from './rateDataMock'
-
+export { fetchContractMockSuccess } from './contractGQLMock'
export { indexRatesMockSuccess, indexRatesMockFailure } from './rateGQLMocks'
\ No newline at end of file
diff --git a/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts
index 29d8bdf003..2b35def6f5 100644
--- a/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts
+++ b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts
@@ -37,7 +37,7 @@ const contractRevisionOnRateDataMock = (
{
__typename: 'StateContact',
name: 'Name',
- title: null,
+ titleRole: null,
email: 'example@example.com',
},
],