Skip to content

Commit

Permalink
CPF-410 Certify reporting period and transition to the next one (#421)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Vikariusu authored Sep 23, 2024
1 parent 10ed4d7 commit c1862d4
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 5 deletions.
3 changes: 3 additions & 0 deletions api/src/graphql/reportingPeriods.sdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
63 changes: 58 additions & 5 deletions api/src/services/reportingPeriods/reportingPeriods.scenarios.ts
Original file line number Diff line number Diff line change
@@ -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<Prisma.ReportingPeriodCreateArgs>({
export const standard = defineScenario<
| Prisma.ReportingPeriodCreateArgs
| Prisma.OrganizationCreateArgs
| Prisma.AgencyCreateArgs
| Prisma.UserCreateArgs
>({
reportingPeriod: {
one: {
data: {
Expand All @@ -28,8 +39,8 @@ export const standard = defineScenario<Prisma.ReportingPeriodCreateArgs>({
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',
Expand All @@ -47,6 +58,48 @@ export const standard = defineScenario<Prisma.ReportingPeriodCreateArgs>({
},
},
},
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: '[email protected]',
name: 'Org Admin',
role: 'ORGANIZATION_ADMIN',
agency: { connect: { id: scenario.agency.one.id } },
},
include: {
agency: true,
},
}),
},
})

export type StandardScenario = ScenarioData<ReportingPeriod, 'reportingPeriod'>
export type StandardScenario = ScenarioData<
ReportingPeriod,
'reportingPeriod'
> &
ScenarioData<Organization, 'organization'> &
ScenarioData<Agency, 'agency'> &
ScenarioData<User, 'user'>
44 changes: 44 additions & 0 deletions api/src/services/reportingPeriods/reportingPeriods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
updateReportingPeriod,
deleteReportingPeriod,
getOrCreateReportingPeriod,
certifyReportingPeriodAndOpenNextPeriod,
} from './reportingPeriods'
import type { StandardScenario } from './reportingPeriods.scenarios'

Expand Down Expand Up @@ -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()
}
)
})
63 changes: 63 additions & 0 deletions api/src/services/reportingPeriods/reportingPeriods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down

0 comments on commit c1862d4

Please sign in to comment.