import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "../prisma/prisma.service"; import { ActivityService } from "../activity/activity.service"; import { TaskStatus } from "@prisma/client"; import type { CreateTaskDto, UpdateTaskDto, QueryTasksDto } from "./dto"; /** * Service for managing tasks */ @Injectable() export class TasksService { constructor( private readonly prisma: PrismaService, private readonly activityService: ActivityService ) {} /** * Create a new task */ async create(workspaceId: string, userId: string, createTaskDto: CreateTaskDto) { const data: any = { ...createTaskDto, workspaceId, creatorId: userId, status: createTaskDto.status || TaskStatus.NOT_STARTED, priority: createTaskDto.priority || createTaskDto.priority, sortOrder: createTaskDto.sortOrder ?? 0, metadata: createTaskDto.metadata || {}, }; // Set completedAt if status is COMPLETED if (data.status === TaskStatus.COMPLETED) { data.completedAt = new Date(); } const task = await this.prisma.task.create({ data, include: { assignee: { select: { id: true, name: true, email: true }, }, creator: { select: { id: true, name: true, email: true }, }, project: { select: { id: true, name: true, color: true }, }, }, }); // Log activity await this.activityService.logTaskCreated(workspaceId, userId, task.id, { title: task.title, }); return task; } /** * Get paginated tasks with filters */ async findAll(query: QueryTasksDto) { const page = query.page || 1; const limit = query.limit || 50; const skip = (page - 1) * limit; // Build where clause const where: any = { workspaceId: query.workspaceId, }; if (query.status) { where.status = query.status; } if (query.priority) { where.priority = query.priority; } if (query.assigneeId) { where.assigneeId = query.assigneeId; } if (query.projectId) { where.projectId = query.projectId; } if (query.parentId) { where.parentId = query.parentId; } if (query.dueDateFrom || query.dueDateTo) { where.dueDate = {}; if (query.dueDateFrom) { where.dueDate.gte = query.dueDateFrom; } if (query.dueDateTo) { where.dueDate.lte = query.dueDateTo; } } // Execute queries in parallel const [data, total] = await Promise.all([ this.prisma.task.findMany({ where, include: { assignee: { select: { id: true, name: true, email: true }, }, creator: { select: { id: true, name: true, email: true }, }, project: { select: { id: true, name: true, color: true }, }, }, orderBy: { createdAt: "desc", }, skip, take: limit, }), this.prisma.task.count({ where }), ]); return { data, meta: { total, page, limit, totalPages: Math.ceil(total / limit), }, }; } /** * Get a single task by ID */ async findOne(id: string, workspaceId: string) { const task = await this.prisma.task.findUnique({ where: { id, workspaceId, }, include: { assignee: { select: { id: true, name: true, email: true }, }, creator: { select: { id: true, name: true, email: true }, }, project: { select: { id: true, name: true, color: true }, }, subtasks: { include: { assignee: { select: { id: true, name: true, email: true }, }, }, }, }, }); if (!task) { throw new NotFoundException(`Task with ID ${id} not found`); } return task; } /** * Update a task */ async update( id: string, workspaceId: string, userId: string, updateTaskDto: UpdateTaskDto ) { // Verify task exists const existingTask = await this.prisma.task.findUnique({ where: { id, workspaceId }, }); if (!existingTask) { throw new NotFoundException(`Task with ID ${id} not found`); } const data: any = { ...updateTaskDto }; // Handle completedAt based on status changes if (updateTaskDto.status) { if ( updateTaskDto.status === TaskStatus.COMPLETED && existingTask.status !== TaskStatus.COMPLETED ) { data.completedAt = new Date(); } else if ( updateTaskDto.status !== TaskStatus.COMPLETED && existingTask.status === TaskStatus.COMPLETED ) { data.completedAt = null; } } const task = await this.prisma.task.update({ where: { id, workspaceId, }, data, include: { assignee: { select: { id: true, name: true, email: true }, }, creator: { select: { id: true, name: true, email: true }, }, project: { select: { id: true, name: true, color: true }, }, }, }); // Log activities await this.activityService.logTaskUpdated(workspaceId, userId, id, { changes: updateTaskDto, }); // Log completion if status changed to COMPLETED if ( updateTaskDto.status === TaskStatus.COMPLETED && existingTask.status !== TaskStatus.COMPLETED ) { await this.activityService.logTaskCompleted(workspaceId, userId, id); } // Log assignment if assigneeId changed if ( updateTaskDto.assigneeId !== undefined && updateTaskDto.assigneeId !== existingTask.assigneeId ) { await this.activityService.logTaskAssigned( workspaceId, userId, id, updateTaskDto.assigneeId || "" ); } return task; } /** * Delete a task */ async remove(id: string, workspaceId: string, userId: string) { // Verify task exists const task = await this.prisma.task.findUnique({ where: { id, workspaceId }, }); if (!task) { throw new NotFoundException(`Task with ID ${id} not found`); } await this.prisma.task.delete({ where: { id, workspaceId, }, }); // Log activity await this.activityService.logTaskDeleted(workspaceId, userId, id, { title: task.title, }); } }