Skip to content

Commit

Permalink
feat: sample update info + result
Browse files Browse the repository at this point in the history
  • Loading branch information
wermarter committed Jan 30, 2024
1 parent 47a9382 commit d9e0bfb
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 64 deletions.
2 changes: 1 addition & 1 deletion apps/hcdc-access-service/src/domain/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { $or, or } from '@ucast/mongo2js'
import { FilterQuery } from 'mongoose'
import { accessibleBy } from '@casl/mongoose'
import { PopulatePath } from '@diut/nestjs-core'

import { AuthAction, AuthActionUnionType } from './action'
import {
Expand All @@ -18,7 +19,6 @@ import { EAuthzPermissionDenied } from 'src/domain/exception'
import { AUTH_ACTION_ALL, AUTH_SUBJECT_ALL } from './constants'
import { BaseEntity, PermissionRule } from '../entity'
import { EntityFindOneOptions } from '../interface'
import { PopulatePath } from '@diut/nestjs-core'

const conditionsMatcher = buildMongoQueryMatcher({ $or }, { or })

Expand Down
1 change: 1 addition & 0 deletions apps/hcdc-access-service/src/domain/use-case/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './sample/search'
export * from './sample/assert-exists'
export * from './sample/validate'
export * from './sample/authorize-populates'
export * from './sample/init-result'

export * from './print-form/create'
export * from './print-form/find-one'
Expand Down
2 changes: 2 additions & 0 deletions apps/hcdc-access-service/src/domain/use-case/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { SampleDeleteManyUseCase } from './sample/delete-many'
import { SampleSearchUseCase } from './sample/search'
import { SampleAssertExistsUseCase } from './sample/assert-exists'
import { SampleAuthorizePopulatesUseCase } from './sample/authorize-populates'
import { SampleInitResultUseCase } from './sample/init-result'

import { PatientTypeCreateUseCase } from './patient-type/create'
import { PatientTypeUpdateUseCase } from './patient-type/update'
Expand Down Expand Up @@ -199,6 +200,7 @@ export const useCaseMetadata: ModuleMetadata = {
SampleAssertExistsUseCase,
SampleValidateUseCase,
SampleAuthorizePopulatesUseCase,
SampleInitResultUseCase,

PatientCreateUseCase,
PatientFindOneUseCase,
Expand Down
43 changes: 6 additions & 37 deletions apps/hcdc-access-service/src/domain/use-case/sample/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
import { Sample, SampleAction, EntityData, SampleInfo } from 'src/domain/entity'
import { AuthSubject, assertPermission } from 'src/domain/auth'
import { SampleValidateUseCase } from './validate'
import { TestSearchUseCase } from '../test/search'
import { TestElementSearchUseCase } from '../test-element/search'
import { PatientGetCategoryUseCase } from '../patient/get-category'
import { EEntitySampleIdAlreadyExists } from 'src/domain/exception'
import { SampleInitResultUseCase } from './init-result'

@Injectable()
export class SampleCreateUseCase {
Expand All @@ -22,8 +21,7 @@ export class SampleCreateUseCase {
@Inject(SampleRepositoryToken)
private readonly sampleRepository: ISampleRepository,
private readonly sampleValidateUseCase: SampleValidateUseCase,
private readonly testSearchUseCase: TestSearchUseCase,
private readonly testElementSearchUseCase: TestElementSearchUseCase,
private readonly sampleInitResultUseCase: SampleInitResultUseCase,
private readonly patientGetCategoryUseCase: PatientGetCategoryUseCase,
) {}

Expand All @@ -42,44 +40,15 @@ export class SampleCreateUseCase {
throw new EEntitySampleIdAlreadyExists('cannot create duplicate samples')
}

const tests = (
await this.testSearchUseCase.execute({
filter: { _id: { $in: input.testIds } },
populates: [{ path: 'bioProduct' }, { path: 'instrument' }],
})
).items

const testElements = await Promise.all(
tests.map((test) =>
this.testElementSearchUseCase
.execute({
filter: { testId: test._id },
projection: { _id: 1, normalRules: 1 },
})
.then((data) => data.items),
),
)

const patientCategory = await this.patientGetCategoryUseCase.execute({
patientId: input.patientId,
})

const entityData = {
...input,
results: tests.map((test, index) => ({
testId: test._id,
isLocked: false,
bioProductName: test.bioProduct?.name,
instrumentName: test.instrument?.name,
elements: testElements[index].map((element) => ({
testElementId: element._id,
value: '',
isAbnormal:
element.normalRules.find(
({ category }) => category === patientCategory,
)?.defaultChecked ?? false,
})),
})),
results: await this.sampleInitResultUseCase.execute({
testIds: input.testIds,
patientCategory,
}),
} satisfies Partial<EntityData<Sample>>

await this.sampleValidateUseCase.execute(entityData)
Expand Down
72 changes: 72 additions & 0 deletions apps/hcdc-access-service/src/domain/use-case/sample/init-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Injectable } from '@nestjs/common'

import { PatientCategory, Sample, Test } from 'src/domain/entity'
import { TestSearchUseCase } from '../test/search'
import { TestElementSearchUseCase } from '../test-element/search'
import { EEntityNotFound } from 'src/domain/exception'

@Injectable()
export class SampleInitResultUseCase {
constructor(
private readonly testSearchUseCase: TestSearchUseCase,
private readonly testElementSearchUseCase: TestElementSearchUseCase,
) {}

async execute(input: {
patientCategory: PatientCategory
testIds: string[]
}) {
const results: Sample['results'] = []

if (input.testIds.length > 0) {
const tests: Test[] = (
await this.testSearchUseCase.execute({
filter: {
_id: { $in: input.testIds },
},
populates: [{ path: 'bioProduct' }, { path: 'instrument' }],
})
).items

input.testIds.forEach((testId) => {
if (!tests.some(({ _id }) => _id === testId)) {
throw new EEntityNotFound(`Test { _id = ${testId} }`)
}
})

const testElements = await Promise.all(
tests.map((test) =>
this.testElementSearchUseCase
.execute({
filter: { testId: test._id },
projection: { _id: 1, normalRules: 1 },
})
.then((data) => data.items),
),
)

tests.forEach((test, index) => {
results.push({
testId: test._id,
isLocked: false,
bioProductName: test.bioProduct?.name,
instrumentName: test.instrument?.name,
elements: testElements[index].map((element) => ({
testElementId: element._id,
value: '',
isAbnormal:
element.normalRules.find(
({ category }) => category === input.patientCategory,
)?.defaultChecked ??
element.normalRules.find(
({ category }) => category === PatientCategory.Any,
)?.defaultChecked ??
false,
})),
})
})
}

return results
}
}
36 changes: 33 additions & 3 deletions apps/hcdc-access-service/src/domain/use-case/sample/update-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from 'src/domain/interface'
import { SampleAssertExistsUseCase } from './assert-exists'
import { SampleValidateUseCase } from './validate'
import { SampleInitResultUseCase } from './init-result'
import { PatientGetCategoryUseCase } from '../patient/get-category'

@Injectable()
export class SampleUpdateInfoUseCase {
Expand All @@ -21,11 +23,16 @@ export class SampleUpdateInfoUseCase {
private readonly authContext: IAuthContext,
private readonly sampleAssertExistsUseCase: SampleAssertExistsUseCase,
private readonly sampleValidateUseCase: SampleValidateUseCase,
private readonly sampleInitResultUseCase: SampleInitResultUseCase,
private readonly patientGetCategoryUseCase: PatientGetCategoryUseCase,
) {}

async execute(input: {
filter: FilterQuery<Sample>
data: UpdateQuery<SampleInfo>
data: UpdateQuery<SampleInfo & Pick<Sample, 'isConfirmed'>> & {
addedTestIds?: string[]
removedTestIds?: string[]
}
}) {
const entity = await this.sampleAssertExistsUseCase.execute(input.filter)
const { ability } = this.authContext.getData()
Expand All @@ -36,10 +43,33 @@ export class SampleUpdateInfoUseCase {
entity,
)

if (input.data.testIds?.length) {
input.data.results = input.data.testIds
let modifiedResults = entity.results

if (input.data.removedTestIds?.length) {
modifiedResults = entity.results.filter(
({ testId }) => !input.data.removedTestIds?.includes(testId),
)
}

const newTestIds = (input.data.addedTestIds ?? []).filter((addedTestId) =>
entity.results.every(({ testId }) => testId !== addedTestId),
)

if (newTestIds.length > 0) {
const patientCategory = await this.patientGetCategoryUseCase.execute({
patientId: entity.patientId,
})

const newResults = await this.sampleInitResultUseCase.execute({
testIds: newTestIds,
patientCategory,
})

modifiedResults.push(...newResults)
}

input.data.results = modifiedResults

await this.sampleValidateUseCase.execute(input.data)

return this.sampleRepository.update(input.filter, input.data)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'
import { FilterQuery, UpdateQuery } from 'mongoose'
import { FilterQuery } from 'mongoose'

import { Sample, SampleAction, SampleResult } from 'src/domain/entity'
import { AuthSubject, assertPermission } from 'src/domain/auth'
Expand All @@ -11,6 +11,8 @@ import {
} from 'src/domain/interface'
import { SampleAssertExistsUseCase } from './assert-exists'
import { SampleValidateUseCase } from './validate'
import { SampleInitResultUseCase } from './init-result'
import { PatientGetCategoryUseCase } from '../patient/get-category'

@Injectable()
export class SampleUpdateResultUseCase {
Expand All @@ -21,14 +23,13 @@ export class SampleUpdateResultUseCase {
private readonly authContext: IAuthContext,
private readonly sampleAssertExistsUseCase: SampleAssertExistsUseCase,
private readonly sampleValidateUseCase: SampleValidateUseCase,
private readonly sampleInitResultUseCase: SampleInitResultUseCase,
private readonly patientGetCategoryUseCase: PatientGetCategoryUseCase,
) {}

async execute(input: {
filter: FilterQuery<Sample>
data: UpdateQuery<SampleResult>
}) {
async execute(input: { filter: FilterQuery<Sample>; data: SampleResult }) {
const entity = await this.sampleAssertExistsUseCase.execute(input.filter)
const { ability } = this.authContext.getData()
const { ability, user } = this.authContext.getData()
assertPermission(
ability,
AuthSubject.Sample,
Expand All @@ -37,6 +38,62 @@ export class SampleUpdateResultUseCase {
)
await this.sampleValidateUseCase.execute(input.data)

return this.sampleRepository.update(input.filter, input.data)
const modifiedResults: SampleResult['results'] = []
const patientCategory = await this.patientGetCategoryUseCase.execute({
patientId: entity.patientId,
})

const defaultResults = await this.sampleInitResultUseCase.execute({
patientCategory,
testIds: entity.results.map(({ testId }) => testId),
})

for (const testResult of entity.results) {
const defaultResult = defaultResults.find(
({ testId }) => testId === testResult.testId,
)!

const newResult = input.data.results.find(
({ testId }) => testId === testResult.testId,
)

if (newResult !== undefined) {
newResult.elements = defaultResult.elements.map((defaultElement) => {
const newElementResult = newResult.elements.find(
({ testElementId }) =>
testElementId === defaultElement.testElementId,
)
const existingElementResult = testResult.elements.find(
({ testElementId }) =>
testElementId === defaultElement.testElementId,
)

return newElementResult ?? existingElementResult ?? defaultElement
})

modifiedResults.push({
...newResult,
resultAt: new Date(),
resultById: user._id,
})
} else {
testResult.elements = defaultResult.elements.map((defaultElement) => {
const existingElementResult = testResult.elements.find(
({ testElementId }) =>
testElementId === defaultElement.testElementId,
)

return existingElementResult ?? defaultElement
})

modifiedResults.push(testResult)
}
}

await this.sampleValidateUseCase.execute({ results: modifiedResults })

return this.sampleRepository.update(input.filter, {
results: modifiedResults,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class SampleValidateUseCase {
const { testElementId } = element
const sample = await this.testElementAssertExistsUseCase.execute({
_id: testElementId,
testId,
})
assertPermission(
ability,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PatientTypeSchema } from '../patient-type'
import { DiagnosisSchema } from '../diagnosis'

@Schema({
_id: false,
virtuals: {
testElement: {
options: {
Expand All @@ -30,14 +31,16 @@ export class SampleResultTestElementSchema {
testElementId: string
testElement?: TestElementSchema | null

@Prop({ required: true })
// The required validator will fail for empty strings.
@Prop({ required: false })
value: string

@Prop({ required: true })
isAbnormal: boolean
}

@Schema({
_id: false,
virtuals: {
test: {
options: {
Expand Down Expand Up @@ -201,8 +204,8 @@ export class SampleSchema extends BaseSchema {
infoById: string
infoBy?: UserSchema | null

@Prop({ required: true, type: Types.ObjectId })
printedById: string
@Prop({ required: false, type: Types.ObjectId })
printedById?: string
printedBy?: UserSchema | null

@Prop({ required: true, type: Types.ObjectId })
Expand Down
Loading

0 comments on commit d9e0bfb

Please sign in to comment.