feature: Phase5 - Lab Cases inbox + task board

This commit is contained in:
2026-06-28 17:49:40 +03:30
parent 7b19d6953c
commit 478cfa085a
9 changed files with 431 additions and 83 deletions

View File

@@ -28,6 +28,13 @@ export class CasesController {
return this.casesService.list(organizationId, req.user.id, query);
}
@Get('filter-options')
@ApiOperation({ summary: 'Clinics and treatment types for inbox filters' })
listFilterOptions(@Req() req) {
const organizationId = this.casesService.getOrganizationIdFromUser(req.user);
return this.casesService.listFilterOptions(organizationId, req.user.id);
}
@Get('assignable-members')
@ApiOperation({ summary: 'List lab staff who can be assigned to tasks' })
listAssignableMembers(@Req() req) {

View File

@@ -72,23 +72,7 @@ export class CasesService {
const limit = Math.min(Math.max(query.limit ?? 20, 1), 100);
const skip = (page - 1) * limit;
const where: Prisma.LabCaseWhereInput = {
sentAt: { not: null },
sends: { some: { organizationId: labOrganizationId } },
...(query.clinicOrganizationId
? { treatment: { organizationId: query.clinicOrganizationId } }
: {}),
...(query.treatmentType
? {
details: {
some: { detail: { treatmentType: query.treatmentType } },
},
}
: {}),
...(query.q?.trim()
? this.buildSearchWhere(query.q.trim())
: {}),
};
const where = this.buildListWhere(labOrganizationId, query);
const [items, total] = await Promise.all([
this.prisma.labCase.findMany({
@@ -128,6 +112,50 @@ export class CasesService {
};
}
async listFilterOptions(labOrganizationId: string, actorUserId: string) {
await this.assertCanReadCases(actorUserId, labOrganizationId);
const rows = await this.prisma.labCase.findMany({
where: {
sentAt: { not: null },
sends: { some: { organizationId: labOrganizationId } },
},
select: {
treatment: {
select: {
organization: { select: { id: true, name: true } },
},
},
details: {
select: { detail: { select: { treatmentType: true } } },
},
},
});
const clinicsById = new Map<string, { id: string; name: string }>();
const typeCodes = new Set<string>();
for (const row of rows) {
clinicsById.set(row.treatment.organization.id, row.treatment.organization);
for (const link of row.details) {
typeCodes.add(link.detail.treatmentType);
}
}
const treatmentTypes = this.treatmentCatalog
.list()
.filter((entry) => entry.labDependent && typeCodes.has(entry.code))
.map((entry) => ({ code: entry.code, labDependent: entry.labDependent }));
return {
success: true,
data: {
clinics: [...clinicsById.values()].sort((a, b) => a.name.localeCompare(b.name)),
treatmentTypes,
},
};
}
async getOne(labCaseId: string, labOrganizationId: string, actorUserId: string) {
await this.assertCanReadCases(actorUserId, labOrganizationId);
@@ -209,6 +237,46 @@ export class CasesService {
};
}
private buildListWhere(
labOrganizationId: string,
query: ListLabCasesDto,
): Prisma.LabCaseWhereInput {
const sentAtFilter: Prisma.DateTimeNullableFilter = { not: null };
if (query.sentFrom) {
const from = new Date(query.sentFrom);
if (Number.isNaN(from.getTime())) {
throw new BadRequestException('Invalid sentFrom date');
}
sentAtFilter.gte = from;
}
if (query.sentTo) {
const to = new Date(query.sentTo);
if (Number.isNaN(to.getTime())) {
throw new BadRequestException('Invalid sentTo date');
}
to.setHours(23, 59, 59, 999);
sentAtFilter.lte = to;
}
return {
sentAt: sentAtFilter,
sends: { some: { organizationId: labOrganizationId } },
...(query.clinicOrganizationId
? { treatment: { organizationId: query.clinicOrganizationId } }
: {}),
...(query.treatmentType
? {
details: {
some: { detail: { treatmentType: query.treatmentType } },
},
}
: {}),
...(query.q?.trim() ? this.buildSearchWhere(query.q.trim()) : {}),
};
}
private buildSearchWhere(q: string): Prisma.LabCaseWhereInput {
const orConditions: Prisma.LabCaseWhereInput[] = [
{

View File

@@ -1,5 +1,5 @@
import { Transform } from 'class-transformer';
import { IsEnum, IsInt, IsOptional, IsString, IsUUID, Max, Min, ValidateIf } from 'class-validator';
import { IsDateString, IsEnum, IsInt, IsOptional, IsString, IsUUID, Max, Min, ValidateIf } from 'class-validator';
import { LabTaskStatus } from '@prisma/client';
export class UpdateLabCaseTaskDto {
@@ -19,13 +19,21 @@ export class ListLabCasesDto {
q?: string;
@IsOptional()
@IsString()
@IsUUID()
clinicOrganizationId?: string;
@IsOptional()
@IsString()
treatmentType?: string;
@IsOptional()
@IsDateString()
sentFrom?: string;
@IsOptional()
@IsDateString()
sentTo?: string;
@IsOptional()
@Transform(({ value }) => Number(value))
@IsInt()