Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8533682303 | |||
| e5c14bf5cc | |||
| e310411a4d | |||
| 0cd5a6a0cc | |||
| da71c20d6d | |||
| 263ede68b2 | |||
| d9283e3a90 | |||
| 5964a9b20d | |||
| 5715cbe9b1 | |||
| 2590a222b4 | |||
| 16a0c14149 |
@@ -1,11 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Migrations1783244654421 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TaskmanagerAddingNewEnumValueForPermissionEnumType1783324039378 implements MigrationInterface {
|
||||
name = 'TaskmanagerAddingNewEnumValueForPermissionEnumType1783324039378'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TMAddingTaskPhasePermissionToPermissionEnumType1783335279945 implements MigrationInterface {
|
||||
name = 'TMAddingTaskPhasePermissionToPermissionEnumType1783335279945'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project', 'task_phase')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -920,11 +920,29 @@ export const enum WorkSpaceMessage {
|
||||
WORKSPACE_EXISTS = "فضای کار با این اسم وجود دارد!",
|
||||
WORKSPACE_NOT_FOUND = "فضای کار مورد نظر شما وجود ندارد!",
|
||||
USERS_DONT_BELONG_TO_THIS_WORKSPACE = "کاربران تعیین شده برای این فضای کاری نیستند!",
|
||||
WORKSPACE_SAME_NAME_EXISTS = "فضای کاری با این اسم از قبل وجود دارد!"
|
||||
WORKSPACE_SAME_NAME_EXISTS = "فضای کاری با این اسم از قبل وجود دارد!",
|
||||
WORKSPACE_ID_PARAM_NOT_EMPTY = "پارامتر شناسه فضای کار نباید خالی باشد!",
|
||||
WORKSPACE_ID_UUID = "شناسه فضای کار باید uuid باشد"
|
||||
}
|
||||
|
||||
export const enum ProjectMessage {
|
||||
PROJECT_EXISTS = "پروژه مورد نظر شما از قبل وجود دارد!",
|
||||
PROJECT_NOT_FOUND = "پروژه مورد نظر شما وجود ندارد!",
|
||||
PROJECT_CONTAINS_TASK_PHASES = "پروژه شامل فاز تسک است. لطفا ابتدا فاز تسک های این پروژه را حذف کنید."
|
||||
PROJECT_CONTAINS_TASK_PHASES = "پروژه شامل فاز تسک است. لطفا ابتدا فاز تسک های این پروژه را حذف کنید.",
|
||||
NOT_MEMBER_OF_PROJECT = "شما عضو این پروژه نیستید!"
|
||||
}
|
||||
|
||||
export const enum TaskPhaseMessage {
|
||||
TASK_PHASE_NOT_FOUND = "فاز تسک مورد نظر پیدا نشد!",
|
||||
TASK_PHASE_NOT_SAME_PROJECT = "فاز تسک جدید با فاز تسک قبلی در یک پروژه نیستند!"
|
||||
}
|
||||
|
||||
export const enum TaskMessage {
|
||||
TASK_NOT_FOUND = "تسک مورد نظر پیدا نشد!",
|
||||
USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER = "حداقل یکی از کاربران تعیین شده عضو پروژه نیستند!",
|
||||
USER_NOT_TASK_MEMBER = "شما عضو این تسک نیستید!"
|
||||
}
|
||||
|
||||
export const enum remarkMessage {
|
||||
REMARK_NOT_FOUND = "برچسب مورد نظر پیدا نشد!"
|
||||
}
|
||||
@@ -8,7 +8,11 @@ import { TMProject } from "../entities/project.entity";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Projects")
|
||||
@Controller("task-manager/projects")
|
||||
@@ -64,16 +68,27 @@ export class ProjectController {
|
||||
return this.projectService.remove(id);
|
||||
}
|
||||
|
||||
@Get("/workspace/:wsId")
|
||||
@PermissionsDec(PermissionEnum.PROJECT)
|
||||
@ApiOperation({ summary: "Get projects of a specific workspace" })
|
||||
@ApiParam({ name: "wsId", description: "Workspace ID" })
|
||||
@ApiResponse({ status: 200, description: "received projects successfully!" })
|
||||
@ApiResponse({ status: 404, description: "WorkSpace not found" })
|
||||
findByWorkspace(
|
||||
@Param("wsId") wsId: string,
|
||||
@Get("user/workspace/:id")
|
||||
@ApiOperation({ summary: "Get user projects!" })
|
||||
@ApiResponse({ status: 200, description: "Got user projects successfully!" })
|
||||
@ApiResponse({status: 400, description: "Bad Request!"})
|
||||
findUserProjects(
|
||||
@UserDec("id") userId: string,
|
||||
@Param() paramDto : ParamDto,
|
||||
@Query() pagination: PaginationDto,
|
||||
): Promise<{ projects: TMProject[]; count: number; paginate: true }> {
|
||||
return this.projectService.findByWorkspace(wsId, pagination);
|
||||
return this.projectService.findUserProjects(userId, paramDto.id, pagination);
|
||||
}
|
||||
|
||||
@Get("project/:id")
|
||||
@ApiOperation({summary: "Get project detail"})
|
||||
@ApiResponse({status: 200, description: "Got Project Detail Successfully!"})
|
||||
@ApiResponse({status: 400, description: "Bad request!"})
|
||||
@ApiResponse({status: 404, description: "The project was not found!"})
|
||||
getProjectDetail(@Param() paramDto: ParamDto,
|
||||
@UserDec('id') userId: string
|
||||
): Promise<TMProject | null> {
|
||||
return this.projectService.getProjectDetail(paramDto.id, userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,14 @@ import { RemarkService } from "../providers/remark.service";
|
||||
import { CreateRemarkDto } from "../dto/remark/create-remark.dto";
|
||||
import { UpdateRemarkDto } from "../dto/remark/update-remark.dto";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.REMARK)
|
||||
@ApiTags("Task Manager - Remarks")
|
||||
@Controller("task-manager/remarks")
|
||||
export class RemarkController {
|
||||
@@ -25,7 +32,7 @@ export class RemarkController {
|
||||
@ApiResponse({ status: 200, description: "Remark found", type: TMRemark })
|
||||
@ApiResponse({ status: 404, description: "Remark not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMRemark> {
|
||||
return this.remarkService.findOne(id);
|
||||
return this.remarkService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
||||
@@ -4,7 +4,14 @@ import { TaskPhaseService } from "../providers/task-phase.service";
|
||||
import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto";
|
||||
import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TASK_PHASE)
|
||||
@ApiTags("Task Manager - Task Phases")
|
||||
@Controller("task-manager/task-phases")
|
||||
export class TaskPhaseController {
|
||||
@@ -25,7 +32,7 @@ export class TaskPhaseController {
|
||||
@ApiResponse({ status: 200, description: "TaskPhase found", type: TMTaskPhase })
|
||||
@ApiResponse({ status: 404, description: "TaskPhase not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMTaskPhase> {
|
||||
return this.taskPhaseService.findOne(id);
|
||||
return this.taskPhaseService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
||||
@@ -5,13 +5,22 @@ import { CreateTaskDto } from '../dto/task/create-task.dto';
|
||||
import { UpdateTaskDto } from '../dto/task/update-task.dto';
|
||||
import { ChangeTaskPhaseDto } from '../dto/task/change-task-phase.dto';
|
||||
import { TMTask } from '../entities/task.entity';
|
||||
import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
|
||||
import { AdminRoute } from '../../../common/decorators/admin.decorator';
|
||||
import { UserDec } from '../../../common/decorators/user.decorator';
|
||||
import { ParamDto } from '../../../common/DTO/param.dto';
|
||||
import { PermissionsDec } from '../../../common/decorators/permission.decorator';
|
||||
import { PermissionEnum } from '../../users/enums/permission.enum';
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags('Task Manager - Tasks')
|
||||
@Controller('task-manager/tasks')
|
||||
export class TaskController {
|
||||
constructor(private readonly taskService: TaskService) {}
|
||||
|
||||
@Get()
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Get all tasks, optionally filtered by phase' })
|
||||
@ApiQuery({ name: 'taskPhaseId', required: false, description: 'Filter tasks by task phase ID' })
|
||||
@ApiResponse({ status: 200, description: 'List of tasks', type: [TMTask] })
|
||||
@@ -21,15 +30,17 @@ export class TaskController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Get a task by ID' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task found', type: TMTask })
|
||||
@ApiResponse({ status: 404, description: 'Task not found' })
|
||||
findOne(@Param('id') id: string): Promise<TMTask> {
|
||||
return this.taskService.findOne(id);
|
||||
return this.taskService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Create a new task' })
|
||||
@ApiResponse({ status: 201, description: 'Task created', type: TMTask })
|
||||
@ApiResponse({ status: 404, description: 'TaskPhase not found' })
|
||||
@@ -38,6 +49,7 @@ export class TaskController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Update a task' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task updated', type: TMTask })
|
||||
@@ -47,6 +59,7 @@ export class TaskController {
|
||||
}
|
||||
|
||||
@Patch(':id/change-phase')
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Move a task to a different phase within the same project' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task phase changed', type: TMTask })
|
||||
@@ -57,6 +70,7 @@ export class TaskController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@PermissionsDec(PermissionEnum.TASK)
|
||||
@ApiOperation({ summary: 'Delete a task' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task deleted' })
|
||||
@@ -64,4 +78,16 @@ export class TaskController {
|
||||
remove(@Param('id') id: string): Promise<void> {
|
||||
return this.taskService.remove(id);
|
||||
}
|
||||
|
||||
@Get('detail/:id')
|
||||
@ApiOperation({summary: 'Get Task Detail'})
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task detail received!' })
|
||||
@ApiResponse({ status: 404, description: 'Task not found' })
|
||||
getTaskDetail(
|
||||
@UserDec('id') userId: string,
|
||||
@Param() paramDto: ParamDto
|
||||
): Promise<TMTask | null> {
|
||||
return this.taskService.getTaskDetail(userId, paramDto.id);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export class WorkspaceController {
|
||||
return this.workspaceService.remove(id);
|
||||
}
|
||||
|
||||
@Get('/byuser')
|
||||
@Get('byuser')
|
||||
@ApiOperation({summary: 'Get User WorkSpaces'})
|
||||
@ApiResponse({ status: 200, description: 'Got Workspaces successfully!' })
|
||||
@ApiResponse({ status: 404, description: 'Bad Request!' })
|
||||
|
||||
@@ -57,4 +57,9 @@ export class TMTask extends BaseEntity {
|
||||
inverseJoinColumn: { name: "userId", referencedColumnName: "id" },
|
||||
})
|
||||
users: User[];
|
||||
|
||||
// deriven(calculated) attributes. not database columns
|
||||
attachmentCount?: number;
|
||||
checkListItemCount?: number;
|
||||
completedCheckListItemCount?: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { ProjectRepository } from "../repositories/project.repository";
|
||||
@@ -42,32 +42,6 @@ export class ProjectService {
|
||||
return { projects, count, paginate: true };
|
||||
}
|
||||
|
||||
async findByWorkspace(workspaceId: string, pagination: PaginationDto): Promise<{ projects: TMProject[]; count: number; paginate: true }> {
|
||||
await this.workspaceService.findOneOrFail(workspaceId);
|
||||
|
||||
const page = pagination.page ?? 1;
|
||||
const limit = pagination.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [projects, count] = await this.projectRepository.findAndCount({
|
||||
where: { workspaceId },
|
||||
relations: {
|
||||
users: true,
|
||||
},
|
||||
select: {
|
||||
users: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
});
|
||||
|
||||
return { projects, count, paginate: true };
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<TMProject> {
|
||||
const project = await this.projectRepository.findOne({ where: { id } });
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
@@ -140,8 +114,8 @@ export class ProjectService {
|
||||
},
|
||||
});
|
||||
|
||||
if(!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
|
||||
const taskPhases = project.taskPhases;
|
||||
if (taskPhases !== undefined && taskPhases.length) {
|
||||
throw new BadRequestException(ProjectMessage.PROJECT_CONTAINS_TASK_PHASES);
|
||||
@@ -149,4 +123,35 @@ export class ProjectService {
|
||||
await this.projectRepository.delete(id);
|
||||
return project;
|
||||
}
|
||||
|
||||
async findUserProjects(
|
||||
userId: string,
|
||||
wsId: string,
|
||||
pagination: PaginationDto,
|
||||
): Promise<{ projects: TMProject[]; count: number; paginate: true }> {
|
||||
const page = pagination.page ?? 1;
|
||||
const limit = pagination.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [projects, count] = await this.projectRepository.findUserProjects(userId, wsId, skip, limit);
|
||||
|
||||
return { projects, count, paginate: true };
|
||||
}
|
||||
|
||||
async getProjectDetail(projectId: string, userId: string): Promise<TMProject | null> {
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: { id: projectId },
|
||||
relations: {
|
||||
users: true,
|
||||
},
|
||||
});
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
|
||||
const isUserMember = project.users.some(user => user.id === userId);
|
||||
if(!isUserMember) throw new ForbiddenException(ProjectMessage.NOT_MEMBER_OF_PROJECT);
|
||||
|
||||
const projectDetail = await this.projectRepository.getProjectDetail(projectId);
|
||||
|
||||
return projectDetail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { RemarkRepository } from "../repositories/remark.repository";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
import { CreateRemarkDto } from "../dto/remark/create-remark.dto";
|
||||
import { UpdateRemarkDto } from "../dto/remark/update-remark.dto";
|
||||
import { remarkMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from './task.service';
|
||||
|
||||
@Injectable()
|
||||
export class RemarkService {
|
||||
constructor(
|
||||
private readonly remarkRepository: RemarkRepository,
|
||||
private readonly taskRepository: TaskRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMRemark[]> {
|
||||
return this.remarkRepository.findAll();
|
||||
return this.remarkRepository.find({ relations: { task: true } });
|
||||
}
|
||||
|
||||
findByTask(taskId: string): Promise<TMRemark[]> {
|
||||
return this.remarkRepository.findByTask(taskId);
|
||||
return this.remarkRepository.find({
|
||||
where: { taskId },
|
||||
relations: {
|
||||
task: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMRemark> {
|
||||
const remark = await this.remarkRepository.findOneById(id);
|
||||
if (!remark) throw new NotFoundException(`Remark #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMRemark> {
|
||||
const remark = await this.remarkRepository.findOne({ where: { id } });
|
||||
if (!remark) throw new NotFoundException(remarkMessage.REMARK_NOT_FOUND);
|
||||
return remark;
|
||||
}
|
||||
|
||||
async create(dto: CreateRemarkDto): Promise<TMRemark> {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const remark = this.remarkRepository.create(dto);
|
||||
return this.remarkRepository.save(remark);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateRemarkDto): Promise<TMRemark> {
|
||||
const remark = await this.findOne(id);
|
||||
const remark = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(remark, dto);
|
||||
@@ -47,7 +51,7 @@ export class RemarkService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.remarkRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { TaskPhaseRepository } from "../repositories/task-phase.repository";
|
||||
import { ProjectRepository } from "../repositories/project.repository";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto";
|
||||
import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto";
|
||||
import { TaskPhaseMessage } from "../../../common/enums/message.enum";
|
||||
import { ProjectService } from "./project.service";
|
||||
|
||||
@Injectable()
|
||||
export class TaskPhaseService {
|
||||
constructor(
|
||||
private readonly taskPhaseRepository: TaskPhaseRepository,
|
||||
private readonly projectRepository: ProjectRepository,
|
||||
private readonly projectService: ProjectService
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMTaskPhase[]> {
|
||||
return this.taskPhaseRepository.findAll();
|
||||
return this.taskPhaseRepository.find();
|
||||
}
|
||||
|
||||
findByProject(projectId: string): Promise<TMTaskPhase[]> {
|
||||
return this.taskPhaseRepository.findByProject(projectId);
|
||||
return this.taskPhaseRepository.find({
|
||||
where: {projectId}
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(id);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOne({where: {id}});
|
||||
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
|
||||
return taskPhase;
|
||||
}
|
||||
|
||||
async create(dto: CreateTaskPhaseDto): Promise<TMTaskPhase> {
|
||||
const project = await this.projectRepository.findOneById(dto.projectId);
|
||||
if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`);
|
||||
|
||||
await this.projectService.findOneOrFail(dto.projectId);
|
||||
const taskPhase = this.taskPhaseRepository.create(dto);
|
||||
return this.taskPhaseRepository.save(taskPhase);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskPhaseDto): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.findOne(id);
|
||||
const taskPhase = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.projectId) {
|
||||
const project = await this.projectRepository.findOneById(dto.projectId);
|
||||
if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`);
|
||||
await this.projectService.findOneOrFail(dto.projectId);
|
||||
}
|
||||
|
||||
Object.assign(taskPhase, dto);
|
||||
@@ -47,7 +47,7 @@ export class TaskPhaseService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskPhaseRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { TaskRepository } from '../repositories/task.repository';
|
||||
import { TaskPhaseRepository } from '../repositories/task-phase.repository';
|
||||
import { TMTask } from '../entities/task.entity';
|
||||
import { CreateTaskDto } from '../dto/task/create-task.dto';
|
||||
import { UpdateTaskDto } from '../dto/task/update-task.dto';
|
||||
import { ChangeTaskPhaseDto } from '../dto/task/change-task-phase.dto';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMTask } from "../entities/task.entity";
|
||||
import { CreateTaskDto } from "../dto/task/create-task.dto";
|
||||
import { UpdateTaskDto } from "../dto/task/update-task.dto";
|
||||
import { ChangeTaskPhaseDto } from "../dto/task/change-task-phase.dto";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { TaskMessage, TaskPhaseMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskPhaseRepository } from "../repositories/task-phase.repository";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskService {
|
||||
@@ -18,25 +20,51 @@ export class TaskService {
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMTask[]> {
|
||||
return this.taskRepository.findAll();
|
||||
async findAll(): Promise<TMTask[]> {
|
||||
return this.taskRepository.find({
|
||||
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
}
|
||||
|
||||
findByPhase(taskPhaseId: string): Promise<TMTask[]> {
|
||||
return this.taskRepository.findByPhase(taskPhaseId);
|
||||
async findByPhase(taskPhaseId: string): Promise<TMTask[]> {
|
||||
return this.taskRepository.find({ where: { taskPhaseId } });
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMTask> {
|
||||
const task = await this.taskRepository.findOneById(id);
|
||||
if (!task) throw new NotFoundException(`Task #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMTask> {
|
||||
const task = await this.taskRepository.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
taskPhase: true,
|
||||
users: true
|
||||
},
|
||||
});
|
||||
if (!task) throw new NotFoundException(TaskMessage.TASK_NOT_FOUND);
|
||||
return task;
|
||||
}
|
||||
|
||||
async findTaskPhaseOrFail(taskPhaseId: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOne({
|
||||
where: { id: taskPhaseId },
|
||||
relations: {
|
||||
project: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
|
||||
|
||||
return taskPhase;
|
||||
}
|
||||
|
||||
async create(dto: CreateTaskDto): Promise<TMTask> {
|
||||
const { userIds, startDate, endDate, ...rest } = dto;
|
||||
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (userIds?.length) {
|
||||
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
||||
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
||||
}
|
||||
|
||||
const task = this.taskRepository.create({
|
||||
...rest,
|
||||
@@ -52,12 +80,16 @@ export class TaskService {
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskDto): Promise<TMTask> {
|
||||
const task = await this.findOne(id);
|
||||
const task = await this.findOneOrFail(id);
|
||||
const { userIds, startDate, endDate, ...rest } = dto;
|
||||
|
||||
if (dto.taskPhaseId) {
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (userIds?.length) {
|
||||
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
||||
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(task, {
|
||||
@@ -67,24 +99,19 @@ export class TaskService {
|
||||
});
|
||||
|
||||
if (userIds) {
|
||||
task.users = userIds.length
|
||||
? await this.userRepository.findBy({ id: In(userIds) })
|
||||
: [];
|
||||
task.users = userIds.length ? await this.userRepository.findBy({ id: In(userIds) }) : [];
|
||||
}
|
||||
|
||||
return this.taskRepository.save(task);
|
||||
}
|
||||
|
||||
async changePhase(id: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
|
||||
const task = await this.findOne(id);
|
||||
const task = await this.findOneOrFail(id);
|
||||
|
||||
const newPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!newPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
const newPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (task.taskPhase.projectId !== newPhase.projectId) {
|
||||
throw new BadRequestException(
|
||||
`TaskPhase #${dto.taskPhaseId} does not belong to the same project as the current phase`,
|
||||
);
|
||||
throw new BadRequestException(TaskPhaseMessage.TASK_PHASE_NOT_SAME_PROJECT);
|
||||
}
|
||||
|
||||
task.taskPhaseId = dto.taskPhaseId;
|
||||
@@ -94,7 +121,17 @@ export class TaskService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
async getTaskDetail(userId: string, taskId: string): Promise<TMTask | null> {
|
||||
const task = await this.findOneOrFail(taskId);
|
||||
|
||||
const isTaskMember = task.users.some((user) => user.id === userId);
|
||||
if(!isTaskMember) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
|
||||
|
||||
const taskDetail = await this.taskRepository.getTaskDetail(taskId);
|
||||
return taskDetail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,56 @@ export class ProjectRepository extends Repository<TMProject> {
|
||||
) {
|
||||
super(projectRepository.target, projectRepository.manager, projectRepository.queryRunner);
|
||||
}
|
||||
|
||||
async findUserProjects(userId: string, wsId: string, skip?: number, take?: number): Promise<[TMProject[], number]> {
|
||||
return this.createQueryBuilder('project')
|
||||
.innerJoin('project.workspace', 'workspace')
|
||||
.innerJoin('project.users', 'user')
|
||||
.where('workspace.id = :wsId', {wsId})
|
||||
.andWhere('user.id = :userId', {userId})
|
||||
.skip(skip ?? 0)
|
||||
.take(take ?? 10)
|
||||
.getManyAndCount();
|
||||
}
|
||||
|
||||
async getProjectDetail(projectId: string): Promise<TMProject | null> {
|
||||
return this.createQueryBuilder("project")
|
||||
.leftJoinAndSelect("project.taskPhases", "taskPhase")
|
||||
.leftJoinAndSelect("taskPhase.tasks", "task")
|
||||
.leftJoinAndSelect("task.remarks", "remark")
|
||||
|
||||
.loadRelationCountAndMap(
|
||||
"task.attachmentCount",
|
||||
"task.attachments",
|
||||
)
|
||||
|
||||
.loadRelationCountAndMap(
|
||||
"task.checkListItemCount",
|
||||
"task.checkListItems",
|
||||
)
|
||||
|
||||
.loadRelationCountAndMap(
|
||||
"task.completedCheckListItemCount",
|
||||
"task.checkListItems",
|
||||
"completedCheckListItems",
|
||||
(qb) => qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
|
||||
)
|
||||
|
||||
.where("project.id = :projectId", { projectId })
|
||||
|
||||
.select([
|
||||
"project.id",
|
||||
"project.name",
|
||||
"taskPhase.id",
|
||||
"taskPhase.name",
|
||||
"task.id",
|
||||
"task.title",
|
||||
"task.startDate",
|
||||
"task.endDate",
|
||||
"remark.title",
|
||||
"remark.color",
|
||||
])
|
||||
|
||||
.getOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,10 @@ import { Repository } from "typeorm";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
|
||||
@Injectable()
|
||||
export class RemarkRepository {
|
||||
export class RemarkRepository extends Repository<TMRemark>{
|
||||
constructor(
|
||||
@InjectRepository(TMRemark)
|
||||
private readonly repository: Repository<TMRemark>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["task"],
|
||||
});
|
||||
}
|
||||
|
||||
findByTask(taskId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskId },
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["task"],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMRemark>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(remark: TMRemark) {
|
||||
return this.repository.save(remark);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMRemark>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMRemark) remarkRepository: Repository<TMRemark>,
|
||||
) {
|
||||
super(remarkRepository.target, remarkRepository.manager, remarkRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,47 +4,10 @@ import { Repository } from "typeorm";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskPhaseRepository {
|
||||
export class TaskPhaseRepository extends Repository<TMTaskPhase> {
|
||||
constructor(
|
||||
@InjectRepository(TMTaskPhase)
|
||||
private readonly repository: Repository<TMTaskPhase>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["project", "tasks"],
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findByProject(projectId: string) {
|
||||
return this.repository.find({
|
||||
where: { projectId },
|
||||
relations: ["tasks"],
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["project", "tasks"],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMTaskPhase>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(taskPhase: TMTaskPhase) {
|
||||
return this.repository.save(taskPhase);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMTaskPhase>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMTaskPhase) taskPhaseRepository: Repository<TMTaskPhase>,
|
||||
) {
|
||||
super(taskPhaseRepository.target, taskPhaseRepository.manager, taskPhaseRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,45 +4,40 @@ import { Repository } from "typeorm";
|
||||
import { TMTask } from "../entities/task.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskRepository {
|
||||
constructor(
|
||||
@InjectRepository(TMTask)
|
||||
private readonly repository: Repository<TMTask>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
export class TaskRepository extends Repository<TMTask> {
|
||||
constructor(@InjectRepository(TMTask) taskRepository: Repository<TMTask>) {
|
||||
super(taskRepository.target, taskRepository.manager, taskRepository.queryRunner);
|
||||
}
|
||||
|
||||
findByPhase(taskPhaseId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskPhaseId },
|
||||
relations: ["remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
}
|
||||
async getTaskDetail(taskId: string): Promise<TMTask | null> {
|
||||
return this.createQueryBuilder("task")
|
||||
.leftJoinAndSelect("task.remarks", "remark")
|
||||
.leftJoinAndSelect("task.attachments", "attachment")
|
||||
.leftJoinAndSelect("task.checkListItems", "checkListItem")
|
||||
.leftJoinAndSelect("task.users", "user")
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
}
|
||||
.loadRelationCountAndMap("task.checkListItemCount", "task.checkListItems")
|
||||
|
||||
create(data: Partial<TMTask>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
.loadRelationCountAndMap("task.completedCheckListItemCount", "task.checkListItems", "completedCheckListItems", (qb) =>
|
||||
qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
|
||||
)
|
||||
|
||||
save(task: TMTask) {
|
||||
return this.repository.save(task);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMTask>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
.select(["remark.id",
|
||||
"remark.title",
|
||||
"remark.color",
|
||||
"checkListItem.id",
|
||||
"checkListItem.title",
|
||||
"checkListItem.isDone",
|
||||
"attachment.id",
|
||||
"attachment.title",
|
||||
"attachment.type",
|
||||
"attachment.file",
|
||||
|
||||
"user.id",
|
||||
"user.firstName",
|
||||
"user.lastName"
|
||||
])
|
||||
.where("task.id = :taskId", { taskId })
|
||||
.getOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,8 @@ export enum PermissionEnum {
|
||||
DMAIL = 'dmail',
|
||||
RESELLER="reseller",
|
||||
WORKSPACE="workspace",
|
||||
PROJECT="project"
|
||||
PROJECT="project",
|
||||
TASK_PHASE="task_phase",
|
||||
TASK="task",
|
||||
REMARK="remark"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user