diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts new file mode 100644 index 0000000..303d9f8 --- /dev/null +++ b/src/controllers/customer-branch-controller.ts @@ -0,0 +1,351 @@ +import { 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 HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import minio from "../services/minio"; + +if (!process.env.MINIO_BUCKET) { + throw Error("Require MinIO bucket."); +} + +const MINIO_BUCKET = process.env.MINIO_BUCKET; + +function imageLocation(id: string) { + return `employee/profile-img-${id}`; +} + +type CustomerBranchCreate = { + customerId: string; + + status?: Status; + + branchNo: string; + legalPersonNo: string; + + taxNo: string; + name: string; + nameEN: string; + addressEN: string; + address: string; + zipCode: string; + email: string; + telephoneNo: string; + longitude: string; + latitude: string; + + registerName: string; + registerDate: Date; + authorizedCapital: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; +}; + +type CustomerBranchUpdate = { + customerId?: string; + + status?: "ACTIVE" | "INACTIVE"; + + branchNo?: string; + legalPersonNo?: string; + + taxNo?: string; + name?: string; + nameEN?: string; + addressEN?: string; + address?: string; + zipCode?: string; + email?: string; + telephoneNo?: string; + longitude?: string; + latitude?: string; + + registerName?: string; + registerDate?: Date; + authorizedCapital?: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; +}; + +@Route("api/customer-branch") +@Tags("Customer Branch") +@Security("keycloak") +export class CustomerBranchController extends Controller { + @Get() + async list( + @Query() zipCode?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + OR: [ + { nameEN: { contains: query }, zipCode }, + { name: { contains: query }, zipCode }, + { email: { contains: query }, zipCode }, + ], + } satisfies Prisma.BranchWhereInput; + + const [result, total] = await prisma.$transaction([ + prisma.customerBranch.findMany({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where, + take: pageSize, + skip: (page - 1) * pageSize, + }), + prisma.customerBranch.count({ where }), + ]); + + return { result, page, pageSize, total }; + } + + @Get("{branchId}") + async getById(@Path() branchId: string) { + const record = await prisma.customerBranch.findFirst({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where: { id: branchId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found"); + } + + return record; + } + + @Get("{branchId}/employee") + async listEmployee( + @Path() branchId: string, + @Query() zipCode?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + AND: { customerBranchId: branchId }, + OR: [ + { firstName: { contains: query }, zipCode }, + { firstNameEN: { contains: query }, zipCode }, + { lastName: { contains: query }, zipCode }, + { lastNameEN: { contains: query }, zipCode }, + { email: { contains: query }, zipCode }, + ], + } satisfies Prisma.EmployeeWhereInput; + + const [result, total] = await prisma.$transaction([ + prisma.employee.findMany({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where, + take: pageSize, + skip: (page - 1) * pageSize, + }), + prisma.employee.count({ where }), + ]); + + return { + result: await Promise.all( + result.map(async (v) => ({ + ...v, + profileImageUrl: await minio.presignedGetObject( + MINIO_BUCKET, + imageLocation(v.id), + 12 * 60 * 60, + ), + })), + ), + page, + pageSize, + total, + }; + } + + @Post() + async create(@Request() req: RequestWithUser, @Body() body: CustomerBranchCreate) { + if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) { + const [province, district, subDistrict, customer] = await prisma.$transaction([ + prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), + prisma.district.findFirst({ where: { id: body.districtId || undefined } }), + prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }), + prisma.customer.findFirst({ where: { id: body.customerId || undefined } }), + ]); + if (body.provinceId && !province) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Province cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.districtId && !district) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "District cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.subDistrictId && !subDistrict) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Sub-district cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.customerId && !customer) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer cannot be found.", + "missing_or_invalid_parameter", + ); + } + + const { provinceId, districtId, subDistrictId, customerId, ...rest } = body; + + const record = await prisma.customerBranch.create({ + include: { + province: true, + district: true, + subDistrict: true, + }, + data: { + ...rest, + customer: { connect: { id: customerId } }, + province: { connect: provinceId ? { id: provinceId } : undefined }, + district: { connect: districtId ? { id: districtId } : undefined }, + subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + + await prisma.customer.updateMany({ + where: { id: customerId, status: Status.CREATED }, + data: { status: Status.ACTIVE }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Put("{branchId}") + async editById( + @Request() req: RequestWithUser, + @Body() body: CustomerBranchUpdate, + @Path() branchId: string, + ) { + if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) { + const [province, district, subDistrict, customer] = await prisma.$transaction([ + prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), + prisma.district.findFirst({ where: { id: body.districtId || undefined } }), + prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }), + prisma.customer.findFirst({ where: { id: body.customerId || undefined } }), + ]); + if (body.provinceId && !province) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Province cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.districtId && !district) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "District cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.subDistrictId && !subDistrict) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Sub-district cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.customerId && !customer) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer cannot be found.", + "missing_or_invalid_parameter", + ); + } + + const { provinceId, districtId, subDistrictId, customerId, ...rest } = body; + + if (!(await prisma.customerBranch.findUnique({ where: { id: branchId } }))) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "data_not_found"); + } + + const record = await prisma.customerBranch.update({ + where: { id: branchId }, + include: { + province: true, + district: true, + subDistrict: true, + }, + data: { + ...rest, + customer: { connect: customerId ? { id: customerId } : undefined }, + province: { + connect: provinceId ? { id: provinceId } : undefined, + disconnect: provinceId === null || undefined, + }, + district: { + connect: districtId ? { id: districtId } : undefined, + disconnect: districtId === null || undefined, + }, + subDistrict: { + connect: subDistrictId ? { id: subDistrictId } : undefined, + disconnect: subDistrictId === null || undefined, + }, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + + this.setStatus(HttpStatus.CREATED); + + return record; + } + + @Delete("{branchId}") + async delete(@Path() branchId: string) { + const record = await prisma.customerBranch.findFirst({ where: { id: branchId } }); + + if (!record) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Customer branch cannot be found.", + "data_not_found", + ); + } + + if (record.status !== Status.CREATED) { + throw new HttpError(HttpStatus.FORBIDDEN, "Customer branch is in used.", "data_in_used"); + } + + return await prisma.customerBranch.delete({ where: { id: branchId } }); + } +}