feature: Phase5 - Lab Cases inbox + task board
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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[] = [
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user