diff --git a/prisma/migrations/20240911080540_update_address_fields/migration.sql b/prisma/migrations/20240911080540_update_address_fields/migration.sql new file mode 100644 index 0000000..20c6e68 --- /dev/null +++ b/prisma/migrations/20240911080540_update_address_fields/migration.sql @@ -0,0 +1,40 @@ +/* + Warnings: + + - You are about to drop the column `zipCode` on the `Branch` table. All the data in the column will be lost. + - You are about to drop the column `zipCode` on the `User` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Branch" DROP COLUMN "zipCode", +ADD COLUMN "moo" TEXT, +ADD COLUMN "mooEN" TEXT, +ADD COLUMN "soi" TEXT, +ADD COLUMN "soiEN" TEXT, +ADD COLUMN "street" TEXT, +ADD COLUMN "streetEN" TEXT; + +-- AlterTable +ALTER TABLE "CustomerBranch" ADD COLUMN "moo" TEXT, +ADD COLUMN "mooEN" TEXT, +ADD COLUMN "soi" TEXT, +ADD COLUMN "soiEN" TEXT, +ADD COLUMN "street" TEXT, +ADD COLUMN "streetEN" TEXT; + +-- AlterTable +ALTER TABLE "Employee" ADD COLUMN "moo" TEXT, +ADD COLUMN "mooEN" TEXT, +ADD COLUMN "soi" TEXT, +ADD COLUMN "soiEN" TEXT, +ADD COLUMN "street" TEXT, +ADD COLUMN "streetEN" TEXT; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "zipCode", +ADD COLUMN "moo" TEXT, +ADD COLUMN "mooEN" TEXT, +ADD COLUMN "soi" TEXT, +ADD COLUMN "soiEN" TEXT, +ADD COLUMN "street" TEXT, +ADD COLUMN "streetEN" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c526fe7..bed5d87 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -190,10 +190,18 @@ model Branch { taxNo String name String nameEN String - address String - addressEN String telephoneNo String + address String + addressEN String + + soi String? + soiEN String? + moo String? + mooEN String? + street String? + streetEN String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) provinceId String? @@ -203,8 +211,6 @@ model Branch { subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) subDistrictId String? - zipCode String - email String contactName String? lineId String? @@ -304,6 +310,13 @@ model User { address String addressEN String + soi String? + soiEN String? + moo String? + mooEN String? + street String? + streetEN String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) provinceId String? @@ -313,8 +326,6 @@ model User { subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) subDistrictId String? - zipCode String - email String telephoneNo String @@ -329,6 +340,10 @@ model User { userType UserType userRole String + // citizenId String? + // citizenIssue DateTime? @db.Date + // citizenExpire DateTime? @db.Date + discountCondition String? licenseNo String? @@ -451,6 +466,13 @@ model CustomerBranch { address String addressEN String + soi String? + soiEN String? + moo String? + mooEN String? + street String? + streetEN String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) provinceId String? @@ -509,6 +531,13 @@ model Employee { address String? addressEN String? + soi String? + soiEN String? + moo String? + mooEN String? + street String? + streetEN String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) provinceId String? diff --git a/src/app.ts b/src/app.ts index 00ff3da..a13d83f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -51,7 +51,6 @@ const APP_PORT = +(process.env.APP_PORT || 3000); gender: "", address: "", addressEN: "", - zipCode: "", userType: "USER", userRole: "system", telephoneNo: "", diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index da0690f..20b58dd 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -25,8 +25,8 @@ import { createPermCondition, } from "../services/permission"; import { filterStatus } from "../services/prisma"; -import { connectOrDisconnect, connectOrNot } from "../utils/relation"; -import { notFoundError } from "../utils/error"; +import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; +import { notFoundError, relationError } from "../utils/error"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -46,7 +46,12 @@ type BranchCreate = { name: string; addressEN: string; address: string; - zipCode: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email: string; contactName?: string | null; webUrl?: string | null; @@ -80,7 +85,12 @@ type BranchUpdate = { name?: string; addressEN?: string; address?: string; - zipCode?: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email?: string; telephoneNo?: string; contactName?: string; @@ -202,7 +212,6 @@ export class BranchController extends Controller { @Security("keycloak") async getBranch( @Request() req: RequestWithUser, - @Query() zipCode?: string, @Query() filter?: "head" | "sub", @Query() headOfficeId?: string, @Query() includeHead?: boolean, @@ -215,7 +224,6 @@ export class BranchController extends Controller { const where = { AND: { ...filterStatus(status), - zipCode, headOfficeId: headOfficeId ?? (filter === "head" || tree ? null : undefined), NOT: { headOfficeId: filter === "sub" && !headOfficeId ? null : undefined }, OR: permissionCond(req.user, true), @@ -225,6 +233,20 @@ export class BranchController extends Controller { { name: { contains: query } }, { email: { contains: query } }, { telephoneNo: { contains: query } }, + ...whereAddressQuery(query), + { + branch: { + some: { + OR: [ + { nameEN: { contains: query } }, + { name: { contains: query } }, + { email: { contains: query } }, + { telephoneNo: { contains: query } }, + ...whereAddressQuery(query), + ], + }, + }, + }, ], } satisfies Prisma.BranchWhereInput; @@ -247,6 +269,15 @@ export class BranchController extends Controller { : false, branch: tree ? { + where: { + OR: [ + { nameEN: { contains: query } }, + { name: { contains: query } }, + { email: { contains: query } }, + { telephoneNo: { contains: query } }, + ...whereAddressQuery(query), + ], + }, include: { province: true, district: true, @@ -312,30 +343,10 @@ export class BranchController extends Controller { 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.", - "relationProvinceNotFound", - ); - if (body.districtId && !district) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "relationDistrictNotFound", - ); - if (body.subDistrictId && !subDistrict) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "relationSubDistrictNotFound", - ); - if (body.headOfficeId && !head) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Headquaters cannot be found.", - "relationHQNotFound", - ); + if (body.provinceId && !province) throw relationError("Province"); + if (body.districtId && !district) throw relationError("District"); + if (body.subDistrictId && !subDistrict) throw relationError("SubDistrict"); + if (body.headOfficeId && !head) throw relationError("HQ"); const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, code, ...rest } = body; @@ -447,30 +458,10 @@ export class BranchController extends Controller { 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.", - "relationProvinceNotFound", - ); - if (body.districtId && !district) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "relationDistrictNotFound", - ); - if (body.subDistrictId && !subDistrict) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "relationSubDistrictNotFound", - ); - if (body.headOfficeId && !branch) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Headquaters cannot be found.", - "relationHQNotFound", - ); + if (body.provinceId && !province) throw relationError("Province"); + if (body.districtId && !district) throw relationError("District"); + if (body.subDistrictId && !subDistrict) throw relationError("SubDistrict"); + if (body.headOfficeId && !branch) throw relationError("HQ"); } const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, ...rest } = body; diff --git a/src/controllers/01-branch-user-controller.ts b/src/controllers/01-branch-user-controller.ts index 645634e..c1ad85e 100644 --- a/src/controllers/01-branch-user-controller.ts +++ b/src/controllers/01-branch-user-controller.ts @@ -98,10 +98,11 @@ export class UserBranchController extends Controller { @Query() pageSize: number = 30, ) { const where = { - OR: [ - { branch: { name: { contains: query }, zipCode }, userId }, - { branch: { nameEN: { contains: query }, zipCode }, userId }, - ], + AND: { + branch: { subDistrict: { zipCode } }, + userId, + }, + OR: [{ branch: { name: { contains: query } } }, { branch: { nameEN: { contains: query } } }], } satisfies Prisma.BranchUserWhereInput; const [result, total] = await prisma.$transaction([ @@ -147,13 +148,17 @@ export class BranchUserController extends Controller { @Query() pageSize: number = 30, ) { const where = { + AND: { + user: { subDistrict: { zipCode } }, + branchId, + }, OR: [ - { user: { firstName: { contains: query }, zipCode }, branchId }, - { user: { firstNameEN: { contains: query }, zipCode }, branchId }, - { user: { lastName: { contains: query }, zipCode }, branchId }, - { user: { lastNameEN: { contains: query }, zipCode }, branchId }, - { user: { email: { contains: query }, zipCode }, branchId }, - { user: { telephoneNo: { contains: query }, zipCode }, branchId }, + { user: { firstName: { contains: query } } }, + { user: { firstNameEN: { contains: query } } }, + { user: { lastName: { contains: query } } }, + { user: { lastNameEN: { contains: query } } }, + { user: { email: { contains: query } } }, + { user: { telephoneNo: { contains: query } } }, ], } satisfies Prisma.BranchUserWhereInput; diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index d2af45f..d05f834 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -36,7 +36,7 @@ import { createPermCheck, createPermCondition, } from "../services/permission"; -import { connectOrDisconnect, connectOrNot } from "../utils/relation"; +import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -84,7 +84,12 @@ type UserCreate = { address: string; addressEN: string; - zipCode: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email: string; telephoneNo: string; @@ -131,7 +136,12 @@ type UserUpdate = { address?: string; addressEN?: string; - zipCode?: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email?: string; telephoneNo?: string; @@ -223,7 +233,6 @@ export class UserController extends Controller { async getUser( @Request() req: RequestWithUser, @Query() userType?: UserType, - @Query() zipCode?: string, @Query() includeBranch: boolean = false, @Query() query: string = "", @Query() page: number = 1, @@ -238,10 +247,10 @@ export class UserController extends Controller { { lastNameEN: { contains: query } }, { email: { contains: query } }, { telephoneNo: { contains: query } }, + ...whereAddressQuery(query), ], AND: { userRole: { not: "system" }, - zipCode, userType, ...filterStatus(status), branch: isSystem(req.user) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index d398ee2..5769911 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -17,10 +17,16 @@ 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"; +import minio, { deleteFolder } from "../services/minio"; import { isSystem } from "../utils/keycloak"; -import { branchRelationPermInclude, createPermCheck } from "../services/permission"; +import { + branchRelationPermInclude, + createPermCheck, + createPermCondition, +} from "../services/permission"; import { filterStatus } from "../services/prisma"; +import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; +import { notFoundError, relationError } from "../utils/error"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -42,6 +48,7 @@ function globalAllow(user: RequestWithUser["user"]) { return allowList.some((v) => user.roles?.includes(v)); } +const permissionCond = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); function imageLocation(id: string) { @@ -74,6 +81,12 @@ export type CustomerBranchCreate = ( workplaceEN: string; address: string; addressEN: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email: string; contactName: string; @@ -116,6 +129,12 @@ export type CustomerBranchUpdate = ( workplaceEN: string; address: string; addressEN: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; email?: string; contactName?: string; @@ -158,14 +177,7 @@ export class CustomerBranchController extends Controller { { registerNameEN: { contains: query } }, { email: { contains: query } }, { code: { contains: query } }, - { address: { contains: query } }, - { addressEN: { contains: query } }, - { province: { name: { contains: query } } }, - { province: { nameEN: { contains: query } } }, - { district: { name: { contains: query } } }, - { district: { nameEN: { contains: query } } }, - { subDistrict: { name: { contains: query } } }, - { subDistrict: { nameEN: { contains: query } } }, + ...whereAddressQuery(query), { customer: { OR: [ @@ -183,26 +195,7 @@ export class CustomerBranchController extends Controller { ? undefined : { registeredBranch: { - OR: [ - { - user: { some: { userId: req.user.sub } }, - }, - { - branch: globalAllow(req.user) - ? { some: { user: { some: { userId: req.user.sub } } } } - : undefined, - }, - { - headOffice: globalAllow(req.user) - ? { branch: { some: { user: { some: { userId: req.user.sub } } } } } - : undefined, - }, - { - headOffice: globalAllow(req.user) - ? { user: { some: { userId: req.user.sub } } } - : undefined, - }, - ], + OR: permissionCond(req.user), }, }, customerId, @@ -248,9 +241,7 @@ export class CustomerBranchController extends Controller { where: { id: branchId }, }); - if (!record) { - throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); - } + if (!record) throw notFoundError("Branch"); return record; } @@ -266,14 +257,6 @@ export class CustomerBranchController extends Controller { @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: [ { firstName: { contains: query } }, @@ -281,6 +264,7 @@ export class CustomerBranchController extends Controller { { lastName: { contains: query } }, { lastNameEN: { contains: query } }, { passportNumber: { contains: query } }, + ...whereAddressQuery(query), ], AND: { ...filterStatus(status), @@ -344,32 +328,14 @@ export class CustomerBranchController extends Controller { }, }), ]); - if (body.provinceId && !province) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Province cannot be found.", - "relationProvinceNotFound", - ); - if (body.districtId && !district) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "relationDistrictNotFound", - ); - if (body.subDistrictId && !subDistrict) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "relationSubDistrictNotFound", - ); - if (!customer) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Customer cannot be found.", - "relationCustomerNotFound", - ); + if (body.provinceId && !province) throw relationError("Province"); + if (body.districtId && !district) throw relationError("District"); + if (body.subDistrictId && !subDistrict) throw relationError("SubDistrict"); + if (!customer) throw relationError("Customer"); - await permissionCheck(req.user, customer.registeredBranch); + let company = await permissionCheck(req.user, customer.registeredBranch).then( + (v) => (v.headOffice || v).code, + ); const { provinceId, districtId, subDistrictId, customerId, ...rest } = body; @@ -381,11 +347,11 @@ export class CustomerBranchController extends Controller { let runningKey = ""; if (headofficeCode) { - runningKey = `CUSTOMER_BRANCH_${headofficeCode}`; + runningKey = `CUSTOMER_BRANCH_${company}_${headofficeCode}`; } else if ("citizenId" in body) { - runningKey = `CUSTOMER_BRANCH_${body.citizenId}`; + runningKey = `CUSTOMER_BRANCH_${company}_${body.citizenId}`; } else { - runningKey = `CUSTOMER_BRANCH_${body.legalPersonNo}`; + runningKey = `CUSTOMER_BRANCH_${company}_${body.legalPersonNo}`; } const last = await tx.runningNo.upsert({ @@ -478,30 +444,10 @@ export class CustomerBranchController extends Controller { }, }), ]); - if (body.provinceId && !province) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Province cannot be found.", - "relationProvinceNotFound", - ); - if (body.districtId && !district) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "relationDistrictNotFound", - ); - if (body.subDistrictId && !subDistrict) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "relationSubDistrictNotFound", - ); - if (!customer) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Customer cannot be found.", - "relationCustomerNotFound", - ); + if (body.provinceId && !province) throw relationError("Province"); + if (body.districtId && !district) throw relationError("District"); + if (body.subDistrictId && !subDistrict) throw relationError("SubDistrict"); + if (!customer) throw relationError("Customer"); await permissionCheck(req.user, customer.registeredBranch); } @@ -519,19 +465,10 @@ export class CustomerBranchController extends Controller { data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - 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, - }, + customer: connectOrNot(customerId), + province: connectOrDisconnect(provinceId), + district: connectOrDisconnect(districtId), + subDistrict: connectOrDisconnect(subDistrictId), updatedBy: { connect: { id: req.user.sub } }, }, }); @@ -553,13 +490,7 @@ export class CustomerBranchController extends Controller { }, }); - if (!record) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "Customer branch cannot be found.", - "customerBranchNotFound", - ); - } + if (!record) throw notFoundError("Customer Branch"); await permissionCheck(req.user, record.customer.registeredBranch); @@ -577,22 +508,7 @@ export class CustomerBranchController extends Controller { where: { id: branchId }, }) .then((v) => { - new Promise((resolve, reject) => { - const item: string[] = []; - - const stream = minio.listObjectsV2( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/`, - ); - - stream.on("data", (v) => v && v.name && item.push(v.name)); - stream.on("end", () => resolve(item)); - stream.on("error", () => reject(new Error("MinIO error."))); - }).then((list) => { - list.map(async (v) => { - await minio.removeObject(MINIO_BUCKET, v, { forceDelete: true }); - }); - }); + deleteFolder(MINIO_BUCKET, `${attachmentLocation(record.customerId, branchId)}/`); return v; }); } diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 284528a..144a62d 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -24,7 +24,7 @@ import { createPermCheck, createPermCondition, } from "../services/permission"; -import { connectOrDisconnect, connectOrNot } from "../utils/relation"; +import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; import { notFoundError, relationError } from "../utils/error"; import { deleteFile, fileLocation, getFile, listFile, setFile } from "../utils/minio"; @@ -70,6 +70,12 @@ type EmployeeCreate = { addressEN: string; address: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; passportType: string; passportNumber: string; @@ -156,6 +162,12 @@ type EmployeeUpdate = { addressEN?: string; address?: string; + soi?: string | null; + soiEN?: string | null; + moo?: string | null; + mooEN?: string | null; + street?: string | null; + streetEN?: string | null; passportType?: string; passportNumber?: string; @@ -253,6 +265,7 @@ export class EmployeeController extends Controller { { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, + ...whereAddressQuery(query), ], AND: { ...filterStatus(status), @@ -294,6 +307,7 @@ export class EmployeeController extends Controller { { lastName: { contains: query } }, { lastNameEN: { contains: query } }, { passportNumber: { contains: query } }, + ...whereAddressQuery(query), ], AND: { ...filterStatus(status),