import { Prisma, RequestDataStatus, RequestWorkStatus, TaskStatus } from "@prisma/client"; import { Body, Controller, Delete, Get, Head, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import { RequestWithUser } from "../interfaces/user"; import prisma from "../db"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; import { queryOrNot } from "../utils/relation"; import { notFoundError } from "../utils/error"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; // User in company can edit. const permissionCheck = createPermCheck((_) => true); // User in company can see. const permissionCond = createPermCondition((_) => true); @Route("/api/v1/request-data") @Tags("Request List") export class RequestDataController extends Controller { @Get("stats") @Security("keycloak") async getRequestDataStats(@Request() req: RequestWithUser) { const where = { quotation: { customerBranch: { customer: { registeredBranch: { OR: permissionCond(req.user) }, }, }, }, } satisfies Prisma.RequestDataWhereInput; const list = await prisma.requestData.groupBy({ _count: true, by: "requestDataStatus", where: where, }); return list.reduce>( (a, c) => Object.assign(a, { [c.requestDataStatus]: c._count }), { [RequestDataStatus.Pending]: 0, [RequestDataStatus.InProgress]: 0, [RequestDataStatus.Completed]: 0, [RequestDataStatus.Canceled]: 0, }, ); } @Get() @Security("keycloak") async getRequestDataList( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() query: string = "", @Query() requestDataStatus?: RequestDataStatus, ) { const where = { OR: queryOrNot(query, [ { quotation: { code: { contains: query, mode: "insensitive" } } }, { quotation: { workName: { contains: query } } }, { quotation: { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, { customerName: { contains: query } }, { firstName: { contains: query } }, { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, ], }, }, employee: { OR: [ { employeePassport: { some: { number: { contains: query } }, }, }, { code: { contains: query, mode: "insensitive" } }, { firstName: { contains: query } }, { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, ], }, }, ]), requestDataStatus, quotation: { customerBranch: { customer: { registeredBranch: { OR: permissionCond(req.user) }, }, }, }, } satisfies Prisma.RequestDataWhereInput; const [result, total] = await prisma.$transaction([ prisma.requestData.findMany({ where, include: { quotation: { include: { customerBranch: { include: { customer: true }, }, }, }, employee: { include: { employeePassport: { orderBy: { expireDate: "desc" }, }, }, }, }, take: pageSize, skip: (page - 1) * pageSize, }), prisma.requestData.count({ where }), ]); return { result, page, pageSize, total }; } @Get("{requestDataId}") @Security("keycloak") async getRequestData(@Path() requestDataId: string) { return await prisma.requestData.findFirst({ where: { id: requestDataId }, include: { quotation: { include: { customerBranch: { include: { customer: true } }, invoice: { include: { installments: true, payment: true, }, }, createdBy: true, }, }, employee: { include: { employeePassport: { orderBy: { expireDate: "desc" }, }, }, }, }, }); } } @Route("/api/v1/request-data/{requestDataId}") @Tags("Request List") export class RequestDataActionController extends Controller { @Post("cancel") @Security("keycloak") async cancelRequestData(@Path() requestDataId: string) { await prisma.$transaction([ prisma.requestData.update({ where: { id: requestDataId }, data: { requestDataStatus: RequestDataStatus.Canceled, }, }), prisma.requestWorkStepStatus.updateMany({ where: { requestWork: { requestDataId } }, data: { workStatus: RequestWorkStatus.Canceled, }, }), prisma.taskOrder.updateMany({ where: { taskList: { every: { taskStatus: RequestWorkStatus.Canceled }, }, }, data: { taskOrderStatus: TaskStatus.Canceled }, }), ]); } } @Route("/api/v1/request-work") @Tags("Request List") export class RequestListController extends Controller { @Get() @Security("keycloak") async getRequestWork( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() requestDataId?: string, @Query() workStatus?: RequestWorkStatus, @Query() readyToTask?: boolean, ) { let statusCondition: Prisma.RequestWorkWhereInput["stepStatus"] = {}; if (readyToTask) { statusCondition = { some: { OR: [ { workStatus: RequestWorkStatus.Ready }, { task: { some: { taskStatus: TaskStatus.Redo } }, }, ], }, }; } else { statusCondition = { some: { workStatus }, }; } const where = { stepStatus: readyToTask || workStatus ? statusCondition : undefined, request: { id: requestDataId, quotation: { customerBranch: { customer: { registeredBranch: { OR: permissionCond(req.user) }, }, }, }, }, } satisfies Prisma.RequestWorkWhereInput; const [result, total] = await prisma.$transaction([ prisma.requestWork.findMany({ where, include: { request: { include: { quotation: true, employee: true, }, }, stepStatus: true, productService: { include: { service: true, work: true, product: { include: { document: true }, }, }, }, }, take: pageSize, skip: (page - 1) * pageSize, }), prisma.requestWork.count({ where }), ]); return { result: result.map((v) => { return Object.assign(v, { productService: Object.assign(v.productService, { product: Object.assign(v.productService.product, { document: v.productService.product.document.map((doc) => doc.name), }), }), }); }), page, pageSize, total, }; } @Get("{requestWorkId}") @Security("keycloak") async getRequestWorkById(@Path() requestWorkId: string) { const record = await prisma.requestWork.findFirst({ include: { request: { include: { quotation: true, employee: true, }, }, stepStatus: true, productService: { include: { service: { include: { workflow: true }, }, work: true, product: { include: { document: true, }, }, }, }, }, where: { id: requestWorkId }, }); if (!record) throw notFoundError("Request Work"); return Object.assign(record, { productService: Object.assign(record.productService, { product: Object.assign(record.productService.product, { document: record.productService.product.document.map((doc) => doc.name), }), }), }); } @Put("{requestWorkId}") @Security("keycloak") async updateRequestWorkById( @Request() req: RequestWithUser, @Path() requestWorkId: string, @Body() payload: { attributes: Record }, ) { const record = await prisma.requestWork.update({ include: { request: { include: { quotation: true, employee: true, }, }, stepStatus: true, productService: { include: { service: true, work: true, product: { include: { document: true, }, }, }, }, }, where: { id: requestWorkId }, data: { attributes: payload.attributes }, }); return record; } @Put("{requestWorkId}/step-status/{step}") @Security("keycloak") async updateRequestWorkStepStatus( @Path() requestWorkId: string, @Path() step: number, @Body() payload: { workStatus?: RequestWorkStatus; attributes?: Record; customerDuty?: boolean | null; customerDutyCost?: number | null; companyDuty?: boolean | null; companyDutyCost?: number | null; individualDuty?: boolean | null; individualDutyCost?: number | null; responsibleUserLocal?: boolean | null; responsibleUserId?: string | null; }, @Query() successAll?: boolean, ) { if (!payload.responsibleUserId) payload.responsibleUserId = undefined; const record = await prisma.requestWorkStepStatus.upsert({ include: { requestWork: true, }, where: { step_requestWorkId: { step: step, requestWorkId, }, }, create: { ...payload, step: step, requestWorkId, }, update: payload, }); switch (payload.workStatus) { case "InProgress": case "Waiting": case "Validate": case "Completed": case "Ended": await prisma.requestData.update({ where: { id: record.requestWork.requestDataId, }, data: { requestDataStatus: "InProgress" }, }); break; } if (successAll && (payload.workStatus === "Completed" || payload.workStatus === "Ended")) { await prisma.requestData.update({ where: { id: record.requestWork.requestDataId, }, data: { requestDataStatus: "Completed" }, }); } return record; } } @Route("api/v1/request-work/{requestId}/step-status/{step}") @Tags("Request List") export class RequestListFileController extends Controller { private async checkPermission(user: RequestWithUser["user"], id: string) { const data = await prisma.requestWork.findUnique({ where: { id }, include: { request: { include: { quotation: { include: { registeredBranch: { include: branchRelationPermInclude(user) } }, }, }, }, }, }); if (!data) throw notFoundError("Request Work"); await permissionCheck(user, data.request.quotation.registeredBranch); } @Get("attachment") @Security("keycloak") async listAttachment( @Request() req: RequestWithUser, @Path() requestId: string, @Path() step: number, ) { await this.checkPermission(req.user, requestId); return await listFile(fileLocation.request.attachment(requestId, step)); } @Get("attachment/{name}") @Security("keycloak") async getAttachment( @Request() req: RequestWithUser, @Path() requestId: string, @Path() step: number, @Path() name: string, ) { await this.checkPermission(req.user, requestId); return await getFile(fileLocation.request.attachment(requestId, step, name)); } @Head("attachment/{name}") @Security("keycloak") async headAttachment( @Request() req: RequestWithUser, @Path() requestId: string, @Path() step: number, @Path() name: string, ) { await this.checkPermission(req.user, requestId); return await getPresigned("head", fileLocation.request.attachment(requestId, step, name)); } @Put("attachment/{name}") @Security("keycloak") async putAttachment( @Request() req: RequestWithUser, @Path() requestId: string, @Path() step: number, @Path() name: string, ) { await this.checkPermission(req.user, requestId); return await setFile(fileLocation.request.attachment(requestId, step, name)); } @Delete("attachment/{name}") @Security("keycloak") async delAttachment( @Request() req: RequestWithUser, @Path() requestId: string, @Path() step: number, @Path() name: string, ) { await this.checkPermission(req.user, requestId); return await deleteFile(fileLocation.request.attachment(requestId, step, name)); } }