import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import moment from 'moment'; import { FindOptionsWhere } from 'typeorm'; import { IJwtPayload } from '~/auth/interfaces'; import { OciService } from '~/document/services'; import { CreateTaskRequestDto, TasksFilterOptions, TaskSubmissionRequestDto } from '../dtos/request'; import { Task } from '../entities'; import { SubmissionStatus, TaskStatus } from '../enums'; import { TaskRepository } from '../repositories'; @Injectable() export class TaskService { private readonly logger = new Logger(TaskService.name); constructor(private readonly taskRepository: TaskRepository, private readonly ociService: OciService) {} async createTask(userId: string, body: CreateTaskRequestDto) { this.logger.log(`Creating task for user ${userId}`); if (moment(body.dueDate).isBefore(moment(body.startDate))) { this.logger.error(`Due date must be after start date`); throw new BadRequestException('TASK.DUE_DATE_BEFORE_START_DATE'); } if (moment(body.dueDate).isBefore(moment())) { this.logger.error(`Due date must be in the future`); throw new BadRequestException('TASK.DUE_DATE_IN_PAST'); } const task = await this.taskRepository.createTask(userId, body); this.logger.log(`Task ${task.id} created successfully`); return this.findTask({ id: task.id }); } async findTask(where: FindOptionsWhere) { this.logger.log(`Finding task with where ${JSON.stringify(where)}`); const task = await this.taskRepository.findTask(where); if (!task) { this.logger.error(`Task not found`); throw new BadRequestException('TASK.NOT_FOUND'); } await this.prepareTasksPictures([task]); this.logger.log(`Task found successfully`); return task; } async findTasks(user: IJwtPayload, query: TasksFilterOptions): Promise<[Task[], number]> { this.logger.log(`Finding tasks for user ${user.sub} and roles ${user.roles} and filters ${JSON.stringify(query)}`); const [tasks, count] = await this.taskRepository.findTasks(user, query); await this.prepareTasksPictures(tasks); this.logger.log(`Returning tasks for user ${user.sub}`); return [tasks, count]; } async submitTask(userId: string, taskId: string, body: TaskSubmissionRequestDto) { this.logger.log(`Submitting task ${taskId} for user ${userId}`); const task = await this.findTask({ id: taskId, assignedToId: userId }); if (task.status == TaskStatus.COMPLETED) { this.logger.error(`Task ${taskId} already completed`); throw new BadRequestException('TASK.ALREADY_COMPLETED'); } if (task.isProofRequired && !body.imageId) { this.logger.error(`Proof of completion is required for task ${taskId}`); throw new BadRequestException('TASK.PROOF_REQUIRED'); } await this.taskRepository.createSubmission(task, body); this.logger.log(`Task ${taskId} submitted successfully`); } async approveTaskSubmission(userId: string, taskId: string) { this.logger.log(`Approving task submission ${taskId} by user ${userId}`); const task = await this.findTask({ id: taskId, assignedById: userId }); if (!task.submission) { this.logger.error(`No submission found for task ${taskId}`); throw new BadRequestException('TASK.NO_SUBMISSION'); } if (task.submission.status == SubmissionStatus.APPROVED) { this.logger.error(`Submission already approved for task ${taskId}`); throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED'); } await this.taskRepository.approveSubmission(task.submission); this.logger.log(`Task submission ${taskId} approved successfully`); } async rejectTaskSubmission(userId: string, taskId: string) { this.logger.log(`Rejecting task submission ${taskId} by user ${userId}`); const task = await this.findTask({ id: taskId, assignedById: userId }); if (!task.submission) { this.logger.error(`No submission found for task ${taskId}`); throw new BadRequestException('TASK.NO_SUBMISSION'); } if (task.submission.status == SubmissionStatus.REJECTED) { this.logger.error(`Submission already rejected for task ${taskId}`); throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED'); } await this.taskRepository.rejectSubmission(task.submission); this.logger.log(`Task submission ${taskId} rejected successfully`); } async prepareTasksPictures(tasks: Task[]) { this.logger.log(`Preparing tasks pictures`); await Promise.all( tasks.map(async (task) => { const [imageUrl, submissionUrl, profilePictureUrl] = await Promise.all([ this.ociService.generatePreSignedUrl(task.image), this.ociService.generatePreSignedUrl(task.submission?.proofOfCompletion), this.ociService.generatePreSignedUrl(task.assignedTo.customer.profilePicture), ]); task.image.url = imageUrl; if (task.submission?.proofOfCompletion) { task.submission.proofOfCompletion.url = submissionUrl; } if (task.assignedTo.customer.profilePicture) { task.assignedTo.customer.profilePicture.url = profilePictureUrl; } }), ); } }