From cc9e551f23dfc0e0a9be868a35610a0cd1a9cae9 Mon Sep 17 00:00:00 2001 From: Mojo Talantikite Date: Thu, 22 Aug 2024 17:30:50 -0400 Subject: [PATCH] decouple healthPlanFormData types --- packages/common-code/package.json | 10 +- packages/common-code/src/ContractType.ts | 10 +- .../healthPlanFormDataEncoding.test.ts | 2 +- .../protoBase64.test.ts | 2 +- .../proto/healthPlanFormDataProto/toDomain.ts | 13 +- .../toLatestVersion.ts | 2 +- .../healthPlanFormDataProto/toProtoBuffer.ts | 19 +-- packages/document-helpers/package.json | 14 ++ .../document-helpers/src/getAllDocuments.ts | 14 ++ packages/document-helpers/src/index.ts | 2 + .../src/makeDocumentDateLookupTable.test.ts | 120 ++++++++++++++++++ .../src/makeDocumentDateLookupTable.ts | 52 ++++++++ .../src/makeDocumentKeyLookupList.test.ts | 113 +++++++++++++++++ .../src/makeDocumentKeyLookupList.ts | 64 ++++++++++ packages/document-helpers/tsconfig.json | 21 +++ packages/gql-helpers/package.json | 2 + .../src/fetchHealthPlanPackageWrapper.ts | 9 +- packages/types/package.json | 18 +++ .../src/data/statePrograms.json | 0 .../healthPlanFormData.ts | 0 .../src/healthPlanFormDataMocks/index.ts | 0 .../FederalAuthorities.ts | 0 .../HealthPlanFormDataType.ts | 0 .../LockedHealthPlanFormDataType.ts | 0 .../ModifiedProvisions.ts | 0 .../src/healthPlanFormDataType/State.ts | 0 .../healthPlanFormDataType/StateCodeType.ts | 0 .../UnlockedHealthPlanFormDataType.ts | 0 .../types/src/healthPlanFormDataType/dayjs.ts | 6 + .../findStatePrograms.ts | 0 .../healthPlanFormData.test.ts | 0 .../healthPlanFormData.ts | 54 ++++---- .../src/healthPlanFormDataType/index.ts | 0 .../provisions.test.ts | 0 .../healthPlanSubmissionHelpers/provisions.ts | 2 +- packages/types/tsconfig.json | 21 +++ pnpm-lock.yaml | 46 +++++++ services/app-proto/package.json | 3 +- 38 files changed, 562 insertions(+), 57 deletions(-) create mode 100644 packages/document-helpers/package.json create mode 100644 packages/document-helpers/src/getAllDocuments.ts create mode 100644 packages/document-helpers/src/index.ts create mode 100644 packages/document-helpers/src/makeDocumentDateLookupTable.test.ts create mode 100644 packages/document-helpers/src/makeDocumentDateLookupTable.ts create mode 100644 packages/document-helpers/src/makeDocumentKeyLookupList.test.ts create mode 100644 packages/document-helpers/src/makeDocumentKeyLookupList.ts create mode 100644 packages/document-helpers/tsconfig.json create mode 100644 packages/types/package.json rename packages/{common-code => types}/src/data/statePrograms.json (100%) rename packages/{common-code => types}/src/healthPlanFormDataMocks/healthPlanFormData.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataMocks/index.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/FederalAuthorities.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/HealthPlanFormDataType.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/LockedHealthPlanFormDataType.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/ModifiedProvisions.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/State.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/StateCodeType.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts (100%) create mode 100644 packages/types/src/healthPlanFormDataType/dayjs.ts rename packages/{common-code => types}/src/healthPlanFormDataType/findStatePrograms.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/healthPlanFormData.test.ts (100%) rename packages/{common-code => types}/src/healthPlanFormDataType/healthPlanFormData.ts (90%) rename packages/{common-code => types}/src/healthPlanFormDataType/index.ts (100%) rename packages/{common-code => types}/src/healthPlanSubmissionHelpers/provisions.test.ts (100%) rename packages/{common-code => types}/src/healthPlanSubmissionHelpers/provisions.ts (99%) create mode 100644 packages/types/tsconfig.json diff --git a/packages/common-code/package.json b/packages/common-code/package.json index b89913621b..1e4965116c 100644 --- a/packages/common-code/package.json +++ b/packages/common-code/package.json @@ -7,10 +7,16 @@ "build": "tsc" }, "dependencies": { + "@mc-review/constants": "workspace:*", + "@mc-review/types": "workspace:*", + "@mc-review/gql-helpers": "workspace:*", "url-parse": "^1.5.10", - "dayjs": "^1.10.5" + "dayjs": "^1.10.5", + "zod": "^3.10.1" }, "devDependencies": { - "@types/url-parse": "^1.4.3" + "@types/url-parse": "^1.4.3", + "@types/jest": "^29.5.6", + "@types/node": "^20.14.12" } } diff --git a/packages/common-code/src/ContractType.ts b/packages/common-code/src/ContractType.ts index 4cb3bc6278..53250c6106 100644 --- a/packages/common-code/src/ContractType.ts +++ b/packages/common-code/src/ContractType.ts @@ -1,5 +1,5 @@ -import { Contract, ContractRevision } from '../gen/gqlClient' -import { getLastContractSubmission } from '../gqlHelpers/contractsAndRates' +import { Contract, ContractRevision } from './gen/gqlClient' +import { getLastContractSubmission } from '@mc-review/gql-helpers' const getContractRev = (contract: Contract): ContractRevision | undefined => { if (contract.draftRevision) { @@ -10,10 +10,9 @@ const getContractRev = (contract: Contract): ContractRevision | undefined => { } const isContractOnly = (contract: Contract): boolean => { const contractRev = getContractRev(contract) - return contractRev?.formData?.submissionType === 'CONTRACT_ONLY' + return contractRev?.formData?.submissionType === 'CONTRACT_ONLY' } - const isBaseContract = (contract: Contract): boolean => { const contractRev = getContractRev(contract) return contractRev?.formData?.contractType === 'BASE' @@ -35,7 +34,8 @@ const isContractAndRates = (contract: Contract): boolean => { } const isContractWithProvisions = (contract: Contract): boolean => - isContractAmendment(contract) || (isBaseContract(contract) && !isCHIPOnly(contract)) + isContractAmendment(contract) || + (isBaseContract(contract) && !isCHIPOnly(contract)) const isSubmitted = (contract: Contract): boolean => contract.status === 'SUBMITTED' diff --git a/packages/common-code/src/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts b/packages/common-code/src/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts index c1249f30b2..259c6c1710 100644 --- a/packages/common-code/src/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts +++ b/packages/common-code/src/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts @@ -1,5 +1,5 @@ import { ZodError } from 'zod' -import { mcreviewproto } from '../../../gen/healthPlanFormDataProto' +import { mcreviewproto } from '../../gen/healthPlanFormDataProto' import { basicLockedHealthPlanFormData, basicHealthPlanFormData, diff --git a/packages/common-code/src/proto/healthPlanFormDataProto/protoBase64.test.ts b/packages/common-code/src/proto/healthPlanFormDataProto/protoBase64.test.ts index 9bb478a644..e4d84111bc 100644 --- a/packages/common-code/src/proto/healthPlanFormDataProto/protoBase64.test.ts +++ b/packages/common-code/src/proto/healthPlanFormDataProto/protoBase64.test.ts @@ -1,5 +1,5 @@ import { ZodError } from 'zod' -import { mcreviewproto } from '../../../gen/healthPlanFormDataProto' +import { mcreviewproto } from '../../gen/healthPlanFormDataProto' import { basicLockedHealthPlanFormData, basicHealthPlanFormData, diff --git a/packages/common-code/src/proto/healthPlanFormDataProto/toDomain.ts b/packages/common-code/src/proto/healthPlanFormDataProto/toDomain.ts index 2b46e66755..7728164354 100644 --- a/packages/common-code/src/proto/healthPlanFormDataProto/toDomain.ts +++ b/packages/common-code/src/proto/healthPlanFormDataProto/toDomain.ts @@ -1,4 +1,4 @@ -import { mcreviewproto } from '../../../gen/healthPlanFormDataProto' +import { mcreviewproto } from '../../gen/healthPlanFormDataProto' import { unlockedHealthPlanFormDataZodSchema, lockedHealthPlanFormDataZodSchema, @@ -338,12 +338,11 @@ function parseRateInfos( if (rateInfos.length > 0) { rateInfos.forEach((rateInfo) => { - /** * Not adding more to the proto schema, instead additional actuaries are added to the actuaryContacts array * from index 1. */ - const certifyingActuary = rateInfo?.actuaryContacts?.slice(0,1) + const certifyingActuary = rateInfo?.actuaryContacts?.slice(0, 1) const additionalActuaries = rateInfo.actuaryContacts?.slice(1) const rate: RecursivePartial = { @@ -372,12 +371,8 @@ function parseRateInfos( rateCertificationName: parseRateCertificationName( rateInfo?.rateCertificationName ), - actuaryContacts: parseActuaryContacts( - certifyingActuary - ), - addtlActuaryContacts: parseActuaryContacts( - additionalActuaries - ), + actuaryContacts: parseActuaryContacts(certifyingActuary), + addtlActuaryContacts: parseActuaryContacts(additionalActuaries), actuaryCommunicationPreference: enumToDomain( mcreviewproto.ActuaryCommunicationType, rateInfo?.actuaryCommunicationPreference diff --git a/packages/common-code/src/proto/healthPlanFormDataProto/toLatestVersion.ts b/packages/common-code/src/proto/healthPlanFormDataProto/toLatestVersion.ts index 4e80a19fe8..d822f38f0b 100644 --- a/packages/common-code/src/proto/healthPlanFormDataProto/toLatestVersion.ts +++ b/packages/common-code/src/proto/healthPlanFormDataProto/toLatestVersion.ts @@ -12,7 +12,7 @@ Right now when we use proto data directly after it is deserialized to typescript toLatestVersion is an quick example of this kind of data translation function - it makes a proto compatible change and updates the data in a proto to match the latest schema */ -import { mcreviewproto } from '../../../gen/healthPlanFormDataProto' +import { mcreviewproto } from '../../gen/healthPlanFormDataProto' const CURRENT_PROTO_VERSION = 4 diff --git a/packages/common-code/src/proto/healthPlanFormDataProto/toProtoBuffer.ts b/packages/common-code/src/proto/healthPlanFormDataProto/toProtoBuffer.ts index ea0989eb48..402bc28e8e 100644 --- a/packages/common-code/src/proto/healthPlanFormDataProto/toProtoBuffer.ts +++ b/packages/common-code/src/proto/healthPlanFormDataProto/toProtoBuffer.ts @@ -1,4 +1,4 @@ -import { mcreviewproto, google } from '../../../gen/healthPlanFormDataProto' +import { mcreviewproto, google } from '../../gen/healthPlanFormDataProto' import { UnlockedHealthPlanFormDataType, LockedHealthPlanFormDataType, @@ -200,14 +200,15 @@ const toProtoBuffer = ( rateInfos: domainData.rateInfos && domainData.rateInfos.length ? domainData.rateInfos.map((rateInfo) => { - - /** - * Not adding more to the proto schema, we will combine certifying actuaries with additional actuaries - * in the same array, where certifying actuary is at index 0 and additional actuaries are from index - * 1 and beyond. - */ - const combinedActuaries = - rateInfo?.actuaryContacts.concat(rateInfo?.addtlActuaryContacts ?? []) ?? [] + /** + * Not adding more to the proto schema, we will combine certifying actuaries with additional actuaries + * in the same array, where certifying actuary is at index 0 and additional actuaries are from index + * 1 and beyond. + */ + const combinedActuaries = + rateInfo?.actuaryContacts.concat( + rateInfo?.addtlActuaryContacts ?? [] + ) ?? [] return { id: rateInfo.id, diff --git a/packages/document-helpers/package.json b/packages/document-helpers/package.json new file mode 100644 index 0000000000..885274e934 --- /dev/null +++ b/packages/document-helpers/package.json @@ -0,0 +1,14 @@ +{ + "name": "@mc-review/document-helpers", + "version": "1.0.0", + "description": "", + "main": "build/index.js", + "scripts": { + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@mc-review/common-code": "workspace:*", + "typescript": "^4.9.5" + } +} diff --git a/packages/document-helpers/src/getAllDocuments.ts b/packages/document-helpers/src/getAllDocuments.ts new file mode 100644 index 0000000000..cb7caefc43 --- /dev/null +++ b/packages/document-helpers/src/getAllDocuments.ts @@ -0,0 +1,14 @@ +import { HealthPlanFormDataType } from '../common-code/healthPlanFormDataType' + +const getAllDocuments = (pkg: HealthPlanFormDataType) => { + let allDocuments = [...pkg.contractDocuments, ...pkg.documents] + if (pkg.rateInfos.length > 0) { + pkg.rateInfos.forEach((rateInfo) => { + allDocuments = allDocuments.concat(rateInfo.rateDocuments) + allDocuments = allDocuments.concat(rateInfo.supportingDocuments) + }) + } + return allDocuments +} + +export { getAllDocuments } diff --git a/packages/document-helpers/src/index.ts b/packages/document-helpers/src/index.ts new file mode 100644 index 0000000000..d45ba2089f --- /dev/null +++ b/packages/document-helpers/src/index.ts @@ -0,0 +1,2 @@ +export { makeDocumentDateTable } from './makeDocumentDateLookupTable' +export { makeDocumentS3KeyLookup } from './makeDocumentKeyLookupList' diff --git a/packages/document-helpers/src/makeDocumentDateLookupTable.test.ts b/packages/document-helpers/src/makeDocumentDateLookupTable.test.ts new file mode 100644 index 0000000000..81ac1cabef --- /dev/null +++ b/packages/document-helpers/src/makeDocumentDateLookupTable.test.ts @@ -0,0 +1,120 @@ +import { makeDocumentDateTable } from './makeDocumentDateLookupTable' +import { + mockDraftHealthPlanPackage, + mockSubmittedHealthPlanPackageWithRevision, +} from '@mc-review/test-helpers' +import { UnlockedHealthPlanFormDataType } from '../common-code/healthPlanFormDataType' +import { TextEncoder, TextDecoder } from 'util' +import { buildRevisionsLookup } from '../gqlHelpers/fetchHealthPlanPackageWrapper' + +Object.assign(global, { TextDecoder, TextEncoder }) + +describe('makeDocumentDateTable', () => { + it('should make a proper lookup table', () => { + const submission = mockSubmittedHealthPlanPackageWithRevision({ + currentSubmitInfo: { + updatedAt: new Date('2022-03-28T17:56:32.953Z'), + }, + previousSubmitInfo: { + updatedAt: new Date('2022-03-25T21:14:43.057Z'), + }, + initialSubmitInfo: { + updatedAt: new Date('2022-03-25T21:13:20.420Z'), + }, + }) + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentDateTable(revisionsLookup) + expect(lookupTable).toEqual({ + fakesha: new Date('2022-03-25T21:13:20.420Z'), + fakesha1: new Date('2022-03-28T17:56:32.953Z'), + fakesha2: new Date('2022-03-25T21:13:20.420Z'), + fakesha3: new Date('2022-03-25T21:13:20.420Z'), + fakesha4: new Date('2022-03-25T21:13:20.420Z'), + fakesha5: new Date('2022-03-25T21:13:20.420Z'), + previousSubmissionDate: new Date('2022-03-25T21:14:43.057Z'), + }) + }) + + it('should return no document dates for submission still in initial draft', () => { + const submission = mockDraftHealthPlanPackage() + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentDateTable(revisionsLookup) + + expect(lookupTable).toEqual({ + previousSubmissionDate: null, + }) + }) + + it('should use earliest document added date based that revisions submit date', () => { + const docs: Partial = { + documents: [ + { + s3URL: 's3://bucketname/testDateDoc/testDateDoc.pdf', + name: 'Test Date Doc', + sha256: 'fakesha', + }, + ], + contractDocuments: [ + { + s3URL: 's3://bucketname/key/replaced-contract.pdf', + name: 'replaced contract', + sha256: 'fakesha1', + }, + ], + rateInfos: [ + { + rateDocuments: [], + supportingDocuments: [], + actuaryContacts: [], + packagesWithSharedRateCerts: [], + }, + ], + } + const submission = mockSubmittedHealthPlanPackageWithRevision({ + currentSubmissionData: { + ...docs, + }, + currentSubmitInfo: { + updatedAt: new Date('2022-03-10T00:00:00.000Z'), + }, + previousSubmissionData: { + ...docs, + }, + previousSubmitInfo: { + updatedAt: new Date('2022-02-10T00:00:00.000Z'), + }, + initialSubmissionData: { + ...docs, + contractDocuments: [ + { + s3URL: 's3://bucketname/key/original-contract.pdf', + name: 'original contract', + sha256: 'fakesha2', + }, + ], + }, + initialSubmitInfo: { + updatedAt: new Date('2022-01-10T00:00:00.000Z'), + }, + }) + + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentDateTable(revisionsLookup) + + expect(lookupTable).toEqual({ + fakesha: new Date('2022-01-10T00:00:00.000Z'), + fakesha1: new Date('2022-02-10T00:00:00.000Z'), + fakesha2: new Date('2022-01-10T00:00:00.00'), + previousSubmissionDate: new Date('2022-02-10T00:00:00.000Z'), + }) + }) +}) diff --git a/packages/document-helpers/src/makeDocumentDateLookupTable.ts b/packages/document-helpers/src/makeDocumentDateLookupTable.ts new file mode 100644 index 0000000000..89100623ce --- /dev/null +++ b/packages/document-helpers/src/makeDocumentDateLookupTable.ts @@ -0,0 +1,52 @@ +import { + ExpandedRevisionsType, + RevisionsLookupType, +} from '../gqlHelpers/fetchHealthPlanPackageWrapper' +import { getAllDocuments } from './getAllDocuments' + +// DocumentDateLookupTableType - { document lookup key string : date string for "date added" } +type DocumentDateLookupTableType = { + previousSubmissionDate: string | null + [key: string]: string | null +} + +// getDateAdded - picks out the submit info updatedAt date for a revision +// value is undefined if document not yet submitted +const getDateAdded = ( + revisionData: ExpandedRevisionsType +): string | undefined => { + return revisionData.submitInfo?.updatedAt +} +// makeDateTable - generates unique document keys and their "date added" +// used for date added column on UploadedDocumentsTable displayed in SubmissionSummary and ReviewSubmit +// documents without a submitted date are excluded from list +// logic for unique document keys comes from getDocumentKey - This can be simplified once we have doc.sha everywhere +function makeDocumentDateTable( + revisionsLookup: RevisionsLookupType +): DocumentDateLookupTableType { + const lookupTable: DocumentDateLookupTableType = { + previousSubmissionDate: null, // the last time there was a submission on this package + } + const listOfRevisionLookups = Object.keys(revisionsLookup) + listOfRevisionLookups.forEach( + (revisionId: string, index) => { + const revision = revisionsLookup[revisionId] + + const submitDate = revision.submitInfo?.updatedAt + if (submitDate && (listOfRevisionLookups.length === 1 || index === 1)) { // if we have a package with only one submitted revision, use that - otherwise use whatever in is the 1 index because thats the last submitted + lookupTable['previousSubmissionDate'] = submitDate + } + + const allDocuments = getAllDocuments(revision.formData) + allDocuments.forEach((doc) => { + const documentKey = doc.sha256 ? doc.sha256 : doc.s3URL + const dateAdded = getDateAdded(revision) + if (dateAdded) lookupTable[documentKey] = dateAdded + }) + } + ) + return lookupTable +} + +export { makeDocumentDateTable } +export type { DocumentDateLookupTableType } diff --git a/packages/document-helpers/src/makeDocumentKeyLookupList.test.ts b/packages/document-helpers/src/makeDocumentKeyLookupList.test.ts new file mode 100644 index 0000000000..8f56015146 --- /dev/null +++ b/packages/document-helpers/src/makeDocumentKeyLookupList.test.ts @@ -0,0 +1,113 @@ +import { makeDocumentS3KeyLookup } from './makeDocumentKeyLookupList' +import { mockSubmittedHealthPlanPackageWithRevision } from '../testHelpers/apolloMocks' +import { UnlockedHealthPlanFormDataType } from '../common-code/healthPlanFormDataType' +import { buildRevisionsLookup } from '../gqlHelpers/fetchHealthPlanPackageWrapper' + +describe('makeDocumentS3KeyLookup', () => { + const noSubmissionDocuments: Partial = { + contractDocuments: [], + rateInfos: [ + { + rateDocuments: [], + actuaryContacts: [], + packagesWithSharedRateCerts: [], + supportingDocuments: [], + }, + ], + documents: [], + } + + it('should make two lists with document s3 keys', () => { + const submission = mockSubmittedHealthPlanPackageWithRevision({}) + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentS3KeyLookup(revisionsLookup) + expect(lookupTable.currentDocuments.sort()).toEqual( + [ + '1648242632157-Amerigroup Texas, Inc.pdf', + '1648490162641-lifeofgalileo.pdf', + '1648242665634-Amerigroup Texas, Inc.pdf', + '1648242711421-Amerigroup Texas Inc copy.pdf', + '1648242711421-529-10-0020-00003_Superior_Health Plan, Inc.pdf', + '1648242873229-covid-ifc-2-flu-rsv-codes 5-5-2021.pdf', + ].sort() + ) + + expect(lookupTable.previousDocuments.sort()).toEqual( + [ + '1648242873229-covid-ifc-2-flu-rsv-codes 5-5-2021.pdf', + '1648242632157-Amerigroup Texas, Inc.pdf', + '1648242665634-Amerigroup Texas, Inc.pdf', + '1648242711421-Amerigroup Texas Inc copy.pdf', + '1648242711421-529-10-0020-00003_Superior_Health Plan, Inc.pdf', + ].sort() + ) + }) + + it('should return empty arrays for no documents in submission', () => { + const submission = mockSubmittedHealthPlanPackageWithRevision({ + currentSubmissionData: noSubmissionDocuments, + previousSubmissionData: noSubmissionDocuments, + initialSubmissionData: noSubmissionDocuments, + }) + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentS3KeyLookup(revisionsLookup) + + expect(lookupTable).toEqual({ + currentDocuments: [], + previousDocuments: [], + }) + }) + + it('should return empty array for currentDocuments when none exist', () => { + const submission = mockSubmittedHealthPlanPackageWithRevision({ + currentSubmissionData: noSubmissionDocuments, + }) + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentS3KeyLookup(revisionsLookup) + + expect(lookupTable.currentDocuments).toEqual([]) + expect(lookupTable.previousDocuments.sort()).toEqual( + [ + '1648242873229-covid-ifc-2-flu-rsv-codes 5-5-2021.pdf', + '1648242632157-Amerigroup Texas, Inc.pdf', + '1648242665634-Amerigroup Texas, Inc.pdf', + '1648242711421-Amerigroup Texas Inc copy.pdf', + '1648242711421-529-10-0020-00003_Superior_Health Plan, Inc.pdf', + ].sort() + ) + }) + + it('should return empty array for previousDocuments when none exist', () => { + const submission = mockSubmittedHealthPlanPackageWithRevision({ + previousSubmissionData: noSubmissionDocuments, + initialSubmissionData: noSubmissionDocuments, + }) + + const revisionsLookup = buildRevisionsLookup(submission) + if (revisionsLookup instanceof Error) { + throw revisionsLookup + } + const lookupTable = makeDocumentS3KeyLookup(revisionsLookup) + + expect(lookupTable.previousDocuments).toEqual([]) + expect(lookupTable.currentDocuments.sort()).toEqual( + [ + '1648242632157-Amerigroup Texas, Inc.pdf', + '1648490162641-lifeofgalileo.pdf', + '1648242665634-Amerigroup Texas, Inc.pdf', + '1648242711421-Amerigroup Texas Inc copy.pdf', + '1648242711421-529-10-0020-00003_Superior_Health Plan, Inc.pdf', + '1648242873229-covid-ifc-2-flu-rsv-codes 5-5-2021.pdf', + ].sort() + ) + }) +}) diff --git a/packages/document-helpers/src/makeDocumentKeyLookupList.ts b/packages/document-helpers/src/makeDocumentKeyLookupList.ts new file mode 100644 index 0000000000..58682c5fda --- /dev/null +++ b/packages/document-helpers/src/makeDocumentKeyLookupList.ts @@ -0,0 +1,64 @@ +import { parseKey } from '../common-code/s3URLEncoding' +import { RevisionsLookupType } from '../gqlHelpers/fetchHealthPlanPackageWrapper' +import { getAllDocuments } from './getAllDocuments' + +// CurrentPreviousDocsLookup - array of document keys for currentDocuments and previousDocuments + +type DocumentKeyLookupType = { + currentDocuments: string[] + previousDocuments: string[] +} + +const getKey = (s3URL: string) => { + const key = parseKey(s3URL) + return key instanceof Error ? null : key +} + +// makeDocumentS3KeyLookup - generates list of currentDocuments and previousDocuments by s3Key +// this is used to to determine if can delete a document from s3 after it removed from the FileUpload UI on the documents pages - ContractDetails, RateDetails, Documents + +function makeDocumentS3KeyLookup( + revisionsLookup: RevisionsLookupType +): DocumentKeyLookupType { + const currentDocumentsSet = new Set() + const previousDocumentsSet = new Set() + + Object.keys(revisionsLookup).forEach( + (revisionId: string, index: number) => { + const revisionData = revisionsLookup[revisionId].formData + const allDocuments = getAllDocuments(revisionData) + if (index === 0) { + allDocuments.forEach((doc) => { + const s3Key = getKey(doc.s3URL) + if (!s3Key) { + console.error( + `makeDocumentS3KeyLookup- Failed to read S3 key for $${doc.name} - ${doc.s3URL}` + ) + // fail silently, just return good documents + } else { + currentDocumentsSet.add(s3Key) + } + }) + } else { + allDocuments.forEach((doc) => { + const s3Key = getKey(doc.s3URL) + if (!s3Key) { + console.error( + `makeDocumentS3KeyLookup - Failed to read S3 key for $${doc.name} - ${doc.s3URL}` + ) + // fail silently, just return good documents + } else { + previousDocumentsSet.add(s3Key) + } + }) + } + } + ) + return { + currentDocuments: [...currentDocumentsSet], + previousDocuments: [...previousDocumentsSet], + } +} + +export { makeDocumentS3KeyLookup } +export type { DocumentKeyLookupType } diff --git a/packages/document-helpers/tsconfig.json b/packages/document-helpers/tsconfig.json new file mode 100644 index 0000000000..a16642c07f --- /dev/null +++ b/packages/document-helpers/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "lib": ["ES2021", "DOM"], + "target": "ES2021", + "moduleResolution": "node", + "baseUrl": "/", + "declaration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "preserveConstEnums": true, + "sourceMap": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "jsx": "react", + "outDir": "build" + }, + "include": ["src"] +} diff --git a/packages/gql-helpers/package.json b/packages/gql-helpers/package.json index 427385f6f6..afe130f5a7 100644 --- a/packages/gql-helpers/package.json +++ b/packages/gql-helpers/package.json @@ -3,12 +3,14 @@ "version": "1.0.0", "main": "build/index.js", "scripts": { + "build": "tsc", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@apollo/client": "3.8.8", "@mc-review/otel-helpers": "workspace:*", "@mc-review/common-code": "workspace:*", + "@mc-review/constants": "workspace:*", "typescript": "4.9.5" } } diff --git a/packages/gql-helpers/src/fetchHealthPlanPackageWrapper.ts b/packages/gql-helpers/src/fetchHealthPlanPackageWrapper.ts index a906b6e47a..d1b06f3a94 100644 --- a/packages/gql-helpers/src/fetchHealthPlanPackageWrapper.ts +++ b/packages/gql-helpers/src/fetchHealthPlanPackageWrapper.ts @@ -14,7 +14,7 @@ import { WrappedApolloResultType, } from './apolloQueryWrapper' import { QueryFunctionOptions } from '@apollo/client' -import { recordJSException } from '../otelHelpers' +import { recordJSException } from '@mc-review/otel-helpers' import { DocumentDateLookupTableType, makeDocumentDateTable, @@ -118,7 +118,10 @@ function parseProtos( // This wraps our call to useFetchHealthPlanPackageQuery, parsing out the protobuf // from the response, returning extra errors in the case that parsing goes wrong -function useFetchHealthPlanPackageWrapper(id: string, skip?: boolean): WrappedFetchResultType { +function useFetchHealthPlanPackageWrapper( + id: string, + skip?: boolean +): WrappedFetchResultType { const results = wrapApolloResult( useFetchHealthPlanPackageQuery({ variables: { @@ -126,7 +129,7 @@ function useFetchHealthPlanPackageWrapper(id: string, skip?: boolean): WrappedFe pkgID: id, }, }, - skip: skip ?? false + skip: skip ?? false, }) ) const result = results.result diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000000..ef8333e79e --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@mc-review/types", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "typescript": "^4.9.5", + "dayjs": "^1.10.5" + }, + "devDependencies": { + "@mc-review/constants": "workspace:*", + "@types/jest": "^29.5.6" + } +} diff --git a/packages/common-code/src/data/statePrograms.json b/packages/types/src/data/statePrograms.json similarity index 100% rename from packages/common-code/src/data/statePrograms.json rename to packages/types/src/data/statePrograms.json diff --git a/packages/common-code/src/healthPlanFormDataMocks/healthPlanFormData.ts b/packages/types/src/healthPlanFormDataMocks/healthPlanFormData.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataMocks/healthPlanFormData.ts rename to packages/types/src/healthPlanFormDataMocks/healthPlanFormData.ts diff --git a/packages/common-code/src/healthPlanFormDataMocks/index.ts b/packages/types/src/healthPlanFormDataMocks/index.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataMocks/index.ts rename to packages/types/src/healthPlanFormDataMocks/index.ts diff --git a/packages/common-code/src/healthPlanFormDataType/FederalAuthorities.ts b/packages/types/src/healthPlanFormDataType/FederalAuthorities.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/FederalAuthorities.ts rename to packages/types/src/healthPlanFormDataType/FederalAuthorities.ts diff --git a/packages/common-code/src/healthPlanFormDataType/HealthPlanFormDataType.ts b/packages/types/src/healthPlanFormDataType/HealthPlanFormDataType.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/HealthPlanFormDataType.ts rename to packages/types/src/healthPlanFormDataType/HealthPlanFormDataType.ts diff --git a/packages/common-code/src/healthPlanFormDataType/LockedHealthPlanFormDataType.ts b/packages/types/src/healthPlanFormDataType/LockedHealthPlanFormDataType.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/LockedHealthPlanFormDataType.ts rename to packages/types/src/healthPlanFormDataType/LockedHealthPlanFormDataType.ts diff --git a/packages/common-code/src/healthPlanFormDataType/ModifiedProvisions.ts b/packages/types/src/healthPlanFormDataType/ModifiedProvisions.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/ModifiedProvisions.ts rename to packages/types/src/healthPlanFormDataType/ModifiedProvisions.ts diff --git a/packages/common-code/src/healthPlanFormDataType/State.ts b/packages/types/src/healthPlanFormDataType/State.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/State.ts rename to packages/types/src/healthPlanFormDataType/State.ts diff --git a/packages/common-code/src/healthPlanFormDataType/StateCodeType.ts b/packages/types/src/healthPlanFormDataType/StateCodeType.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/StateCodeType.ts rename to packages/types/src/healthPlanFormDataType/StateCodeType.ts diff --git a/packages/common-code/src/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts b/packages/types/src/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts rename to packages/types/src/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts diff --git a/packages/types/src/healthPlanFormDataType/dayjs.ts b/packages/types/src/healthPlanFormDataType/dayjs.ts new file mode 100644 index 0000000000..403dbc1756 --- /dev/null +++ b/packages/types/src/healthPlanFormDataType/dayjs.ts @@ -0,0 +1,6 @@ +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import timezone from 'dayjs/plugin/timezone' + +dayjs.extend(utc) +dayjs.extend(timezone) diff --git a/packages/common-code/src/healthPlanFormDataType/findStatePrograms.ts b/packages/types/src/healthPlanFormDataType/findStatePrograms.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/findStatePrograms.ts rename to packages/types/src/healthPlanFormDataType/findStatePrograms.ts diff --git a/packages/common-code/src/healthPlanFormDataType/healthPlanFormData.test.ts b/packages/types/src/healthPlanFormDataType/healthPlanFormData.test.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/healthPlanFormData.test.ts rename to packages/types/src/healthPlanFormDataType/healthPlanFormData.test.ts diff --git a/packages/common-code/src/healthPlanFormDataType/healthPlanFormData.ts b/packages/types/src/healthPlanFormDataType/healthPlanFormData.ts similarity index 90% rename from packages/common-code/src/healthPlanFormDataType/healthPlanFormData.ts rename to packages/types/src/healthPlanFormDataType/healthPlanFormData.ts index bdda50e03f..0a7a53cfbc 100644 --- a/packages/common-code/src/healthPlanFormDataType/healthPlanFormData.ts +++ b/packages/types/src/healthPlanFormDataType/healthPlanFormData.ts @@ -10,7 +10,7 @@ import { GeneralizedModifiedProvisions, } from './ModifiedProvisions' import { generateApplicableProvisionsList } from '../healthPlanSubmissionHelpers/provisions' -import { formatRateNameDate } from '../../common-code/dateHelpers' +import dayjs from 'dayjs' import type { LockedHealthPlanFormDataType } from './LockedHealthPlanFormDataType' import type { HealthPlanFormDataType } from './HealthPlanFormDataType' import type { ProgramArgType } from '.' @@ -96,32 +96,32 @@ const hasValidActuaries = (actuaries: ActuaryContact[]): boolean => const hasValidRates = (sub: LockedHealthPlanFormDataType): boolean => { const validRates = sub.rateInfos.every((rateInfo) => { - const validBaseRate = - rateInfo.rateType !== undefined && - rateInfo.rateDateCertified !== undefined && - rateInfo.rateDateStart !== undefined && - rateInfo.rateDateEnd !== undefined && - rateInfo.rateProgramIDs !== undefined && - rateInfo.rateProgramIDs.length > 0 && - hasValidActuaries(rateInfo.actuaryContacts) - - if (isRateAmendment(rateInfo)) { - return ( - validBaseRate && - Boolean( - rateInfo.rateAmendmentInfo && - rateInfo.rateAmendmentInfo.effectiveDateEnd && - rateInfo.rateAmendmentInfo.effectiveDateStart - ) - ) - } - - return validBaseRate - }) + const validBaseRate = + rateInfo.rateType !== undefined && + rateInfo.rateDateCertified !== undefined && + rateInfo.rateDateStart !== undefined && + rateInfo.rateDateEnd !== undefined && + rateInfo.rateProgramIDs !== undefined && + rateInfo.rateProgramIDs.length > 0 && + hasValidActuaries(rateInfo.actuaryContacts) + + if (isRateAmendment(rateInfo)) { + return ( + validBaseRate && + Boolean( + rateInfo.rateAmendmentInfo && + rateInfo.rateAmendmentInfo.effectiveDateEnd && + rateInfo.rateAmendmentInfo.effectiveDateStart + ) + ) + } + + return validBaseRate + }) // Contract only - skip all validations for hasValidRates if (sub.submissionType === 'CONTRACT_ONLY') { - return true + return true } else { return validRates } @@ -325,6 +325,12 @@ const removeInvalidProvisionsAndAuthorities = ( return pkg } +function formatRateNameDate(date: Date | undefined): string { + if (!date) { + return '' + } + return dayjs(date).tz('UTC').format('YYYYMMDD') +} export { isContractWithProvisions, hasValidModifiedProvisions, diff --git a/packages/common-code/src/healthPlanFormDataType/index.ts b/packages/types/src/healthPlanFormDataType/index.ts similarity index 100% rename from packages/common-code/src/healthPlanFormDataType/index.ts rename to packages/types/src/healthPlanFormDataType/index.ts diff --git a/packages/common-code/src/healthPlanSubmissionHelpers/provisions.test.ts b/packages/types/src/healthPlanSubmissionHelpers/provisions.test.ts similarity index 100% rename from packages/common-code/src/healthPlanSubmissionHelpers/provisions.test.ts rename to packages/types/src/healthPlanSubmissionHelpers/provisions.test.ts diff --git a/packages/common-code/src/healthPlanSubmissionHelpers/provisions.ts b/packages/types/src/healthPlanSubmissionHelpers/provisions.ts similarity index 99% rename from packages/common-code/src/healthPlanSubmissionHelpers/provisions.ts rename to packages/types/src/healthPlanSubmissionHelpers/provisions.ts index d8327cc81e..87d5735404 100644 --- a/packages/common-code/src/healthPlanSubmissionHelpers/provisions.ts +++ b/packages/types/src/healthPlanSubmissionHelpers/provisions.ts @@ -2,7 +2,7 @@ import { ModifiedProvisionsAmendmentRecord, ModifiedProvisionsBaseContractRecord, ModifiedProvisionsCHIPRecord, -} from '../../constants/modifiedProvisions' +} from '@mc-review/constants' import type { HealthPlanFormDataType } from '../healthPlanFormDataType' import { diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000000..a16642c07f --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "lib": ["ES2021", "DOM"], + "target": "ES2021", + "moduleResolution": "node", + "baseUrl": "/", + "declaration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "preserveConstEnums": true, + "sourceMap": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "jsx": "react", + "outDir": "build" + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5994f2ea76..0b33dc75a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,13 +66,31 @@ importers: packages/common-code: dependencies: + '@mc-review/constants': + specifier: workspace:* + version: link:../constants + '@mc-review/gql-helpers': + specifier: workspace:* + version: link:../gql-helpers + '@mc-review/types': + specifier: workspace:* + version: link:../types dayjs: specifier: ^1.10.5 version: 1.11.12 url-parse: specifier: ^1.5.10 version: 1.5.10 + zod: + specifier: ^3.10.1 + version: 3.23.8 devDependencies: + '@types/jest': + specifier: ^29.5.6 + version: 29.5.12 + '@types/node': + specifier: ^20.14.12 + version: 20.16.1 '@types/url-parse': specifier: ^1.4.3 version: 1.4.11 @@ -86,6 +104,15 @@ importers: specifier: 4.9.5 version: 4.9.5 + packages/document-helpers: + dependencies: + '@mc-review/common-code': + specifier: workspace:* + version: link:../common-code + typescript: + specifier: ^4.9.5 + version: 4.9.5 + packages/gql-helpers: dependencies: '@apollo/client': @@ -94,6 +121,9 @@ importers: '@mc-review/common-code': specifier: workspace:* version: link:../common-code + '@mc-review/constants': + specifier: workspace:* + version: link:../constants '@mc-review/otel-helpers': specifier: workspace:* version: link:../otel-helpers @@ -114,6 +144,22 @@ importers: specifier: 4.9.5 version: 4.9.5 + packages/types: + dependencies: + dayjs: + specifier: ^1.10.5 + version: 1.11.12 + typescript: + specifier: ^4.9.5 + version: 4.9.5 + devDependencies: + '@mc-review/constants': + specifier: workspace:* + version: link:../constants + '@types/jest': + specifier: ^29.5.6 + version: 29.5.12 + scripts: dependencies: '@aws-sdk/client-cloudformation': diff --git a/services/app-proto/package.json b/services/app-proto/package.json index 8513e29526..f495caa7c1 100644 --- a/services/app-proto/package.json +++ b/services/app-proto/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "description": "app-proto Typescript definitions and helpers", "scripts": { - "generate": "pnpm proto:compile && pnpm proto:ts && pnpm proto:generate-cjs && pnpm proto:copy-api && pnpm proto:copy-web && pnpm proto:copy-cypress", + "generate": "pnpm proto:compile && pnpm proto:ts && pnpm proto:generate-cjs && pnpm proto:copy-api && pnpm proto:copy-web && pnpm proto:copy-cypress && pnpm proto:copy-common-code", "generate:watch": "find src | entr pnpm generate", "proto:compile": "rm -rf gen && mkdir gen && pbjs -t static-module -w es6 -o ./gen/healthPlanFormDataProto.js src/health_plan_form_data.proto", "proto:generate-cjs": "pbjs -t static-module -w commonjs -o ./gen/healthPlanFormDataProto.cjs src/health_plan_form_data.proto", @@ -12,6 +12,7 @@ "proto:copy-web": "rsync -av ./gen/ ../app-web/src/gen", "proto:copy-cypress": "rsync -av ./gen/ ../cypress/gen", "proto:copy-api": "rsync -av ./gen/healthPlanFormDataProto.cjs ../app-api/src/gen/ && rsync -av ./gen/healthPlanFormDataProto.d.ts ../app-api/src/gen/ && mv ../app-api/src/gen/healthPlanFormDataProto.cjs ../app-api/src/gen/healthPlanFormDataProto.js && rm ./gen/healthPlanFormDataProto.cjs", + "proto:copy-common-code": "rsync -av ./gen/ ../../packages/common-code/src/gen", "precommit": "lint-staged", "lint": "protolint lint ." },