import { CustomerType, Prisma, Status } from "@prisma/client"; import { Body, Controller, Delete, Get, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import { RequestWithUser } from "../interfaces/user"; import prisma from "../db"; import minio from "../services/minio"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; import { CustomerBranchCreate, CustomerBranchUpdate } from "./customer-branch-controller"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); } const MINIO_BUCKET = process.env.MINIO_BUCKET; export type CustomerCreate = { status?: Status; customerType: CustomerType; customerName: string; customerNameEN: string; customerBranch?: Omit[]; }; export type CustomerUpdate = { status?: "ACTIVE" | "INACTIVE"; customerType?: CustomerType; customerName?: string; customerNameEN?: string; customerBranch?: (Omit & { id: string })[]; }; function imageLocation(id: string) { return `customer/img-${id}`; } @Route("api/customer") @Tags("Customer") @Security("keycloak") export class CustomerController extends Controller { @Get() async list( @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, ) { const where = { OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }], } satisfies Prisma.CustomerWhereInput; const [result, total] = await prisma.$transaction([ prisma.customer.findMany({ orderBy: { createdAt: "asc" }, where, take: pageSize, skip: (page - 1) * pageSize, }), prisma.customer.count({ where }), ]); return { result: await Promise.all( result.map(async (v) => ({ ...v, imageUrl: await minio.presignedGetObject(MINIO_BUCKET, imageLocation(v.id), 12 * 60 * 60), })), ), page, pageSize, total, }; } @Get("{customerId}") async getById(@Path() customerId: string) { const record = await prisma.customer.findFirst({ where: { id: customerId } }); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found"); return Object.assign(record, { imageUrl: await minio.presignedGetObject( MINIO_BUCKET, imageLocation(record.id), 12 * 60 * 60, ), }); } @Post() async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) { const last = await prisma.customer.findFirst({ orderBy: { createdAt: "desc" }, where: { customerType: body.customerType }, }); const code = `${body.customerType}${(+(last?.code.slice(-6) || 0) + 1).toString().padStart(6, "0")}`; const { customerBranch, ...payload } = body; const provinceId = body.customerBranch?.reduce((acc, cur) => { if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId); return acc; }, []); const districtId = body.customerBranch?.reduce((acc, cur) => { if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId); return acc; }, []); const subDistrictId = body.customerBranch?.reduce((acc, cur) => { if (cur.subDistrictId && !acc.includes(cur.subDistrictId)) return acc.concat(cur.subDistrictId); return acc; }, []); const [province, district, subDistrict] = await prisma.$transaction([ prisma.province.findMany({ where: { id: { in: provinceId } } }), prisma.district.findMany({ where: { id: { in: districtId } } }), prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }), ]); if (provinceId && province.length !== provinceId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some province cannot be found.", "missing_or_invalid_parameter", ); } if (districtId && district.length !== districtId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some district cannot be found.", "missing_or_invalid_parameter", ); } if (subDistrictId && subDistrict.length !== subDistrictId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some sub district cannot be found.", "missing_or_invalid_parameter", ); } const record = await prisma.customer.create({ include: { branch: { include: { province: true, district: true, subDistrict: true, }, }, }, data: { ...payload, code, branch: { createMany: { data: customerBranch?.map((v, i) => ({ ...v, branchNo: `${i + 1}`, createdBy: req.user.name, updateBy: req.user.name, })) || [], }, }, createdBy: req.user.name, updateBy: req.user.name, }, }); this.setStatus(HttpStatus.CREATED); return Object.assign(record, { imageUrl: await minio.presignedGetObject( MINIO_BUCKET, imageLocation(record.id), 12 * 60 * 60, ), imageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, imageLocation(record.id), 12 * 60 * 60, ), }); } @Put("{customerId}") async editById( @Path() customerId: string, @Request() req: RequestWithUser, @Body() body: CustomerUpdate, ) { if (!(await prisma.customer.findUnique({ where: { id: customerId } }))) { throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found"); } const provinceId = body.customerBranch?.reduce((acc, cur) => { if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId); return acc; }, []); const districtId = body.customerBranch?.reduce((acc, cur) => { if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId); return acc; }, []); const subDistrictId = body.customerBranch?.reduce((acc, cur) => { if (cur.subDistrictId && !acc.includes(cur.subDistrictId)) return acc.concat(cur.subDistrictId); return acc; }, []); const [province, district, subDistrict] = await prisma.$transaction([ prisma.province.findMany({ where: { id: { in: provinceId } } }), prisma.district.findMany({ where: { id: { in: districtId } } }), prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }), ]); if (provinceId && province.length !== provinceId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some province cannot be found.", "missing_or_invalid_parameter", ); } if (districtId && district.length !== districtId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some district cannot be found.", "missing_or_invalid_parameter", ); } if (subDistrictId && subDistrict.length !== subDistrictId?.length) { throw new HttpError( HttpStatus.BAD_REQUEST, "Some sub district cannot be found.", "missing_or_invalid_parameter", ); } const { customerBranch, ...payload } = body; const record = await prisma.customer.update({ where: { id: customerId }, data: { ...payload, branch: { deleteMany: { id: { notIn: customerBranch?.map((v) => v.id) || [] }, }, updateMany: customerBranch?.map((v) => ({ where: { id: v.id }, data: { ...v, updateBy: req.user.name }, })) || [], }, updateBy: req.user.name, }, }); return Object.assign(record, { imageUrl: await minio.presignedGetObject( MINIO_BUCKET, imageLocation(record.id), 12 * 60 * 60, ), imageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, imageLocation(record.id), 12 * 60 * 60, ), }); } @Delete("{customerId}") async deleteById(@Path() customerId: string) { const record = await prisma.customer.findFirst({ where: { id: customerId } }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "data_not_found"); } if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "data_in_used"); } return await prisma.customer.delete({ where: { id: customerId } }); } }