improvement: duedate added for shipped cases. cases and tasks ui and ux updated accordingly.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
@@ -84,6 +85,16 @@ export class SaveLabCaseDto {
|
||||
@IsArray()
|
||||
@IsUUID(undefined, { each: true })
|
||||
attachmentIds?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dueDate?: string | null;
|
||||
}
|
||||
|
||||
export class UpdateLabCaseDueDateDto {
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dueDate?: string | null;
|
||||
}
|
||||
|
||||
export class SaveTreatmentLabCasesDto {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
@@ -22,6 +23,7 @@ import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import {
|
||||
SaveTreatmentDraftDto,
|
||||
SaveTreatmentLabCasesDto,
|
||||
UpdateLabCaseDueDateDto,
|
||||
} from './dto/treatment.dto';
|
||||
import { CreateLabCaseCommentDto } from '../lab-case-comments/dto/lab-case-comment.dto';
|
||||
import { LabCaseCommentsService } from '../lab-case-comments/lab-case-comments.service';
|
||||
@@ -200,6 +202,22 @@ export class TreatmentsController {
|
||||
);
|
||||
}
|
||||
|
||||
@Patch('lab-cases/:labCaseId/due-date')
|
||||
@ApiOperation({ summary: 'Update expected due date for a sent lab case' })
|
||||
updateLabCaseDueDate(
|
||||
@Param('labCaseId') labCaseId: string,
|
||||
@Body() dto: UpdateLabCaseDueDateDto,
|
||||
@Req() req: { user: { id: string; organizationId?: string } },
|
||||
) {
|
||||
const organizationId = this.treatmentsService.getOrganizationIdFromUser(req.user);
|
||||
return this.treatmentsService.updateLabCaseDueDate(
|
||||
labCaseId,
|
||||
dto,
|
||||
organizationId,
|
||||
req.user.id,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('lab-cases/:labCaseId/comments')
|
||||
@ApiOperation({ summary: 'List comments for a lab case during treatment dispatch' })
|
||||
listLabCaseComments(
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { LabTaskStatus, LinkStatus } from '@prisma/client';
|
||||
import { LabTaskStatus, LinkStatus, Prisma } from '@prisma/client';
|
||||
import { createReadStream, existsSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { randomUUID } from 'crypto';
|
||||
@@ -15,7 +15,12 @@ import { TreatmentCatalogService } from '../treatment-catalog/treatment-catalog.
|
||||
import {
|
||||
SaveTreatmentDraftDto,
|
||||
SaveTreatmentLabCasesDto,
|
||||
UpdateLabCaseDueDateDto,
|
||||
} from './dto/treatment.dto';
|
||||
import {
|
||||
isLabCaseFullyCompleted,
|
||||
parseDueDateInput,
|
||||
} from '../../common/lab-case-due-date';
|
||||
import {
|
||||
generateTreatmentTitle,
|
||||
normalizeTeeth,
|
||||
@@ -408,9 +413,15 @@ export class TreatmentsService {
|
||||
|
||||
for (const [index, lc] of dto.labCases.entries()) {
|
||||
if (lc.id && sentLabCaseIds.has(lc.id)) {
|
||||
if (lc.dueDate !== undefined) {
|
||||
await this.updateLabCaseDueDateInTx(tx, lc.id, organizationId, lc.dueDate);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const dueDate =
|
||||
lc.dueDate !== undefined ? parseDueDateInput(lc.dueDate) : undefined;
|
||||
|
||||
const row = lc.id
|
||||
? await tx.labCase.update({
|
||||
where: { id: lc.id },
|
||||
@@ -418,6 +429,7 @@ export class TreatmentsService {
|
||||
clientKey: lc.clientId,
|
||||
sortOrder: index,
|
||||
destinationOrganizationId: lc.destinationOrganizationId ?? null,
|
||||
...(dueDate !== undefined ? { dueDate } : {}),
|
||||
},
|
||||
})
|
||||
: await tx.labCase.create({
|
||||
@@ -426,6 +438,7 @@ export class TreatmentsService {
|
||||
clientKey: lc.clientId,
|
||||
sortOrder: index,
|
||||
destinationOrganizationId: lc.destinationOrganizationId ?? null,
|
||||
...(dueDate !== undefined ? { dueDate } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -586,6 +599,88 @@ export class TreatmentsService {
|
||||
return { success: true, data: this.mapLabCase(refreshed) };
|
||||
}
|
||||
|
||||
async updateLabCaseDueDate(
|
||||
labCaseId: string,
|
||||
dto: UpdateLabCaseDueDateDto,
|
||||
organizationId: string,
|
||||
actorUserId: string,
|
||||
) {
|
||||
await this.assertCanEditTreatment(actorUserId, organizationId);
|
||||
|
||||
const updated = await this.prisma.$transaction(async (tx) => {
|
||||
await this.updateLabCaseDueDateInTx(tx, labCaseId, organizationId, dto.dueDate ?? null);
|
||||
return tx.labCase.findFirstOrThrow({
|
||||
where: { id: labCaseId },
|
||||
include: {
|
||||
details: {
|
||||
include: {
|
||||
detail: {
|
||||
select: { id: true, clientKey: true, treatmentType: true, teeth: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
sends: {
|
||||
orderBy: [{ sentAt: 'asc' }],
|
||||
include: { organization: { select: { id: true, name: true } } },
|
||||
},
|
||||
toothProsthesis: true,
|
||||
tasks: { select: { id: true, status: true } },
|
||||
attachments: {
|
||||
include: {
|
||||
attachment: {
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
mimeType: true,
|
||||
sizeBytes: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { success: true, data: this.mapLabCase(updated) };
|
||||
}
|
||||
|
||||
private async updateLabCaseDueDateInTx(
|
||||
tx: Prisma.TransactionClient,
|
||||
labCaseId: string,
|
||||
organizationId: string,
|
||||
dueDateInput?: string | null,
|
||||
) {
|
||||
const labCase = await tx.labCase.findFirst({
|
||||
where: {
|
||||
id: labCaseId,
|
||||
treatment: { organizationId },
|
||||
sentAt: { not: null },
|
||||
},
|
||||
include: { tasks: { select: { status: true } } },
|
||||
});
|
||||
|
||||
if (!labCase) {
|
||||
throw new NotFoundException('Lab case not found');
|
||||
}
|
||||
|
||||
if (isLabCaseFullyCompleted(labCase.tasks)) {
|
||||
throw new BadRequestException('Due date cannot be changed after all tasks are completed');
|
||||
}
|
||||
|
||||
let dueDate: Date | null;
|
||||
try {
|
||||
dueDate = parseDueDateInput(dueDateInput ?? null);
|
||||
} catch {
|
||||
throw new BadRequestException('Invalid due date');
|
||||
}
|
||||
|
||||
await tx.labCase.update({
|
||||
where: { id: labCaseId },
|
||||
data: { dueDate },
|
||||
});
|
||||
}
|
||||
|
||||
async uploadDetailAttachments(
|
||||
appointmentId: string,
|
||||
detailClientKey: string,
|
||||
@@ -804,6 +899,7 @@ export class TreatmentsService {
|
||||
sortOrder?: number;
|
||||
destinationOrganizationId?: string | null;
|
||||
sentAt?: Date | null;
|
||||
dueDate?: Date | null;
|
||||
details?: Array<{
|
||||
treatmentDetailId: string;
|
||||
detail?: { id: string; clientKey: string | null; treatmentType: string; teeth: unknown };
|
||||
@@ -827,12 +923,16 @@ export class TreatmentsService {
|
||||
createdAt: Date;
|
||||
};
|
||||
}>;
|
||||
tasks?: Array<{ id: string; status: LabTaskStatus }>;
|
||||
}) {
|
||||
const taskProgress = this.mapTaskProgress(lc.tasks ?? []);
|
||||
return {
|
||||
id: lc.id,
|
||||
clientId: lc.clientKey ?? lc.id,
|
||||
destinationOrganizationId: lc.destinationOrganizationId ?? null,
|
||||
sentAt: lc.sentAt?.toISOString() ?? null,
|
||||
dueDate: lc.dueDate?.toISOString() ?? null,
|
||||
taskProgress,
|
||||
treatmentDetailId: lc.details?.[0]?.treatmentDetailId ?? null,
|
||||
detail: lc.details?.[0]
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user