Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ model Contact {
agreementEndDate DateTime? @map("agreement_end_date") @db.Date
terminationDate DateTime? @map("termination_date") @db.Date
mcfdContract String? @map("mcfd_contract")
sourceAgreement String? @map("source_agreement")
orderNumber String? @map("order_number")
orderType String? @map("order_type")
orderStatus String? @map("order_status")
Expand Down
1 change: 1 addition & 0 deletions backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function generateContact(csaStatus: string) {
agreementEndDate: agreementEnd ?? null,
terminationDate: terminationDate ?? null,
mcfdContract: faker.string.alphanumeric(10).toUpperCase(),
sourceAgreement: faker.helpers.arrayElement(SOURCES),

orderNumber: faker.string.alphanumeric(8).toUpperCase(),
orderType: faker.helpers.arrayElement(ORDER_TYPES),
Expand Down
5 changes: 4 additions & 1 deletion backend/src/api/contacts/dto/contact.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class ContactDto {
@ApiPropertyOptional({ description: 'Whether placement was interrupted' })
interruptedPlacement?: string

@ApiPropertyOptional({ description: 'Source of placement' })
@ApiPropertyOptional({ description: 'Source of the primary placement (ICM or MIS)' })
sourcePlacement?: string

@ApiPropertyOptional({ description: 'Service provider name' })
Expand Down Expand Up @@ -171,6 +171,9 @@ export class ContactDto {
@ApiPropertyOptional({ description: 'MCFD contract identifier' })
mcfdContract?: string

@ApiPropertyOptional({ description: 'Source of the primary agreement or contract (ICM or MIS)' })
sourceAgreement?: string

@ApiPropertyOptional({ description: 'Order number' })
orderNumber?: string

Expand Down
161 changes: 161 additions & 0 deletions backend/src/sync/eligibility/eligibility.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,167 @@ describe('EligibilityService', () => {
expect(mockPrisma.$executeRawUnsafe).toHaveBeenCalledTimes(1) // batchUpsertRows only
})

const SOURCE_PLACEMENT_COLUMN_INDEX = 39
const SOURCE_AGREEMENT_COLUMN_INDEX = 49

function upsertedColumnValues(index: number): unknown[] {
expect(mockPrisma.$executeRawUnsafe).toHaveBeenCalled()
return mockPrisma.$executeRawUnsafe.mock.calls[0][index + 1] as unknown[]
}

function makeOocIcmContact(overrides: Record<string, unknown> = {}) {
return makeEligibleContact({
misLegalAuthCode: 'OPC',
icmPlacements: [],
icmOrders: [],
icmAgreements: [
{
rowId: 'AGR-OOC',
agreementType: 'Out of Care',
agreementStatus: 'Active',
agreementStartDate: '2024-01-01',
agreementEndDate: null,
terminationDate: null,
mcfdContract: 'C-OOC',
serviceProviderName: 'OOC Provider',
providerId: 'PROV-OOC',
},
],
csaStatus: null,
existingContactId: null,
...overrides,
})
}

function makeOocMisFallbackContact(overrides: Record<string, unknown> = {}) {
return makeEligibleContact({
misLegalAuthCode: 'OPC',
icmPlacements: [],
icmOrders: [],
icmAgreements: [],
misPlacements: [
{
type: 'PL',
status: 'ACTIVE',
startDate: '2024-01-01',
endDate: null,
contractNumber: 'CON-MIS',
placementNumber: 'MIS-PL-1',
serviceType: '54',
serviceProviderName: 'MIS Provider',
providerId: 'RE-1',
placeOfServiceName: 'Home',
},
],
misContracts: [
{
contractNumber: 'CON-MIS',
providerId: 'RE-1',
type: 'Out of Care',
status: 'Active',
startDate: '2024-01-01',
endDate: null,
terminationDate: null,
serviceProviderName: 'MIS Provider',
},
],
misPayments: [],
csaStatus: null,
existingContactId: null,
...overrides,
})
}

it('sets source_agreement from primaryAgreement when OOC has no placement', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([makeOocIcmContact()])

await service.run(null)

expect(upsertedColumnValues(SOURCE_PLACEMENT_COLUMN_INDEX)).toEqual([null])
expect(upsertedColumnValues(SOURCE_AGREEMENT_COLUMN_INDEX)).toEqual(['ICM'])
})

it('sets source_agreement from MIS contract when OOC falls back to MIS', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([makeOocMisFallbackContact()])

await service.run(null)

expect(upsertedColumnValues(SOURCE_PLACEMENT_COLUMN_INDEX)).toEqual([null])
expect(upsertedColumnValues(SOURCE_AGREEMENT_COLUMN_INDEX)).toEqual(['MIS'])
})

it('leaves placement and agreement source null when OOC has no agreement', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([
makeEligibleContact({
misLegalAuthCode: 'OPT',
icmPlacements: [],
icmOrders: [],
icmAgreements: [],
csaStatus: null,
existingContactId: null,
}),
])

await service.run(null)

expect(upsertedColumnValues(SOURCE_PLACEMENT_COLUMN_INDEX)).toEqual([null])
expect(upsertedColumnValues(SOURCE_AGREEMENT_COLUMN_INDEX)).toEqual([null])
})

it('sets placement and agreement source from placement for non-OOC contacts', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([makeEligibleContact()])

await service.run(null)

expect(upsertedColumnValues(SOURCE_PLACEMENT_COLUMN_INDEX)).toEqual(['ICM'])
expect(upsertedColumnValues(SOURCE_AGREEMENT_COLUMN_INDEX)).toEqual(['ICM'])
})

