feature: Phase3 - Task templates + generation on send

This commit is contained in:
2026-06-28 16:46:42 +03:30
parent 8b4ef6195d
commit 21f545ebdb
25 changed files with 1448 additions and 31 deletions

View File

@@ -1,7 +1,6 @@
import {
ArrayMinSize,
IsArray,
IsIn,
IsOptional,
IsString,
IsUUID,
@@ -10,8 +9,6 @@ import {
} from 'class-validator';
import { Type } from 'class-transformer';
const TREATMENT_TYPES = ['consultation', 'filling', 'endo', 'visit', 'hygiene'] as const;
export class SaveTreatmentDetailDto {
@IsString()
@MaxLength(64)
@@ -21,7 +18,8 @@ export class SaveTreatmentDetailDto {
@IsUUID()
id?: string;
@IsIn(TREATMENT_TYPES)
@IsString()
@MaxLength(64)
treatmentType: string;
@IsArray()

View File

@@ -1,13 +1,5 @@
import { TreatmentStatus } from '@prisma/client';
const TREATMENT_TYPES = ['consultation', 'filling', 'endo', 'visit', 'hygiene'] as const;
export type TreatmentTypeValue = (typeof TREATMENT_TYPES)[number];
export function isTreatmentType(value: string): value is TreatmentTypeValue {
return (TREATMENT_TYPES as readonly string[]).includes(value);
}
const FDI_TOOTH_IDS = new Set([
'11', '12', '13', '14', '15', '16', '17', '18',
'21', '22', '23', '24', '25', '26', '27', '28',

View File

@@ -9,13 +9,14 @@ import { createReadStream, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { randomUUID } from 'crypto';
import { PrismaService } from '../../../prisma/prisma.service';
import { generateLabCaseTasks } from '../cases/lab-case-task.generator';
import { TreatmentCatalogService } from '../treatment-catalog/treatment-catalog.service';
import {
SaveTreatmentDraftDto,
SaveTreatmentLabCasesDto,
} from './dto/treatment.dto';
import {
generateTreatmentTitle,
isTreatmentType,
mapTreatmentStatusForApi,
normalizeTeeth,
} from './treatment.utils';
@@ -61,7 +62,10 @@ const treatmentInclude = {
export class TreatmentsService {
private readonly uploadRoot = join(process.cwd(), 'uploads', 'treatments');
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly treatmentCatalog: TreatmentCatalogService,
) {}
getOrganizationIdFromUser(user: { organizationId?: string }) {
if (!user?.organizationId) {
@@ -163,9 +167,7 @@ export class TreatmentsService {
);
for (const d of dto.details) {
if (!isTreatmentType(d.treatmentType)) {
throw new BadRequestException(`Invalid treatment type: ${d.treatmentType}`);
}
this.treatmentCatalog.assertKnownTreatmentType(d.treatmentType);
}
const normalizedDetails = dto.details.map((d, index) => ({
@@ -328,12 +330,16 @@ export class TreatmentsService {
const details = await this.prisma.treatmentDetail.findMany({
where: { treatmentId: treatment.id, id: { in: detailIds } },
select: { id: true },
select: { id: true, treatmentType: true },
});
if (details.length !== uniqueDetailIds.size) {
throw new BadRequestException('One or more treatment details were not found');
}
for (const detail of details) {
this.treatmentCatalog.assertLabDependentTreatmentType(detail.treatmentType);
}
const linkedOrgIds = await this.getActiveLinkedOrganizationIds(organizationId);
for (const lc of dto.labCases) {
@@ -470,6 +476,8 @@ export class TreatmentsService {
data: { sentAt: now },
});
}
await generateLabCaseTasks(tx, labCaseId);
});
const refreshed = await this.prisma.labCase.findUniqueOrThrow({