diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5b72ed7..2e26fda 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,6 +65,11 @@ model SubDistrict { employee Employee[] } +enum Status { + CREATED + USED +} + model Branch { id String @id @default(uuid()) code String @@ -96,7 +101,7 @@ model Branch { headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id]) headOfficeId String? - status String? + status Status @default(CREATED) createdBy String? createdAt DateTime @default(now()) @@ -109,10 +114,9 @@ model Branch { } model BranchContact { - id String @id @default(uuid()) - telephoneNo String - lineId String - qrCodeImageUrl String? + id String @id @default(uuid()) + telephoneNo String + lineId String branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade) branchId String @@ -141,6 +145,8 @@ model BranchUser { model User { id String @id @default(uuid()) + keycloakId String + code String firstNameTH String firstNameEN String @@ -169,8 +175,6 @@ model User { startDate DateTime retireDate DateTime - profileImageUrl String? - userType String userRole String @@ -185,7 +189,7 @@ model User { trainingPlace String - status String? + status Status @default(CREATED) createdBy String? createdAt DateTime @default(now()) diff --git a/src/controllers/branch/branch-controller.ts b/src/controllers/branch/branch-controller.ts new file mode 100644 index 0000000..197ec21 --- /dev/null +++ b/src/controllers/branch/branch-controller.ts @@ -0,0 +1,213 @@ +import { Prisma, Status } from "@prisma/client"; +import { + Body, + Controller, + Delete, + Get, + Patch, + Path, + Post, + Query, + Request, + Route, + Security, + Tags, +} from "tsoa"; + +import prisma from "../../db"; +import HttpError from "../../interfaces/http-error"; +import HttpStatus from "../../interfaces/http-status"; +import { RequestWithUser } from "../../interfaces/user"; + +type BranchCreate = { + code: string; + taxNo: string; + nameEN: string; + nameTH: string; + addressEN: string; + addressTH: string; + zipCode: string; + email: string; + telephoneNo: string; + longitude: string; + latitude: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; + headOfficeId?: string | null; +}; + +type BranchUpdate = { + code?: string; + taxNo?: string; + nameEN?: string; + nameTH?: string; + addressEN?: string; + addressTH?: string; + zipCode?: string; + email?: string; + telephoneNo?: string; + longitude?: string; + latitude?: string; + + subDistrictId?: string | null; + districtId?: string | null; + provinceId?: string | null; + headOfficeId?: string | null; +}; + +@Route("api/branch") +@Tags("Branch") +@Security("keycloak") +export class BranchController extends Controller { + @Get() + async getBranch( + @Query() zipCode?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + OR: [ + { nameEN: { contains: query }, zipCode }, + { nameTH: { contains: query }, zipCode }, + { email: { contains: query }, zipCode }, + ], + } satisfies Prisma.BranchWhereInput; + + const [result, total] = await prisma.$transaction([ + prisma.branch.findMany({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where, + take: pageSize, + skip: (page - 1) * pageSize, + }), + prisma.branch.count({ where }), + ]); + + return { result, page, pageSize, total }; + } + + @Get("{branchId}") + async getBranchById(@Path() branchId: string) { + return await prisma.branch.findFirst({ + include: { + province: true, + district: true, + subDistrict: true, + }, + where: { id: branchId }, + }); + } + + @Post() + async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) { + if (body.provinceId || body.districtId || body.subDistrictId || body.headOfficeId) { + const [province, district, subDistrict, branch] = 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.branch.findFirst({ where: { id: body.headOfficeId || undefined } }), + ]); + if (body.provinceId && !province) + throw new HttpError(HttpStatus.BAD_REQUEST, "Province cannot be found."); + if (body.districtId && !district) + throw new HttpError(HttpStatus.BAD_REQUEST, "District cannot be found."); + if (body.subDistrictId && !subDistrict) + throw new HttpError(HttpStatus.BAD_REQUEST, "Sub-district cannot be found."); + if (body.headOfficeId && !branch) + throw new HttpError(HttpStatus.BAD_REQUEST, "Head branch cannot be found."); + } + + const { provinceId, districtId, subDistrictId, headOfficeId, ...rest } = body; + + return await prisma.branch.create({ + data: { + ...rest, + isHeadOffice: headOfficeId === null, + province: { connect: provinceId ? { id: provinceId } : undefined }, + district: { connect: districtId ? { id: districtId } : undefined }, + subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, + headOffice: { connect: headOfficeId ? { id: headOfficeId } : undefined }, + createdBy: req.user.name, + updateBy: req.user.name, + }, + }); + } + + @Patch("{branchId}") + async editBranch( + @Request() req: RequestWithUser, + @Body() body: BranchUpdate, + @Path() branchId: string, + ) { + if (body.subDistrictId || body.districtId || body.provinceId || body.headOfficeId) { + const [province, district, subDistrict, branch] = 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.branch.findFirst({ where: { id: body.headOfficeId || undefined } }), + ]); + if (body.provinceId && !province) + throw new HttpError(HttpStatus.BAD_REQUEST, "Province cannot be found."); + if (body.districtId && !district) + throw new HttpError(HttpStatus.BAD_REQUEST, "District cannot be found."); + if (body.subDistrictId && !subDistrict) + throw new HttpError(HttpStatus.BAD_REQUEST, "Sub-district cannot be found."); + if (body.headOfficeId && !branch) + throw new HttpError(HttpStatus.BAD_REQUEST, "Head branch cannot be found."); + } + + const { provinceId, districtId, subDistrictId, headOfficeId, ...rest } = body; + + const record = await prisma.branch.update({ + include: { province: true, district: true, subDistrict: true }, + data: { + ...rest, + isHeadOffice: headOfficeId === null, + 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, + }, + headOffice: { + connect: headOfficeId ? { id: headOfficeId } : undefined, + disconnect: headOfficeId === null || undefined, + }, + updateBy: req.user.name, + }, + where: { id: branchId }, + }); + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found."); + } + return record; + } + + @Delete("{branchId}") + async deleteBranch(@Path() branchId: string) { + const record = await prisma.branch.findFirst({ where: { id: branchId } }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found."); + } + + if (record.status === Status.USED) { + throw new HttpError(HttpStatus.FORBIDDEN, "Branch is in used."); + } + + return await prisma.branch.delete({ where: { id: branchId } }); + } +}