import { Body, Controller, Delete, Get, Put, Path, Post, Query, Request, Route, Security, Tags, } from "tsoa"; import { Prisma, Status } from "@prisma/client"; import prisma from "../db"; import { RequestWithUser } from "../interfaces/user"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { isSystem } from "../utils/keycloak"; type ProductGroupCreate = { name: string; detail: string; remark: string; status?: Status; registeredBranchId?: string; }; type ProductGroupUpdate = { name?: string; detail?: string; remark?: string; status?: "ACTIVE" | "INACTIVE"; registeredBranchId?: string; }; const MANAGE_ROLES = [ "system", "head_of_admin", "admin", "head_of_account", "account", "head_of_sale", ]; function globalAllow(user: RequestWithUser["user"]) { const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; return allowList.some((v) => user.roles?.includes(v)); } async function permissionCheck(user: RequestWithUser["user"], branchId: string) { const record = await prisma.branch.findUnique({ include: { headOffice: { include: { branch: { where: { user: { some: { userId: user.sub } } } }, user: { where: { userId: user.sub } }, }, }, user: { where: { userId: user.sub } }, }, where: { id: branchId }, }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); } if (!isSystem(user)) { if (!globalAllow(user) && record.user.length === 0) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } else { if ( (record.user.length === 0 && !record.headOffice) || (record.headOffice && record.headOffice.user.length === 0 && record.headOffice.branch.length === 0) ) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } } } return record; } @Route("api/v1/product-group") @Tags("Product Group") export class ProductGroup extends Controller { @Get("stats") @Security("keycloak") async getProductGroupStats() { return await prisma.productGroup.count(); } @Get() @Security("keycloak") async getProductGroup( @Request() req: RequestWithUser, @Query() query: string = "", @Query() status?: Status, @Query() page: number = 1, @Query() pageSize: number = 30, ) { const filterStatus = (val?: Status) => { if (!val) return {}; return val !== Status.CREATED && val !== Status.ACTIVE ? { status: val } : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; }; const where = { OR: [ { name: { contains: query }, ...filterStatus(status) }, { detail: { contains: query }, ...filterStatus(status) }, ], AND: [ { registeredBranch: isSystem(req.user) ? undefined : { OR: [ { user: { some: { userId: req.user.sub } }, }, { branch: globalAllow(req.user) ? { some: { user: { some: { userId: req.user.sub } } } } : undefined, }, { headOffice: globalAllow(req.user) ? { branch: { some: { user: { some: { userId: req.user.sub } } } } } : undefined, }, { headOffice: globalAllow(req.user) ? { user: { some: { userId: req.user.sub } } } : undefined, }, ], }, }, ], } satisfies Prisma.ProductGroupWhereInput; const [result, total] = await prisma.$transaction([ prisma.productGroup.findMany({ include: { _count: { select: { service: true, product: true, }, }, createdBy: true, updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, take: pageSize, skip: (page - 1) * pageSize, }), prisma.productGroup.count({ where }), ]); return { result, page, pageSize, total, }; } @Get("{groupId}") @Security("keycloak") async getProductGroupById(@Path() groupId: string) { const record = await prisma.productGroup.findFirst({ where: { id: groupId }, }); if (!record) throw new HttpError( HttpStatus.NOT_FOUND, "Product group cannot be found.", "productGroupNotFound", ); return record; } @Post() @Security("keycloak", MANAGE_ROLES) async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) { if (body.registeredBranchId) { await permissionCheck(req.user, body.registeredBranchId); } const record = await prisma.$transaction( async (tx) => { const last = await tx.runningNo.upsert({ where: { key: `PRODGRP`, }, create: { key: `PRODGRP`, value: 1, }, update: { value: { increment: 1 } }, }); return await tx.productGroup.create({ include: { createdBy: true, updatedBy: true, }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), code: `G${last.value.toString().padStart(2, "0")}`, createdByUserId: req.user.sub, updatedByUserId: req.user.sub, }, }); }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, ); this.setStatus(HttpStatus.CREATED); return record; } @Put("{groupId}") @Security("keycloak", MANAGE_ROLES) async editProductGroup( @Request() req: RequestWithUser, @Body() body: ProductGroupUpdate, @Path() groupId: string, ) { const record = await prisma.productGroup.findUnique({ where: { id: groupId }, include: { registeredBranch: { include: { headOffice: { include: { branch: { where: { user: { some: { userId: req.user.sub } } } }, user: { where: { userId: req.user.sub } }, }, }, user: { where: { userId: req.user.sub } }, }, }, }, }); if (!record) { throw new HttpError( HttpStatus.NOT_FOUND, "Product group cannot be found.", "productGroupNotFound", ); } if (!isSystem(req.user)) { if (!globalAllow(req.user) && record.registeredBranch?.user.length === 0) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } else { if ( (record.registeredBranch?.user.length === 0 && !record.registeredBranch?.headOffice) || (record.registeredBranch?.headOffice && record.registeredBranch?.headOffice.user.length === 0 && record.registeredBranch?.headOffice.branch.length === 0) ) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } } } const [branch] = await prisma.$transaction([ prisma.branch.findFirst({ where: { id: body.registeredBranchId }, include: { user: { where: { userId: req.user.sub }, }, headOffice: { include: { user: { where: { userId: req.user.sub }, }, }, }, }, }), ]); if (body.registeredBranchId !== undefined && !isSystem(req.user)) { if (!globalAllow(req.user)) { if (body.registeredBranchId === null || (branch && branch.user.length === 0)) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } } else { if ( body.registeredBranchId === null || (branch && branch.user.length === 0 && branch.headOffice && branch.headOffice.user.length === 0) ) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } } } if (!!body.registeredBranchId && !branch) { throw new HttpError( HttpStatus.BAD_REQUEST, "Branch cannot be found.", "relationBranchNotFound", ); } const result = await prisma.productGroup.update({ include: { createdBy: true, updatedBy: true, }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: groupId }, }); return result; } @Delete("{groupId}") @Security("keycloak", MANAGE_ROLES) async deleteProductGroup(@Request() req: RequestWithUser, @Path() groupId: string) { const record = await prisma.productGroup.findFirst({ where: { id: groupId }, include: { registeredBranch: { include: { headOffice: { include: { branch: { where: { user: { some: { userId: req.user.sub } } } }, user: { where: { userId: req.user.sub } }, }, }, user: { where: { userId: req.user.sub } }, }, }, }, }); if (!record) { throw new HttpError( HttpStatus.NOT_FOUND, "Product group cannot be found.", "productGroupNotFound", ); } if (!isSystem(req.user)) { if (!globalAllow(req.user) && record.registeredBranch?.user.length === 0) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } else { if ( (record.registeredBranch?.user.length === 0 && !record.registeredBranch?.headOffice) || (record.registeredBranch?.headOffice && record.registeredBranch?.headOffice.user.length === 0 && record.registeredBranch?.headOffice.branch.length === 0) ) { throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", "noPermission", ); } } } if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Product group is in used.", "productGroupInUsed"); } return await prisma.productGroup.delete({ include: { createdBy: true, updatedBy: true, }, where: { id: groupId }, }); } }