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"; type ProductGroupCreate = { name: string; detail: string; remark: string; status?: Status; }; type ProductGroupUpdate = { name?: string; detail?: string; remark?: string; status?: "ACTIVE" | "INACTIVE"; }; @Route("api/v1/product-group") @Tags("Product Group") @Security("keycloak") export class ProductGroup extends Controller { @Get("stats") async getProductGroupStats() { return await prisma.productGroup.count(); } @Get() async getProductGroup( @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) }, ], } satisfies Prisma.ProductGroupWhereInput; const [result, total] = await prisma.$transaction([ prisma.productGroup.findMany({ include: { _count: { select: { type: true, }, }, createdBy: true, updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, take: pageSize, skip: (page - 1) * pageSize, }), prisma.productGroup.count({ where }), ]); const statsProduct = await prisma.productType.findMany({ include: { _count: { select: { product: true } }, }, where: { productGroupId: { in: result.map((v) => v.id) }, }, }); return { result: result.map((v) => ({ ...v, _count: { ...v._count, product: statsProduct.reduce( (a, c) => (c.productGroupId === v.id ? a + c._count.product : a), 0, ), }, })), page, pageSize, total, }; } @Get("{groupId}") 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() async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) { 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}") async editProductGroup( @Request() req: RequestWithUser, @Body() body: ProductGroupUpdate, @Path() groupId: string, ) { if (!(await prisma.productGroup.findUnique({ where: { id: groupId } }))) { throw new HttpError( HttpStatus.NOT_FOUND, "Product group cannot be found.", "productGroupNotFound", ); } const record = await prisma.productGroup.update({ include: { createdBy: true, updatedBy: true, }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: groupId }, }); return record; } @Delete("{groupId}") async deleteProductGroup(@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", ); } 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 }, }); } }