improvement: task assignment flow added. cases and tasks feature updated accordingly.
This commit is contained in:
@@ -14,7 +14,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { LabOrgGuard } from '../../common/guards/lab-org.guard';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { CasesService } from './cases.service';
|
||||
import { ListLabCasesDto, UpdateLabCaseImportantDto } from './dto/cases.dto';
|
||||
import { ListLabCasesDto, UpdateLabCaseImportantDto, AssignLabCaseTaskDto } from './dto/cases.dto';
|
||||
|
||||
@ApiTags('cases')
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@@ -30,6 +30,13 @@ export class CasesController {
|
||||
return this.casesService.list(organizationId, req.user.id, query);
|
||||
}
|
||||
|
||||
@Get('assignable-staff')
|
||||
@ApiOperation({ summary: 'Staff who can be assigned lab tasks' })
|
||||
listAssignableStaff(@Req() req) {
|
||||
const organizationId = this.casesService.getOrganizationIdFromUser(req.user);
|
||||
return this.casesService.listAssignableStaff(organizationId, req.user.id);
|
||||
}
|
||||
|
||||
@Get('filter-options')
|
||||
@ApiOperation({ summary: 'Clinics and treatment types for inbox filters' })
|
||||
listFilterOptions(@Req() req) {
|
||||
@@ -74,4 +81,23 @@ export class CasesController {
|
||||
const organizationId = this.casesService.getOrganizationIdFromUser(req.user);
|
||||
return this.casesService.setCaseImportant(id, dto, organizationId, req.user.id, req.user.language);
|
||||
}
|
||||
|
||||
@Patch(':caseId/tasks/:taskId/assign')
|
||||
@ApiOperation({ summary: 'Assign or unassign a task to lab staff' })
|
||||
assignTask(
|
||||
@Param('caseId') caseId: string,
|
||||
@Param('taskId') taskId: string,
|
||||
@Body() dto: AssignLabCaseTaskDto,
|
||||
@Req() req,
|
||||
) {
|
||||
const organizationId = this.casesService.getOrganizationIdFromUser(req.user);
|
||||
return this.casesService.assignTask(
|
||||
caseId,
|
||||
taskId,
|
||||
dto,
|
||||
organizationId,
|
||||
req.user.id,
|
||||
req.user.language,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import {
|
||||
} from '../catalog/catalog-label.service';
|
||||
import { TreatmentCatalogService } from '../treatment-catalog/treatment-catalog.service';
|
||||
import { normalizeTeeth } from '../treatments/treatment.utils';
|
||||
import { ListLabCasesDto, UpdateLabCaseImportantDto } from './dto/cases.dto';
|
||||
import { ListLabCasesDto, UpdateLabCaseImportantDto, AssignLabCaseTaskDto } from './dto/cases.dto';
|
||||
import { normalizeTaskTeeth } from './lab-case-task.util';
|
||||
import { hasEffectivePermission } from '../../common/membership-permissions';
|
||||
|
||||
const labCaseListInclude = {
|
||||
treatment: {
|
||||
@@ -49,6 +50,7 @@ const labCaseListInclude = {
|
||||
],
|
||||
include: {
|
||||
lastStatusChangedBy: { select: { id: true, name: true } },
|
||||
assignee: { select: { id: true, name: true } },
|
||||
statusEvents: {
|
||||
orderBy: { changedAt: 'asc' as const },
|
||||
include: { changedBy: { select: { id: true, name: true } } },
|
||||
@@ -74,6 +76,7 @@ const labCaseListInclude = {
|
||||
type LabCaseTaskWithRelations = Prisma.LabCaseTaskGetPayload<{
|
||||
include: {
|
||||
lastStatusChangedBy: { select: { id: true; name: true } };
|
||||
assignee: { select: { id: true; name: true } };
|
||||
statusEvents: {
|
||||
include: { changedBy: { select: { id: true; name: true } } };
|
||||
};
|
||||
@@ -370,6 +373,95 @@ export class CasesService {
|
||||
return { success: true, data: await this.mapLabCaseDetail(labCase, localeInput) };
|
||||
}
|
||||
|
||||
async listAssignableStaff(labOrganizationId: string, actorUserId: string) {
|
||||
await this.assertCanEditCases(actorUserId, labOrganizationId);
|
||||
|
||||
const memberships = await this.prisma.membership.findMany({
|
||||
where: {
|
||||
organizationId: labOrganizationId,
|
||||
OR: [{ isOwner: true }, { isActive: true }],
|
||||
},
|
||||
include: {
|
||||
permissions: { include: { permission: true } },
|
||||
organization: { include: { type: true, plan: true } },
|
||||
user: { select: { id: true, name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const staff = memberships
|
||||
.filter((m) => hasEffectivePermission(m, 'TAB_TASKS_EDIT'))
|
||||
.map((m) => ({
|
||||
id: m.user.id,
|
||||
name: m.user.name,
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return { success: true, data: staff };
|
||||
}
|
||||
|
||||
async assignTask(
|
||||
labCaseId: string,
|
||||
taskId: string,
|
||||
dto: AssignLabCaseTaskDto,
|
||||
labOrganizationId: string,
|
||||
actorUserId: string,
|
||||
localeInput?: string | null,
|
||||
) {
|
||||
await this.assertCanEditCases(actorUserId, labOrganizationId);
|
||||
|
||||
const task = await this.prisma.labCaseTask.findFirst({
|
||||
where: {
|
||||
id: taskId,
|
||||
labCaseId,
|
||||
labCase: {
|
||||
sentAt: { not: null },
|
||||
sends: { some: { organizationId: labOrganizationId } },
|
||||
},
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!task) {
|
||||
throw new NotFoundException('Task not found');
|
||||
}
|
||||
|
||||
const assigneeUserId = dto.assigneeUserId ?? null;
|
||||
|
||||
if (assigneeUserId) {
|
||||
const memberships = await this.prisma.membership.findMany({
|
||||
where: {
|
||||
organizationId: labOrganizationId,
|
||||
userId: assigneeUserId,
|
||||
OR: [{ isOwner: true }, { isActive: true }],
|
||||
},
|
||||
include: {
|
||||
permissions: { include: { permission: true } },
|
||||
organization: { include: { type: true, plan: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const canReceive = memberships.some((m) => hasEffectivePermission(m, 'TAB_TASKS_EDIT'));
|
||||
if (!canReceive) {
|
||||
throw new BadRequestException('Selected user cannot be assigned tasks');
|
||||
}
|
||||
}
|
||||
|
||||
await this.prisma.labCaseTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
assigneeUserId,
|
||||
assignedAt: assigneeUserId ? new Date() : null,
|
||||
},
|
||||
});
|
||||
|
||||
const labCase = await this.prisma.labCase.findFirstOrThrow({
|
||||
where: { id: labCaseId },
|
||||
include: labCaseListInclude,
|
||||
});
|
||||
|
||||
return { success: true, data: await this.mapLabCaseDetail(labCase, localeInput) };
|
||||
}
|
||||
|
||||
private buildListWhere(
|
||||
labOrganizationId: string,
|
||||
query: ListLabCasesDto,
|
||||
@@ -579,6 +671,10 @@ export class CasesService {
|
||||
stepOrder: task.stepOrder,
|
||||
stepLabel: task.stepLabel,
|
||||
status: task.status,
|
||||
assignee: task.assignee
|
||||
? { id: task.assignee.id, name: task.assignee.name }
|
||||
: null,
|
||||
assignedAt: task.assignedAt?.toISOString() ?? null,
|
||||
createdAt: task.createdAt.toISOString(),
|
||||
lastStatusChangedAt: task.lastStatusChangedAt?.toISOString() ?? null,
|
||||
lastStatusChangedBy: task.lastStatusChangedBy
|
||||
|
||||
@@ -6,6 +6,12 @@ export class UpdateLabCaseImportantDto {
|
||||
isImportant: boolean;
|
||||
}
|
||||
|
||||
export class AssignLabCaseTaskDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
assigneeUserId?: string | null;
|
||||
}
|
||||
|
||||
export class ListLabCasesDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
Reference in New Issue
Block a user