From c1862d44d03ff3c6f7359a7ef576124b93fef1da Mon Sep 17 00:00:00 2001 From: Weronika Tomaszewska Date: Mon, 23 Sep 2024 20:53:15 +0200 Subject: [PATCH] CPF-410 Certify reporting period and transition to the next one (#421) * create certifyReportingPeriodAndOpenNextPeriod mutation, make it accessible to admin roles only * modify reportingPeriods scenarios and add tests to verify certifyReportingPeriodAndOpenNextPeriod works as expected * add missing types to scenarios; eslint * code cleanup * throw error when no next period exists, return a descriptive error if possible --- api/src/graphql/reportingPeriods.sdl.ts | 3 + .../reportingPeriods.scenarios.ts | 63 +++++++++++++++++-- .../reportingPeriods/reportingPeriods.test.ts | 44 +++++++++++++ .../reportingPeriods/reportingPeriods.ts | 63 +++++++++++++++++++ 4 files changed, 168 insertions(+), 5 deletions(-) diff --git a/api/src/graphql/reportingPeriods.sdl.ts b/api/src/graphql/reportingPeriods.sdl.ts index fc0fa8c1..f465e89c 100644 --- a/api/src/graphql/reportingPeriods.sdl.ts +++ b/api/src/graphql/reportingPeriods.sdl.ts @@ -41,6 +41,9 @@ export const schema = gql` type Mutation { createReportingPeriod(input: CreateReportingPeriodInput!): ReportingPeriod! @requireAuth + certifyReportingPeriodAndOpenNextPeriod( + reportingPeriodId: Int! + ): ReportingPeriod @requireAuth(roles: ["USDR_ADMIN", "ORGANIZATION_ADMIN"]) updateReportingPeriod( id: Int! input: UpdateReportingPeriodInput! diff --git a/api/src/services/reportingPeriods/reportingPeriods.scenarios.ts b/api/src/services/reportingPeriods/reportingPeriods.scenarios.ts index 2dae1194..1a23d8e5 100644 --- a/api/src/services/reportingPeriods/reportingPeriods.scenarios.ts +++ b/api/src/services/reportingPeriods/reportingPeriods.scenarios.ts @@ -1,8 +1,19 @@ -import type { Prisma, ReportingPeriod } from '@prisma/client' +import type { + Prisma, + ReportingPeriod, + Organization, + Agency, + User, +} from '@prisma/client' import type { ScenarioData } from '@redwoodjs/testing/api' -export const standard = defineScenario({ +export const standard = defineScenario< + | Prisma.ReportingPeriodCreateArgs + | Prisma.OrganizationCreateArgs + | Prisma.AgencyCreateArgs + | Prisma.UserCreateArgs +>({ reportingPeriod: { one: { data: { @@ -28,8 +39,8 @@ export const standard = defineScenario({ two: { data: { name: 'String', - startDate: '2024-01-12T15:48:11.499Z', - endDate: '2024-01-12T15:48:11.499Z', + startDate: '2024-01-14T15:48:11.499Z', + endDate: '2024-01-14T15:48:11.499Z', inputTemplate: { create: { name: 'INPUT TEMPLATE TWO', @@ -47,6 +58,48 @@ export const standard = defineScenario({ }, }, }, + organization: { + one: (scenario) => ({ + data: { + name: 'USDR1', + preferences: { + current_reporting_period_id: scenario.reportingPeriod.one.id, + }, + }, + }), + }, + agency: { + one: (scenario) => ({ + data: { + name: 'String', + code: 'String', + organization: { + connect: { + id: scenario.organization.one.id, + }, + }, + }, + }), + }, + user: { + one: (scenario) => ({ + data: { + email: 'orgadmin@example.com', + name: 'Org Admin', + role: 'ORGANIZATION_ADMIN', + agency: { connect: { id: scenario.agency.one.id } }, + }, + include: { + agency: true, + }, + }), + }, }) -export type StandardScenario = ScenarioData +export type StandardScenario = ScenarioData< + ReportingPeriod, + 'reportingPeriod' +> & + ScenarioData & + ScenarioData & + ScenarioData diff --git a/api/src/services/reportingPeriods/reportingPeriods.test.ts b/api/src/services/reportingPeriods/reportingPeriods.test.ts index e08a4460..66bcb2d1 100644 --- a/api/src/services/reportingPeriods/reportingPeriods.test.ts +++ b/api/src/services/reportingPeriods/reportingPeriods.test.ts @@ -7,6 +7,7 @@ import { updateReportingPeriod, deleteReportingPeriod, getOrCreateReportingPeriod, + certifyReportingPeriodAndOpenNextPeriod, } from './reportingPeriods' import type { StandardScenario } from './reportingPeriods.scenarios' @@ -113,4 +114,47 @@ describe('reportingPeriods', () => { expect(result).toEqual(null) }) + + scenario( + 'certifies a reporting period and sets the next reporting period as current', + async (scenario: StandardScenario) => { + mockCurrentUser(scenario.user.one) + const result = await certifyReportingPeriodAndOpenNextPeriod({ + reportingPeriodId: scenario.reportingPeriod.one.id, + }) + + expect(result.id).toEqual(scenario.reportingPeriod.two.id) + expect(result.startDate.getTime()).toBeGreaterThan( + scenario.reportingPeriod.one.endDate.getTime() + ) + } + ) + + scenario( + 'prevents certifying when no next period exists', + async (scenario: StandardScenario) => { + mockCurrentUser(scenario.user.one) + await expect( + certifyReportingPeriodAndOpenNextPeriod({ + reportingPeriodId: scenario.reportingPeriod.two.id, + }) + ).rejects.toThrow('No next reporting period found') + } + ) + + scenario( + 'prevents certifying the same reporting period twice', + async (scenario: StandardScenario) => { + mockCurrentUser(scenario.user.one) + await certifyReportingPeriodAndOpenNextPeriod({ + reportingPeriodId: scenario.reportingPeriod.one.id, + }) + + await expect( + certifyReportingPeriodAndOpenNextPeriod({ + reportingPeriodId: scenario.reportingPeriod.one.id, + }) + ).rejects.toThrow() + } + ) }) diff --git a/api/src/services/reportingPeriods/reportingPeriods.ts b/api/src/services/reportingPeriods/reportingPeriods.ts index e199323b..983bdb10 100644 --- a/api/src/services/reportingPeriods/reportingPeriods.ts +++ b/api/src/services/reportingPeriods/reportingPeriods.ts @@ -85,6 +85,69 @@ export const createReportingPeriod: MutationResolvers['createReportingPeriod'] = }) } +export const certifyReportingPeriodAndOpenNextPeriod = async ({ + reportingPeriodId, +}: { + reportingPeriodId: number +}) => { + const currentUser = context.currentUser + const organizationId = currentUser?.agency?.organizationId + + if (!organizationId) { + throw new Error('Current user must be associated with an organization') + } + + try { + const newReportingPeriod = await db.$transaction(async (prisma) => { + const currentReportingPeriod = await db.reportingPeriod.findUnique({ + where: { id: reportingPeriodId }, + }) + if (!currentReportingPeriod) { + throw new Error( + `Reporting period with id ${reportingPeriodId} not found` + ) + } + + // Find the next reporting period + const nextReportingPeriod = await db.reportingPeriod.findFirst({ + where: { startDate: { gt: currentReportingPeriod.startDate } }, + orderBy: { startDate: 'asc' }, + }) + if (!nextReportingPeriod) { + throw new Error('No next reporting period found') + } + + await prisma.reportingPeriodCertification.create({ + data: { + reportingPeriodId, + organizationId, + certifiedById: currentUser.id, + }, + }) + + const preferences = { + current_reporting_period_id: nextReportingPeriod.id, + } + await db.organization.update({ + data: { preferences }, + where: { id: organizationId }, + }) + + return nextReportingPeriod + }) + + return newReportingPeriod + } catch (err) { + // If anything goes wrong, the transaction will be rolled back + if (err instanceof Error) { + throw err + } + throw new Error( + `Couldn't certify reporting period with id ${reportingPeriodId}` + ) + } +} + export const updateReportingPeriod: MutationResolvers['updateReportingPeriod'] = ({ id, input }) => { return db.reportingPeriod.update({