it('sets source_agreement from MIS contract when ICM placement falls back to MIS agreement', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([
makeEligibleContact({
icmPlacements: [
{
type: 'Placement',
status: 'Active',
startDate: '2024-01-01',
endDate: null,
contractNumber: 'CON-1',
agreementRowId: 'AGR-MISSING',
paidUnpaid: 'Paid',
placementNumber: 'PL-1',
serviceType: 'Foster Care',
serviceProviderName: 'Provider A',
providerId: 'RE-1',
placeOfServiceName: 'Home A',
interruptedPlacementId: null,
},
],
icmAgreements: [],
icmOrders: [],
misContracts: [
{
contractNumber: 'con-1',
providerId: 're-1',
type: 'MIS-CONTRACT',
status: 'Active',
startDate: '2024-01-01',
endDate: null,
terminationDate: null,
serviceProviderName: 'MIS Provider',
},
],
misPlacements: [],
misPayments: [],
}),
])

await service.run(null)

expect(upsertedColumnValues(SOURCE_PLACEMENT_COLUMN_INDEX)).toEqual(['ICM'])
expect(upsertedColumnValues(SOURCE_AGREEMENT_COLUMN_INDEX)).toEqual(['MIS'])
})

it('should skip new contacts who are already over 18', async () => {
mockPrisma.$queryRawUnsafe.mockResolvedValueOnce([makeOver18Contact()])

Expand Down
5 changes: 5 additions & 0 deletions backend/src/sync/eligibility/eligibility.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,11 @@ const CONTACT_COLUMNS: ContactColumnDef[] = [
extract: (row) =>
row.primaryAgreement?.mcfdContract ?? row.primaryPlacement?.contractNumber ?? null,
},
{
dbColumn: 'source_agreement',
pgType: 'text',
extract: (row) => row.primaryAgreement?.source ?? row.primaryPlacement?.source ?? null,
},
{
dbColumn: 'order_number',
pgType: 'text',
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2867,6 +2867,7 @@ function App() {
agreementEndDate: contact.agreementEndDate ? formatDateYMD(contact.agreementEndDate) : '',
terminationDate: contact.terminationDate ? formatDateYMD(contact.terminationDate) : '',
mcfdContract: contact.mcfdContract || '',
sourceAgreement: contact.sourceAgreement || '',
product: contact.product || '',
isOver18: contact.isOver18 || false,
cgwrks3: contact.holdBy || '',
Expand Down Expand Up @@ -5984,7 +5985,7 @@ function App() {
wordBreak: 'break-word',
}}
>
{childData.sourcePlacement ? (
{childData.sourceAgreement ? (
<Typography
component="span"
sx={{
Expand All @@ -5996,7 +5997,7 @@ function App() {
fontSize: '0.75rem',
}}
>
{childData.sourcePlacement}
{childData.sourceAgreement}
</Typography>
) : (
'-'
Expand Down
1 change: 1 addition & 0 deletions frontend/src/service/contacts-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface Contact {
agreementEndDate?: string
terminationDate?: string
mcfdContract?: string
sourceAgreement?: string
// Order fields
orderNumber?: string
orderType?: string
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/utils/__tests__/mock-placement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,20 @@ describe('mock placement helpers', () => {
})
expect(formatDateYMD).toHaveBeenCalledTimes(2)
})

test('hides placement source when placement location is blank', () => {
const result = buildPlacementDisplayValues(
{
placementLocation: '',
locationType: '',
locationSubType: '',
placementStatus: '',
sourcePlacement: 'MIS',
placeOfServiceName: '',
},
vi.fn(),
)

expect(result.sourcePlacement).toBe('')
})
})
5 changes: 4 additions & 1 deletion frontend/src/utils/mock-placement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ export const isMockSection54Placement = (contact: MockPlacementMatchInput): bool
normalizeMatchValue(contact.locationSubType) === '54' &&
normalizeMatchValue(contact.placementStatus) === 'ACTIVE'

const hasPlacementDetails = (contact: PlacementDisplayInput): boolean =>
Boolean(contact.placementLocation?.trim())

export const buildPlacementDisplayValues = (
contact: PlacementDisplayInput,
formatDateYMD: (dateString: string) => string,
): PlacementDisplayValues => {
const hidePlacementDetails = isMockSection54Placement(contact)
const hidePlacementDetails = isMockSection54Placement(contact) || !hasPlacementDetails(contact)

return {
placementLocation: hidePlacementDetails ? '' : contact.placementLocation || '',
Expand Down
9 changes: 9 additions & 0 deletions migrations/sql/V24__add_source_agreement_to_contacts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TABLE csa.contacts
ADD COLUMN IF NOT EXISTS source_agreement TEXT;

COMMENT ON COLUMN csa.contacts.source_agreement IS 'ICM or MIS source for the primary agreement/contract displayed in CSA details';

UPDATE csa.contacts
SET source_agreement = source_placement
WHERE source_placement IS NOT NULL
AND source_agreement IS NULL;
Loading