import { Body, Controller, Delete, Get, Head, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import prisma from "../db"; import { notFoundError } from "../utils/error"; import { RequestDataStatus, TaskOrderStatus, TaskStatus } from "@prisma/client"; import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"]; function globalAllow(user: RequestWithUser["user"]) { const allowList = ["system", "head_of_admin"]; return allowList.some((v) => user.roles?.includes(v)); } const permissionCondCompany = createPermCondition((_) => true); const permissionCheck = createPermCheck(globalAllow); const permissionCheckCompany = createPermCheck((_) => true); @Route("/api/v1/task") @Tags("Task Order") export class TaskController extends Controller { @Get("stats") async getTaskOrderStats() { const task = await prisma.taskOrder.groupBy({ by: ["taskOrderStatus"], _count: true, }); return task.reduce>( (a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }), { [TaskOrderStatus.Pending]: 0, [TaskOrderStatus.InProgress]: 0, [TaskOrderStatus.Validate]: 0, [TaskOrderStatus.Complete]: 0, [TaskOrderStatus.Canceled]: 0, }, ); } @Get() @Security("keycloak") async getTaskOrderList( @Request() req: RequestWithUser, @Query() query: string = "", @Query() page = 1, @Query() pageSize = 30, @Query() assignedByUserId?: string, @Query() taskOrderStatus?: TaskOrderStatus, ) { return this.getTaskOrderListByCriteria( req, query, page, pageSize, assignedByUserId, taskOrderStatus, ); } @Post("list") @Security("keycloak") async getTaskOrderListByCriteria( @Request() req: RequestWithUser, @Query() query: string = "", @Query() page = 1, @Query() pageSize = 30, @Query() assignedByUserId?: string, @Query() taskOrderStatus?: TaskOrderStatus, @Body() body?: { code?: string[] }, ) { const [result, total] = await prisma.$transaction([ prisma.taskOrder.findMany({ where: { taskOrderStatus, registeredBranch: { OR: permissionCondCompany(req.user) }, taskList: assignedByUserId ? { some: { requestWorkStep: { responsibleUserId: assignedByUserId }, }, } : undefined, code: body?.code ? { in: body.code } : undefined, OR: [ { code: { contains: query, mode: "insensitive" } }, { taskName: { contains: query } }, { contactName: { contains: query } }, { contactTel: { contains: query } }, ], }, include: { institution: true, createdBy: true, }, }), prisma.taskOrder.count(), ]); return { result, total, page, pageSize }; } @Get("{taskId}") @Security("keycloak") async getTaskOrder( @Request() req: RequestWithUser, @Path() taskId: string, @Query() taskAssignedByUserId?: string, ) { const record = await prisma.taskOrder.findFirst({ where: { id: taskId, registeredBranch: { OR: permissionCondCompany(req.user) } }, include: { taskList: { where: { requestWorkStep: { responsibleUserId: taskAssignedByUserId }, }, include: { requestWorkStep: { include: { requestWork: { include: { request: { include: { employee: true, quotation: true, }, }, productService: { include: { service: true, work: true, product: true, }, }, }, }, }, }, }, }, institution: true, createdBy: true, }, }); if (!record) throw notFoundError("Task Order"); return record; } @Post() @Security("keycloak", MANAGE_ROLES) async createTaskOrderList( @Request() req: RequestWithUser, @Body() body: { taskName: string; contactName: string; contactTel: string; institutionId: string; registeredBranchId?: string; taskList: { requestWorkId: string; step: number }[]; }, ) { return await prisma.$transaction(async (tx) => { const last = await tx.runningNo.upsert({ where: { key: "TASK", }, create: { key: "TASK", value: 1, }, update: { value: { increment: 1 }, }, }); const current = new Date(); const year = `${current.getFullYear()}`.slice(-2).padStart(2, "0"); const month = `${current.getMonth() + 1}`.padStart(2, "0"); const code = `PO${year}${month}${last.value.toString().padStart(6, "0")}`; const { taskList, ...rest } = body; const userAffiliatedBranch = await tx.branch.findFirst({ include: branchRelationPermInclude(req.user), where: body.registeredBranchId ? { id: body.registeredBranchId } : { user: { some: { userId: req.user.sub } }, }, }); if (!userAffiliatedBranch) { throw new HttpError( HttpStatus.BAD_REQUEST, "You must be affilated with at least one branch or specify branch to be registered (System permission required).", "reqMinAffilatedBranch", ); } await permissionCheckCompany(req.user, userAffiliatedBranch); return await tx.taskOrder.create({ include: { taskList: { include: { requestWorkStep: { include: { requestWork: { include: { request: { include: { employee: true, quotation: true, }, }, productService: { include: { service: true, work: true, product: true, }, }, }, }, }, }, }, }, institution: true, createdBy: true, }, data: { ...rest, code, registeredBranchId: userAffiliatedBranch.id, createdByUserId: req.user.sub, taskList: { create: taskList }, }, }); }); } @Put("{taskId}") @Security("keycloak") async editTaskById( @Request() req: RequestWithUser, @Path() taskId: string, @Body() body: { taskName: string; taskOrderStatus: TaskOrderStatus; contactName: string; contactTel: string; institutionId: string; taskList: { requestWorkId: string; step: number }[]; }, ) { const record = await prisma.taskOrder.findFirst({ where: { id: taskId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user) }, taskList: { include: { requestWorkStep: { include: { requestWork: true }, }, }, }, institution: true, createdBy: true, }, }); if (!record) throw notFoundError("Task Order"); await permissionCheckCompany(req.user, record.registeredBranch); await prisma.taskOrder.update({ where: { id: taskId }, include: { taskList: { include: { requestWorkStep: { include: { requestWork: true, }, }, }, }, institution: true, registeredBranch: true, createdBy: true, }, data: { ...body, taskList: { deleteMany: record?.taskList.filter( (lhs) => !body.taskList.find( (rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step, ), ), createMany: { data: body.taskList, skipDuplicates: true, }, }, }, }); } @Delete("{taskId}") @Security("keycloak", MANAGE_ROLES) async deleteTask(@Request() req: RequestWithUser, @Path() taskId: string) { await prisma.$transaction(async (tx) => { let record = await tx.taskOrder.findFirst({ where: { id: taskId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user), }, }, }); if (!record) throw notFoundError("Task Order"); await permissionCheck(req.user, record.registeredBranch); }); } } @Route("/api/v1/task/{taskId}") @Tags("Task Order") export class TaskActionController extends Controller { @Post("accept") @Security("keycloak") async acceptTaskOrder(@Request() req: RequestWithUser, @Path() taskId: string) { const record = await prisma.taskOrder.findFirst({ include: { taskList: { orderBy: { step: "asc" }, }, }, where: { id: taskId }, }); if (!record) throw notFoundError("Task Order"); await prisma.$transaction([ prisma.taskOrder.update({ where: { id: taskId }, data: { taskOrderStatus: TaskOrderStatus.InProgress, }, }), prisma.task.updateMany({ where: { taskOrderId: taskId, requestWorkStep: { responsibleUserId: req.user.sub }, }, data: { taskStatus: TaskStatus.InProgress, }, }), prisma.requestData.updateMany({ where: { requestWork: { some: { stepStatus: { some: { task: { some: { taskOrderId: taskId } } }, }, }, }, }, data: { requestDataStatus: RequestDataStatus.InProgress }, }), ]); } @Post("submit") @Security("keycloak") async submitTaskOrder( @Request() req: RequestWithUser, @Path() taskId: string, @Query() submitUserId?: string, // for explicit ) { submitUserId = submitUserId ?? req.user.sub; const record = await prisma.taskOrder.findFirst({ where: { id: taskId } }); if (!record) throw notFoundError("Task Order"); await prisma.task.updateMany({ where: { taskOrderId: taskId, taskStatus: TaskStatus.Success, requestWorkStep: { responsibleUserId: submitUserId }, }, data: { taskStatus: TaskStatus.Validate, }, }); } @Post("complete") @Security("keycloak") async completeTaskOrder(@Request() req: RequestWithUser, @Path() taskId: string) {} } @Route("api/v1/task/{taskId}") @Tags("Task Order") export class TaskOrderAttachmentController extends Controller { private async checkPermission(user: RequestWithUser["user"], id: string) { const data = await prisma.taskOrder.findUnique({ include: { registeredBranch: { include: branchRelationPermInclude(user) } }, where: { id }, }); if (!data) throw notFoundError("Task Order"); await permissionCheck(user, data.registeredBranch); } @Get("attachment") @Security("keycloak") async listAttachment(@Request() req: RequestWithUser, @Path() taskId: string) { await this.checkPermission(req.user, taskId); return await listFile(fileLocation.task.attachment(taskId)); } @Get("attachment/{name}") @Security("keycloak") async getAttachment(@Path() taskId: string, @Path() name: string) { return await getFile(fileLocation.task.attachment(taskId, name)); } @Head("attachment/{name}") async headAttachment(@Path() taskId: string, @Path() name: string) { return await getPresigned("head", fileLocation.task.attachment(taskId, name)); } @Put("attachment/{name}") @Security("keycloak") async putAttachment( @Request() req: RequestWithUser, @Path() taskId: string, @Path() name: string, ) { await this.checkPermission(req.user, taskId); return await setFile(fileLocation.task.attachment(taskId, name)); } @Delete("attachment/{name}") @Security("keycloak") async delAttachment( @Request() req: RequestWithUser, @Path() taskId: string, @Path() name: string, ) { await this.checkPermission(req.user, taskId); return await deleteFile(fileLocation.task.attachment(taskId, name)); } }