diff --git a/services/app-api/src/domain-models/contractAndRates/convertContractToHPP.ts b/services/app-api/src/domain-models/contractAndRates/convertContractToHPP.ts deleted file mode 100644 index 4ee5d28453..0000000000 --- a/services/app-api/src/domain-models/contractAndRates/convertContractToHPP.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { - SubmissionDocument, - UnlockedHealthPlanFormDataType, -} from 'app-web/src/common-code/healthPlanFormDataType' -import type { - HealthPlanPackageType, - HealthPlanRevisionType, -} from '../HealthPlanPackageType' -import type { ContractType } from './contractTypes' -import { - toDomain, - toProtoBuffer, -} from 'app-web/src/common-code/proto/healthPlanFormDataProto' - -function convertContractToUnlockedHealthPlanPackage( - contract: ContractType -): HealthPlanPackageType | Error { - console.info('Attempting to convert contract to health plan package') - - // Since drafts come in separate on the Contract type, we push it onto the revisions before converting below - if (contract.draftRevision) { - contract.revisions.unshift(contract.draftRevision) - } - - const healthPlanRevisions = - convertContractRevisionToHealthPlanRevision(contract) - - if (healthPlanRevisions instanceof Error) { - return healthPlanRevisions - } - - return { - id: contract.id, - stateCode: contract.stateCode, - revisions: healthPlanRevisions, - } -} - -function convertContractRevisionToHealthPlanRevision( - contract: ContractType -): HealthPlanRevisionType[] | Error { - if (contract.status !== 'DRAFT') { - return new Error( - `Contract with ID: ${contract.id} status is not "DRAFT". Cannot convert to unlocked health plan package` - ) - } - - let healthPlanRevisions: HealthPlanRevisionType[] | Error = [] - for (const contractRev of contract.revisions) { - const unlockedHealthPlanFormData: UnlockedHealthPlanFormDataType = { - id: contractRev.id, - createdAt: contractRev.createdAt, - updatedAt: contractRev.updatedAt, - status: contract.status, - stateCode: contract.stateCode, - stateNumber: contract.stateNumber, - programIDs: contractRev.formData.programIDs, - populationCovered: contractRev.formData.populationCovered, - submissionType: contractRev.formData.submissionType, - riskBasedContract: contractRev.formData.riskBasedContract, - submissionDescription: contractRev.formData.submissionDescription, - stateContacts: contractRev.formData.stateContacts, - addtlActuaryCommunicationPreference: undefined, - addtlActuaryContacts: [], - documents: contractRev.formData.supportingDocuments.map((doc) => ({ - ...doc, - documentCategories: ['CONTRACT_RELATED'], - })) as SubmissionDocument[], - contractType: contractRev.formData.contractType, - contractExecutionStatus: - contractRev.formData.contractExecutionStatus, - contractDocuments: contractRev.formData.contractDocuments.map( - (doc) => ({ - ...doc, - documentCategories: ['CONTRACT'], - }) - ) as SubmissionDocument[], - contractDateStart: contractRev.formData.contractDateStart, - contractDateEnd: contractRev.formData.contractDateEnd, - managedCareEntities: contractRev.formData.managedCareEntities, - federalAuthorities: contractRev.formData.federalAuthorities, - contractAmendmentInfo: { - modifiedProvisions: { - inLieuServicesAndSettings: - contractRev.formData.inLieuServicesAndSettings, - modifiedBenefitsProvided: - contractRev.formData.modifiedBenefitsProvided, - modifiedGeoAreaServed: - contractRev.formData.modifiedGeoAreaServed, - modifiedMedicaidBeneficiaries: - contractRev.formData.modifiedMedicaidBeneficiaries, - modifiedRiskSharingStrategy: - contractRev.formData.modifiedRiskSharingStrategy, - modifiedIncentiveArrangements: - contractRev.formData.modifiedIncentiveArrangements, - modifiedWitholdAgreements: - contractRev.formData.modifiedWitholdAgreements, - modifiedStateDirectedPayments: - contractRev.formData.modifiedStateDirectedPayments, - modifiedPassThroughPayments: - contractRev.formData.modifiedPassThroughPayments, - modifiedPaymentsForMentalDiseaseInstitutions: - contractRev.formData - .modifiedPaymentsForMentalDiseaseInstitutions, - modifiedMedicalLossRatioStandards: - contractRev.formData.modifiedMedicalLossRatioStandards, - modifiedOtherFinancialPaymentIncentive: - contractRev.formData - .modifiedOtherFinancialPaymentIncentive, - modifiedEnrollmentProcess: - contractRev.formData.modifiedEnrollmentProcess, - modifiedGrevienceAndAppeal: - contractRev.formData.modifiedGrevienceAndAppeal, - modifiedNetworkAdequacyStandards: - contractRev.formData.modifiedNetworkAdequacyStandards, - modifiedLengthOfContract: - contractRev.formData.modifiedLengthOfContract, - modifiedNonRiskPaymentArrangements: - contractRev.formData.modifiedNonRiskPaymentArrangements, - }, - }, - rateInfos: [], - } - - const formDataProto = toProtoBuffer(unlockedHealthPlanFormData) - - // check that we can encode then decode with no issues - const domainData = toDomain(formDataProto) - - // If any revision has en error in decoding we break the loop and return an error - if (domainData instanceof Error) { - healthPlanRevisions = new Error( - `Could not convert contract revision with ID: ${contractRev.id} to health plan package revision: ${domainData}` - ) - break - } - - const healthPlanRevision: HealthPlanRevisionType = { - id: contractRev.id, - unlockInfo: contractRev.unlockInfo, - submitInfo: contractRev.submitInfo, - createdAt: contractRev.createdAt, - formDataProto, - } - - healthPlanRevisions.push(healthPlanRevision) - } - - return healthPlanRevisions -} - -export { - convertContractRevisionToHealthPlanRevision, - convertContractToUnlockedHealthPlanPackage, -} diff --git a/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts b/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts new file mode 100644 index 0000000000..12457cb753 --- /dev/null +++ b/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts @@ -0,0 +1,198 @@ +import type { + ActuaryContact, + RateInfoType, + SubmissionDocument, + UnlockedHealthPlanFormDataType, +} from 'app-web/src/common-code/healthPlanFormDataType' +import type { + HealthPlanPackageType, + HealthPlanRevisionType, +} from '../HealthPlanPackageType' +import type { ContractType } from './contractTypes' +import { + toDomain, + toProtoBuffer, +} from 'app-web/src/common-code/proto/healthPlanFormDataProto' +import type { ContractRevisionWithRatesType } from './revisionTypes' + +function convertContractWithRatesToUnlockedHPP( + contract: ContractType +): HealthPlanPackageType | Error { + console.info('Attempting to convert contract to health plan package') + + // Since drafts come in separate on the Contract type, we push it onto the revisions before converting below + if (contract.draftRevision) { + contract.revisions.unshift(contract.draftRevision) + } + + const healthPlanRevisions = + convertContractWithRatesRevtoHPPRev(contract) + + if (healthPlanRevisions instanceof Error) { + return healthPlanRevisions + } + + return { + id: contract.id, + stateCode: contract.stateCode, + revisions: healthPlanRevisions, + } +} + +function convertContractWithRatesRevtoHPPRev( + contract: ContractType +): HealthPlanRevisionType[] | Error { + if (contract.status !== 'DRAFT') { + return new Error( + `Contract with ID: ${contract.id} status is not "DRAFT". Cannot convert to unlocked health plan package` + ) + } + + let healthPlanRevisions: HealthPlanRevisionType[] | Error = [] + for (const contractRev of contract.revisions) { + const unlockedHealthPlanFormData = convertContractWithRatesToFormData(contractRev, contract.stateCode, contract.stateNumber) + + const formDataProto = toProtoBuffer(unlockedHealthPlanFormData) + + // check that we can encode then decode with no issues + const domainData = toDomain(formDataProto) + + // If any revision has en error in decoding we break the loop and return an error + if (domainData instanceof Error) { + healthPlanRevisions = new Error( + `Could not convert contract revision with ID: ${contractRev.id} to health plan package revision: ${domainData}` + ) + break + } + + const healthPlanRevision: HealthPlanRevisionType = { + id: contractRev.id, + unlockInfo: contractRev.unlockInfo, + submitInfo: contractRev.submitInfo, + createdAt: contractRev.createdAt, + formDataProto, + } + + healthPlanRevisions.push(healthPlanRevision) + } + + return healthPlanRevisions +} + +// TODO: Clean up parameters into args and improve types to make things more strict +const convertContractWithRatesToFormData = (contractRev: ContractRevisionWithRatesType, stateCode: string, stateNumber: number): UnlockedHealthPlanFormDataType => { + const rateInfos: RateInfoType[] = contractRev.rateRevisions.map((rateRev) => { + const { rateType, rateCapitationType, rateCertificationName, rateDateCertified, rateDateEnd, rateDateStart, rateDocuments = [], supportingDocuments = [], rateProgramIDs, packagesWithSharedRateCerts, certifyingActuaryContacts = [], addtlActuaryContacts = [], amendmentEffectiveDateEnd, amendmentEffectiveDateStart, actuaryCommunicationPreference } = rateRev.formData + return { + id: rateRev.id, // form data ids are always revision ID + rateType, + rateCapitationType, + rateDocuments: rateDocuments.map( + (doc) => ({ + ...doc, + documentCategories: ['RATES'], + }) + ) as SubmissionDocument[], + supportingDocuments: supportingDocuments.map( + (doc) => ({ + ...doc, + documentCategories: ['RATES_RELATED'], + }) + ) as SubmissionDocument[], + rateAmendmentInfo: { + effectiveDateEnd: amendmentEffectiveDateEnd, + effectiveDateStart: amendmentEffectiveDateStart + }, + rateDateStart, + rateDateEnd, + rateDateCertified, + rateProgramIDs, + rateCertificationName, + actuaryContacts: [...certifyingActuaryContacts, addtlActuaryContacts] as ActuaryContact[], + actuaryCommunicationPreference, + packagesWithSharedRateCerts + } + }) + const unlockedHealthPlanFormData: UnlockedHealthPlanFormDataType = { + id: contractRev.id, + createdAt: contractRev.createdAt, + updatedAt: contractRev.updatedAt, + status: 'DRAFT', + stateCode: stateCode, + stateNumber: stateNumber, + programIDs: contractRev.formData.programIDs, + populationCovered: contractRev.formData.populationCovered, + submissionType: contractRev.formData.submissionType, + riskBasedContract: contractRev.formData.riskBasedContract, + submissionDescription: contractRev.formData.submissionDescription, + stateContacts: contractRev.formData.stateContacts, + addtlActuaryCommunicationPreference: undefined, + addtlActuaryContacts: [], + documents: contractRev.formData.supportingDocuments.map((doc) => ({ + ...doc, + documentCategories: ['CONTRACT_RELATED'], + })) as SubmissionDocument[], + contractType: contractRev.formData.contractType, + contractExecutionStatus: + contractRev.formData.contractExecutionStatus, + contractDocuments: contractRev.formData.contractDocuments.map( + (doc) => ({ + ...doc, + documentCategories: ['CONTRACT'], + }) + ) as SubmissionDocument[], + contractDateStart: contractRev.formData.contractDateStart, + contractDateEnd: contractRev.formData.contractDateEnd, + managedCareEntities: contractRev.formData.managedCareEntities, + federalAuthorities: contractRev.formData.federalAuthorities, + contractAmendmentInfo: { + modifiedProvisions: { + inLieuServicesAndSettings: + contractRev.formData.inLieuServicesAndSettings, + modifiedBenefitsProvided: + contractRev.formData.modifiedBenefitsProvided, + modifiedGeoAreaServed: + contractRev.formData.modifiedGeoAreaServed, + modifiedMedicaidBeneficiaries: + contractRev.formData.modifiedMedicaidBeneficiaries, + modifiedRiskSharingStrategy: + contractRev.formData.modifiedRiskSharingStrategy, + modifiedIncentiveArrangements: + contractRev.formData.modifiedIncentiveArrangements, + modifiedWitholdAgreements: + contractRev.formData.modifiedWitholdAgreements, + modifiedStateDirectedPayments: + contractRev.formData.modifiedStateDirectedPayments, + modifiedPassThroughPayments: + contractRev.formData.modifiedPassThroughPayments, + modifiedPaymentsForMentalDiseaseInstitutions: + contractRev.formData + .modifiedPaymentsForMentalDiseaseInstitutions, + modifiedMedicalLossRatioStandards: + contractRev.formData.modifiedMedicalLossRatioStandards, + modifiedOtherFinancialPaymentIncentive: + contractRev.formData + .modifiedOtherFinancialPaymentIncentive, + modifiedEnrollmentProcess: + contractRev.formData.modifiedEnrollmentProcess, + modifiedGrevienceAndAppeal: + contractRev.formData.modifiedGrevienceAndAppeal, + modifiedNetworkAdequacyStandards: + contractRev.formData.modifiedNetworkAdequacyStandards, + modifiedLengthOfContract: + contractRev.formData.modifiedLengthOfContract, + modifiedNonRiskPaymentArrangements: + contractRev.formData.modifiedNonRiskPaymentArrangements, + }, + }, + rateInfos + } + + return unlockedHealthPlanFormData +} + +export { + convertContractWithRatesRevtoHPPRev, + convertContractWithRatesToUnlockedHPP, + convertContractWithRatesToFormData +} diff --git a/services/app-api/src/domain-models/contractAndRates/index.ts b/services/app-api/src/domain-models/contractAndRates/index.ts index 928919506a..bca12e667a 100644 --- a/services/app-api/src/domain-models/contractAndRates/index.ts +++ b/services/app-api/src/domain-models/contractAndRates/index.ts @@ -13,9 +13,16 @@ export { rateRevisionSchema, } from './revisionTypes' +export { + convertContractWithRatesRevtoHPPRev, + convertContractWithRatesToUnlockedHPP, + convertContractWithRatesToFormData +} from './convertContractWithRatesToHPP' + + export type { ContractType } from './contractTypes' -export type { ContractStatusType, UpdateInfoType } from './updateInfoType' +export type { PackageStatusType , UpdateInfoType } from './updateInfoType' export type { ContractFormDataType, RateFormDataType } from './formDataTypes' diff --git a/services/app-api/src/domain-models/contractAndRates/updateInfoType.ts b/services/app-api/src/domain-models/contractAndRates/updateInfoType.ts index 5dff83667e..3ad7c7ecaf 100644 --- a/services/app-api/src/domain-models/contractAndRates/updateInfoType.ts +++ b/services/app-api/src/domain-models/contractAndRates/updateInfoType.ts @@ -8,8 +8,8 @@ const updateInfoSchema = z.object({ }) type UpdateInfoType = z.infer -type ContractStatusType = z.infer +type PackageStatusType = z.infer -export type { ContractStatusType, UpdateInfoType } +export type { PackageStatusType , UpdateInfoType } export { updateInfoSchema } diff --git a/services/app-api/src/domain-models/healthPlanPackage.ts b/services/app-api/src/domain-models/healthPlanPackage.ts index 4bf659cf01..623e63b8fb 100644 --- a/services/app-api/src/domain-models/healthPlanPackage.ts +++ b/services/app-api/src/domain-models/healthPlanPackage.ts @@ -69,147 +69,9 @@ function packageSubmitters(pkg: HealthPlanPackageType): string[] { return pruneDuplicateEmails(submitters) } -function convertContractToUnlockedHealthPlanPackage( - contract: ContractType -): HealthPlanPackageType | Error { - console.info('Attempting to convert contract to health plan package') - - // Since drafts come in separate on the Contract type, we push it onto the revisions before converting below - if (contract.draftRevision) { - contract.revisions.unshift(contract.draftRevision) - } - - const healthPlanRevisions = - convertContractRevisionToHealthPlanRevision(contract) - - if (healthPlanRevisions instanceof Error) { - return healthPlanRevisions - } - - return { - id: contract.id, - stateCode: contract.stateCode, - revisions: healthPlanRevisions, - } -} - -function convertContractRevisionToHealthPlanRevision( - contract: ContractType -): HealthPlanRevisionType[] | Error { - if (contract.status !== 'DRAFT') { - return new Error( - `Contract with ID: ${contract.id} status is not "DRAFT". Cannot convert to unlocked health plan package` - ) - } - - let healthPlanRevisions: HealthPlanRevisionType[] | Error = [] - for (const contractRev of contract.revisions) { - const unlockedHealthPlanFormData: UnlockedHealthPlanFormDataType = { - id: contractRev.id, - createdAt: contractRev.createdAt, - updatedAt: contractRev.updatedAt, - status: contract.status, - stateCode: contract.stateCode, - stateNumber: contract.stateNumber, - programIDs: contractRev.formData.programIDs, - populationCovered: contractRev.formData.populationCovered, - submissionType: contractRev.formData.submissionType, - riskBasedContract: contractRev.formData.riskBasedContract, - submissionDescription: contractRev.formData.submissionDescription, - stateContacts: contractRev.formData.stateContacts, - addtlActuaryCommunicationPreference: undefined, - addtlActuaryContacts: [], - documents: contractRev.formData.supportingDocuments.map((doc) => ({ - ...doc, - documentCategories: ['CONTRACT_RELATED'], - })) as SubmissionDocument[], - contractType: contractRev.formData.contractType, - contractExecutionStatus: - contractRev.formData.contractExecutionStatus, - contractDocuments: contractRev.formData.contractDocuments.map( - (doc) => ({ - ...doc, - documentCategories: ['CONTRACT'], - }) - ) as SubmissionDocument[], - contractDateStart: contractRev.formData.contractDateStart, - contractDateEnd: contractRev.formData.contractDateEnd, - managedCareEntities: contractRev.formData.managedCareEntities, - federalAuthorities: contractRev.formData.federalAuthorities, - contractAmendmentInfo: { - modifiedProvisions: { - inLieuServicesAndSettings: - contractRev.formData.inLieuServicesAndSettings, - modifiedBenefitsProvided: - contractRev.formData.modifiedBenefitsProvided, - modifiedGeoAreaServed: - contractRev.formData.modifiedGeoAreaServed, - modifiedMedicaidBeneficiaries: - contractRev.formData.modifiedMedicaidBeneficiaries, - modifiedRiskSharingStrategy: - contractRev.formData.modifiedRiskSharingStrategy, - modifiedIncentiveArrangements: - contractRev.formData.modifiedIncentiveArrangements, - modifiedWitholdAgreements: - contractRev.formData.modifiedWitholdAgreements, - modifiedStateDirectedPayments: - contractRev.formData.modifiedStateDirectedPayments, - modifiedPassThroughPayments: - contractRev.formData.modifiedPassThroughPayments, - modifiedPaymentsForMentalDiseaseInstitutions: - contractRev.formData - .modifiedPaymentsForMentalDiseaseInstitutions, - modifiedMedicalLossRatioStandards: - contractRev.formData.modifiedMedicalLossRatioStandards, - modifiedOtherFinancialPaymentIncentive: - contractRev.formData - .modifiedOtherFinancialPaymentIncentive, - modifiedEnrollmentProcess: - contractRev.formData.modifiedEnrollmentProcess, - modifiedGrevienceAndAppeal: - contractRev.formData.modifiedGrevienceAndAppeal, - modifiedNetworkAdequacyStandards: - contractRev.formData.modifiedNetworkAdequacyStandards, - modifiedLengthOfContract: - contractRev.formData.modifiedLengthOfContract, - modifiedNonRiskPaymentArrangements: - contractRev.formData.modifiedNonRiskPaymentArrangements, - }, - }, - rateInfos: [], - } - - const formDataProto = toProtoBuffer(unlockedHealthPlanFormData) - - // check that we can encode then decode with no issues - const domainData = toDomain(formDataProto) - - // If any revision has en error in decoding we break the loop and return an error - if (domainData instanceof Error) { - healthPlanRevisions = new Error( - `Could not convert contract revision with ID: ${contractRev.id} to health plan package revision: ${domainData}` - ) - break - } - - const healthPlanRevision: HealthPlanRevisionType = { - id: contractRev.id, - unlockInfo: contractRev.unlockInfo, - submitInfo: contractRev.submitInfo, - createdAt: contractRev.createdAt, - formDataProto, - } - - healthPlanRevisions.push(healthPlanRevision) - } - - return healthPlanRevisions -} - export { packageCurrentRevision, packageStatus, packageSubmittedAt, packageSubmitters, - convertContractToUnlockedHealthPlanPackage, } diff --git a/services/app-api/src/domain-models/index.ts b/services/app-api/src/domain-models/index.ts index a911f6aff2..f4c299e78b 100644 --- a/services/app-api/src/domain-models/index.ts +++ b/services/app-api/src/domain-models/index.ts @@ -23,9 +23,13 @@ export { packageStatus, packageSubmittedAt, packageSubmitters, - convertContractToUnlockedHealthPlanPackage, } from './healthPlanPackage' +export { + convertContractWithRatesRevtoHPPRev, + convertContractWithRatesToUnlockedHPP, +} from './contractAndRates' + export type { HealthPlanRevisionType, HealthPlanPackageType, diff --git a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts index ef216b7c86..9424bcdc98 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts @@ -2,9 +2,10 @@ import type { Prisma } from '@prisma/client' import type { DocumentCategoryType } from 'app-web/src/common-code/healthPlanFormDataType' import type { ContractFormDataType, + ContractType, RateFormDataType, RateRevisionType, - ContractStatusType, + PackageStatusType, UpdateInfoType, } from '../../domain-models/contractAndRates' @@ -35,13 +36,10 @@ function convertUpdateInfoToDomainModel( } // ----- -function getContractRateStatus( - revisions: - | ContractRevisionTableWithFormData[] - | RateRevisionTableWithFormData[] -): ContractStatusType { +function getContractRateStatus(contractWithRates: ContractType): PackageStatusType { + // need to order revisions from latest to earliest - const revs = revisions.sort( + const revs = contractWithRates.revisions.sort( (revA, revB) => revB.createdAt.getTime() - revA.createdAt.getTime() ) const latestRevision = revs[0] diff --git a/services/app-api/src/resolvers/healthPlanPackage/createHealthPlanPackage.ts b/services/app-api/src/resolvers/healthPlanPackage/createHealthPlanPackage.ts index 2f730616a2..0949a10361 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/createHealthPlanPackage.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/createHealthPlanPackage.ts @@ -12,7 +12,7 @@ import { } from '../attributeHelper' import { GraphQLError } from 'graphql/index' import type { LDService } from '../../launchDarkly/launchDarkly' -import { convertContractToUnlockedHealthPlanPackage } from '../../domain-models' +import { convertContractWithRatesToUnlockedHPP } from '../../domain-models' export function createHealthPlanPackageResolver( store: Store, @@ -93,7 +93,7 @@ export function createHealthPlanPackageResolver( // Now we do the conversions const pkg = - convertContractToUnlockedHealthPlanPackage(contractResult) + convertContractWithRatesToUnlockedHPP(contractResult) if (pkg instanceof Error) { const errMessage = `Error converting draft contract. Message: ${pkg.message}` diff --git a/services/app-api/src/resolvers/healthPlanPackage/fetchHealthPlanPackage.ts b/services/app-api/src/resolvers/healthPlanPackage/fetchHealthPlanPackage.ts index 9a760686ad..f4d3c07100 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/fetchHealthPlanPackage.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/fetchHealthPlanPackage.ts @@ -5,7 +5,7 @@ import { isStateUser, isAdminUser, packageStatus, - convertContractToUnlockedHealthPlanPackage, + convertContractWithRatesToUnlockedHPP, } from '../../domain-models' import { isHelpdeskUser } from '../../domain-models/user' import type { QueryResolvers, State } from '../../gen/gqlServer' @@ -66,7 +66,7 @@ export function fetchHealthPlanPackageResolver( } const convertedPkg = - convertContractToUnlockedHealthPlanPackage(contractWithHistory) + convertContractWithRatesToUnlockedHPP(contractWithHistory) if (convertedPkg instanceof Error) { const errMessage = `Issue converting contract. Message: ${convertedPkg.message}` diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts index 159e7646ba..79e733146c 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts @@ -14,7 +14,6 @@ import { } from '../../../../app-web/src/common-code/healthPlanFormDataType/healthPlanFormData' import type { UpdateInfoType, HealthPlanPackageType } from '../../domain-models' import { - convertContractToUnlockedHealthPlanPackage, isStateUser, packageStatus, packageSubmitters, @@ -38,6 +37,10 @@ import type { LockedHealthPlanFormDataType, } from '../../../../app-web/src/common-code/healthPlanFormDataType' import type { LDService } from '../../launchDarkly/launchDarkly' +import { convertContractWithRatesToFormData, convertContractWithRatesToUnlockedHPP } from '../../domain-models/contractAndRates/convertContractWithRatesToHPP' +import { getContractRateStatus } from '../../postgres/contractAndRates/prismaSharedContractRateHelpers' +import type { Span } from '@opentelemetry/api' +import type { PackageStatusType } from '../../domain-models/contractAndRates' export const SubmissionErrorCodes = ['INCOMPLETE', 'INVALID'] as const type SubmissionErrorCode = (typeof SubmissionErrorCodes)[number] // iterable union type @@ -66,6 +69,30 @@ export function isSubmissionError(err: unknown): err is SubmissionError { return false } +// Throw error if resubmitted without reason or already submitted. +const validateStatusAndUpdateInfo = (status: PackageStatusType, updateInfo: UpdateInfoType, span?: Span, submittedReason?: string) => { + if (status === 'UNLOCKED' && submittedReason) { + updateInfo.updatedReason = submittedReason // ! destrcutive - edit the actual update info that will be attached to submission + } else if (status === 'UNLOCKED' && !submittedReason) { + const errMessage = 'Resubmission requires a reason' + logError('submitHealthPlanPackage', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + throw new UserInputError(errMessage) + } else if ( + status === 'RESUBMITTED' || + status === 'SUBMITTED' + ) { + const errMessage = `Attempted to submit an already submitted package.` + logError('submitHealthPlanPackage', errMessage) + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'INVALID_PACKAGE_STATUS', + }, + }) + } +} + // This is a state machine transition to turn an Unlocked to Locked Form Data // It will return an error if there are any missing fields that are required to submit // This strategy (returning a different type from validation) is taken from the @@ -143,7 +170,7 @@ export function submitHealthPlanPackageResolver( setResolverDetailsOnActiveSpan('submitHealthPlanPackage', user, span) span?.setAttribute('mcreview.package_id', pkgID) - let initialPackage: HealthPlanPackageType // before submit + let currentFormData: HealthPlanFormDataType // data from revision that is being submitted let updatedPackage: HealthPlanPackageType // after submit //Set updateInfo default to initial submission @@ -168,7 +195,8 @@ export function submitHealthPlanPackageResolver( const stateFromCurrentUser: State['code'] = user.stateCode if (ratesDatabaseRefactor) { - // fetch from package and convert to HealthPlanPackage to match pattern for flag off + // fetch contract and related reates - convert to HealthPlanPackage and proto-ize to match the pattern for flag off\ + // this could be replaced with parsing to locked versus unlocked contracts and rates when types are available const contractWithHistory = await store.findContractWithHistory( input.pkgID ) @@ -194,23 +222,49 @@ export function submitHealthPlanPackageResolver( }, }) } + const maybeHealthPlanPackage = - convertContractToUnlockedHealthPlanPackage(contractWithHistory) + convertContractWithRatesToUnlockedHPP(contractWithHistory) - if (maybeHealthPlanPackage instanceof Error) { - const message = 'TODO CONVERT ERROR' - console.error(message) - throw new Error(message) - } - initialPackage = maybeHealthPlanPackage + if (maybeHealthPlanPackage instanceof Error) { + const errMessage = `Error convert to contractWithHistory health plan package. Message: ${maybeHealthPlanPackage.message}` + logError('submitHealthPlanPackage', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'PROTO_DECODE_ERROR', + }, + }) + } + + // Validate user authorized to fetch state + if (contractWithHistory.stateCode !== stateFromCurrentUser) { + logError( + 'submitHealthPlanPackage', + 'user not authorized to fetch data from a different state' + ) + setErrorAttributesOnActiveSpan( + 'user not authorized to fetch data from a different state', + span + ) + throw new ForbiddenError( + 'user not authorized to fetch data from a different state' + ) + } + + validateStatusAndUpdateInfo(getContractRateStatus(contractWithHistory),updateInfo, span, submittedReason || undefined) + + // reassign variable set up before rates feature flag + currentFormData = convertContractWithRatesToFormData(contractWithHistory.revisions[0], contractWithHistory.stateCode, contractWithHistory.stateNumber) } else { // fetch from package flag off - returns HealthPlanPackage - const originalPackage = await store.findHealthPlanPackage( + const initialPackage = await store.findHealthPlanPackage( input.pkgID ) - if (isStoreError(originalPackage) || !originalPackage) { - if (!originalPackage) { + if (isStoreError(initialPackage) || !initialPackage) { + if (!initialPackage) { throw new GraphQLError('Issue finding package.', { extensions: { code: 'NOT_FOUND', @@ -218,7 +272,7 @@ export function submitHealthPlanPackageResolver( }, }) } - const errMessage = `Issue finding a package of type ${originalPackage.code}. Message: ${originalPackage.message}` + const errMessage = `Issue finding a package of type ${initialPackage.code}. Message: ${initialPackage.message}` logError('submitHealthPlanPackage', errMessage) setErrorAttributesOnActiveSpan(errMessage, span) throw new GraphQLError(errMessage, { @@ -229,10 +283,22 @@ export function submitHealthPlanPackageResolver( }) } - initialPackage = originalPackage - } + // unwrap HealthPlanPackage again to make further edits to data + const maybeFormData = toDomain( + initialPackage.revisions[0].formDataProto + ) + if (maybeFormData instanceof Error) { + const errMessage = `Failed to decode draft proto ${maybeFormData}.` + logError('submitHealthPlanPackage', errMessage) + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'PROTO_DECODE_ERROR', + }, + }) + } - // Validate user authorized to fetch state + // Validate user authorized to fetch state if (initialPackage.stateCode !== stateFromCurrentUser) { logError( 'submitHealthPlanPackage', @@ -247,52 +313,14 @@ export function submitHealthPlanPackageResolver( ) } - /* - Validate package is in right status to be submitted - - if currently UNLOCKED, also update submitInfo - */ - const initialPackageStatus = packageStatus(initialPackage) - if (initialPackageStatus === 'UNLOCKED' && submittedReason) { - updateInfo.updatedReason = submittedReason - // Throw error if resubmitted without reason. We want to require an input reason for resubmission, but not for - // initial submission - } else if (initialPackageStatus === 'UNLOCKED' && !submittedReason) { - const errMessage = 'Resubmission requires a reason' - logError('submitHealthPlanPackage', errMessage) - setErrorAttributesOnActiveSpan(errMessage, span) - throw new UserInputError(errMessage) - } else if ( - initialPackageStatus === 'RESUBMITTED' || - initialPackageStatus === 'SUBMITTED' - ) { - const errMessage = `Attempted to submit an already submitted package.` - logError('submitHealthPlanPackage', errMessage) - throw new GraphQLError(errMessage, { - extensions: { - code: 'INTERNAL_SERVER_ERROR', - cause: 'INVALID_PACKAGE_STATUS', - }, - }) - } + validateStatusAndUpdateInfo(packageStatus(initialPackage),updateInfo, span, submittedReason || undefined) + // reassign variable set up before rates feature flag + currentFormData = maybeFormData - /* - Unwrap HealthPlanPackage again to make further edits to data - */ - const currentRevision = initialPackage.revisions[0] - const currentFormData = toDomain( - initialPackage.revisions[0].formDataProto - ) - if (currentFormData instanceof Error) { - const errMessage = `Failed to decode draft proto ${currentFormData}.` - logError('submitHealthPlanPackage', errMessage) - throw new GraphQLError(errMessage, { - extensions: { - code: 'INTERNAL_SERVER_ERROR', - cause: 'PROTO_DECODE_ERROR', - }, - }) } + + /* Clean form data and remove fields from edits on irrelevant logic branches - CONTRACT_ONLY submission type should not contain any CONTRACT_AND_RATE rates data. @@ -304,19 +332,18 @@ export function submitHealthPlanPackageResolver( isContractOnly(currentFormData) && hasAnyValidRateData(currentFormData) ) { - Object.assign(initialPackage, removeRatesData(currentFormData)) + Object.assign(currentFormData, removeRatesData(currentFormData)) } if (isCHIPOnly(currentFormData)) { Object.assign( - initialPackage, + currentFormData, removeInvalidProvisionsAndAuthorities(currentFormData) ) } /* - Final check of data before submit - - Parse to state submission - */ + Final check of data before submit - Parse to state submission + */ const submissionResult = submit(currentFormData) if (isSubmissionError(submissionResult)) { @@ -333,8 +360,8 @@ export function submitHealthPlanPackageResolver( if (ratesDatabaseRefactor) { // Save the submitted package const updateResult = await store.updateHealthPlanRevision( - initialPackage.id, - currentRevision.id, + input.pkgID, + currentFormData.id, lockedFormData, updateInfo ) @@ -354,8 +381,8 @@ export function submitHealthPlanPackageResolver( } else { // Save the package! const updateResult = await store.updateHealthPlanRevision( - initialPackage.id, - currentRevision.id, + input.pkgID, + currentFormData.id, lockedFormData, updateInfo ) diff --git a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts index 1421d791d3..a8388dbd13 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts @@ -6,7 +6,7 @@ import { } from '../../../../app-web/src/common-code/proto/healthPlanFormDataProto' import type { HealthPlanPackageType } from '../../domain-models' import { - convertContractToUnlockedHealthPlanPackage, + convertContractWithRatesToUnlockedHPP, isStateUser, packageStatus, } from '../../domain-models' @@ -228,7 +228,7 @@ export function updateHealthPlanFormDataResolver( } // Convert back to health plan package - const pkg = convertContractToUnlockedHealthPlanPackage(updateResult) + const pkg = convertContractWithRatesToUnlockedHPP(updateResult) if (pkg instanceof Error) { const errMessage = `Error converting draft contract. Message: ${pkg.message}`