From 7ebbba6f1447fc1b1ef4f8f9911e477f69c7c079 Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Mon, 15 Apr 2024 11:50:56 -0700 Subject: [PATCH] wow everything passes --- .../contractAndRates/revisionTypes.ts | 7 +- ...indAllRatesWithHistoryBySubmitInfo.test.ts | 123 ++-- .../findContractWithHistory.test.ts | 56 +- .../findRateWithHistory.test.ts | 633 +++++++----------- .../contractAndRates/insertRate.test.ts | 79 --- .../postgres/contractAndRates/insertRate.ts | 20 + .../parseContractWithHistory.ts | 12 +- .../contractAndRates/parseRateWithHistory.ts | 10 +- .../prismaContractRateAdaptors.ts | 14 + .../prismaDraftRatesHelpers.ts | 2 +- .../prismaSharedContractRateHelpers.ts | 27 +- .../contractAndRates/submitContract.test.ts | 121 +--- .../contractAndRates/submitContract.ts | 60 +- .../contractAndRates/submitRate.test.ts | 309 +-------- .../contractAndRates/unlockContract.test.ts | 124 ++-- .../updateDraftContractRates.ts | 388 ++++++----- .../updateDraftContractWithRates.test.ts | 83 +-- .../updateDraftContractWithRates.ts | 256 ++----- .../contractAndRates/updateDraftRate.test.ts | 43 +- .../src/resolvers/configureResolvers.ts | 2 + .../resolvers/contract/submitContract.test.ts | 171 ++++- .../contract/updateDraftContractRates.test.ts | 2 +- .../src/resolvers/rate/fetchRate.test.ts | 86 +-- .../src/resolvers/rate/indexRates.test.ts | 184 ++--- .../resolvers/rate/rateRevisionResolver.ts | 7 + .../src/resolvers/rate/submitRate.test.ts | 69 +- .../src/resolvers/rate/unlockRate.test.ts | 52 +- .../app-api/src/testHelpers/gqlRateHelpers.ts | 170 +++-- services/app-api/src/testHelpers/index.ts | 2 - services/app-graphql/codegen.yml | 2 +- .../src/mutations/submitContract.graphql | 4 +- .../src/queries/fetchContract.graphql | 4 +- .../app-graphql/src/queries/fetchRate.graphql | 1 + .../src/queries/indexRates.graphql | 3 + services/app-graphql/src/schema.graphql | 6 +- .../SingleRateSummarySection.test.tsx | 2 +- .../RateDetailsSummarySectionV2.test.tsx | 4 + .../apolloMocks/contractPackageDataMock.ts | 2 + .../testHelpers/apolloMocks/rateDataMock.ts | 1 + 39 files changed, 1276 insertions(+), 1865 deletions(-) delete mode 100644 services/app-api/src/postgres/contractAndRates/insertRate.test.ts create mode 100644 services/app-api/src/resolvers/rate/rateRevisionResolver.ts diff --git a/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts b/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts index 8377d66879..b8c078f8d6 100644 --- a/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts +++ b/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts @@ -18,12 +18,7 @@ const contractRevisionSchema = z.object({ const rateRevisionSchema = z.object({ id: z.string().uuid(), - rate: z.object({ - id: z.string().uuid(), - stateCode: z.string(), - stateNumber: z.number().min(1), - createdAt: z.date(), - }), + rateID: z.string().uuid(), submitInfo: updateInfoSchema.optional(), unlockInfo: updateInfoSchema.optional(), createdAt: z.date(), diff --git a/services/app-api/src/postgres/contractAndRates/findAllRatesWithHistoryBySubmitInfo.test.ts b/services/app-api/src/postgres/contractAndRates/findAllRatesWithHistoryBySubmitInfo.test.ts index c5e56a241a..03e5982e2c 100644 --- a/services/app-api/src/postgres/contractAndRates/findAllRatesWithHistoryBySubmitInfo.test.ts +++ b/services/app-api/src/postgres/contractAndRates/findAllRatesWithHistoryBySubmitInfo.test.ts @@ -1,10 +1,15 @@ import { findAllRatesWithHistoryBySubmitInfo } from './findAllRatesWithHistoryBySubmitInfo' import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' -import { mockInsertRateArgs, must } from '../../testHelpers' +import { + mockInsertContractArgs, + mockInsertRateArgs, + must, +} from '../../testHelpers' import { v4 as uuidv4 } from 'uuid' import { insertDraftRate } from './insertRate' -import { submitRate } from './submitRate' -import { unlockRate } from './unlockRate' +import { insertDraftContract } from './insertContract' +import { submitContract } from './submitContract' +import { unlockContract } from './unlockContract' describe('findAllRatesWithHistoryBySubmittedInfo', () => { it('returns only rates that have been submitted or unlocked', async () => { @@ -20,59 +25,49 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { }, }) - const cmsUser = await client.user.create({ - data: { - id: uuidv4(), - givenName: 'Zuko', - familyName: 'Hotman', - email: 'zuko@example.com', - role: 'CMS_USER', - }, + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', }) + const contractA = must( + await insertDraftContract(client, draftContractData) + ) const draftRateData = mockInsertRateArgs({ rateCertificationName: 'one rate', }) // make two submitted rates and submit them - const rateOne = must(await insertDraftRate(client, draftRateData)) - const rateTwo = must(await insertDraftRate(client, draftRateData)) - const submittedRateOne = must( - await submitRate(client, { - rateID: rateOne.id, - submittedByUserID: stateUser.id, - submittedReason: 'rateOne submit', - }) + const rateOne = must( + await insertDraftRate(client, contractA.id, draftRateData) ) - const submittedRateTwo = must( - await submitRate(client, { - rateID: rateTwo.id, - submittedByUserID: stateUser.id, - submittedReason: 'rateTwo submit', - }) + const rateTwo = must( + await insertDraftRate(client, contractA.id, draftRateData) ) - // make two draft rates - const draftRateOne = must(await insertDraftRate(client, draftRateData)) - const draftRateTwo = must(await insertDraftRate(client, draftRateData)) - - // make one unlocked rate - const rateThree = must(await insertDraftRate(client, draftRateData)) must( - await submitRate(client, { - rateID: rateThree.id, + await submitContract(client, { + contractID: contractA.id, submittedByUserID: stateUser.id, - submittedReason: 'unlockRateOne submit', + submittedReason: 'Submitting A.1', }) ) - const unlockedRate = must( - await unlockRate(client, { - rateID: rateThree.id, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock unlockRateOne', + + must( + await unlockContract(client, { + contractID: contractA.id, + unlockedByUserID: stateUser.id, + unlockReason: 'Unlock A.1', }) ) + // make two draft rates + const draftRateOne = must( + await insertDraftRate(client, contractA.id, draftRateData) + ) + const draftRateTwo = must( + await insertDraftRate(client, contractA.id, draftRateData) + ) + // call the find by submit info function const rates = must(await findAllRatesWithHistoryBySubmitInfo(client)) @@ -80,19 +75,10 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { expect(rates).toEqual( expect.arrayContaining([ expect.objectContaining({ - rateID: submittedRateOne.id, + rateID: rateOne.id, }), expect.objectContaining({ - rateID: submittedRateTwo.id, - }), - ]) - ) - - // expect our one unlocked rate - expect(rates).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rateID: unlockedRate.id, + rateID: rateTwo.id, }), ]) ) @@ -123,23 +109,30 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { }, }) + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contractA = must( + await insertDraftContract(client, draftContractData) + ) + const rateDataForAS = must( await insertDraftRate( client, + contractA.id, mockInsertRateArgs({ stateCode: 'AS', rateCertificationName: 'one rate', }) ) ) - const submittedRateAmericanSamoa = must( - await submitRate(client, { - rateID: rateDataForAS.id, + must( + await submitContract(client, { + contractID: contractA.id, submittedByUserID: stateUser.id, - submittedReason: 'rateOne submit', + submittedReason: 'Submitting A.1', }) ) - // call the find by submit info function const rates = must(await findAllRatesWithHistoryBySubmitInfo(client)) @@ -147,7 +140,7 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { expect(rates).not.toEqual( expect.arrayContaining([ expect.objectContaining({ - rateID: submittedRateAmericanSamoa.id, + rateID: rateDataForAS.id, }), ]) ) @@ -166,20 +159,28 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { }, }) + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contractA = must( + await insertDraftContract(client, draftContractData) + ) + const rateDataForMN = must( await insertDraftRate( client, + contractA.id, mockInsertRateArgs({ stateCode: 'MN', rateCertificationName: 'one rate', }) ) ) - const submittedRateMinnesota = must( - await submitRate(client, { - rateID: rateDataForMN.id, + must( + await submitContract(client, { + contractID: contractA.id, submittedByUserID: stateUser.id, - submittedReason: 'rateOne submit', + submittedReason: 'Submitting A.1', }) ) @@ -192,7 +193,7 @@ describe('findAllRatesWithHistoryBySubmittedInfo', () => { expect(rates).toEqual( expect.arrayContaining([ expect.objectContaining({ - rateID: submittedRateMinnesota.id, + rateID: rateDataForMN.id, }), ]) ) diff --git a/services/app-api/src/postgres/contractAndRates/findContractWithHistory.test.ts b/services/app-api/src/postgres/contractAndRates/findContractWithHistory.test.ts index 4482c8d877..ffa1e204f3 100644 --- a/services/app-api/src/postgres/contractAndRates/findContractWithHistory.test.ts +++ b/services/app-api/src/postgres/contractAndRates/findContractWithHistory.test.ts @@ -57,7 +57,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () // Add 3 rates 1, 2, 3 pointing to contract A const rate1 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'someurle.en', }) @@ -78,7 +78,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () ) const rate2 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'twopointo', }) @@ -99,7 +99,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () ) const rate3 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'threepointo', }) @@ -397,7 +397,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () // Add 3 rates 1, 2, 3 pointing to contract A const rate1 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'someurle.en', }) @@ -422,7 +422,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () ) const rate2 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'twopointo', }) @@ -443,7 +443,7 @@ describe.skip('findContractWithHistory with full contract and rate history', () ) const rate3 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'threepointo', }) @@ -895,16 +895,7 @@ describe('findContractWithHistory with only contract history', () => { } const contractID = updatedContract.id - const rateID = updatedContract.draftRevision.rateRevisions[0].rate.id - - // Submit rate - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate A revision 1.0', - }) - ) + const rateID = updatedContract.draftRevision.rateRevisions[0].rateID // Submit contract must( @@ -979,22 +970,6 @@ describe('findContractWithHistory with only contract history', () => { }) ) - // Unlock and resubmit rate again - must( - await unlockRate(client, { - rateID, - unlockReason: 'unlock rate A revision 1.3', - unlockedByUserID: cmsUser.id, - }) - ) - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate A revision 1.4', - }) - ) - // Resubmit contract must( await submitContract(client, { @@ -1090,13 +1065,6 @@ describe('findContractWithHistory with only contract history', () => { throw new Error('Unexpected Error: No rate found in contract') } - must( - await submitRate(client, { - rateID: secondRate.rate.id, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate B revision 1.0', - }) - ) must( await submitContract(client, { contractID, @@ -1108,28 +1076,28 @@ describe('findContractWithHistory with only contract history', () => { // Unlock and resubmit rate B twice must( await unlockRate(client, { - rateID: secondRate.rate.id, + rateID: secondRate.rateID, unlockedByUserID: cmsUser.id, unlockReason: 'unlock rate B revision 1.0', }) ) must( await submitRate(client, { - rateID: secondRate.rate.id, + rateID: secondRate.rateID, submittedByUserID: stateUser.id, submittedReason: 'submit rate B revision 1.1', }) ) must( await unlockRate(client, { - rateID: secondRate.rate.id, + rateID: secondRate.rateID, unlockedByUserID: cmsUser.id, unlockReason: 'unlock rate B revision 1.1', }) ) must( await submitRate(client, { - rateID: secondRate.rate.id, + rateID: secondRate.rateID, submittedByUserID: stateUser.id, submittedReason: 'submit rate B revision 1.2', }) @@ -1150,7 +1118,7 @@ describe('findContractWithHistory with only contract history', () => { expect( submittedContract.revisions[0].rateRevisions[0].submitInfo ?.updatedReason - ).toBe('submit rate A revision 1.5') + ).toBe('submit contract revision 1.2') expect( submittedContract.revisions[0].rateRevisions[1].submitInfo ?.updatedReason diff --git a/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts b/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts index 7bc5f48afc..dca33e9766 100644 --- a/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts +++ b/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts @@ -13,6 +13,7 @@ import { must, mockInsertContractArgs } from '../../testHelpers' import { mockInsertRateArgs } from '../../testHelpers/rateDataMocks' import { findContractWithHistory } from './findContractWithHistory' import type { DraftContractType } from '../../domain-models/contractAndRates/contractTypes' +import { updateDraftContractRates } from './updateDraftContractRates' describe('findRate', () => { // TODO: Enable this tests again after reimplementing rate change history that was in contractWithHistoryToDomainModel @@ -41,11 +42,27 @@ describe('findRate', () => { }, }) + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contractA = must( + await insertDraftContract(client, draftContractData) + ) + must( + await submitContract(client, { + contractID: contractA.id, + submittedByUserID: stateUser.id, + submittedReason: 'Submitting A.1', + }) + ) + // setup a single test rate const draftRateData = mockInsertRateArgs({ rateCertificationName: 'one contract', }) - const rateA = must(await insertDraftRate(client, draftRateData)) + const rateA = must( + await insertDraftRate(client, contractA.id, draftRateData) + ) if (!rateA.draftRevision) { throw new Error( @@ -412,15 +429,18 @@ describe('findRate', () => { // Create rate 1 const draftRateOne = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractID, { stateCode: 'MN', rateCertificationName: 'first submission rate revision', }) ) + //TODO these rates are bs, new ones are being created with the connect call + // redo it with actual API calls. + // Create rate 2 const draftRateTwo = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractID, { stateCode: 'MN', rateCertificationName: 'second submission rate revision', }) @@ -429,34 +449,6 @@ describe('findRate', () => { const rateIDOne = draftRateOne.id const rateIDTwo = draftRateTwo.id - // Update contract with both rates - must( - await updateDraftContractWithRates(client, { - contractID, - formData: {}, - rateFormDatas: [ - { ...draftRateOne.draftRevision?.formData }, - { ...draftRateTwo.draftRevision?.formData }, - ], - }) - ) - - // Submit rates then contract - must( - await submitRate(client, { - rateID: rateIDOne, - submittedByUserID: stateUser.id, - submittedReason: 'initial rate one submit', - }) - ) - // Submit rate then contract - must( - await submitRate(client, { - rateID: rateIDTwo, - submittedByUserID: stateUser.id, - submittedReason: 'initial rate two submit', - }) - ) must( await submitContract(client, { contractID, @@ -474,10 +466,6 @@ describe('findRate', () => { // Expect initial rate two submit to be attached to first submission rate revision expect( fetchSubmittedRateOne.revisions[0].submitInfo?.updatedReason - ).toBe('initial rate one submit') - expect( - fetchSubmittedRateOne.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('initial contract submit') //Rate two history @@ -488,29 +476,8 @@ describe('findRate', () => { // Expect initial rate two submit to be attached to first submission rate revision expect( fetchSubmittedRateTwo.revisions[0].submitInfo?.updatedReason - ).toBe('initial rate two submit') - expect( - fetchSubmittedRateTwo.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('initial contract submit') - // Unlock rate and contract - must( - await unlockRate(client, { - rateID: rateIDOne, - unlockedByUserID: cmsUser.id, - unlockReason: 'Unlock rate one submission', - }) - ) - - must( - await unlockRate(client, { - rateID: rateIDTwo, - unlockedByUserID: cmsUser.id, - unlockReason: 'Unlock rate two submission', - }) - ) - must( await unlockContract(client, { contractID, @@ -519,21 +486,6 @@ describe('findRate', () => { }) ) - // Resubmit rates then contract - must( - await submitRate(client, { - rateID: rateIDOne, - submittedByUserID: stateUser.id, - submittedReason: 'resubmit rate one', - }) - ) - must( - await submitRate(client, { - rateID: rateIDTwo, - submittedByUserID: stateUser.id, - submittedReason: 'resubmit rate two', - }) - ) must( await submitContract(client, { contractID, @@ -550,18 +502,10 @@ describe('findRate', () => { // Expect the earliest submission contract revision to be attached to first submission rate revision expect( fetchResubmittedRateOne.revisions[1].submitInfo?.updatedReason - ).toBe('initial rate one submit') - expect( - fetchResubmittedRateOne.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('initial contract submit') // Expect the latest submission contract revision to be attached to latest submission rate revision expect( fetchResubmittedRateOne.revisions[0].submitInfo?.updatedReason - ).toBe('resubmit rate one') - expect( - fetchResubmittedRateOne.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('resubmit contract') // Rate two resubmission history @@ -572,38 +516,13 @@ describe('findRate', () => { // Expect the earliest submission contract revision to be attached to first submission rate revision expect( fetchResubmittedRateTwo.revisions[1].submitInfo?.updatedReason - ).toBe('initial rate two submit') - expect( - fetchResubmittedRateTwo.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('initial contract submit') // Expect the latest submission contract revision to be attached to latest submission rate revision expect( fetchResubmittedRateTwo.revisions[0].submitInfo?.updatedReason - ).toBe('resubmit rate two') - expect( - fetchResubmittedRateTwo.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('resubmit contract') - // Unlock rate and contract and remove rate one from contract - must( - await unlockRate(client, { - rateID: rateIDOne, - unlockedByUserID: cmsUser.id, - unlockReason: 'Unlock to remove this rate from contract', - }) - ) - - const unlockedRateTwo = must( - await unlockRate(client, { - rateID: rateIDTwo, - unlockedByUserID: cmsUser.id, - unlockReason: 'No edits to this rate', - }) - ) - - must( + const unlockedContract = must( await unlockContract(client, { contractID, unlockedByUserID: cmsUser.id, @@ -611,30 +530,36 @@ describe('findRate', () => { }) ) + expect(unlockedContract.draftRates).toHaveLength(2) + // Remove rate one from contact - must( - await updateDraftContractWithRates(client, { + const removedContract = must( + await updateDraftContractRates(client, { contractID, - formData: { - submissionDescription: 'remove rate', + rateUpdates: { + create: [], + update: [ + { + rateID: rateIDTwo, + formData: draftRateOne, + ratePosition: 1, + }, + ], + link: [], + unlink: [ + { + rateID: rateIDOne, + }, + ], + delete: [], }, - rateFormDatas: [ - { - ...unlockedRateTwo.draftRevision?.formData, - }, - ], }) ) + expect(removedContract.draftRates).toHaveLength(1) + // Submit rate two and contract - must( - await submitRate(client, { - rateID: rateIDTwo, - submittedByUserID: stateUser.id, - submittedReason: 're-resubmit rate two', - }) - ) - must( + const resubmittedContract = must( await submitContract(client, { contractID, submittedByUserID: stateUser.id, @@ -643,6 +568,11 @@ describe('findRate', () => { }) ) + const resubmittedRateIDs = resubmittedContract.packageSubmissions[ + resubmittedContract.packageSubmissions.length - 1 + ].rateRevisions.map((rr) => rr.rateID) + expect(resubmittedRateIDs).toHaveLength(1) + // Unlocked Rate one history const fetchUnlockedRateOne = must( await findRateWithHistory(client, rateIDOne) @@ -657,52 +587,34 @@ describe('findRate', () => { // Expect the earliest submission contract revision to be attached to first submission rate revision expect( fetchUnlockedRateOne.revisions[1].submitInfo?.updatedReason - ).toBe('initial rate one submit') - expect( - fetchUnlockedRateOne.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('initial contract submit') // Expect the resubmitted rate revision to have the attached contract revision that was attached to this rate expect( fetchUnlockedRateOne.revisions[0].submitInfo?.updatedReason - ).toBe('resubmit rate one') - expect( - fetchUnlockedRateOne.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason ).toBe('resubmit contract') // Rate two re-resubmission history - const latestRateOneResubmit = must( + const latestRateTwoResubmit = must( await findRateWithHistory(client, rateIDTwo) ) - expect(latestRateOneResubmit.revisions).toHaveLength(3) + expect(latestRateTwoResubmit.revisions).toHaveLength(3) // Expect the earliest submission contract revision to be attached to first submission rate revision expect( - latestRateOneResubmit.revisions[2].submitInfo?.updatedReason - ).toBe('initial rate two submit') - expect( - latestRateOneResubmit.revisions[2].contractRevisions[0].submitInfo - ?.updatedReason + latestRateTwoResubmit.revisions[2].submitInfo?.updatedReason ).toBe('initial contract submit') + // Expect the first resubmission contract revision to be attached to first resubmission rate revision expect( - latestRateOneResubmit.revisions[1].submitInfo?.updatedReason - ).toBe('resubmit rate two') - expect( - latestRateOneResubmit.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason + latestRateTwoResubmit.revisions[1].submitInfo?.updatedReason ).toBe('resubmit contract') + // Expect the latest submission contract revision to be attached to latest submission rate revision expect( - latestRateOneResubmit.revisions[0].submitInfo?.updatedReason - ).toBe('re-resubmit rate two') - expect( - latestRateOneResubmit.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason + latestRateTwoResubmit.revisions[0].submitInfo?.updatedReason ).toBe('resubmit contract removing rate one leaving only rate two') // Rate one re-resubmission history - const latestRateTwoResubmit = must( + const latestRateOneResubmit = must( await submitRate(client, { rateID: rateIDOne, submittedReason: 'resubmit without contract', @@ -711,34 +623,23 @@ describe('findRate', () => { ) // Expect no draft revision - expect(latestRateTwoResubmit.draftRevision).toBeUndefined() + expect(latestRateOneResubmit.draftRevision).toBeUndefined() // Expect our resubmitted rate to still have the same revision history along with the newest submitted rate // Expect the earliest submission contract revision to be attached to first submission rate revision expect( - latestRateTwoResubmit.revisions[2].submitInfo?.updatedReason - ).toBe('initial rate one submit') - expect( - latestRateTwoResubmit.revisions[2].contractRevisions[0].submitInfo - ?.updatedReason + latestRateOneResubmit.revisions[2].submitInfo?.updatedReason ).toBe('initial contract submit') // Expect the resubmitted rate revision to have the attached contract revision that was attached to this rate expect( - latestRateTwoResubmit.revisions[1].submitInfo?.updatedReason - ).toBe('resubmit rate one') - expect( - latestRateTwoResubmit.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason + latestRateOneResubmit.revisions[1].submitInfo?.updatedReason ).toBe('resubmit contract') // Expect the latest resubmitted rate revision to have no attached contract revision expect( - latestRateTwoResubmit.revisions[0].submitInfo?.updatedReason + latestRateOneResubmit.revisions[0].submitInfo?.updatedReason ).toBe('resubmit without contract') - expect( - latestRateTwoResubmit.revisions[0].contractRevisions - ).toHaveLength(0) }) it('matches contract revision to rate revision with independent rate submit and unlocks', async () => { @@ -794,16 +695,7 @@ describe('findRate', () => { } const contractID = updatedContract.id - const rateID = draftRateRevision?.rate.id - - // Submit rate - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate revision 1.0', - }) - ) + const rateID = draftRateRevision?.rateID // Submit contract must( @@ -818,12 +710,8 @@ describe('findRate', () => { // Expect rate revision 1.0 to have contract revision 1.0 expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' + 'submit contract revision 1.0' ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.0') // Unlock and resubmit contract must( @@ -845,12 +733,8 @@ describe('findRate', () => { // Expect rate revision 1.0 to have contract revision 1.1 expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' + 'submit contract revision 1.1' ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') // Unlock and resubmit rate must( @@ -875,19 +759,16 @@ describe('findRate', () => { expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( 'submit rate revision 1.1' ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') // Expect earilest rate revision to be 1.0 and have contract revision 1.1 expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' + 'submit contract revision 1.1' + ) + + // Expect earilest rate revision to be 1.0 and have contract revision 1.1 + expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( + 'submit contract revision 1.0' ) - expect( - submittedRate.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') // Unlock both contract and rate and resubmit must( @@ -897,13 +778,6 @@ describe('findRate', () => { unlockReason: 'unlock rate revision 1.1', }) ) - must( - await unlockContract(client, { - contractID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock contract revision 1.1', - }) - ) must( await submitRate(client, { rateID, @@ -911,13 +785,6 @@ describe('findRate', () => { submittedReason: 'submit rate revision 1.2', }) ) - must( - await submitContract(client, { - contractID, - submittedByUserID: stateUser.id, - submittedReason: 'submit contract revision 1.2', - }) - ) // Fetch fresh data submittedRate = must(await findRateWithHistory(client, rateID)) @@ -926,193 +793,183 @@ describe('findRate', () => { expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( 'submit rate revision 1.2' ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.2') // Expect previous rate revisions to still be connected to the same contract revision expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( 'submit rate revision 1.1' ) - expect( - submittedRate.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' - ) - expect( - submittedRate.revisions[2].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') - - // Multiple contract unlocks and resubmits - must( - await unlockContract(client, { - contractID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock contract revision 1.2', - }) - ) - must( - await submitContract(client, { - contractID, - submittedByUserID: stateUser.id, - submittedReason: 'submit contract revision 1.3', - }) - ) - must( - await unlockContract(client, { - contractID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock contract revision 1.3', - }) - ) - must( - await submitContract(client, { - contractID, - submittedByUserID: stateUser.id, - submittedReason: 'submit contract revision 1.4', - }) - ) - must( - await unlockContract(client, { - contractID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock contract revision 1.4', - }) - ) - must( - await submitContract(client, { - contractID, - submittedByUserID: stateUser.id, - submittedReason: 'submit contract revision 1.5', - }) - ) - - // Fetch fresh data - submittedRate = must(await findRateWithHistory(client, rateID)) - - // Expect latest rate revision to be 1.2 and have contract revision 1.5 - expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.2' - ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.5') - - // Expect previous rate revisions to still be connected to the same contract revision - expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.1' - ) - expect( - submittedRate.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') - expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' - ) - expect( - submittedRate.revisions[2].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') - - // 3 rate unlocks and resubmits - must( - await unlockRate(client, { - rateID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock rate revision 1.2', - }) - ) - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate revision 1.3', - }) - ) - must( - await unlockRate(client, { - rateID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock rate revision 1.3', - }) - ) - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate revision 1.4', - }) - ) - must( - await unlockRate(client, { - rateID, - unlockedByUserID: cmsUser.id, - unlockReason: 'unlock rate revision 1.4', - }) - ) - must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate revision 1.5', - }) - ) - - // Fetch fresh data - submittedRate = must(await findRateWithHistory(client, rateID)) - - // Expect to have 6 revisions, 3 additional from 3 unlocks and resubmits - expect(submittedRate.revisions).toHaveLength(6) - - // Expect three latest revisions to have contract version 1.5 - expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.5' - ) - expect( - submittedRate.revisions[0].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.5') - expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.4' - ) - expect( - submittedRate.revisions[1].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.5') - expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.3' - ) - expect( - submittedRate.revisions[2].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.5') - - // Expect earliest 3 rate revisions to have the same contract revision as before. - expect(submittedRate.revisions[3].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.2' - ) - expect( - submittedRate.revisions[3].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.5') - expect(submittedRate.revisions[4].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.1' - ) - expect( - submittedRate.revisions[4].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') - expect(submittedRate.revisions[5].submitInfo?.updatedReason).toBe( - 'submit rate revision 1.0' - ) - expect( - submittedRate.revisions[5].contractRevisions[0].submitInfo - ?.updatedReason - ).toBe('submit contract revision 1.1') + 'submit contract revision 1.1' + ) + + // TODO: This stuff only really makes sense once we have package history + + // // Multiple contract unlocks and resubmits + // must( + // await unlockContract(client, { + // contractID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock contract revision 1.2', + // }) + // ) + // must( + // await submitContract(client, { + // contractID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit contract revision 1.3', + // }) + // ) + // must( + // await unlockContract(client, { + // contractID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock contract revision 1.3', + // }) + // ) + // must( + // await submitContract(client, { + // contractID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit contract revision 1.4', + // }) + // ) + // must( + // await unlockContract(client, { + // contractID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock contract revision 1.4', + // }) + // ) + // must( + // await submitContract(client, { + // contractID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit contract revision 1.5', + // }) + // ) + + // // Fetch fresh data + // submittedRate = must(await findRateWithHistory(client, rateID)) + + // // Expect latest rate revision to be 1.2 and have contract revision 1.5 + // expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.2' + // ) + // expect( + // submittedRate.revisions[0].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.5') + + // // Expect previous rate revisions to still be connected to the same contract revision + // expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.1' + // ) + // expect( + // submittedRate.revisions[1].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.1') + // expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.0' + // ) + // expect( + // submittedRate.revisions[2].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.1') + + // // 3 rate unlocks and resubmits + // must( + // await unlockRate(client, { + // rateID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock rate revision 1.2', + // }) + // ) + // must( + // await submitRate(client, { + // rateID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit rate revision 1.3', + // }) + // ) + // must( + // await unlockRate(client, { + // rateID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock rate revision 1.3', + // }) + // ) + // must( + // await submitRate(client, { + // rateID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit rate revision 1.4', + // }) + // ) + // must( + // await unlockRate(client, { + // rateID, + // unlockedByUserID: cmsUser.id, + // unlockReason: 'unlock rate revision 1.4', + // }) + // ) + // must( + // await submitRate(client, { + // rateID, + // submittedByUserID: stateUser.id, + // submittedReason: 'submit rate revision 1.5', + // }) + // ) + + // // Fetch fresh data + // submittedRate = must(await findRateWithHistory(client, rateID)) + + // // Expect to have 6 revisions, 3 additional from 3 unlocks and resubmits + // expect(submittedRate.revisions).toHaveLength(6) + + // // Expect three latest revisions to have contract version 1.5 + // expect(submittedRate.revisions[0].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.5' + // ) + // expect( + // submittedRate.revisions[0].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.5') + // expect(submittedRate.revisions[1].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.4' + // ) + // expect( + // submittedRate.revisions[1].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.5') + // expect(submittedRate.revisions[2].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.3' + // ) + // expect( + // submittedRate.revisions[2].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.5') + + // // Expect earliest 3 rate revisions to have the same contract revision as before. + // expect(submittedRate.revisions[3].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.2' + // ) + // expect( + // submittedRate.revisions[3].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.5') + // expect(submittedRate.revisions[4].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.1' + // ) + // expect( + // submittedRate.revisions[4].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.1') + // expect(submittedRate.revisions[5].submitInfo?.updatedReason).toBe( + // 'submit rate revision 1.0' + // ) + // expect( + // submittedRate.revisions[5].contractRevisions[0].submitInfo + // ?.updatedReason + // ).toBe('submit contract revision 1.1') }) }) diff --git a/services/app-api/src/postgres/contractAndRates/insertRate.test.ts b/services/app-api/src/postgres/contractAndRates/insertRate.test.ts deleted file mode 100644 index d6bdeacf7f..0000000000 --- a/services/app-api/src/postgres/contractAndRates/insertRate.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' -import { must, getStateRecord } from '../../testHelpers' -import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library' -import { mockInsertRateArgs } from '../../testHelpers/rateDataMocks' -import { insertDraftRate } from './insertRate' -import type { StateCodeType } from '../../../../app-web/src/common-code/healthPlanFormDataType' - -describe('insertRate', () => { - afterEach(() => { - jest.clearAllMocks() - }) - - it('creates a new draft rate', async () => { - const client = await sharedTestPrismaClient() - - // create a draft rate - const draftRateData = mockInsertRateArgs({ rateType: 'NEW' }) - const draftRate = must(await insertDraftRate(client, draftRateData)) - - // Expect a new draft rate to have a draftRevision and no submitted revisions - expect(draftRate.draftRevision).toBeDefined() - expect(draftRate.revisions).toHaveLength(0) - - // Expect draft rate to contain expected data. - expect(draftRate).toEqual( - expect.objectContaining({ - id: expect.any(String), - stateCode: 'MN', - status: 'DRAFT', - stateNumber: expect.any(Number), - draftRevision: expect.objectContaining({ - formData: expect.objectContaining({ - rateType: 'NEW', - }), - }), - revisions: [], - }) - ) - }) - it('increments state number count', async () => { - const client = await sharedTestPrismaClient() - const stateCode = 'VA' - const initialState = await getStateRecord(client, stateCode) - - const rateA = mockInsertRateArgs({ - stateCode, - }) - const rateB = mockInsertRateArgs({ - stateCode, - }) - - const submittedRateA = must(await insertDraftRate(client, rateA)) - - // Expect state record count to be incremented - expect(submittedRateA.stateNumber).toBeGreaterThan( - initialState.latestStateRateCertNumber - ) - - const submittedRateB = must(await insertDraftRate(client, rateB)) - - // Expect state record count to be incremented further - expect(submittedRateB.stateNumber).toBeGreaterThan( - submittedRateA.stateNumber - ) - }) - it('returns an error when invalid state code is provided', async () => { - jest.spyOn(console, 'error').mockImplementation() - const client = await sharedTestPrismaClient() - - const draftRateData = mockInsertRateArgs({ - stateCode: 'CANADA' as StateCodeType, - }) - const draftRate = await insertDraftRate(client, draftRateData) - - // Expect a prisma error - expect(draftRate).toBeInstanceOf(PrismaClientKnownRequestError) - expect(console.error).toHaveBeenCalled() - }) -}) diff --git a/services/app-api/src/postgres/contractAndRates/insertRate.ts b/services/app-api/src/postgres/contractAndRates/insertRate.ts index 0e8cb939d3..c7badd73eb 100644 --- a/services/app-api/src/postgres/contractAndRates/insertRate.ts +++ b/services/app-api/src/postgres/contractAndRates/insertRate.ts @@ -14,6 +14,7 @@ type InsertRateArgsType = RateFormEditableType & { // creates a new rate, with a new revision async function insertDraftRate( client: PrismaClient, + draftContractID: string, args: InsertRateArgsType ): Promise { const { @@ -47,10 +48,29 @@ async function insertDraftRate( }, }) + const contract = await tx.contractTable.findUnique({ + where: { id: draftContractID }, + include: { + draftRates: true, + }, + }) + + if (!contract) { + throw new Error('No Contract found for new Rate') + } + + const nextRatePosition = contract?.draftRates.length + 1 + const rate = await tx.rateTable.create({ data: { stateCode: stateCode, stateNumber: latestStateRateCertNumber, + draftContracts: { + create: { + contractID: draftContractID, + ratePosition: nextRatePosition, + }, + }, revisions: { create: { rateType, diff --git a/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts b/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts index 5cec5839cc..4d4576a938 100644 --- a/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts +++ b/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts @@ -13,7 +13,10 @@ import type { ContractRevisionTableWithFormData, UpdateInfoTableWithUpdater, } from './prismaSharedContractRateHelpers' -import { rateRevisionToDomainModel } from './prismaSharedContractRateHelpers' +import { + rateRevisionToDomainModel, + unsortedRatesRevisionsToDomainModel, +} from './prismaSharedContractRateHelpers' import { contractFormDataToDomainModel, convertUpdateInfoToDomainModel, @@ -40,7 +43,7 @@ function parseContractWithHistory( const parseContract = contractSchema.safeParse(contractWithHistory) if (!parseContract.success) { const error = `ERROR: attempting to parse prisma contract with history failed: ${parseContract.error}` - console.warn(error) + console.warn(error, contractWithHistory, parseContract.error) return parseContract.error } @@ -163,7 +166,7 @@ function contractWithHistoryToDomainModel( updatedAt: r.updatedAt, status: getContractRateStatus(r.revisions), stateCode: r.stateCode, - parentContractID: 'old-style-rate-pull', + parentContractID: contractRev.contractID, // all pre-migrated rates are parented to their only contract. stateNumber: r.stateNumber, revisions: [], } @@ -326,10 +329,11 @@ function contractWithHistoryToDomainModel( const relatedRateRevisions = submission.submissionPackages .filter((p) => p.contractRevisionID === revision.id) + .sort((a, b) => a.ratePosition - b.ratePosition) .map((p) => p.rateRevision) const rateRevisions = - ratesRevisionsToDomainModel(relatedRateRevisions) + unsortedRatesRevisionsToDomainModel(relatedRateRevisions) if (rateRevisions instanceof Error) { return rateRevisions diff --git a/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts b/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts index fbcd0dfdcd..10da43f8b8 100644 --- a/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts +++ b/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts @@ -85,7 +85,7 @@ function rateRevisionToDomainModel( return { id: revision.id, - rate: revision.rate, + rateID: revision.rateID, createdAt: revision.createdAt, updatedAt: revision.updatedAt, submitInfo: convertUpdateInfoToDomainModel(revision.submitInfo), @@ -221,7 +221,7 @@ function rateWithHistoryToDomainModel( } // Find this rate's parent contract. It'll be the contract it was initially submitted with - // or the contract it is associated with as an initial draft. + // or the contract it is associated with as an initial draft. const firstRevision = rate.revisions[0] const submission = firstRevision.submitInfo @@ -229,7 +229,8 @@ function rateWithHistoryToDomainModel( if (!submission) { // this is a draft, never submitted, rate if (rate.draftContracts.length !== 1) { - const msg = 'programming error: its an unsubmitted rate with no draft contracts' + const msg = + 'programming error: its an unsubmitted rate with no draft contracts' console.error(msg) return new Error(msg) } @@ -238,7 +239,8 @@ function rateWithHistoryToDomainModel( } else { // check the initial submission if (submission.submittedContracts.length !== 1) { - const msg = 'programming error: its a submitted rate that was not submitted with a contract initially' + const msg = + 'programming error: its a submitted rate that was not submitted with a contract initially' console.error(msg) return new Error(msg) } diff --git a/services/app-api/src/postgres/contractAndRates/prismaContractRateAdaptors.ts b/services/app-api/src/postgres/contractAndRates/prismaContractRateAdaptors.ts index 8de30fa57b..1953ea625c 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaContractRateAdaptors.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaContractRateAdaptors.ts @@ -64,6 +64,13 @@ function prismaRateCreateFormDataFromDomain( }, actuaryCommunicationPreference: rateFormData.actuaryCommunicationPreference, + contractsWithSharedRateRevision: { + connect: rateFormData.packagesWithSharedRateCerts + ? rateFormData.packagesWithSharedRateCerts.map((p) => ({ + id: p.packageId, + })) + : [], + }, } } @@ -112,6 +119,13 @@ function prismaUpdateRateFormDataFromDomain( }, actuaryCommunicationPreference: rateFormData.actuaryCommunicationPreference, + contractsWithSharedRateRevision: { + set: rateFormData.packagesWithSharedRateCerts + ? rateFormData.packagesWithSharedRateCerts.map((p) => ({ + id: p.packageId, + })) + : [], + }, } } diff --git a/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts index 71ec87ddb9..d72c8cdf07 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts @@ -46,7 +46,7 @@ function draftRateRevToDomainModel( return { id: revision.id, - rate: revision.rate, + rateID: revision.rateID, createdAt: revision.createdAt, updatedAt: revision.updatedAt, formData, diff --git a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts index 347b2860f5..725aea2448 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts @@ -238,7 +238,7 @@ function rateRevisionToDomainModel( return { id: revision.id, - rate: revision.rate, + rateID: revision.rateID, createdAt: revision.createdAt, updatedAt: revision.updatedAt, unlockInfo: convertUpdateInfoToDomainModel(revision.unlockInfo), @@ -252,6 +252,10 @@ function ratesRevisionsToDomainModel( ): RateRevisionType[] | Error { const domainRevisions: RateRevisionType[] = [] + rateRevisions.sort( + (a, b) => a.rate.createdAt.getTime() - b.rate.createdAt.getTime() + ) + for (const revision of rateRevisions) { const domainRevision = rateRevisionToDomainModel(revision) @@ -262,9 +266,23 @@ function ratesRevisionsToDomainModel( domainRevisions.push(domainRevision) } - domainRevisions.sort( - (a, b) => a.rate.createdAt.getTime() - b.rate.createdAt.getTime() - ) + return domainRevisions +} + +function unsortedRatesRevisionsToDomainModel( + rateRevisions: RateRevisionTableWithFormData[] +): RateRevisionType[] | Error { + const domainRevisions: RateRevisionType[] = [] + + for (const revision of rateRevisions) { + const domainRevision = rateRevisionToDomainModel(revision) + + if (domainRevision instanceof Error) { + return domainRevision + } + + domainRevisions.push(domainRevision) + } return domainRevisions } @@ -396,4 +414,5 @@ export { rateFormDataToDomainModel, rateRevisionToDomainModel, ratesRevisionsToDomainModel, + unsortedRatesRevisionsToDomainModel, } diff --git a/services/app-api/src/postgres/contractAndRates/submitContract.test.ts b/services/app-api/src/postgres/contractAndRates/submitContract.test.ts index 4bf097379a..349b6e74bb 100644 --- a/services/app-api/src/postgres/contractAndRates/submitContract.test.ts +++ b/services/app-api/src/postgres/contractAndRates/submitContract.test.ts @@ -141,16 +141,6 @@ describe('submitContract', () => { throw new Error('The draft rates should have been inserted') } - // submit the first rate - const submittedRate = must( - await submitRate(client, { - rateID: draftContractWithRates.draftRates[0].id, - submittedByUserID: stateUser.id, - submittedReason: 'submit rate A', - }) - ) - console.info(JSON.stringify(submittedRate, null, ' ')) - // submit the draft contract and connect submitInfo // the second rate will have the same submitInfo here // and the first rate will have a different submitInfo @@ -174,7 +164,7 @@ describe('submitContract', () => { expect(contractSubmitInfo).toEqual(rateSubmitInfoB) // but rate A does not - expect(rateSubmitInfoA?.updatedReason).toBe('submit rate A') + expect(rateSubmitInfoA?.updatedReason).toBe('initial submit') }) it('creates a submission from a draft', async () => { const client = await sharedTestPrismaClient() @@ -238,112 +228,7 @@ describe('submitContract', () => { }) // resubmitting should be a store error - expect(resubmitStoreError).toBeInstanceOf(NotFoundError) - }) - - it('invalidates old revisions when new revisions are submitted', async () => { - const client = await sharedTestPrismaClient() - - const stateUser = must( - await client.user.create({ - data: { - id: uuidv4(), - givenName: 'Aang', - familyName: 'Avatar', - email: 'aang@example.com', - role: 'STATE_USER', - stateCode: 'NM', - }, - }) - ) - - // create a draft contract - const draftContractData = mockInsertContractArgs({ - submissionDescription: 'first contract', - }) - const contractA = must( - await insertDraftContract(client, draftContractData) - ) - - // create a draft rate - const rateA = must( - await insertDraftRate(client, { - stateCode: 'MN', - rateCertificationName: 'first rate', - }) - ) - - // submit the first draft contract - const submittedContractA = must( - await submitContract(client, { - contractID: contractA.id, - submittedByUserID: stateUser.id, - submittedReason: 'initial submit', - }) - ) - - // submit the first draft rate - const rateA1 = must( - await submitRate(client, { - rateID: rateA.id, - submittedByUserID: stateUser.id, - submittedReason: 'initial rate submit', - }) - ) - // set up the relation between the submitted contract and the rate - await client.rateRevisionsOnContractRevisionsTable.create({ - data: { - contractRevisionID: submittedContractA.revisions[0].id, - rateRevisionID: rateA1.revisions[0].id, - validAfter: new Date(), - }, - }) - - // create a second draft contract - const contractASecondRevision = must( - await client.contractTable.update({ - where: { - id: contractA.id, - }, - data: { - revisions: { - create: { - submissionType: 'CONTRACT_AND_RATES', - submissionDescription: 'second contract revision', - contractType: 'BASE', - programIDs: draftContractData.programIDs, - populationCovered: 'MEDICAID', - riskBasedContract: false, - }, - }, - }, - include: { - revisions: true, - }, - }) - ) - - // submit the second draft contract - must( - await submitContract(client, { - contractID: contractASecondRevision.id, - submittedByUserID: stateUser.id, - submittedReason: 'second submit', - }) - ) - - /* now that the second contract revision has been submitted, the first contract revision should be invalidated. - Something is invalidated when it gets a validUntil value, which marks the time it stopped being valid */ - const invalidatedRevision = must( - await client.rateRevisionsOnContractRevisionsTable.findFirst({ - where: { - contractRevisionID: submittedContractA.revisions[0].id, - validUntil: { not: null }, - }, - }) - ) - - expect(invalidatedRevision).not.toBeNull() + expect(resubmitStoreError).toBeInstanceOf(Error) }) it('handles concurrent drafts correctly', async () => { @@ -369,7 +254,7 @@ describe('submitContract', () => { // Attempt to submit a rate related to this draft contract const rate1 = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'onepoint0', }) diff --git a/services/app-api/src/postgres/contractAndRates/submitContract.ts b/services/app-api/src/postgres/contractAndRates/submitContract.ts index 16e9094582..1aaf73a677 100644 --- a/services/app-api/src/postgres/contractAndRates/submitContract.ts +++ b/services/app-api/src/postgres/contractAndRates/submitContract.ts @@ -1,5 +1,9 @@ import type { PrismaClient } from '@prisma/client' -import type { ContractType } from '../../domain-models/contractAndRates' +import type { + ContractType, + RateRevisionType, + RateRevisionWithContractsType, +} from '../../domain-models/contractAndRates' import { findContractWithHistory } from './findContractWithHistory' import { NotFoundError } from '../postgresErrors' import type { UpdateInfoType } from '../../domain-models' @@ -57,12 +61,16 @@ export async function submitContract( } // get the related rate revisions and any unsubmitted rates - const relatedRateRevs = currentRev.draftRates.map( - (c) => c.revisions[0] - ) - const unsubmittedRates = relatedRateRevs.filter( - (rev) => rev.submitInfo === null - ) + const relatedRates = currentContract.draftRates + const relatedRateRevs = relatedRates + ? relatedRates.map((r) => r.draftRevision || r.revisions[0]) + : [] + + const unsubmittedRateRevs: RateRevisionType[] = relatedRates + ? relatedRates + .map((r) => r.draftRevision) + .filter((rr): rr is RateRevisionWithContractsType => !!rr) + : [] // Create the submitInfo record in the updateInfoTable const submitInfo = await tx.updateInfoTable.create({ @@ -115,7 +123,7 @@ export async function submitContract( await tx.rateRevisionTable.updateMany({ where: { id: { - in: unsubmittedRates.map((rev) => rev.id), + in: unsubmittedRateRevs.map((rev) => rev.id), }, }, data: { @@ -198,7 +206,7 @@ export async function submitContract( }, }) - const relatedRateRevisionsIDs: { + const draftRateRevisionIDs: { rateID: string revisionID: string }[] = currentContract.draftRates.map((r) => { @@ -219,29 +227,33 @@ export async function submitContract( const previousRateRevisions = pastSubmission.rateRevisions for (const previousRateRevision of previousRateRevisions) { if ( - !relatedRateRevisionsIDs.find( - (r) => r.rateID === previousRateRevision.rate.id + !draftRateRevisionIDs.find( + (r) => r.rateID === previousRateRevision.rateID ) ) { - relatedRateRevisionsIDs.push({ - rateID: previousRateRevision.rate.id, - revisionID: previousRateRevision.id, - }) + // draftRateRevisionIDs.push({ + // rateID: previousRateRevision.rate.id, + // revisionID: previousRateRevision.id, + // }) disconnectedRateRevs.push(previousRateRevision) } } } - // get previously connected contracts - // get the related rates, all of their previously connected contracts need to + // submitted contract+rates all get the submission pointed at them. + // submitted contract+rates + related rates get a related revision + // everything that gets a related revision, gets links to the current state of the world + + // all currently draft rates and disconnected rates have all their connections to old + // contract revisions that are not this contract revision copied, in addition to the connection + // (or disconnection) from the new contract revision. + // get the related rates, all of their previously connected contracts need to // get links. const allRelatedRateRevisionsBefore = await tx.rateRevisionTable.findMany({ where: { id: { - in: relatedRateRevisionsIDs.map( - (rr) => rr.revisionID - ), + in: draftRateRevisionIDs.map((rr) => rr.revisionID), }, }, include: { @@ -260,14 +272,14 @@ export async function submitContract( }, }) - // all related rates get an entry in the relatedRates connection + // all related rates get an entry in the relatedRates connection await tx.updateInfoTable.update({ where: { id: submitInfo.id, }, data: { relatedRates: { - connect: relatedRateRevisionsIDs.map((rr) => ({ + connect: draftRateRevisionIDs.map((rr) => ({ id: rr.revisionID, })), }, @@ -310,7 +322,7 @@ export async function submitContract( } let ratePosition = 0 - const newLinks = relatedRateRevisionsIDs.map((rr) => { + const newLinks = draftRateRevisionIDs.map((rr) => { ratePosition++ return { rateRevID: rr.revisionID, @@ -332,7 +344,7 @@ export async function submitContract( // delete Contract.draftRates await tx.draftRateJoinTable.deleteMany({ - where: { contractID: contractID } + where: { contractID: contractID }, }) return await findContractWithHistory(tx, contractID) diff --git a/services/app-api/src/postgres/contractAndRates/submitRate.test.ts b/services/app-api/src/postgres/contractAndRates/submitRate.test.ts index 746e93f87a..99ecdc4c8c 100644 --- a/services/app-api/src/postgres/contractAndRates/submitRate.test.ts +++ b/services/app-api/src/postgres/contractAndRates/submitRate.test.ts @@ -1,276 +1,17 @@ import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' import { v4 as uuidv4 } from 'uuid' import { submitRate } from './submitRate' -import { NotFoundError } from '../postgresErrors' import { - clearDocMetadata, mockInsertContractArgs, mockInsertRateArgs, must, } from '../../testHelpers' -import { insertDraftRate } from './insertRate' import { submitContract } from './submitContract' import { insertDraftContract } from './insertContract' import { updateDraftContractWithRates } from './updateDraftContractWithRates' import { unlockRate } from './unlockRate' -import { findContractWithHistory } from './findContractWithHistory' -import { findStatePrograms } from '../state' -import { unlockContract } from './unlockContract' -import type { RateFormEditableType } from '../../domain-models/contractAndRates' describe('submitRate', () => { - it('creates a standalone rate submission from a draft', async () => { - const client = await sharedTestPrismaClient() - - const stateUser = await client.user.create({ - data: { - id: uuidv4(), - givenName: 'Aang', - familyName: 'Avatar', - email: 'aang@example.com', - role: 'STATE_USER', - stateCode: 'NM', - }, - }) - - // submitting before there's a draft should be an error - const submitError = await submitRate(client, { - rateID: '1111', - submittedByUserID: '1111', - submittedReason: 'failed submit', - }) - expect(submitError).toBeInstanceOf(NotFoundError) - - // create a draft rate - const draftRateData = mockInsertRateArgs({ - rateCertificationName: 'rate-cert-name', - }) - const rateA = must(await insertDraftRate(client, draftRateData)) - // submit the draft contract - const result = must( - await submitRate(client, { - rateID: rateA.id, - submittedByUserID: stateUser.id, - submittedReason: 'Initial submission', - formData: { - ...draftRateData, - rateType: 'AMENDMENT', - }, - }) - ) - - // Expect default submit reason - expect(result.revisions[0].submitInfo?.updatedReason).toBe( - 'Initial submission' - ) - - // Expect rate form data to be what was inserted - expect(result.revisions[0]).toEqual( - expect.objectContaining({ - formData: expect.objectContaining({ - rateCertificationName: 'rate-cert-name', - rateType: 'AMENDMENT', - }), - }) - ) - - const resubmitStoreError = await submitRate(client, { - rateID: rateA.id, - submittedByUserID: stateUser.id, - submittedReason: 'Resubmit', - }) - - // resubmitting should be a store error - expect(resubmitStoreError).toBeInstanceOf(NotFoundError) - }) - - it('invalidates old revisions when new revisions are submitted', async () => { - const client = await sharedTestPrismaClient() - - const stateUser = must( - await client.user.create({ - data: { - id: uuidv4(), - givenName: 'Aang', - familyName: 'Avatar', - email: 'aang@example.com', - role: 'STATE_USER', - stateCode: 'NM', - }, - }) - ) - - // create a draft rate - const draftRateData = mockInsertRateArgs({ - rateCertificationName: 'first rate ', - }) - const rateA = must(await insertDraftRate(client, draftRateData)) - - // create a draft contract - const contractA = must( - await insertDraftContract( - client, - mockInsertContractArgs({ - submissionDescription: 'first contract', - }) - ) - ) - - // submit the first draft rate with no associated contracts - const submittedRateA = must( - await submitRate(client, { - rateID: rateA.id, - submittedByUserID: stateUser.id, - submittedReason: 'initial submit', - }) - ) - - // submit the contract - const contractA1 = must( - await submitContract(client, { - contractID: contractA.id, - submittedByUserID: stateUser.id, - submittedReason: 'initial rate submit', - }) - ) - // set up the relation between the submitted contract and the rate - await client.rateRevisionsOnContractRevisionsTable.create({ - data: { - rateRevisionID: submittedRateA.revisions[0].id, - contractRevisionID: contractA1.revisions[0].id, - validAfter: new Date(), - }, - }) - - // create a second draft rate - const rateASecondRevision = must( - await client.rateTable.update({ - where: { - id: rateA.id, - }, - data: { - revisions: { - create: { - rateCertificationName: 'second contract revision', - }, - }, - }, - include: { - revisions: true, - }, - }) - ) - - // submit the second draft rate - must( - await submitRate(client, { - rateID: rateASecondRevision.id, - submittedByUserID: stateUser.id, - submittedReason: 'second submit', - }) - ) - - /* now that the second rate revision has been submitted, the first rate revision should be invalidated. - Something is invalidated when it gets a validUntil value, which marks the time it stopped being valid */ - const invalidatedRevision = must( - await client.rateRevisionsOnContractRevisionsTable.findFirst({ - where: { - rateRevisionID: submittedRateA.revisions[0].id, - validUntil: { not: null }, - }, - }) - ) - - expect(invalidatedRevision).not.toBeNull() - }) - - it('submits rate with updates', async () => { - const client = await sharedTestPrismaClient() - - const stateUser = must( - await client.user.create({ - data: { - id: uuidv4(), - givenName: 'Aang', - familyName: 'Avatar', - email: 'aang@example.com', - role: 'STATE_USER', - stateCode: 'NM', - }, - }) - ) - - // create a draft rate - const draftRateData = mockInsertRateArgs({ - rateCertificationName: 'first rate ', - }) - const draftRate = must(await insertDraftRate(client, draftRateData)) - - if (!draftRate.draftRevision) { - throw new Error( - 'Unexpected error: No draft rate revision in draft rate' - ) - } - - const rateID = draftRate.draftRevision.rate.id - - const statePrograms = must(findStatePrograms(draftRate.stateCode)) - - const updateRateData: RateFormEditableType = { - ...draftRate.draftRevision.formData, - rateType: 'NEW', - rateID, - rateCertificationName: 'testState-123', - rateProgramIDs: [statePrograms[0].id], - rateCapitationType: 'RATE_CELL', - rateDateStart: new Date('2024-01-01'), - rateDateEnd: new Date('2025-01-01'), - rateDateCertified: new Date('2024-01-01'), - amendmentEffectiveDateEnd: new Date('2024-02-01'), - amendmentEffectiveDateStart: new Date('2025-02-01'), - actuaryCommunicationPreference: 'OACT_TO_ACTUARY', - certifyingActuaryContacts: [], - addtlActuaryContacts: [], - supportingDocuments: [ - { - name: 'rate supporting doc', - s3URL: 'fakeS3URL', - sha256: '2342fwlkdmwvw', - }, - { - name: 'rate supporting doc 2', - s3URL: 'fakeS3URL', - sha256: '45662342fwlkdmwvw', - }, - ], - rateDocuments: [ - { - name: 'contract doc', - s3URL: 'fakeS3URL', - sha256: '8984234fwlkdmwvw', - }, - ], - } - - const submittedRate = must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit and update rate', - formData: updateRateData, - }) - ) - - expect({ - ...submittedRate.revisions[0].formData, - rateDocuments: clearDocMetadata( - submittedRate.revisions[0].formData.rateDocuments - ), - supportingDocuments: clearDocMetadata( - submittedRate.revisions[0].formData.supportingDocuments - ), - }).toEqual(expect.objectContaining(updateRateData)) - }) it('submits rate independent of contract status', async () => { const client = await sharedTestPrismaClient() @@ -330,43 +71,7 @@ describe('submitRate', () => { } const rateID = - updatedDraftContract.draftRevision.rateRevisions[0].rate.id - - // submit rate - const submittedRate = must( - await submitRate(client, { - rateID, - submittedByUserID: stateUser.id, - submittedReason: 'submit and update rate', - formData: { - rateCertificationName: 'rate revision 1.1', - rateType: 'AMENDMENT', - }, - }) - ) - - // expect submitted rate not to have error. - expect(submittedRate).not.toBeInstanceOf(Error) - - const fetchedDraftContract = must( - await findContractWithHistory(client, contractID) - ) - - if (!fetchedDraftContract.draftRevision) { - throw new Error( - 'Unexpected error: draft revision not found in draft contract' - ) - } - - // expect updated and submitted rate revision to be on draft contract revision - expect( - fetchedDraftContract.draftRevision.rateRevisions[0].formData - ).toEqual( - expect.objectContaining({ - rateCertificationName: 'rate revision 1.1', - rateType: 'AMENDMENT', - }) - ) + updatedDraftContract.draftRevision.rateRevisions[0].rateID const submittedContract = must( await submitContract(client, { @@ -381,8 +86,8 @@ describe('submitRate', () => { submittedContract.revisions[0].rateRevisions[0].formData ).toEqual( expect.objectContaining({ - rateCertificationName: 'rate revision 1.1', - rateType: 'AMENDMENT', + rateCertificationName: 'rate revision 1.0', + rateType: 'NEW', }) ) @@ -395,14 +100,6 @@ describe('submitRate', () => { }) ) - must( - await unlockContract(client, { - contractID: draftContract.id, - unlockReason: 'dosmsdfs', - unlockedByUserID: cmsUser.id, - }) - ) - must( await submitRate(client, { rateID, diff --git a/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts b/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts index cdc0ae8559..5829a316d5 100644 --- a/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts +++ b/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts @@ -10,6 +10,7 @@ import { updateDraftRate } from './updateDraftRate' import { submitContract } from './submitContract' import { findContractWithHistory } from './findContractWithHistory' import { must, mockInsertContractArgs } from '../../testHelpers' +import { updateDraftContractRates } from './updateDraftContractRates' describe('unlockContract', () => { it('Unlocks a rate without breaking connected draft contract', async () => { @@ -45,51 +46,60 @@ describe('unlockContract', () => { await insertDraftContract(client, draftContractData) ) const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', rateCertificationName: 'Rate 1.0', }) ) - // Submit Rate A - const submittedRate = must( - await submitRate(client, { - rateID: rate.id, + // Submit Contract With Rate A + const submittedContractWithRateA = must( + await submitContract(client, { + contractID: contract.id, submittedByUserID: stateUser.id, submittedReason: 'Rate A 1.0 submit', }) ) + const submittedRateAID = + submittedContractWithRateA.packageSubmissions[0].rateRevisions[0] + .rateID + + const contract2 = must( + await insertDraftContract(client, draftContractData) + ) // Connect draft contract to submitted rate must( - await updateDraftContractWithRates(client, { - contractID: contract.id, - formData: { - submissionType: 'CONTRACT_AND_RATES', - submissionDescription: 'Connecting rate', - contractType: 'BASE', - programIDs: ['PMAP'], - populationCovered: 'MEDICAID', - riskBasedContract: false, + await updateDraftContractRates(client, { + contractID: contract2.id, + rateUpdates: { + create: [], + update: [], + link: [ + { + rateID: rate.id, + ratePosition: 1, + }, + ], + unlink: [], + delete: [], }, - rateFormDatas: [submittedRate.revisions[0].formData], }) ) const fullDraftContract = must( - await findContractWithHistory(client, contract.id) + await findContractWithHistory(client, contract2.id) ) - const draftContract = fullDraftContract.draftRevision + const draftContractRev = fullDraftContract.draftRevision + const draftRates = fullDraftContract.draftRates - if (draftContract === undefined) { + if (draftContractRev === undefined || draftRates === undefined) { throw Error('Unexpect error: draft contract missing draft revision') } // Rate revision should be connected to contract - expect(draftContract.rateRevisions[0].id).toEqual( - submittedRate.revisions[0].id - ) + expect(draftRates[0].id).toEqual(submittedRateAID) // Unlock the rate must( @@ -116,19 +126,21 @@ describe('unlockContract', () => { ) const fullDraftContractTwo = must( - await findContractWithHistory(client, contract.id) + await findContractWithHistory(client, contract2.id) ) - const draftContractTwo = fullDraftContractTwo.draftRevision + const draftContractRevTwo = fullDraftContractTwo.draftRevision + const draftContractTwoDraftRates = fullDraftContractTwo.draftRates - if (draftContractTwo === undefined) { + if ( + draftContractRevTwo === undefined || + draftContractTwoDraftRates === undefined + ) { throw Error('Unexpect error: draft contract missing draft revision') } // Contract should now have the latest rate revision - expect(draftContractTwo.rateRevisions[0].id).toEqual( - resubmittedRate.revisions[0].id - ) + expect(draftContractTwoDraftRates[0].id).toEqual(resubmittedRate.id) }) // This is unlocking a rate without unlocking the contract that this rate belongs to. Then it updates the rate and resubmits. @@ -136,6 +148,7 @@ describe('unlockContract', () => { // This test does not simulate how creating/updating a rate currently works in our app and the contract revision history // will not match. // Skipping this for now, revisit during rate only feature work. + // eslint-disable-next-line jest/no-disabled-tests it.skip('Unlocks a rate without breaking connected submitted contract', async () => { const client = await sharedTestPrismaClient() @@ -169,7 +182,7 @@ describe('unlockContract', () => { await insertDraftContract(client, draftContractData) ) const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', rateCertificationName: 'Rate 1.0', }) @@ -285,7 +298,7 @@ describe('unlockContract', () => { await insertDraftContract(client, draftContractData) ) const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', rateCertificationName: 'rate 1.0', }) @@ -297,31 +310,6 @@ describe('unlockContract', () => { ) } - // Connect draft contract to draft rate - must( - await updateDraftContractWithRates(client, { - contractID: contract.id, - formData: { - submissionType: 'CONTRACT_AND_RATES', - submissionDescription: 'contract 1.0', - contractType: 'BASE', - programIDs: ['PMAP'], - populationCovered: 'MEDICAID', - riskBasedContract: false, - }, - rateFormDatas: [rate.draftRevision?.formData], - }) - ) - - // Submit rate - const submittedRate = must( - await submitRate(client, { - rateID: rate.id, - submittedByUserID: stateUser.id, - submittedReason: 'Submit rate 1.0', - }) - ) - // Submit contract const submittedContract = must( await submitContract(client, { @@ -330,10 +318,10 @@ describe('unlockContract', () => { submittedReason: 'Submit contract 1.0', }) ) - const latestContractRev = submittedContract.revisions[0] + const lastestSubmission = submittedContract.packageSubmissions[0] - expect(latestContractRev.rateRevisions[0].id).toEqual( - submittedRate.revisions[0].id + expect(lastestSubmission.rateRevisions[0].id).toEqual( + rate.draftRevision.id ) // Unlock and resubmit contract @@ -344,20 +332,6 @@ describe('unlockContract', () => { unlockReason: 'First unlock', }) ) - must( - await updateDraftContractWithRates(client, { - contractID: contract.id, - formData: { - submissionType: 'CONTRACT_AND_RATES', - submissionDescription: 'contract 2.0', - contractType: 'BASE', - programIDs: ['PMAP'], - populationCovered: 'MEDICAID', - riskBasedContract: false, - }, - rateFormDatas: [rate.draftRevision?.formData], - }) - ) const resubmittedContract = must( await submitContract(client, { @@ -366,11 +340,11 @@ describe('unlockContract', () => { submittedReason: 'Submit contract 2.0', }) ) - const latestResubmittedRev = resubmittedContract.revisions[0] + const latestResubmission = resubmittedContract.packageSubmissions[0] // Expect rate revision to still be connected - expect(latestResubmittedRev.rateRevisions[0].id).toEqual( - submittedRate.revisions[0].id + expect(latestResubmission.rateRevisions[0].id).toEqual( + rate.draftRevision.id ) }) it('errors when unlocking a draft contract or rate', async () => { @@ -395,7 +369,7 @@ describe('unlockContract', () => { await insertDraftContract(client, draftContractData) ) const rateA = must( - await insertDraftRate(client, { + await insertDraftRate(client, contractA.id, { stateCode: 'MN', rateCertificationName: 'rate A 1.1', }) diff --git a/services/app-api/src/postgres/contractAndRates/updateDraftContractRates.ts b/services/app-api/src/postgres/contractAndRates/updateDraftContractRates.ts index 97b2c990bd..abaab1d861 100644 --- a/services/app-api/src/postgres/contractAndRates/updateDraftContractRates.ts +++ b/services/app-api/src/postgres/contractAndRates/updateDraftContractRates.ts @@ -4,6 +4,7 @@ import type { RateFormEditableType, } from '../../domain-models/contractAndRates' import { NotFoundError } from '../postgresErrors' +import type { PrismaTransactionType } from '../prismaTypes' import { findContractWithHistory } from './findContractWithHistory' import { prismaRateCreateFormDataFromDomain, @@ -37,194 +38,227 @@ interface UpdateDraftContractRatesArgsType { rateUpdates: UpdatedRatesType } -async function updateDraftContractRates( - client: PrismaClient, +async function updateDraftContractRatesInTransaction( + tx: PrismaTransactionType, args: UpdateDraftContractRatesArgsType ): Promise { - try { - return await client.$transaction(async (tx) => { - // for now, get the latest contract revision, eventually we'll have rate revisions directly on this - const contract = await tx.contractTable.findUnique({ - where: { - id: args.contractID, + // for now, get the latest contract revision, eventually we'll have rate revisions directly on this + const contract = await tx.contractTable.findUnique({ + where: { + id: args.contractID, + }, + include: { + revisions: { + take: 1, + orderBy: { + createdAt: 'desc', }, - include: { - revisions: { - take: 1, - orderBy: { - createdAt: 'desc', - }, - }, - }, - }) - - if (!contract) { - return new NotFoundError( - 'contract not found with ID: ' + args.contractID - ) - } - - const draftRevision = contract.revisions[0] - if (!draftRevision) { - return new Error( - 'PROGRAMMER ERROR: This draft contract has no draft revision' - ) - } - - // figure out the rate number range for created rates. - const state = await tx.state.findUnique({ - where: { stateCode: contract.stateCode }, - }) - - if (!state) { - return new Error( - 'PROGRAMER ERROR: No state found with code: ' + - contract.stateCode - ) - } - - let nextRateNumber = state.latestStateRateCertNumber + 1 - - // create new rates with new revisions - const createdRateJoins: { rateID: string; ratePosition: number }[] = - [] - for (const createRateArg of args.rateUpdates.create) { - const rateFormData = createRateArg.formData - const thisRateNumber = nextRateNumber - nextRateNumber++ - - const rateToCreate = { - stateCode: contract.stateCode, - stateNumber: thisRateNumber, - revisions: { - create: prismaRateCreateFormDataFromDomain( - rateFormData - ), - }, - } + }, + }, + }) - const createdRate = await tx.rateTable.create({ - data: rateToCreate, - include: { - revisions: true, - }, - }) - - createdRateJoins.push({ - rateID: createdRate.id, - ratePosition: createRateArg.ratePosition, - }) - } - - // to delete draft rates, we need to delete their revisions first - await tx.rateRevisionTable.deleteMany({ - where: { - rateID: { - in: args.rateUpdates.delete.map((ru) => ru.rateID), - }, - }, - }) - await tx.rateTable.deleteMany({ - where: { - id: { - in: args.rateUpdates.delete.map((ru) => ru.rateID), - }, - }, - }) - - const oldLinksToCreate = [ - ...createdRateJoins.map((lr) => lr.rateID), - ...args.rateUpdates.link.map((ru) => ru.rateID), - ] - - // create new rates and link and unlink others - await tx.contractRevisionTable.update({ - where: { id: draftRevision.id }, - data: { - draftRates: { - connect: oldLinksToCreate.map((rID) => ({ - id: rID, - })), - disconnect: args.rateUpdates.unlink.map((ru) => ({ - id: ru.rateID, - })), - }, - }, - include: { - draftRates: true, - }, - }) + if (!contract) { + return new NotFoundError( + 'contract not found with ID: ' + args.contractID + ) + } + + const draftRevision = contract.revisions[0] + if (!draftRevision) { + return new Error( + 'PROGRAMMER ERROR: This draft contract has no draft revision' + ) + } - // new rate + contract Linking tables + // figure out the rate number range for created rates. + const state = await tx.state.findUnique({ + where: { stateCode: contract.stateCode }, + }) - // for each of the links, we have to get the order right - // all the newly valid links are from create/update/link - const links: { rateID: string; ratePosition: number }[] = [ - ...createdRateJoins.map((rj) => ({ - rateID: rj.rateID, - ratePosition: rj.ratePosition, - })), - ...args.rateUpdates.update.map((ru) => ({ - rateID: ru.rateID, - ratePosition: ru.ratePosition, + if (!state) { + return new Error( + 'PROGRAMER ERROR: No state found with code: ' + contract.stateCode + ) + } + + let nextRateNumber = state.latestStateRateCertNumber + 1 + + // create new rates with new revisions + const createdRateJoins: { rateID: string; ratePosition: number }[] = [] + for (const createRateArg of args.rateUpdates.create) { + const rateFormData = createRateArg.formData + const thisRateNumber = nextRateNumber + nextRateNumber++ + + const rateToCreate = { + stateCode: contract.stateCode, + stateNumber: thisRateNumber, + revisions: { + create: prismaRateCreateFormDataFromDomain(rateFormData), + }, + } + + const createdRate = await tx.rateTable.create({ + data: rateToCreate, + include: { + revisions: true, + }, + }) + + createdRateJoins.push({ + rateID: createdRate.id, + ratePosition: createRateArg.ratePosition, + }) + } + + // to delete draft rates, we need to delete their revisions first + await tx.rateRevisionTable.deleteMany({ + where: { + rateID: { + in: args.rateUpdates.delete.map((ru) => ru.rateID), + }, + }, + }) + await tx.rateTable.deleteMany({ + where: { + id: { + in: args.rateUpdates.delete.map((ru) => ru.rateID), + }, + }, + }) + + const oldLinksToCreate = [ + ...createdRateJoins.map((lr) => lr.rateID), + ...args.rateUpdates.link.map((ru) => ru.rateID), + ] + + // create new rates and link and unlink others + await tx.contractRevisionTable.update({ + where: { id: draftRevision.id }, + data: { + draftRates: { + connect: oldLinksToCreate.map((rID) => ({ + id: rID, })), - ...args.rateUpdates.link.map((ru) => ({ - rateID: ru.rateID, - ratePosition: ru.ratePosition, + disconnect: args.rateUpdates.unlink.map((ru) => ({ + id: ru.rateID, })), - ] - - // Check our work, these should be an incrementing list of ratePositions. - const ratePositions = links.map((l) => l.ratePosition).sort() - let lastPosition = 0 - for (const ratePosition of ratePositions) { - if (ratePosition !== lastPosition + 1) { - console.error( - 'Updated Rate ratePositions Are Not Ordered', - ratePositions - ) - return new Error( - 'updateDraftContractRates called with discontinuous order ratePositions' - ) - } - lastPosition++ - } - - await tx.contractTable.update({ - where: { id: args.contractID }, - data: { - draftRates: { - deleteMany: {}, - create: links, + }, + }, + include: { + draftRates: true, + }, + }) + + // new rate + contract Linking tables + + // for each of the links, we have to get the order right + // all the newly valid links are from create/update/link + const links: { rateID: string; ratePosition: number }[] = [ + ...createdRateJoins.map((rj) => ({ + rateID: rj.rateID, + ratePosition: rj.ratePosition, + })), + ...args.rateUpdates.update.map((ru) => ({ + rateID: ru.rateID, + ratePosition: ru.ratePosition, + })), + ...args.rateUpdates.link.map((ru) => ({ + rateID: ru.rateID, + ratePosition: ru.ratePosition, + })), + ] + + // Check our work, these should be an incrementing list of ratePositions. + const ratePositions = links.map((l) => l.ratePosition).sort() + let lastPosition = 0 + for (const ratePosition of ratePositions) { + if (ratePosition !== lastPosition + 1) { + console.error( + 'Updated Rate ratePositions Are Not Ordered', + ratePositions + ) + return new Error( + 'updateDraftContractRates called with discontinuous order ratePositions' + ) + } + lastPosition++ + } + + await tx.contractTable.update({ + where: { id: args.contractID }, + data: { + draftRates: { + deleteMany: {}, + create: links, + }, + }, + }) + + // end new R+C Contract Linking Tables + + // update existing rates + for (const ru of args.rateUpdates.update) { + const draftRev = await tx.rateRevisionTable.findFirst({ + where: { + rateID: ru.rateID, + submitInfoID: null, + }, + }) + + if (!draftRev) { + return new Error( + 'attempting to update a rate that is not editable: ' + ru.rateID + ) + } + + await tx.rateRevisionTable.update({ + where: { id: draftRev.id }, + data: prismaUpdateRateFormDataFromDomain(ru.formData), + }) + } + + // unlink old data from disconnected rates + for (const ru of args.rateUpdates.unlink) { + const draftRev = await tx.rateRevisionTable.findFirst({ + where: { + rateID: ru.rateID, + }, + orderBy: { + createdAt: 'desc', + }, + }) + + if (!draftRev) { + return new Error( + 'attempting to unlink a rate with no revision: ' + ru.rateID + ) + } + + await tx.rateRevisionTable.update({ + where: { id: draftRev.id }, + data: { + draftContracts: { + disconnect: { + id: args.contractID, }, }, - }) + }, + }) + } - // end new R+C Contract Linking Tables + return findContractWithHistory(tx, args.contractID) +} - // update existing rates - for (const ru of args.rateUpdates.update) { - const draftRev = await tx.rateRevisionTable.findFirst({ - where: { - rateID: ru.rateID, - submitInfoID: null, - }, - }) - - if (!draftRev) { - return new Error( - 'attempting to update a rate that is not editable: ' + - ru.rateID - ) - } - - await tx.rateRevisionTable.update({ - where: { id: draftRev.id }, - data: prismaUpdateRateFormDataFromDomain(ru.formData), - }) - } - - return findContractWithHistory(tx, args.contractID) +async function updateDraftContractRates( + client: PrismaClient, + args: UpdateDraftContractRatesArgsType +): Promise { + try { + return await client.$transaction(async (tx) => { + const result = await updateDraftContractRatesInTransaction(tx, args) + + return result }) } catch (err) { console.error('PRISMA ERR', err) @@ -234,4 +268,4 @@ async function updateDraftContractRates( export type { UpdateDraftContractRatesArgsType } -export { updateDraftContractRates } +export { updateDraftContractRates, updateDraftContractRatesInTransaction } diff --git a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.test.ts b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.test.ts index 074146312f..7436513bfe 100644 --- a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.test.ts +++ b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.test.ts @@ -17,6 +17,8 @@ import { mockInsertRateArgs } from '../../testHelpers/rateDataMocks' import { v4 as uuidv4 } from 'uuid' import { insertDraftRate } from './insertRate' import { submitRate } from './submitRate' +import { submitContract } from './submitContract' +import { unlockContract } from './unlockContract' describe('updateDraftContractWithRates postgres', () => { afterEach(() => { @@ -758,6 +760,7 @@ describe('updateDraftContractWithRates postgres', () => { const draftRate = must( await insertDraftRate( client, + draftContract.id, mockInsertRateArgs({ rateType: 'NEW', stateCode: 'MN', @@ -861,19 +864,41 @@ describe('updateDraftContractWithRates postgres', () => { // expect 1 rate expect(newlyCreatedRates).toHaveLength(1) - // submit rate - const submittedExistingRate = must( + // submit contract + must( + await submitContract(client, { + contractID: draftContract.id, + submittedByUserID: stateUser.id, + submittedReason: 'Contract submit', + }) + ) + + must( + await unlockContract(client, { + contractID: draftContract.id, + unlockedByUserID: stateUser.id, + unlockReason: 'Contract unlock', + }) + ) + + // resubmit this rate separate from the contract. Technically probably not allowed. + must( await submitRate(client, { - rateID: newlyCreatedRates[0].formData.rateID, + rateID: newlyCreatedRates[0].rateID, submittedByUserID: stateUser.id, submittedReason: 'Rate submit', }) ) // Create and submit a new rate that is type 'AMENDMENT' + const secondContract = must( + await insertDraftContract(client, draftContractFormData) + ) + const newDraftRate = must( await insertDraftRate( client, + secondContract.id, mockInsertRateArgs({ id: uuidv4(), rateType: 'AMENDMENT', @@ -881,61 +906,39 @@ describe('updateDraftContractWithRates postgres', () => { ) ) - const newSubmittedRate = must( - await submitRate(client, { - rateID: newDraftRate.id, + if (!newDraftRate.draftRevision) { + throw new Error('NO draft') + } + + must( + await submitContract(client, { + contractID: secondContract.id, submittedByUserID: stateUser.id, - submittedReason: 'Rate 2 submit', + submittedReason: 'Contract submit with amendment rate', }) ) - if ( - !submittedExistingRate.revisions[0] || - !newSubmittedRate.revisions[0] - ) { - throw new Error( - 'Unexpected error. Submitted rates did not contain revisions' - ) - } - // Update contract with submitted rate and try to update the submitted rate revision - const attemptToUpdateSubmittedRate = must( - await updateDraftContractWithRates(client, { + const attemptToUpdateSubmittedRate = await updateDraftContractWithRates( + client, + { contractID: updatedContractWithNewRates.id, formData: {}, rateFormDatas: [ // attempt to update the revision data of a submitted rate 1. { - ...submittedExistingRate.revisions[0].formData, + ...newlyCreatedRates[0].formData, rateType: 'AMENDMENT', }, // Connect submitted rate 2 and try to update the rate data { - ...newSubmittedRate.revisions[0].formData, + ...newDraftRate.draftRevision.formData, rateType: 'NEW', }, ], - }) + } ) - if (!attemptToUpdateSubmittedRate.draftRevision) { - throw new Error( - 'Unexpected error: draft rate is missing a draftRevision.' - ) - } - - // Expect 2 connected rates - expect( - attemptToUpdateSubmittedRate.draftRevision.rateRevisions - ).toHaveLength(2) - - // Expect the first rates data not to have changed - expect( - attemptToUpdateSubmittedRate.draftRevision.rateRevisions[0].formData - ).toEqual(submittedExistingRate.revisions[0].formData) - // Expect the second rate to be connected and data not to be changed - expect( - attemptToUpdateSubmittedRate.draftRevision.rateRevisions[1].formData - ).toEqual(newSubmittedRate.revisions[0].formData) + expect(attemptToUpdateSubmittedRate).toBeInstanceOf(Error) }) }) diff --git a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts index 5d9d024dfe..717976ed87 100644 --- a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts +++ b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts @@ -7,15 +7,11 @@ import type { RateFormEditableType, ContractFormEditableType, } from '../../domain-models/contractAndRates' -import type { StateCodeType } from '../../../../app-web/src/common-code/healthPlanFormDataType' import { includeDraftRates } from './prismaDraftContractHelpers' import { rateRevisionToDomainModel } from './prismaSharedContractRateHelpers' -import { isEqualData } from '../../resolvers/healthPlanPackage/contractAndRates/resolverHelpers' -import { - prismaUpdateRateFormDataFromDomain, - prismaUpdateContractFormDataFromDomain, - prismaRateCreateFormDataFromDomain, -} from './prismaContractRateAdaptors' +import type { UpdateDraftContractRatesArgsType } from './updateDraftContractRates' +import { updateDraftContractRatesInTransaction } from './updateDraftContractRates' +import { prismaUpdateContractFormDataFromDomain } from './prismaContractRateAdaptors' type UpdateContractArgsType = { contractID: string @@ -23,72 +19,68 @@ type UpdateContractArgsType = { rateFormDatas?: RateFormEditableType[] } -const sortRatesForUpdate = ( +// going down the old path, from the updateHPFD code, we construct the new +// call to the new updateContractDraftRates API. +// no rates will be linked here, only updated/created +function makeUpdateCommandsFromOldContract( + contractID: string, ratesFromDB: RateRevisionType[], ratesFromClient: RateFormEditableType[] -): { - upsertRates: RateFormEditableType[] - disconnectRates: { - rateID: string - revisionID: string - }[] -} => { - const upsertRates = [] - const disconnectRates = [] +): UpdateDraftContractRatesArgsType { + const updateArgs: UpdateDraftContractRatesArgsType = { + contractID, + rateUpdates: { + create: [], + update: [], + link: [], + unlink: [], + delete: [], + }, + } - // Find rates to create or update + // all rates are child rates in the old world, only create/update. + let thisPosition = 1 for (const clientRateData of ratesFromClient) { - // Find a matching rate revision id in the draftRatesFromDB array. const matchingDBRate = ratesFromDB.find( (dbRate) => dbRate.formData.rateID === clientRateData.id ) - // If there are no matching rates we push into createRates - if (!matchingDBRate) { - upsertRates.push({ - id: clientRateData.id, - ...clientRateData, + if (matchingDBRate) { + updateArgs.rateUpdates.update.push({ + rateID: matchingDBRate.rateID, + formData: clientRateData, + ratePosition: thisPosition, }) - continue - } - - // If a match is found then we deep compare to figure out if we need to update. - const isRateDataEqual = isEqualData( - matchingDBRate.formData, - clientRateData - ) - - // If rates are not equal we then make the update - if (!isRateDataEqual) { - upsertRates.push({ - id: clientRateData.id, - rateID: matchingDBRate.id, - ...clientRateData, + } else { + updateArgs.rateUpdates.create.push({ + formData: clientRateData, + ratePosition: thisPosition, }) } + thisPosition++ } - // Find rates to disconnect - for (const dbRateRev of ratesFromDB) { - //Find a matching rate revision id in the ratesFromClient - const matchingHPPRate = ratesFromClient.find( - (clientRateData) => clientRateData.id === dbRateRev.formData.rateID + // any rates that have been removed need to be deleted or unlinked. + for (const dbRate of ratesFromDB) { + const matchingClientRate = ratesFromClient.find( + (clientRateData) => dbRate.formData.rateID === clientRateData.id ) - // If convertedRateData does not contain the rate revision id from DB, we push these revisions id and rate id - // in disconnectRates - if (!matchingHPPRate && dbRateRev.formData.rateID) { - disconnectRates.push({ - rateID: dbRateRev.formData.rateID, - revisionID: dbRateRev.id, - }) + if (!matchingClientRate) { + // if it's been submitted before, it's unlink, if not, it's delete + if (dbRate.unlockInfo) { + updateArgs.rateUpdates.unlink.push({ + rateID: dbRate.rateID, + }) + } else { + updateArgs.rateUpdates.delete.push({ + rateID: dbRate.rateID, + }) + } } } - return { - upsertRates, - disconnectRates, - } + return updateArgs } // Update the given draft @@ -123,8 +115,6 @@ async function updateDraftContractWithRates( return new NotFoundError(err) } - const stateCode = currentContractRev.contract - .stateCode as StateCodeType const ratesFromDB: RateRevisionType[] = [] // Convert all rates from DB to domain model @@ -142,148 +132,34 @@ async function updateDraftContractWithRates( ratesFromDB.push(domainRateRevision) } - // Parsing rates from request for update or create - const updateRates = - rateFormDatas && sortRatesForUpdate(ratesFromDB, rateFormDatas) - - if (updateRates) { - for (const rateFormData of updateRates.upsertRates) { - // Current rate with the latest revision - let currentRate = undefined - - // If no rate id is undefined we know this is a new rate that needs to be inserted into the DB. - if (rateFormData.rateID) { - currentRate = await tx.rateTable.findUnique({ - where: { - id: rateFormData.id, - }, - include: { - // include the single most recent revision that is not submitted - revisions: { - where: { - submitInfoID: null, - }, - take: 1, - orderBy: { - createdAt: 'desc', - }, - }, - }, - }) - } - - const contractsWithSharedRates = - rateFormData.packagesWithSharedRateCerts?.map( - (pkg) => ({ - id: pkg.packageId, - }) - ) ?? [] - - // If rate does not exist, we need to create a new rate. - if (!currentRate) { - const { latestStateRateCertNumber } = - await tx.state.update({ - data: { - latestStateRateCertNumber: { - increment: 1, - }, - }, - where: { - stateCode: stateCode, - }, - }) - - await tx.rateTable.create({ - data: { - id: rateFormData.id, - stateCode: stateCode, - stateNumber: latestStateRateCertNumber, - revisions: { - create: { - ...prismaRateCreateFormDataFromDomain( - rateFormData - ), - contractsWithSharedRateRevision: { - connect: contractsWithSharedRates, - }, - }, - }, - draftContractRevisions: { - connect: { - id: currentContractRev.id, - }, - }, - }, - }) - } else { - // If the current rate has no draft revisions, based form our find with revision with no submitInfoID - // then this is a submitted rate - const isSubmitted = currentRate.revisions.length === 0 - - await tx.rateTable.update({ - where: { - id: currentRate.id, - }, - data: { - // if rate is not submitted, we update the revision data, otherwise we only make the - // connection to the draft contract revision. - revisions: !isSubmitted - ? { - update: { - where: { - id: currentRate.revisions[0] - .id, - }, - data: { - ...prismaUpdateRateFormDataFromDomain( - rateFormData - ), - contractsWithSharedRateRevision: - { - set: contractsWithSharedRates, - }, - }, - }, - } - : undefined, - draftContractRevisions: { - connect: { - id: currentContractRev.id, - }, - }, - }, - }) - } + // call new style rate updates. + if (rateFormDatas) { + const rateUpdateCommands: UpdateDraftContractRatesArgsType = + makeUpdateCommandsFromOldContract( + contractID, + ratesFromDB, + rateFormDatas + ) + const rateUpdates = await updateDraftContractRatesInTransaction( + tx, + rateUpdateCommands + ) + if (rateUpdates instanceof Error) { + console.error( + 'failed to update new style rates in old style update', + rateUpdates + ) + return rateUpdates } } - // Then update resource, adjusting all simple fields and creating new linked resources for fields holding relationships to other day, + // Then update the contractRevision, adjusting all simple fields await tx.contractRevisionTable.update({ where: { id: currentContractRev.id, }, data: { ...prismaUpdateContractFormDataFromDomain(formData), - draftRates: { - disconnect: updateRates?.disconnectRates - ? updateRates.disconnectRates.map((rate) => ({ - id: rate.rateID, - })) - : [], - }, - contract: { - update: { - draftRateRevisions: { - disconnect: updateRates?.disconnectRates - ? updateRates.disconnectRates.map( - (rate) => ({ - id: rate.revisionID, - }) - ) - : [], - }, - }, - }, }, }) diff --git a/services/app-api/src/postgres/contractAndRates/updateDraftRate.test.ts b/services/app-api/src/postgres/contractAndRates/updateDraftRate.test.ts index 5e00f3c192..a187d120cc 100644 --- a/services/app-api/src/postgres/contractAndRates/updateDraftRate.test.ts +++ b/services/app-api/src/postgres/contractAndRates/updateDraftRate.test.ts @@ -1,11 +1,16 @@ import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' import { insertDraftRate } from './insertRate' -import { clearDocMetadata, must } from '../../testHelpers' +import { + clearDocMetadata, + mockInsertContractArgs, + must, +} from '../../testHelpers' import { updateDraftRate } from './updateDraftRate' import { PrismaClientValidationError } from '@prisma/client/runtime/library' import type { RateType } from '@prisma/client' import type { RateFormEditableType } from '../../domain-models/contractAndRates' +import { insertDraftContract } from './insertContract' describe('updateDraftRate', () => { afterEach(() => { @@ -17,8 +22,15 @@ describe('updateDraftRate', () => { const draftRateForm1 = { rateCertificationName: 'draftData' } + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contract = must( + await insertDraftContract(client, draftContractData) + ) + const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', ...draftRateForm1, }) @@ -90,8 +102,15 @@ describe('updateDraftRate', () => { supportingDocuments: draftRateForm1.supportingDocuments, } + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contract = must( + await insertDraftContract(client, draftContractData) + ) + const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', }) ) @@ -196,8 +215,15 @@ describe('updateDraftRate', () => { addtlActuaryContacts: draftRateForm1.addtlActuaryContacts, } + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contract = must( + await insertDraftContract(client, draftContractData) + ) + const rate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', }) ) @@ -256,8 +282,15 @@ describe('updateDraftRate', () => { it('returns an error when invalid form data for rate type provided', async () => { jest.spyOn(console, 'error').mockImplementation() const client = await sharedTestPrismaClient() + const draftContractData = mockInsertContractArgs({ + submissionDescription: 'one contract', + }) + const contract = must( + await insertDraftContract(client, draftContractData) + ) + const newRate = must( - await insertDraftRate(client, { + await insertDraftRate(client, contract.id, { stateCode: 'MN', }) ) diff --git a/services/app-api/src/resolvers/configureResolvers.ts b/services/app-api/src/resolvers/configureResolvers.ts index 81c0633a22..6829d62fbe 100644 --- a/services/app-api/src/resolvers/configureResolvers.ts +++ b/services/app-api/src/resolvers/configureResolvers.ts @@ -39,6 +39,7 @@ import { contractResolver } from './contract/contractResolver' import { contractRevisionResolver } from './contract/contractRevisionResolver' import { fetchContractResolver } from './contract/fetchContract' import { submitContract } from './contract/submitContract' +import { rateRevisionResolver } from './rate/rateRevisionResolver' export function configureResolvers( store: Store, @@ -137,6 +138,7 @@ export function configureResolvers( CMSUser: cmsUserResolver, HealthPlanPackage: healthPlanPackageResolver(store), Rate: rateResolver, + RateRevision: rateRevisionResolver, Contract: contractResolver(store), ContractRevision: contractRevisionResolver(store), } diff --git a/services/app-api/src/resolvers/contract/submitContract.test.ts b/services/app-api/src/resolvers/contract/submitContract.test.ts index 622cc44e71..dd72b15565 100644 --- a/services/app-api/src/resolvers/contract/submitContract.test.ts +++ b/services/app-api/src/resolvers/contract/submitContract.test.ts @@ -58,7 +58,7 @@ describe('submitContract', () => { ) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const rateID = sub.rateRevisions[0].rate!.id + const rateID = sub.rateRevisions[0].rateID const rate = await fetchTestRateById(stateServer, rateID) expect(rate.status).toBe('SUBMITTED') }) @@ -67,14 +67,11 @@ describe('submitContract', () => { const stateServer = await constructTestPostgresServer() const contract1 = await createAndSubmitTestContractWithRate(stateServer) - const rate1 = contract1.packageSubmissions[0].rateRevisions[0].rate - if (!rate1) { - throw new Error('NO RATE') - } + const rate1ID = contract1.packageSubmissions[0].rateRevisions[0].rateID const draft2 = await createAndUpdateTestContractWithoutRates(stateServer) - await addLinkedRateToTestContract(stateServer, draft2, rate1.id) + await addLinkedRateToTestContract(stateServer, draft2, rate1ID) const contract2 = await submitTestContract(stateServer, draft2.id) expect(contract2.draftRevision).toBeNull() @@ -118,9 +115,9 @@ describe('submitContract', () => { const contractA0 = await submitTestContract(stateServer, AID) const subA0 = contractA0.packageSubmissions[0] const rate10 = subA0.rateRevisions[0] - const OneID = rate10.rate!.id + const OneID = rate10.rateID const rate20 = subA0.rateRevisions[1] - const TwoID = rate20.rate!.id + const TwoID = rate20.rateID // 2. Submit B0 with Rate1 and Rate3 const draftB0 = @@ -135,9 +132,9 @@ describe('submitContract', () => { const contractB0 = await submitTestContract(stateServer, draftB0.id) const subB0 = contractB0.packageSubmissions[0] const rate30 = subB0.rateRevisions[1] - const ThreeID = rate30.rate!.id + const ThreeID = rate30.rateID - expect(subB0.rateRevisions[0].rate!.id).toBe(OneID) + expect(subB0.rateRevisions[0].rateID).toBe(OneID) // 3. Submit C0 with Rate20 and Rate40 const draftC0 = @@ -152,8 +149,8 @@ describe('submitContract', () => { const contractC0 = await submitTestContract(stateServer, draftC0.id) const subC0 = contractC0.packageSubmissions[0] const rate40 = subC0.rateRevisions[1] - const FourID = rate40.rate!.id - expect(subC0.rateRevisions[0].rate!.id).toBe(TwoID) + const FourID = rate40.rateID + expect(subC0.rateRevisions[0].rateID).toBe(TwoID) // 4. Submit D0, contract only const draftD0 = await createAndUpdateTestHealthPlanPackage( @@ -190,7 +187,7 @@ describe('submitContract', () => { const contractA0 = await submitTestContract(stateServer, AID) const subA0 = contractA0.packageSubmissions[0] const rate10 = subA0.rateRevisions[0] - const OneID = rate10.rate!.id + const OneID = rate10.rateID console.info('2.') // 2. Submit B0 with Rate1 and Rate3 @@ -206,7 +203,7 @@ describe('submitContract', () => { const contractB0 = await submitTestContract(stateServer, draftB0.id) const subB0 = contractB0.packageSubmissions[0] - expect(subB0.rateRevisions[0].rate!.id).toBe(OneID) + expect(subB0.rateRevisions[0].rateID).toBe(OneID) // unlock B, rate 3 should unlock, rate 1 should not. await unlockTestHealthPlanPackage( @@ -261,14 +258,18 @@ describe('submitContract', () => { const draftA0 = await createAndUpdateTestContractWithoutRates(stateServer) const AID = draftA0.id - const draftA010 = await addNewRateToTestContract(stateServer, draftA0) + const draftA010 = await addNewRateToTestContract(stateServer, draftA0, { + rateDateStart: '2021-01-01', + }) - await addNewRateToTestContract(stateServer, draftA010) + await addNewRateToTestContract(stateServer, draftA010, { + rateDateStart: '2022-02-02', + }) const contractA0 = await submitTestContract(stateServer, AID) const subA0 = contractA0.packageSubmissions[0] const rate10 = subA0.rateRevisions[0] - const OneID = rate10.rate!.id + const OneID = rate10.rateID console.info('2.') // 2. Submit B0 with Rate1 and Rate3 @@ -279,12 +280,19 @@ describe('submitContract', () => { draftB0, OneID ) - await addNewRateToTestContract(stateServer, draftB010) + await addNewRateToTestContract(stateServer, draftB010, { + rateDateStart: '2023-03-03', + }) const contractB0 = await submitTestContract(stateServer, draftB0.id) const subB0 = contractB0.packageSubmissions[0] - expect(subB0.rateRevisions[0].rate!.id).toBe(OneID) + expect(subB0.rateRevisions[0].rateID).toBe(OneID) + + // rate1 then rate3 + expect( + subB0.rateRevisions.map((r) => r.formData.rateDateStart) + ).toEqual(['2021-01-01', '2023-03-03']) // unlock A await unlockTestHealthPlanPackage(cmsServer, contractA0.id, 'unlock a') @@ -300,7 +308,122 @@ describe('submitContract', () => { throw new Error('no draft rates') } - expect(unlockedB.draftRates?.length).toBe(2) // this feels like it shouldnt work, probably pulling from the old rev. + expect(unlockedB.draftRates?.length).toBe(2) + expect( + unlockedB.draftRates.map( + (r) => r.draftRevision!.formData.rateDateStart + ) + ).toEqual(['2021-01-01', '2023-03-03']) + + const rate1 = unlockedB.draftRates[0] + const rate3 = unlockedB.draftRates[1] + + expect(rate1.status).toBe('UNLOCKED') + expect(rate3.status).toBe('UNLOCKED') + + const rateUpdateInput = updateRatesInputFromDraftContract(unlockedB) + expect(rateUpdateInput.updatedRates).toHaveLength(2) + expect(rateUpdateInput.updatedRates[0].type).toBe('LINK') + expect(rateUpdateInput.updatedRates[1].type).toBe('UPDATE') + if (!rateUpdateInput.updatedRates[1].formData) { + throw new Error('should be set') + } + + // attempt to update a link + rateUpdateInput.updatedRates[0].type = 'UPDATE' + rateUpdateInput.updatedRates[0].formData = + rateUpdateInput.updatedRates[1].formData + + rateUpdateInput.updatedRates[1].formData.rateDateCertified = + '2000-01-22' + + const updateResult = await stateServer.executeOperation({ + query: UPDATE_DRAFT_CONTRACT_RATES, + variables: { + input: rateUpdateInput, + }, + }) + + expect(updateResult.errors).toBeDefined() + if (!updateResult.errors) { + throw new Error('must be defined') + } + + expect(updateResult.errors[0].message).toMatch( + /^Attempted to update a rate that is not a child of this contract/ + ) + }) + + it('can remove a child unlocked rate', async () => { + //TODO: make a child rate, submit and unlock, then remove it. + const stateServer = await constructTestPostgresServer() + const cmsServer = await constructTestPostgresServer({ + context: { + user: testCMSUser(), + }, + }) + + console.info('1.') + // 1. Submit A0 with Rate1 and Rate2 + const draftA0 = + await createAndUpdateTestContractWithoutRates(stateServer) + const AID = draftA0.id + const draftA010 = await addNewRateToTestContract(stateServer, draftA0, { + rateDateStart: '2021-01-01', + }) + + await addNewRateToTestContract(stateServer, draftA010, { + rateDateStart: '2022-02-02', + }) + + const contractA0 = await submitTestContract(stateServer, AID) + const subA0 = contractA0.packageSubmissions[0] + const rate10 = subA0.rateRevisions[0] + const OneID = rate10.rateID + + console.info('2.') + // 2. Submit B0 with Rate1 and Rate3 + const draftB0 = + await createAndUpdateTestContractWithoutRates(stateServer) + const draftB010 = await addLinkedRateToTestContract( + stateServer, + draftB0, + OneID + ) + await addNewRateToTestContract(stateServer, draftB010, { + rateDateStart: '2023-03-03', + }) + + const contractB0 = await submitTestContract(stateServer, draftB0.id) + const subB0 = contractB0.packageSubmissions[0] + + expect(subB0.rateRevisions[0].rateID).toBe(OneID) + + // rate1 then rate3 + expect( + subB0.rateRevisions.map((r) => r.formData.rateDateStart) + ).toEqual(['2021-01-01', '2023-03-03']) + + // unlock A + await unlockTestHealthPlanPackage(cmsServer, contractA0.id, 'unlock a') + // unlock B, rate 3 should unlock, rate 1 should not. + await unlockTestHealthPlanPackage( + cmsServer, + contractB0.id, + 'test unlock' + ) + + const unlockedB = await fetchTestContract(stateServer, contractB0.id) + if (!unlockedB.draftRates) { + throw new Error('no draft rates') + } + + expect(unlockedB.draftRates?.length).toBe(2) + expect( + unlockedB.draftRates.map( + (r) => r.draftRevision!.formData.rateDateStart + ) + ).toEqual(['2021-01-01', '2023-03-03']) const rate1 = unlockedB.draftRates[0] const rate3 = unlockedB.draftRates[1] @@ -421,13 +544,13 @@ describe('submitContract', () => { // We now have contract 1.2 with A.1 and B.1 // unlock rate A and update it - const rateA = S2.packageSubmissions[0].rateRevisions[0].rate as Rate - console.info(`unlocking rate ${rateA.id}`) + const rateAID = S2.packageSubmissions[0].rateRevisions[0].rateID + console.info(`unlocking rate ${rateAID}`) const unlockRateARes = await cmsServer.executeOperation({ query: UNLOCK_RATE, variables: { input: { - rateID: rateA.id, + rateID: rateAID, unlockedReason: 'Unlocking Rate A for update', }, }, @@ -443,7 +566,7 @@ describe('submitContract', () => { // make changes to Rate A and re-submit for Rate A.2 // TODO: this uses Prisma directly, we want to use updateRate resolver // once we have one - const updateRateA2res = await updateTestRate(rateA.id, { + const updateRateA2res = await updateTestRate(rateAID, { rateDateStart: new Date(Date.UTC(2024, 2, 1)), rateDateEnd: new Date(Date.UTC(2025, 1, 31)), rateDateCertified: new Date(Date.UTC(2024, 1, 31)), diff --git a/services/app-api/src/resolvers/contract/updateDraftContractRates.test.ts b/services/app-api/src/resolvers/contract/updateDraftContractRates.test.ts index 398ac0f704..f86864086e 100644 --- a/services/app-api/src/resolvers/contract/updateDraftContractRates.test.ts +++ b/services/app-api/src/resolvers/contract/updateDraftContractRates.test.ts @@ -910,7 +910,7 @@ describe('updateDraftContractRates', () => { } expect(result.errors[0].message).toContain( - 'Attempted to update a rate that is not a DRAFT' + 'Attempted to update a rate that is not a child of this contract' ) expect(result.errors[0].extensions?.code).toBe('BAD_USER_INPUT') // TODO: This test must be updated to account for CHILDREN diff --git a/services/app-api/src/resolvers/rate/fetchRate.test.ts b/services/app-api/src/resolvers/rate/fetchRate.test.ts index dc96685784..1d6a38708d 100644 --- a/services/app-api/src/resolvers/rate/fetchRate.test.ts +++ b/services/app-api/src/resolvers/rate/fetchRate.test.ts @@ -5,13 +5,9 @@ import { defaultFloridaRateProgram, } from '../../testHelpers/gqlHelpers' import { testCMSUser } from '../../testHelpers/userHelpers' -import { - createAndSubmitTestRate, - submitTestRate, - unlockTestRate, - updateTestRate, -} from '../../testHelpers' +import { submitTestRate, updateTestRate } from '../../testHelpers' import { v4 as uuidv4 } from 'uuid' +import { createSubmitAndUnlockTestRate } from '../../testHelpers/gqlRateHelpers' describe('fetchRate', () => { const ldService = testLDService({ @@ -30,43 +26,9 @@ describe('fetchRate', () => { ldService, }) - const initialRate = () => ({ - id: uuidv4(), - rateType: 'NEW' as const, - rateDateStart: new Date(Date.UTC(2025, 5, 1)), - rateDateEnd: new Date(Date.UTC(2026, 4, 30)), - rateDateCertified: new Date(Date.UTC(2025, 3, 15)), - rateDocuments: [ - { - name: 'rateDocument.pdf', - s3URL: 'fakeS3URL', - sha256: 'fakesha', - }, - ], - supportingDocuments: [], - rateProgramIDs: [defaultFloridaRateProgram().id], - actuaryContacts: [ - { - name: 'test name', - titleRole: 'test title', - email: 'email@example.com', - actuarialFirm: 'MERCER' as const, - actuarialFirmOther: '', - }, - ], - actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, - packagesWithSharedRateCerts: [], - }) - - // First, submit and unlock a rate - const submittedRate = await createAndSubmitTestRate(stateServer, { - stateCode: 'FL', - ...initialRate(), - }) - await unlockTestRate( - cmsServer, - submittedRate.id, - 'Unlock to edit an existing rate' + const submittedRate = await createSubmitAndUnlockTestRate( + stateServer, + cmsServer ) // editrate with new data and resubmit @@ -97,10 +59,10 @@ describe('fetchRate', () => { ) // the initial submit data is correct expect(resubmittedRate.revisions[1].formData.rateDateStart).toBe( - '2025-06-01' + '2024-01-01' ) expect(resubmittedRate.revisions[1].formData.rateDateEnd).toBe( - '2026-05-30' + '2025-01-01' ) expect(resubmittedRate.revisions[1].submitInfo?.updatedReason).toBe( 'Initial submission' @@ -147,21 +109,14 @@ describe('fetchRate', () => { }) // First, create new rate and unlock to edit it - const submittedInitial = await createAndSubmitTestRate(server, { - stateCode: 'MS', - rateDateStart: new Date(Date.UTC(2030, 1, 1)), - rateDateEnd: new Date(Date.UTC(2031, 1, 1)), - }) - - const existingRate = await unlockTestRate( - cmsServer, - submittedInitial.id, - 'Unlock to edit add a new rate' + const submittedInitial = await createSubmitAndUnlockTestRate( + server, + cmsServer ) // add new rate - const firstRateID = existingRate.id - expect(existingRate.revisions).toHaveLength(1) + const firstRateID = submittedInitial.id + expect(submittedInitial.revisions).toHaveLength(1) const updatedRate = await updateTestRate(submittedInitial.id, { ...initialRateInfos(), rateDateStart: new Date(Date.UTC(2034, 1, 1)), @@ -175,7 +130,7 @@ describe('fetchRate', () => { 'Resubmit with an additional rate' ) - // fetch and check rate 1 which was resubmitted with no changese + // fetch and check rate 1 which was resubmitted with no changes expect(firstRateID).toBe(resubmittedRate.id) // first rate ID should be unchanged const result1 = await cmsServer.executeOperation({ @@ -200,10 +155,10 @@ describe('fetchRate', () => { // check that initial rate is correct expect(resubmittedRate1.revisions[1].formData.rateDateStart).toBe( - '2030-02-01' + '2024-01-01' ) expect(resubmittedRate1.revisions[1].formData.rateDateEnd).toBe( - '2031-02-01' + '2025-01-01' ) expect(resubmittedRate1.revisions[1].submitInfo.updatedReason).toBe( 'Initial submission' @@ -221,16 +176,13 @@ describe('fetchRate', () => { ldService, }) - const submittedRate = await createAndSubmitTestRate(server) - - const unlockRate = await unlockTestRate( - cmsServer, - submittedRate.id, - 'Unlock to edit a rate' + const submittedRate = await createSubmitAndUnlockTestRate( + server, + cmsServer ) const input = { - rateID: unlockRate.id, + rateID: submittedRate.id, } // fetch rate diff --git a/services/app-api/src/resolvers/rate/indexRates.test.ts b/services/app-api/src/resolvers/rate/indexRates.test.ts index 554c1b95da..a021f0df78 100644 --- a/services/app-api/src/resolvers/rate/indexRates.test.ts +++ b/services/app-api/src/resolvers/rate/indexRates.test.ts @@ -1,23 +1,24 @@ -import { v4 as uuidv4 } from 'uuid' import { testLDService } from '../../testHelpers/launchDarklyHelpers' import INDEX_RATES from '../../../../app-graphql/src/queries/indexRates.graphql' import { constructTestPostgresServer, - defaultFloridaRateProgram, + createAndUpdateTestHealthPlanPackage, } from '../../testHelpers/gqlHelpers' -import type { StateCodeType } from '../../../../app-web/src/common-code/healthPlanFormDataType' import type { RateEdge, Rate } from '../../gen/gqlServer' import { testCMSUser, testStateUser } from '../../testHelpers/userHelpers' import { formatGQLDate } from 'app-web/src/common-code/dateHelpers' import { submitTestRate, - createAndSubmitTestRate, - createTestRate, unlockTestRate, updateTestRate, createAndSubmitTestContract, } from '../../testHelpers' +import { + createAndSubmitTestContractWithRate, + submitTestContract, + createAndUpdateTestContractWithRate, +} from '../../testHelpers/gqlContractHelpers' describe('indexRates', () => { const ldService = testLDService({ @@ -33,9 +34,14 @@ describe('indexRates', () => { }, ldService, }) - // first, submit 2 rates - const submit1 = await createAndSubmitTestRate(stateServer) - const submit2 = await createAndSubmitTestRate(stateServer) + + const contract1 = await createAndSubmitTestContractWithRate(stateServer) + const contract2 = await createAndSubmitTestContractWithRate(stateServer) + + const submit1ID = + contract1.packageSubmissions[0].rateRevisions[0].rateID + const submit2ID = + contract2.packageSubmissions[0].rateRevisions[0].rateID // index rates const result = await cmsServer.executeOperation({ @@ -44,7 +50,7 @@ describe('indexRates', () => { expect(result.data).toBeDefined() const ratesIndex = result.data?.indexRates - const testRateIDs = [submit1.id, submit2.id] + const testRateIDs = [submit1ID, submit2ID] expect(result.errors).toBeUndefined() const matchedTestRates: Rate[] = ratesIndex.edges @@ -58,14 +64,23 @@ describe('indexRates', () => { it('does not return rates still in initial draft', async () => { const cmsUser = testCMSUser() + const stateServer = await constructTestPostgresServer({ ldService }) const cmsServer = await constructTestPostgresServer({ context: { user: cmsUser, }, }) + + const contract1 = await createAndUpdateTestContractWithRate(stateServer) + const contract2 = await createAndUpdateTestContractWithRate(stateServer) + + if (!contract1.draftRates || !contract2.draftRates) { + throw new Error('no draft rates') + } + // First, create new submissions - const draft1 = await createTestRate() - const draft2 = await createTestRate() + const draft1 = contract1.draftRates[0] + const draft2 = contract2.draftRates[0] // index rates const result = await cmsServer.executeOperation({ @@ -136,61 +151,31 @@ describe('indexRates', () => { ldService, }) - const florida: StateCodeType = 'FL' - const initialRateInfos = () => ({ - id: uuidv4(), - rateType: 'NEW' as const, - rateDateStart: new Date(Date.UTC(2025, 5, 1)), - rateDateEnd: new Date(Date.UTC(2026, 4, 30)), - rateDateCertified: new Date(Date.UTC(2025, 3, 15)), - stateCode: florida, - rateDocuments: [ - { - name: 'rateDocument.pdf', - s3URL: 'fakeS3URL', - sha256: 'fakesha', - }, - ], - supportingDocuments: [], - rateProgramIDs: [defaultFloridaRateProgram().id], - actuaryContacts: [ - { - name: 'test name', - titleRole: 'test title', - email: 'email@example.com', - actuarialFirm: 'MERCER' as const, - actuarialFirmOther: '', - }, - ], - actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, - packagesWithSharedRateCerts: [], - }) + const contract1 = await createAndSubmitTestContractWithRate(server) + const contract2 = await createAndSubmitTestContractWithRate(server) - // First, create and submit new rates - const firstRate = await createAndSubmitTestRate(server, { - ...initialRateInfos(), - }) - const secondRate = await createAndSubmitTestRate(server, { - ...initialRateInfos(), - }) + const firstRateID = + contract1.packageSubmissions[0].rateRevisions[0].rateID + const secondRateID = + contract2.packageSubmissions[0].rateRevisions[0].rateID // Unlock one to be rate edited in place const firstRateUnlocked = await unlockTestRate( cmsServer, - firstRate.id, + firstRateID, 'Unlock to edit an existing rate' ) const secondRateUnlocked = await unlockTestRate( cmsServer, - secondRate.id, + secondRateID, 'Unlock to edit an existing rate' ) // update one with a new rate start and end date const existingFormData = firstRateUnlocked.draftRevision?.formData expect(existingFormData).toBeDefined() - await updateTestRate(firstRate.id, { + await updateTestRate(firstRateID, { rateDateStart: new Date(Date.UTC(2025, 1, 1)), rateDateEnd: new Date(Date.UTC(2027, 1, 1)), }) @@ -198,21 +183,20 @@ describe('indexRates', () => { // update the other with additional new rate const existingFormData2 = secondRateUnlocked.draftRevision?.formData expect(existingFormData2).toBeDefined() - const newRate = await createAndSubmitTestRate(server, { - ...initialRateInfos(), - rateDateStart: new Date(Date.UTC(2030, 1, 1)), - rateDateEnd: new Date(Date.UTC(2030, 12, 1)), - }) + + const contract3 = await createAndSubmitTestContractWithRate(server) + const newRateID = + contract3.packageSubmissions[0].rateRevisions[0].rateID // resubmit const firstRateResubmitted = await submitTestRate( server, - firstRate.id, + firstRateID, 'Resubmit with edited rate description' ) const secondRateResubmitted = await submitTestRate( server, - secondRate.id, + secondRateID, 'Resubmit with an additional rate added' ) @@ -233,7 +217,7 @@ describe('indexRates', () => { return test.id == secondRateResubmitted.id }) const newlyAdded = rates.find((test: Rate) => { - return test.id === newRate.id + return test.id === newRateID }) if (!resubmittedWithEdits || !resubmittedUnchanged || !newlyAdded) { @@ -253,10 +237,10 @@ describe('indexRates', () => { resubmittedWithEdits.revisions[0].submitInfo?.updatedReason ).toBe('Resubmit with edited rate description') expect(resubmittedWithEdits.revisions[1].formData.rateDateStart).toBe( - formatGQLDate(initialRateInfos().rateDateStart) + '2024-01-01' ) expect(resubmittedWithEdits.revisions[1].formData.rateDateEnd).toBe( - formatGQLDate(initialRateInfos().rateDateEnd) + '2025-01-01' ) expect( resubmittedWithEdits.revisions[1].submitInfo?.updatedReason @@ -265,10 +249,10 @@ describe('indexRates', () => { // check unchanged rate most recent revision and previous expect(resubmittedUnchanged.revisions).toHaveLength(2) expect(resubmittedUnchanged.revisions[0].formData.rateDateStart).toBe( - formatGQLDate(initialRateInfos().rateDateStart) + '2024-01-01' ) expect(resubmittedUnchanged.revisions[0].formData.rateDateEnd).toBe( - formatGQLDate(initialRateInfos().rateDateEnd) + '2025-01-01' ) expect( resubmittedUnchanged.revisions[0].submitInfo?.updatedReason @@ -279,20 +263,18 @@ describe('indexRates', () => { ).toBe('Initial submission') expect(resubmittedUnchanged.revisions[1].formData.rateDateStart).toBe( - formatGQLDate(initialRateInfos().rateDateStart) + '2024-01-01' ) expect(resubmittedUnchanged.revisions[1].formData.rateDateEnd).toBe( - formatGQLDate(initialRateInfos().rateDateEnd) + '2025-01-01' ) // check newly added rate expect(newlyAdded.revisions).toHaveLength(1) expect(newlyAdded.revisions[0].formData.rateDateStart).toBe( - formatGQLDate(new Date(Date.UTC(2030, 1, 1))) - ) - expect(newlyAdded.revisions[0].formData.rateDateEnd).toBe( - formatGQLDate(new Date(Date.UTC(2030, 12, 1))) + '2024-01-01' ) + expect(newlyAdded.revisions[0].formData.rateDateEnd).toBe('2025-01-01') }) it('synthesizes the right statuses as a rate is submitted/unlocked/etc', async () => { @@ -307,16 +289,23 @@ describe('indexRates', () => { }) // First, create new submissions - const submittedRate = await createAndSubmitTestRate(server) - const unlockedRate = await createAndSubmitTestRate(server) - const relockedRate = await createAndSubmitTestRate(server) + const contract1 = await createAndSubmitTestContractWithRate(server) + const contract2 = await createAndSubmitTestContractWithRate(server) + const contract3 = await createAndSubmitTestContractWithRate(server) + + const submittedRateID = + contract1.packageSubmissions[0].rateRevisions[0].rateID + const unlockedRateID = + contract2.packageSubmissions[0].rateRevisions[0].rateID + const relockedRateID = + contract3.packageSubmissions[0].rateRevisions[0].rateID // unlock two - await unlockTestRate(cmsServer, unlockedRate.id, 'Test reason') - await unlockTestRate(cmsServer, relockedRate.id, 'Test reason') + await unlockTestRate(cmsServer, unlockedRateID, 'Test reason') + await unlockTestRate(cmsServer, relockedRateID, 'Test reason') // resubmit one - await submitTestRate(server, relockedRate.id, 'Test first resubmission') + await submitTestRate(server, relockedRateID, 'Test first resubmission') // index rates const result = await cmsServer.executeOperation({ @@ -326,7 +315,7 @@ describe('indexRates', () => { expect(result.errors).toBeUndefined() // pull out test related rates and order them - const testRateIDs = [submittedRate.id, unlockedRate.id, relockedRate.id] + const testRateIDs = [submittedRateID, unlockedRateID, relockedRateID] const testRates: Rate[] = ratesIndex.edges .map((edge: RateEdge) => edge.node) @@ -358,18 +347,22 @@ describe('indexRates', () => { }) // First, create new rates - const submittedRate2 = await createAndSubmitTestRate(server) - const unlockedRate2 = await createAndSubmitTestRate(server) - const relockedRate2 = await createAndSubmitTestRate(server) + const contract1 = await createAndSubmitTestContractWithRate(server) + const contract2 = await createAndSubmitTestContractWithRate(server) + const contract3 = await createAndSubmitTestContractWithRate(server) + + const submittedRate2 = contract1.packageSubmissions[0].rateRevisions[0] + const unlockedRate2 = contract2.packageSubmissions[0].rateRevisions[0] + const relockedRate2 = contract3.packageSubmissions[0].rateRevisions[0] // unlock two - await unlockTestRate(cmsServer, unlockedRate2.id, 'Test reason') - await unlockTestRate(cmsServer, relockedRate2.id, 'Test reason') + await unlockTestRate(cmsServer, unlockedRate2.rateID, 'Test reason') + await unlockTestRate(cmsServer, relockedRate2.rateID, 'Test reason') // resubmit one await submitTestRate( server, - relockedRate2.id, + relockedRate2.rateID, 'Test first resubmission' ) @@ -381,9 +374,9 @@ describe('indexRates', () => { const ratesIndex = result.data?.indexRates expect(result.errors).toBeUndefined() - const submittedRateID = submittedRate2.id - const unlockedRateID = unlockedRate2.id - const resubmittedRateID = relockedRate2.id + const submittedRateID = submittedRate2.rateID + const unlockedRateID = unlockedRate2.rateID + const resubmittedRateID = relockedRate2.rateID if (!submittedRateID || !unlockedRateID || !resubmittedRateID) { throw new Error('Missing Rate ID') @@ -447,16 +440,21 @@ describe('indexRates', () => { }, ldService, }) + // submit packages from two different states - const defaultState1 = await createAndSubmitTestRate(stateServer) - const defaultState2 = await createAndSubmitTestRate(stateServer) - const draft = await createTestRate() + const contract1 = await createAndSubmitTestContractWithRate(stateServer) + const contract2 = await createAndSubmitTestContractWithRate(stateServer) - const otherState1 = await submitTestRate( + const pkg3 = await createAndUpdateTestHealthPlanPackage( otherStateServer, - draft.id, - 'submitted reason' + {}, + 'VA' ) + const contract3 = await submitTestContract(otherStateServer, pkg3.id) + + const defaultState1 = contract1.packageSubmissions[0].rateRevisions[0] + const defaultState2 = contract2.packageSubmissions[0].rateRevisions[0] + const otherState1 = contract3.packageSubmissions[0].rateRevisions[0] // index rates const result = await cmsServer.executeOperation({ @@ -472,9 +470,11 @@ describe('indexRates', () => { const defaultStateRates: Rate[] = [] const otherStateRates: Rate[] = [] allRates.forEach((rate) => { - if ([defaultState1.id, defaultState2.id].includes(rate.id)) { + if ( + [defaultState1.rateID, defaultState2.rateID].includes(rate.id) + ) { defaultStateRates.push(rate) - } else if (otherState1.id === rate.id) { + } else if (otherState1.rateID === rate.id) { otherStateRates.push(rate) } return diff --git a/services/app-api/src/resolvers/rate/rateRevisionResolver.ts b/services/app-api/src/resolvers/rate/rateRevisionResolver.ts new file mode 100644 index 0000000000..16cf2a4946 --- /dev/null +++ b/services/app-api/src/resolvers/rate/rateRevisionResolver.ts @@ -0,0 +1,7 @@ +import type { Resolvers } from '../../gen/gqlServer' + +export const rateRevisionResolver: Resolvers['RateRevision'] = { + contractRevisions(parent) { + return parent.contractRevisions || [] + }, +} diff --git a/services/app-api/src/resolvers/rate/submitRate.test.ts b/services/app-api/src/resolvers/rate/submitRate.test.ts index da0cad7862..65d3bf6116 100644 --- a/services/app-api/src/resolvers/rate/submitRate.test.ts +++ b/services/app-api/src/resolvers/rate/submitRate.test.ts @@ -8,12 +8,9 @@ import { testStateUser, testCMSUser } from '../../testHelpers/userHelpers' import SUBMIT_RATE from '../../../../app-graphql/src/mutations/submitRate.graphql' import FETCH_RATE from '../../../../app-graphql/src/queries/fetchRate.graphql' import UNLOCK_RATE from '../../../../app-graphql/src/mutations/unlockRate.graphql' -import { - createTestRate, - submitTestRate, - updateTestRate, -} from '../../testHelpers' +import { submitTestRate, updateTestRate } from '../../testHelpers' import SUBMIT_HEALTH_PLAN_PACKAGE from '../../../../app-graphql/src/mutations/submitHealthPlanPackage.graphql' +import { createSubmitAndUnlockTestRate } from '../../testHelpers/gqlRateHelpers' describe('submitRate', () => { const ldService = testLDService({ @@ -30,12 +27,19 @@ describe('submitRate', () => { ldService, }) - // createRate with full data - const draftRate = await createTestRate() + const cmsServer = await constructTestPostgresServer({ + context: { + user: testCMSUser(), + }, + ldService, + }) + + const rate = await createSubmitAndUnlockTestRate(stateServer, cmsServer) + const fetchDraftRate = await stateServer.executeOperation({ query: FETCH_RATE, variables: { - input: { rateID: draftRate.id }, + input: { rateID: rate.id }, }, }) @@ -47,7 +51,7 @@ describe('submitRate', () => { query: SUBMIT_RATE, variables: { input: { - rateID: draftRate.id, + rateID: rate.id, }, }, }) @@ -61,7 +65,7 @@ describe('submitRate', () => { // expect rate data to be returned expect(submittedRate).toBeDefined() // expect status to be submitted. - expect(submittedRate.status).toBe('SUBMITTED') + expect(submittedRate.status).toBe('RESUBMITTED') // expect formData to be the same expect(submittedRateFormData).toEqual(draftFormData) }) @@ -75,29 +79,36 @@ describe('submitRate', () => { ldService, }) - const draftRate = await createTestRate() + const cmsServer = await constructTestPostgresServer({ + context: { + user: testCMSUser(), + }, + ldService, + }) + + const rate = await createSubmitAndUnlockTestRate(stateServer, cmsServer) const fetchDraftRate = await stateServer.executeOperation({ query: FETCH_RATE, variables: { - input: { rateID: draftRate.id }, + input: { rateID: rate.id }, }, }) - const draftFormData = draftRate.draftRevision?.formData + const draftFormData = rate.draftRevision?.formData // expect draft rate created in contract to exist expect(fetchDraftRate.errors).toBeUndefined() expect(draftFormData).toBeDefined() // update rate - await updateTestRate(draftRate.id, { + await updateTestRate(rate.id, { rateDateStart: new Date(Date.UTC(2025, 1, 1)), }) const submittedRate = await submitTestRate( stateServer, - draftRate.id, + rate.id, 'Submit with edited rate description' ) @@ -106,7 +117,7 @@ describe('submitRate', () => { // expect rate data to be returned expect(submittedRate).toBeDefined() // expect status to be submitted. - expect(submittedRate.status).toBe('SUBMITTED') + expect(submittedRate.status).toBe('RESUBMITTED') // expect formData to NOT be the same expect(submittedRateFormData.rateDateStart).not.toEqual( draftFormData?.rateDateStart @@ -122,7 +133,17 @@ describe('submitRate', () => { ldService, }) - const draftRate = await createTestRate() + const cmsServer = await constructTestPostgresServer({ + context: { + user: testCMSUser(), + }, + ldService, + }) + + const draftRate = await createSubmitAndUnlockTestRate( + stateServer, + cmsServer + ) const fetchDraftRate = await stateServer.executeOperation({ query: FETCH_RATE, @@ -155,7 +176,7 @@ describe('submitRate', () => { // expect rate data to be returned expect(submittedRate).toBeDefined() // expect status to be submitted. - expect(submittedRate.status).toBe('SUBMITTED') + expect(submittedRate.status).toBe('RESUBMITTED') // expect formData to be the same expect(submittedRateFormData).toEqual(draftFormData) }) @@ -251,7 +272,17 @@ describe('submitRate', () => { }), }) - const draftRate = await createTestRate() + const cmsServer = await constructTestPostgresServer({ + context: { + user: testCMSUser(), + }, + ldService, + }) + + const draftRate = await createSubmitAndUnlockTestRate( + stateServer, + cmsServer + ) const fetchDraftRate = await stateServer.executeOperation({ query: FETCH_RATE, diff --git a/services/app-api/src/resolvers/rate/unlockRate.test.ts b/services/app-api/src/resolvers/rate/unlockRate.test.ts index c50b7d615e..9224c03a4d 100644 --- a/services/app-api/src/resolvers/rate/unlockRate.test.ts +++ b/services/app-api/src/resolvers/rate/unlockRate.test.ts @@ -2,8 +2,9 @@ import { constructTestPostgresServer } from '../../testHelpers/gqlHelpers' import UNLOCK_RATE from '../../../../app-graphql/src/mutations/unlockRate.graphql' import { testCMSUser } from '../../testHelpers/userHelpers' import { expectToBeDefined } from '../../testHelpers/assertionHelpers' -import { createAndSubmitTestRate } from '../../testHelpers/gqlRateHelpers' import { testLDService } from '../../testHelpers/launchDarklyHelpers' +import { createSubmitAndUnlockTestRate } from '../../testHelpers/gqlRateHelpers' +import { createAndSubmitTestContractWithRate } from '../../testHelpers/gqlContractHelpers' describe(`unlockRate`, () => { const ldService = testLDService({ @@ -21,23 +22,23 @@ describe(`unlockRate`, () => { }) // Create and unlock a rate - const rate = await createAndSubmitTestRate(stateServer) - const rateID = rate.id - const unlockedReason = 'Super duper good reason.' - const unlockResult = await cmsServer.executeOperation({ - query: UNLOCK_RATE, - variables: { - input: { - rateID, - unlockedReason, - }, - }, - }) + const updatedRate = await createSubmitAndUnlockTestRate( + stateServer, + cmsServer + ) - const updatedRate = unlockResult.data?.unlockRate.rate expect(updatedRate.status).toBe('UNLOCKED') - expect(updatedRate.draftRevision.unlockInfo.updatedReason).toEqual( - unlockedReason + + if (!updatedRate.draftRevision) { + throw new Error('no draftrate') + } + + if (!updatedRate.draftRevision.unlockInfo) { + throw new Error('no unlockinfo') + } + + expect(updatedRate.draftRevision.unlockInfo.updatedReason).toBe( + 'test unlock' ) }) @@ -51,20 +52,7 @@ describe(`unlockRate`, () => { }) // Create a rate - const rate = await createAndSubmitTestRate(stateServer) - - // Unlock the rate once - const unlockResult1 = await cmsServer.executeOperation({ - query: UNLOCK_RATE, - variables: { - input: { - rateID: rate.id, - unlockedReason: 'Super duper good reason.', - }, - }, - }) - - expect(unlockResult1.errors).toBeUndefined() + const rate = await createSubmitAndUnlockTestRate(stateServer, cmsServer) // Try to unlock the rate again const unlockResult2 = await cmsServer.executeOperation({ @@ -85,8 +73,10 @@ describe(`unlockRate`, () => { it('returns unauthorized error for state user', async () => { const stateServer = await constructTestPostgresServer({ ldService }) + + const contract = await createAndSubmitTestContractWithRate(stateServer) // Create a rate - const rate = await createAndSubmitTestRate(stateServer) + const rate = contract.packageSubmissions[0].rateRevisions[0] // Unlock the rate const unlockResult = await stateServer.executeOperation({ diff --git a/services/app-api/src/testHelpers/gqlRateHelpers.ts b/services/app-api/src/testHelpers/gqlRateHelpers.ts index 1003980d5c..5e84fd384f 100644 --- a/services/app-api/src/testHelpers/gqlRateHelpers.ts +++ b/services/app-api/src/testHelpers/gqlRateHelpers.ts @@ -2,16 +2,10 @@ import SUBMIT_RATE from 'app-graphql/src/mutations/submitRate.graphql' import FETCH_RATE from 'app-graphql/src/queries/fetchRate.graphql' import UNLOCK_RATE from 'app-graphql/src/mutations/unlockRate.graphql' import UPDATE_DRAFT_CONTRACT_RATES from 'app-graphql/src/mutations/updateDraftContractRates.graphql' -import { findStatePrograms } from '../postgres' import { must } from './assertionHelpers' import { defaultFloridaRateProgram } from './gqlHelpers' -import { - mockDraftRate, - mockInsertRateArgs, - mockRateFormDataInput, -} from './rateDataMocks' +import { mockRateFormDataInput } from './rateDataMocks' import { sharedTestPrismaClient } from './storeHelpers' -import { insertDraftRate } from '../postgres/contractAndRates/insertRate' import { updateDraftRate } from '../postgres/contractAndRates/updateDraftRate' import type { @@ -21,11 +15,12 @@ import type { ActuaryContactInput, RateFormDataInput, UpdateDraftContractRatesInput, + Rate, } from '../gen/gqlServer' import type { RateType } from '../domain-models' -import type { InsertRateArgsType } from '../postgres/contractAndRates/insertRate' import type { ApolloServer } from 'apollo-server-lambda' import type { RateFormEditableType } from '../domain-models/contractAndRates' +import { createAndSubmitTestContractWithRate } from './gqlContractHelpers' const fetchTestRateById = async ( server: ApolloServer, @@ -50,12 +45,18 @@ const fetchTestRateById = async ( return result.data.fetchRate.rate } -const createAndSubmitTestRate = async ( - server: ApolloServer, - rateData?: InsertRateArgsType -): Promise => { - const rate = await createTestRate(rateData) - return await must(submitTestRate(server, rate.id, 'Initial submission')) +// rates must be initially submitted with a contract before they can be unlocked and submitted on their own. +async function createSubmitAndUnlockTestRate( + stateServer: ApolloServer, + cmsServer: ApolloServer +): Promise { + const contract = await createAndSubmitTestContractWithRate(stateServer) + const rateRevision = contract.packageSubmissions[0].rateRevisions[0] + const rateID = rateRevision.rateID + + const unlockedRate = await unlockTestRate(cmsServer, rateID, 'test unlock') + + return unlockedRate } const submitTestRate = async ( @@ -116,31 +117,6 @@ const unlockTestRate = async ( return updateResult.data.unlockRate.rate } -// USING PRISMA DIRECTLY BELOW --- we have no createRate or updateRate resolvers yet, but we have integration tests needing the workflows -const createTestRate = async ( - rateData?: Partial -): Promise => { - const prismaClient = await sharedTestPrismaClient() - const defaultRateData = { ...mockDraftRate() } - const initialData = { - ...defaultRateData, - ...rateData, // override with any new fields passed in - } - const programs = initialData.stateCode - ? [must(findStatePrograms(initialData.stateCode))[0]] - : [defaultFloridaRateProgram()] - - const programIDs = programs.map((program) => program.id) - - const draftRateData = mockInsertRateArgs({ - ...initialData, - rateProgramIDs: programIDs, - stateCode: 'FL', - }) - - return must(await insertDraftRate(prismaClient, draftRateData)) -} - async function updateTestDraftRatesOnContract( server: ApolloServer, input: UpdateDraftContractRatesInput @@ -163,73 +139,82 @@ async function updateTestDraftRatesOnContract( async function addNewRateToTestContract( server: ApolloServer, - contract: Contract + contract: Contract, + rateFormDataOverrides?: Partial ): Promise { const rateUpdateInput = updateRatesInputFromDraftContract(contract) - const addedInput = addNewRateToRateInput(rateUpdateInput) + const addedInput = addNewRateToRateInput( + rateUpdateInput, + rateFormDataOverrides + ) return await updateTestDraftRatesOnContract(server, addedInput) } function addNewRateToRateInput( - input: UpdateDraftContractRatesInput + input: UpdateDraftContractRatesInput, + rateFormDataOverrides?: Partial ): UpdateDraftContractRatesInput { + const newFormData: RateFormDataInput = { + rateType: 'AMENDMENT', + rateCapitationType: 'RATE_CELL', + rateDateStart: '2024-01-01', + rateDateEnd: '2025-01-01', + rateDateCertified: '2024-01-02', + amendmentEffectiveDateStart: '2024-02-01', + amendmentEffectiveDateEnd: '2025-02-01', + rateProgramIDs: [defaultFloridaRateProgram().id], + + rateDocuments: [ + { + s3URL: 'foo://bar', + name: 'ratedoc1.doc', + sha256: 'foobar', + }, + ], + supportingDocuments: [ + { + s3URL: 'foo://bar1', + name: 'ratesupdoc1.doc', + sha256: 'foobar1', + }, + { + s3URL: 'foo://bar2', + name: 'ratesupdoc2.doc', + sha256: 'foobar2', + }, + ], + certifyingActuaryContacts: [ + { + name: 'Foo Person', + titleRole: 'Bar Job', + email: 'foo@example.com', + actuarialFirm: 'GUIDEHOUSE', + }, + ], + addtlActuaryContacts: [ + { + name: 'Bar Person', + titleRole: 'Baz Job', + email: 'bar@example.com', + actuarialFirm: 'OTHER', + actuarialFirmOther: 'Some Firm', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY', + packagesWithSharedRateCerts: [], + + ...rateFormDataOverrides, + } + return { contractID: input.contractID, updatedRates: [ ...input.updatedRates, { type: 'CREATE' as const, - formData: { - rateType: 'AMENDMENT', - rateCapitationType: 'RATE_CELL', - rateDateStart: '2024-01-01', - rateDateEnd: '2025-01-01', - rateDateCertified: '2024-01-02', - amendmentEffectiveDateStart: '2024-02-01', - amendmentEffectiveDateEnd: '2025-02-01', - rateProgramIDs: [defaultFloridaRateProgram().id], - - rateDocuments: [ - { - s3URL: 'foo://bar', - name: 'ratedoc1.doc', - sha256: 'foobar', - }, - ], - supportingDocuments: [ - { - s3URL: 'foo://bar1', - name: 'ratesupdoc1.doc', - sha256: 'foobar1', - }, - { - s3URL: 'foo://bar2', - name: 'ratesupdoc2.doc', - sha256: 'foobar2', - }, - ], - certifyingActuaryContacts: [ - { - name: 'Foo Person', - titleRole: 'Bar Job', - email: 'foo@example.com', - actuarialFirm: 'GUIDEHOUSE', - }, - ], - addtlActuaryContacts: [ - { - name: 'Bar Person', - titleRole: 'Baz Job', - email: 'bar@example.com', - actuarialFirm: 'OTHER', - actuarialFirmOther: 'Some Firm', - }, - ], - actuaryCommunicationPreference: 'OACT_TO_ACTUARY', - packagesWithSharedRateCerts: [], - }, + formData: newFormData, }, ], } @@ -418,9 +403,8 @@ const updateTestRate = async ( } export { - createTestRate, - createAndSubmitTestRate, createTestDraftRateOnContract, + createSubmitAndUnlockTestRate, updateTestDraftRateOnContract, updateTestDraftRatesOnContract, updateRatesInputFromDraftContract, diff --git a/services/app-api/src/testHelpers/index.ts b/services/app-api/src/testHelpers/index.ts index f49892e3f3..728fa7505d 100644 --- a/services/app-api/src/testHelpers/index.ts +++ b/services/app-api/src/testHelpers/index.ts @@ -23,8 +23,6 @@ export { getStateRecord } from './stateHelpers' export { consoleLogFullData } from './debugHelpers' export { - createTestRate, - createAndSubmitTestRate, fetchTestRateById, submitTestRate, unlockTestRate, diff --git a/services/app-graphql/codegen.yml b/services/app-graphql/codegen.yml index 9a35fe5107..bb0079ca2d 100644 --- a/services/app-graphql/codegen.yml +++ b/services/app-graphql/codegen.yml @@ -21,7 +21,7 @@ generates: CMSUser: ../domain-models#CMSUserType HealthPlanPackage: ../domain-models#HealthPlanPackageType Rate: ../domain-models#RateType - RateRevision: ../domain-models#RateRevisionType + RateRevision: ../domain-models#RateRevisionWithContractsType Contract: ../domain-models#ContractType as ContractDomainType ContractRevision: ../domain-models#ContractRevisionType ContractPackageSubmission: ../domain-models#ContractPackageSubmissionWithCauseType diff --git a/services/app-graphql/src/mutations/submitContract.graphql b/services/app-graphql/src/mutations/submitContract.graphql index b4cc8f3b65..281272d878 100644 --- a/services/app-graphql/src/mutations/submitContract.graphql +++ b/services/app-graphql/src/mutations/submitContract.graphql @@ -70,9 +70,7 @@ fragment rateFields on Rate { fragment rateRevisionFragmentForFetchContract on RateRevision { id - rate { - id - } + rateID createdAt updatedAt unlockInfo { diff --git a/services/app-graphql/src/queries/fetchContract.graphql b/services/app-graphql/src/queries/fetchContract.graphql index 06da274c35..788ee46f82 100644 --- a/services/app-graphql/src/queries/fetchContract.graphql +++ b/services/app-graphql/src/queries/fetchContract.graphql @@ -69,9 +69,7 @@ fragment rateFields on Rate { fragment rateRevisionFragmentForFetchContract on RateRevision { id - rate { - id - } + rateID createdAt updatedAt unlockInfo { diff --git a/services/app-graphql/src/queries/fetchRate.graphql b/services/app-graphql/src/queries/fetchRate.graphql index 98694cd5c1..f5dc643996 100644 --- a/services/app-graphql/src/queries/fetchRate.graphql +++ b/services/app-graphql/src/queries/fetchRate.graphql @@ -1,5 +1,6 @@ fragment rateRevisionFragmentForFetchRate on RateRevision { id + rateID createdAt updatedAt unlockInfo { diff --git a/services/app-graphql/src/queries/indexRates.graphql b/services/app-graphql/src/queries/indexRates.graphql index f70cbb8d00..653c3de58b 100644 --- a/services/app-graphql/src/queries/indexRates.graphql +++ b/services/app-graphql/src/queries/indexRates.graphql @@ -19,9 +19,11 @@ query indexRates { } status initiallySubmittedAt + parentContractID draftRevision { id + rateID createdAt updatedAt unlockInfo { @@ -151,6 +153,7 @@ query indexRates { revisions { id + rateID createdAt updatedAt unlockInfo { diff --git a/services/app-graphql/src/schema.graphql b/services/app-graphql/src/schema.graphql index 085c6dd0bf..4a16e5e334 100644 --- a/services/app-graphql/src/schema.graphql +++ b/services/app-graphql/src/schema.graphql @@ -1285,6 +1285,7 @@ for the rate and contains the full data from when the rate cert was submitted type RateRevision { id: ID! rate: Rate + rateID: String! createdAt: DateTime! updatedAt: DateTime! """ @@ -1297,7 +1298,7 @@ type RateRevision { "The rate related form data that was inputed by the state" formData: RateFormData! "Contract revisions related to the rate" - contractRevisions: [RelatedContractRevisions!] + contractRevisions: [RelatedContractRevisions!]! } """ @@ -1428,7 +1429,8 @@ type Contract { """ packageSubmissions are a snapshot of the contract and its related rates through time each packageSubmission was created by a submission of this contract and/or its related rates - a DRAFT Contract will have no packageSubmissions + a DRAFT Contract will have no packageSubmissions. Returned in _ascending_ order. Most recent + submission is in the first position in the array. """ packageSubmissions: [ContractPackageSubmission!]! } diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx index dc541a00bb..4d1a58a418 100644 --- a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx @@ -448,7 +448,7 @@ describe('SingleRateSummarySection', () => { // no unlock rate button present expect( - await screen.queryByRole('button', { + screen.queryByRole('button', { name: 'Unlock rate', }) ).toBeNull() 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 index 4655328eca..c4f3d3bae8 100644 --- 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 @@ -25,9 +25,11 @@ describe('RateDetailsSummarySection', () => { state: mockMNState(), stateCode: 'MN', stateNumber: 5, + parentContractID: 'fake-id', revisions: [], draftRevision: { id: '1234', + rateID: '5678', createdAt: new Date('01/01/2021'), updatedAt: new Date('01/01/2021'), contractRevisions: [], @@ -72,9 +74,11 @@ describe('RateDetailsSummarySection', () => { state: mockMNState(), stateCode: 'MN', stateNumber: 5, + parentContractID: 'fake-id', revisions: [], draftRevision: { id: '1234', + rateID: '5678', createdAt: new Date('01/01/2021'), updatedAt: new Date('01/01/2021'), contractRevisions: [], diff --git a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts index d0e13a5d04..3f42136deb 100644 --- a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts +++ b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts @@ -81,6 +81,7 @@ function mockContractPackageDraft( parentContractID: 'foo-baz', draftRevision: { id: '123', + rateID: '456', contractRevisions: [], createdAt: new Date(), updatedAt: new Date(), @@ -200,6 +201,7 @@ function mockContractPackageSubmitted( rateRevisions: [ { id: '1234', + rateID: '456', createdAt: new Date('01/01/2023'), updatedAt: new Date('01/01/2023'), contractRevisions: [], diff --git a/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts index 763f0d30b3..26a2acca1c 100644 --- a/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts +++ b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts @@ -80,6 +80,7 @@ const contractRevisionOnRateDataMock = ( const rateRevisionDataMock = (data?: Partial): RateRevision => { return { id: data?.id ?? uuidv4(), + rateID: '456', createdAt: '2023-10-16T19:01:21.389Z', updatedAt: '2023-10-16T19:02:26.767Z', unlockInfo: null,