Skip to content

Commit

Permalink
Add ability to manually kick-off subrecipient file generation (#497)
Browse files Browse the repository at this point in the history
* feat: allow graphql lambda to upload subrecipient files

* feat: add backend service to upload valid subrecipients for the reporting period

* fix: ensure subrecipients are chosen based on closing-month. ensure subrecipients are scoped to organization
  • Loading branch information
as1729 authored Oct 24, 2024
1 parent 882cef2 commit 25a4836
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 24 deletions.
16 changes: 14 additions & 2 deletions api/src/functions/processValidationJson/processValidationJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,21 @@ export const processRecord = async (

try {
const subrecipientKey = `treasuryreports/${organizationId}/${reportingPeriod.id}/subrecipients.json`
const { startDate, endDate } = reportingPeriod
const startDate = new Date(
reportingPeriod.endDate.getFullYear(),
reportingPeriod.endDate.getMonth() + 1,
1
)
const endDate = new Date(
reportingPeriod.endDate.getFullYear(),
reportingPeriod.endDate.getMonth() + 2,
0
)
const subrecipientsWithUploads = await db.subrecipient.findMany({
where: { createdAt: { lte: endDate, gte: startDate } },
where: {
createdAt: { lte: endDate, gte: startDate },
organizationId,
},
include: { subrecipientUploads: true },
})
const subrecipients = {
Expand Down
114 changes: 94 additions & 20 deletions api/src/services/subrecipients/subrecipients.scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,65 @@ export const standard = defineScenario<
| Prisma.SubrecipientCreateArgs
| Prisma.SubrecipientUploadCreateArgs
>({
reportingPeriod: {
one: () => ({
data: {
name: 'String',
startDate: '2024-01-26T15:11:27.688Z',
endDate: '2024-01-26T15:11:27.688Z',
inputTemplate: {
create: {
name: 'String',
version: 'String',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
outputTemplate: {
create: {
name: 'String',
version: 'String',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
},
}),
q3: () => ({
data: {
name: 'Q3 2024 [July 1 - September 30]',
startDate: '2024-07-01T00:00:00.000Z',
endDate: '2024-09-30T00:00:00.000Z',
inputTemplate: {
create: {
name: 'Q3 - input',
version: '2024_q3',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
outputTemplate: {
create: {
name: 'Q3 - output',
version: '2024_q3',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
},
}),
},
organization: {
one: {
data: {
name: 'USDR',
preferences: {},
},
},
two: (scenario) => ({
data: {
name: 'Q3 Testing Org',
preferences: {
current_reporting_period_id: scenario.reportingPeriod.q3.id,
},
},
}),
},
agency: {
one: (scenario) => ({
Expand All @@ -37,6 +89,16 @@ export const standard = defineScenario<
organization: true,
},
}),
two: (scenario) => ({
data: {
name: 'Q3Agency',
organizationId: scenario.organization.two.id,
code: 'AQ3',
},
include: {
organization: true,
},
}),
},
user: {
one: (scenario) => ({
Expand All @@ -50,27 +112,15 @@ export const standard = defineScenario<
agency: true,
},
}),
},
reportingPeriod: {
one: () => ({
two: (scenario) => ({
data: {
name: 'String',
startDate: '2024-01-26T15:11:27.688Z',
endDate: '2024-01-26T15:11:27.688Z',
inputTemplate: {
create: {
name: 'String',
version: 'String',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
outputTemplate: {
create: {
name: 'String',
version: 'String',
effectiveDate: '2024-01-26T15:11:27.688Z',
},
},
email: '[email protected]',
name: 'Q3 User',
role: 'USDR_ADMIN',
agencyId: scenario.agency.two.id,
},
include: {
agency: true,
},
}),
},
Expand All @@ -89,6 +139,30 @@ export const standard = defineScenario<
ueiTinCombo: '12485_920485',
},
}),
q3_createdOctober: (scenario) => ({
data: {
name: 'October Subrecipient',
organization: { connect: { id: scenario.organization.two.id } },
ueiTinCombo: '17290_172900',
createdAt: '2024-10-15T00:00:00.000Z',
},
}),
q3_createdNovember: (scenario) => ({
data: {
name: 'November Subrecipient',
organization: { connect: { id: scenario.organization.two.id } },
ueiTinCombo: '17291_172911',
createdAt: '2024-11-26T00:00:00.000Z',
},
}),
q3_createdSeptember: (scenario) => ({
data: {
name: 'September Subrecipient',
organization: { connect: { id: scenario.organization.two.id } },
ueiTinCombo: '17292_172922',
createdAt: '2024-09-26T00:00:00.000Z',
},
}),
},
subrecipientUpload: {
one: (scenario) => ({
Expand Down
27 changes: 25 additions & 2 deletions api/src/services/subrecipients/subrecipients.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import type { GraphQLResolveInfo } from 'graphql'

import type { RedwoodGraphQLContext } from '@redwoodjs/graphql-server'

import { sendPutObjectToS3Bucket } from 'src/lib/aws'

import {
subrecipients,
subrecipient,
createSubrecipient,
updateSubrecipient,
deleteSubrecipient,
Subrecipient as SubrecipientResolver,
uploadSubrecipients,
} from './subrecipients'
import type { StandardScenario } from './subrecipients.scenarios'

Expand All @@ -18,13 +21,16 @@ import type { StandardScenario } from './subrecipients.scenarios'
// Please refer to the RedwoodJS Testing Docs:
// https://redwoodjs.com/docs/testing#testing-services
// https://redwoodjs.com/docs/testing#jest-expect-type-considerations

jest.mock('src/lib/aws', () => ({
...jest.requireActual('src/lib/aws'),
sendPutObjectToS3Bucket: jest.fn(),
}))
describe('subrecipients', () => {
scenario('returns all subrecipients', async (scenario: StandardScenario) => {
mockCurrentUser(scenario.user.one)
const result = await subrecipients()

expect(result.length).toEqual(Object.keys(scenario.subrecipient).length)
expect(result.length).toEqual(2)
})

scenario(
Expand Down Expand Up @@ -122,4 +128,21 @@ describe('subrecipients', () => {
)
}
)

scenario(
'uploads all valid newly created subrecipients',
async (scenario: StandardScenario) => {
// uploadSubrecipients
const result = await uploadSubrecipients({
input: {
organizationId: scenario.organization.two.id,
reportingPeriodId: scenario.reportingPeriod.q3.id,
},
})
expect(sendPutObjectToS3Bucket).toHaveBeenCalled()
expect(result.message).toEqual('Subrecipients uploaded successfully')
expect(result.success).toBe(true)
expect(result.countSubrecipients).toBe(1)
}
)
})
60 changes: 60 additions & 0 deletions api/src/services/subrecipients/subrecipients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type {
SubrecipientRelationResolvers,
} from 'types/graphql'

import { sendPutObjectToS3Bucket } from 'src/lib/aws'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

export const subrecipients: QueryResolvers['subrecipients'] = () => {
const currentUser = context.currentUser
Expand Down Expand Up @@ -48,6 +50,64 @@ export const deleteSubrecipient: MutationResolvers['deleteSubrecipient'] = ({
})
}

export const uploadSubrecipients: MutationResolvers['uploadSubrecipients'] =
async ({ input }) => {
const { organizationId, reportingPeriodId } = input
const organization = await db.organization.findUnique({
where: { id: organizationId },
})
const reportingPeriod = await db.reportingPeriod.findFirst({
where: { id: reportingPeriodId },
})
if (!organization || !reportingPeriod) {
throw new Error('Organization or reporting period not found')
}
if (
organization.preferences?.current_reporting_period_id !==
reportingPeriod.id
) {
throw new Error(
'Reporting period does not match current reporting period'
)
}

try {
const subrecipientKey = `treasuryreports/${organizationId}/${reportingPeriodId}/subrecipients.json`

const startDate = new Date(
reportingPeriod.endDate.getFullYear(),
reportingPeriod.endDate.getMonth() + 1,
1
)
const endDate = new Date(
reportingPeriod.endDate.getFullYear(),
reportingPeriod.endDate.getMonth() + 2,
0
)

const subrecipientsWithUploads = await db.subrecipient.findMany({
where: { createdAt: { lte: endDate, gte: startDate }, organizationId },
include: { subrecipientUploads: true },
})
const subrecipients = {
subrecipients: subrecipientsWithUploads,
}
await sendPutObjectToS3Bucket(
`${process.env.REPORTING_DATA_BUCKET_NAME}`,
subrecipientKey,
JSON.stringify(subrecipients)
)
return {
message: 'Subrecipients uploaded successfully',
success: true,
countSubrecipients: subrecipientsWithUploads.length,
}
} catch (err) {
logger.error(`Error saving subrecipients JSON file to S3: ${err}`)
throw new Error('Error saving subrecipient info to S3')
}
}

export const Subrecipient: SubrecipientRelationResolvers = {
organization: (_obj, { root }) => {
return db.subrecipient
Expand Down
11 changes: 11 additions & 0 deletions terraform/functions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ module "lambda_function-graphql" {
"${module.reporting_data_bucket.bucket_arn}/treasuryreports/output-templates/*/*.xlsx",
]
}
AllowUploadSubrecipientsFile = {
effect = "Allow"
actions = [
"s3:PutObject",
]
resources = [
# These are temporary files shared across services containing subrecipient data.
# Path: treasuryreports/{organization_id}/{reporting_period_id}/subrecipients.json
"${module.reporting_data_bucket.bucket_arn}/treasuryreports/*/*/subrecipients.json",
]
}

AllowStepFunctionInvocation = {
effect = "Allow"
Expand Down

0 comments on commit 25a4836

Please sign in to comment.