feature: Tasks tab added for lab organizations. tasks now can be assigned and their status can be updated by the assignee.
This commit is contained in:
@@ -50,7 +50,7 @@ export class CasesController {
|
||||
}
|
||||
|
||||
@Patch(':id/tasks/:taskId')
|
||||
@ApiOperation({ summary: 'Update task assignee or status' })
|
||||
@ApiOperation({ summary: 'Update task assignee or priority' })
|
||||
updateTask(
|
||||
@Param('id') id: string,
|
||||
@Param('taskId') taskId: string,
|
||||
|
||||
@@ -200,14 +200,19 @@ export class CasesService {
|
||||
}
|
||||
|
||||
if (dto.assigneeUserId !== undefined && dto.assigneeUserId !== null) {
|
||||
await this.ensureLabMember(dto.assigneeUserId, labOrganizationId);
|
||||
await this.ensureAssignableMember(dto.assigneeUserId, labOrganizationId);
|
||||
}
|
||||
|
||||
const updated = await this.prisma.labCaseTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
...(dto.assigneeUserId !== undefined ? { assigneeUserId: dto.assigneeUserId } : {}),
|
||||
...(dto.status !== undefined ? { status: dto.status } : {}),
|
||||
...(dto.assigneeUserId !== undefined
|
||||
? {
|
||||
assigneeUserId: dto.assigneeUserId,
|
||||
assignedAt: dto.assigneeUserId === null ? null : new Date(),
|
||||
}
|
||||
: {}),
|
||||
...(dto.priority !== undefined ? { priority: dto.priority } : {}),
|
||||
},
|
||||
include: {
|
||||
assignee: { select: { id: true, name: true, email: true } },
|
||||
@@ -222,18 +227,27 @@ export class CasesService {
|
||||
|
||||
const memberships = await this.prisma.membership.findMany({
|
||||
where: { organizationId: labOrganizationId, isActive: true },
|
||||
include: { user: { select: { id: true, name: true, email: true } } },
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true } },
|
||||
permissions: { include: { permission: true } },
|
||||
},
|
||||
orderBy: [{ isOwner: 'desc' }, { createdAt: 'asc' }],
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: memberships.map((m) => ({
|
||||
userId: m.user.id,
|
||||
name: m.user.name,
|
||||
email: m.user.email,
|
||||
isOwner: m.isOwner,
|
||||
})),
|
||||
data: memberships
|
||||
.filter((m) => {
|
||||
if (m.isOwner) return true;
|
||||
const names = m.permissions.map((p) => p.permission.name);
|
||||
return names.includes('TAB_TASKS_READ') || names.includes('TAB_TASKS_EDIT');
|
||||
})
|
||||
.map((m) => ({
|
||||
userId: m.user.id,
|
||||
name: m.user.name,
|
||||
email: m.user.email,
|
||||
isOwner: m.isOwner,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,7 +391,10 @@ export class CasesService {
|
||||
stepOrder: number;
|
||||
stepLabel: string;
|
||||
status: LabTaskStatus;
|
||||
priority: number;
|
||||
assigneeUserId: string | null;
|
||||
assignedAt: Date | null;
|
||||
createdAt: Date;
|
||||
assignee: { id: string; name: string; email: string } | null;
|
||||
}>,
|
||||
) {
|
||||
@@ -411,7 +428,10 @@ export class CasesService {
|
||||
stepOrder: number;
|
||||
stepLabel: string;
|
||||
status: LabTaskStatus;
|
||||
priority: number;
|
||||
assigneeUserId: string | null;
|
||||
assignedAt: Date | null;
|
||||
createdAt: Date;
|
||||
assignee: { id: string; name: string; email: string } | null;
|
||||
}) {
|
||||
return {
|
||||
@@ -421,6 +441,9 @@ export class CasesService {
|
||||
stepOrder: task.stepOrder,
|
||||
stepLabel: task.stepLabel,
|
||||
status: task.status,
|
||||
priority: task.priority,
|
||||
assignedAt: task.assignedAt?.toISOString() ?? null,
|
||||
createdAt: task.createdAt.toISOString(),
|
||||
assigneeUserId: task.assigneeUserId,
|
||||
assignee: task.assignee
|
||||
? { id: task.assignee.id, name: task.assignee.name, email: task.assignee.email }
|
||||
@@ -428,14 +451,20 @@ export class CasesService {
|
||||
};
|
||||
}
|
||||
|
||||
private async ensureLabMember(userId: string, labOrganizationId: string) {
|
||||
private async ensureAssignableMember(userId: string, labOrganizationId: string) {
|
||||
const membership = await this.prisma.membership.findFirst({
|
||||
where: { userId, organizationId: labOrganizationId, isActive: true },
|
||||
select: { id: true },
|
||||
include: { permissions: { include: { permission: true } } },
|
||||
});
|
||||
if (!membership) {
|
||||
throw new BadRequestException('Assignee must be an active member of this lab');
|
||||
}
|
||||
if (membership.isOwner) return;
|
||||
const names = membership.permissions.map((p) => p.permission.name);
|
||||
if (names.includes('TAB_TASKS_READ') || names.includes('TAB_TASKS_EDIT')) {
|
||||
return;
|
||||
}
|
||||
throw new BadRequestException('Assignee must have access to the Tasks tab');
|
||||
}
|
||||
|
||||
private async assertCanReadCases(userId: string, organizationId: string) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsDateString, IsEnum, IsInt, IsOptional, IsString, IsUUID, Max, Min, ValidateIf } from 'class-validator';
|
||||
import { LabTaskStatus } from '@prisma/client';
|
||||
import { IsDateString, IsInt, IsOptional, IsString, IsUUID, Max, Min, ValidateIf } from 'class-validator';
|
||||
|
||||
export class UpdateLabCaseTaskDto {
|
||||
@IsOptional()
|
||||
@@ -9,8 +8,11 @@ export class UpdateLabCaseTaskDto {
|
||||
assigneeUserId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(LabTaskStatus)
|
||||
status?: LabTaskStatus;
|
||||
@Transform(({ value }) => Number(value))
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(5)
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export class ListLabCasesDto {
|
||||
@@ -46,4 +48,4 @@ export class ListLabCasesDto {
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit = 20;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user