diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts new file mode 100644 index 0000000..aeff13e --- /dev/null +++ b/src/controllers/employee-controller.ts @@ -0,0 +1,330 @@ +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 EmployeeCreate = { + customerBranchId: string; + + status?: Status; + + code: string; + nrcNo: string; + + dateOfBirth: Date; + gender: string; + nationality: string; + + firstName: string; + firstNameEN: string; + lastName: string; + lastNameEN: string; + + addressEN: string; + address: string; + zipCode: string; + email: string; + telephoneNo: string; + + arrivalBarricade: string; + arrivalCardNo: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; +}; + +type EmployeeUpdate = { + customerBranchId?: string; + status?: "ACTIVE" | "INACTIVE"; + + code?: string; + nrcNo?: string; + + dateOfBirth?: Date; + gender?: string; + nationality?: string; + + firstName?: string; + firstNameEN?: string; + lastName?: string; + lastNameEN?: string; + + addressEN?: string; + address?: string; + zipCode?: string; + email?: string; + telephoneNo?: string; + + arrivalBarricade?: string; + arrivalCardNo?: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; +}; + +@Route("api/employee") +@Tags("Employee") +@Security("keycloak") +export class EmployeeController extends Controller { + @Get() + async list( + @Query() zipCode?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + 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({ + orderBy: { createdAt: "asc" }, + 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, + }; + } + + @Get("{employeeId}") + async getById(@Path() employeeId: string) { + const record = await prisma.employee.findFirst({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where: { id: employeeId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found"); + } + + return record; + } + + @Post() + async create(@Request() req: RequestWithUser, @Body() body: EmployeeCreate) { + if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) { + const [province, district, subDistrict, customerBranch] = 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.customerBranch.findFirst({ where: { id: body.customerBranchId || 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.customerBranchId && !customerBranch) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer Branch cannot be found.", + "missing_or_invalid_parameter", + ); + } + + const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body; + + const record = await prisma.employee.create({ + include: { + province: true, + district: true, + subDistrict: true, + }, + data: { + ...rest, + province: { connect: provinceId ? { id: provinceId } : undefined }, + district: { connect: districtId ? { id: districtId } : undefined }, + subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, + customerBranch: { connect: { id: customerBranchId } }, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + + await prisma.customerBranch.updateMany({ + where: { id: customerBranchId, status: Status.CREATED }, + data: { status: Status.ACTIVE }, + }); + + this.setStatus(HttpStatus.CREATED); + + return Object.assign(record, { + profileImageUrl: await minio.presignedPutObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + profileImageUploadUrl: await minio.presignedPutObject( + MINIO_BUCKET, + imageLocation(record.id), + 12 * 60 * 60, + ), + }); + } + + @Put("{employeeId}") + async editById( + @Request() req: RequestWithUser, + @Body() body: EmployeeUpdate, + @Path() employeeId: string, + ) { + if (body.provinceId || body.districtId || body.subDistrictId || body.customerBranchId) { + const [province, district, subDistrict, customerBranch] = 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.customerBranch.findFirst({ where: { id: body.customerBranchId || 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.customerBranchId && !customerBranch) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer cannot be found.", + "missing_or_invalid_parameter", + ); + } + + const { provinceId, districtId, subDistrictId, customerBranchId, ...rest } = body; + + const record = await prisma.employee.update({ + where: { id: employeeId }, + include: { + province: true, + district: true, + subDistrict: true, + }, + data: { + ...rest, + customerBranch: { connect: customerBranchId ? { id: customerBranchId } : 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("{employeeId}") + async delete(@Path() employeeId: string) { + const record = await prisma.employee.findFirst({ where: { id: employeeId } }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "data_not_found"); + } + + if (record.status !== Status.CREATED) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "Emplyee is in used.", + "missing_or_invalid_parameter", + ); + } + + return await prisma.employee.delete({ where: { id: employeeId } }); + } +}