refactor: customer create and update process

This commit is contained in:
Methapon Metanipat 2024-08-20 15:41:48 +07:00
parent b167a612b4
commit 5bb8da818c
6 changed files with 261 additions and 462 deletions

View file

@ -0,0 +1,56 @@
/*
Warnings:
- You are about to drop the column `customerName` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `customerNameEN` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `personName` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `personNameEN` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `taxNo` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `branchNo` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `bussinessType` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `bussinessTypeEN` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `name` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `nameEN` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `taxNo` on the `CustomerBranch` table. All the data in the column will be lost.
- You are about to drop the column `zipCode` on the `CustomerBranch` table. All the data in the column will be lost.
- Added the required column `birthDate` to the `Customer` table without a default value. This is not possible if the table is not empty.
- Added the required column `firstName` to the `Customer` table without a default value. This is not possible if the table is not empty.
- Added the required column `gender` to the `Customer` table without a default value. This is not possible if the table is not empty.
- Added the required column `lastName` to the `Customer` table without a default value. This is not possible if the table is not empty.
- Added the required column `businessType` to the `CustomerBranch` table without a default value. This is not possible if the table is not empty.
- Added the required column `businessTypeEN` to the `CustomerBranch` table without a default value. This is not possible if the table is not empty.
- Added the required column `workplace` to the `CustomerBranch` table without a default value. This is not possible if the table is not empty.
- Added the required column `workplaceEN` to the `CustomerBranch` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Customer" DROP COLUMN "customerName",
DROP COLUMN "customerNameEN",
DROP COLUMN "personName",
DROP COLUMN "personNameEN",
DROP COLUMN "taxNo",
ADD COLUMN "birthDate" TIMESTAMP(3) NOT NULL,
ADD COLUMN "firstName" TEXT NOT NULL,
ADD COLUMN "firstNameEN" TEXT,
ADD COLUMN "gender" TEXT NOT NULL,
ADD COLUMN "lastName" TEXT NOT NULL,
ADD COLUMN "lastNameEN" TEXT,
ADD COLUMN "namePrefix" TEXT;
-- AlterTable
ALTER TABLE "CustomerBranch" DROP COLUMN "branchNo",
DROP COLUMN "bussinessType",
DROP COLUMN "bussinessTypeEN",
DROP COLUMN "name",
DROP COLUMN "nameEN",
DROP COLUMN "taxNo",
DROP COLUMN "zipCode",
ADD COLUMN "businessType" TEXT NOT NULL,
ADD COLUMN "businessTypeEN" TEXT NOT NULL,
ADD COLUMN "citizenId" TEXT,
ADD COLUMN "workplace" TEXT NOT NULL,
ADD COLUMN "workplaceEN" TEXT NOT NULL,
ALTER COLUMN "legalPersonNo" DROP NOT NULL,
ALTER COLUMN "registerName" DROP NOT NULL,
ALTER COLUMN "registerDate" DROP NOT NULL,
ALTER COLUMN "authorizedCapital" DROP NOT NULL;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "CustomerBranch" ADD COLUMN "legalPersonName" TEXT;

View file

@ -0,0 +1,13 @@
/*
Warnings:
- You are about to drop the column `code` on the `Customer` table. All the data in the column will be lost.
- You are about to drop the column `legalPersonName` on the `CustomerBranch` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Customer" DROP COLUMN "code";
-- AlterTable
ALTER TABLE "CustomerBranch" DROP COLUMN "legalPersonName",
ADD COLUMN "registerNameEN" TEXT;

View file

@ -403,14 +403,17 @@ enum CustomerType {
} }
model Customer { model Customer {
id String @id @default(uuid()) id String @id @default(uuid())
code String
personName String customerType CustomerType
personNameEN String?
customerType CustomerType namePrefix String?
customerName String firstName String
customerNameEN String firstNameEN String?
taxNo String? lastName String
lastNameEN String?
gender String
birthDate DateTime
status Status @default(CREATED) status Status @default(CREATED)
statusOrder Int @default(0) statusOrder Int @default(0)
@ -430,24 +433,25 @@ model Customer {
} }
model CustomerBranch { model CustomerBranch {
id String @id @default(uuid()) id String @id @default(uuid())
branchNo Int
code String
legalPersonNo String
name String
nameEN String
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
customerId String customerId String
taxNo String? code String
registerName String
registerDate DateTime
authorizedCapital String
address String // NOTE: About (Natural Person)
addressEN String citizenId String?
// NOTE: About (Legal Entity)
legalPersonNo String?
registerName String?
registerNameEN String?
registerDate DateTime?
authorizedCapital String?
workplace String
workplaceEN String
address String
addressEN String
province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull)
provinceId String? provinceId String?
@ -458,14 +462,12 @@ model CustomerBranch {
subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull)
subDistrictId String? subDistrictId String?
zipCode String
email String email String
telephoneNo String telephoneNo String
employmentOffice String employmentOffice String
bussinessType String businessType String
bussinessTypeEN String businessTypeEN String
jobPosition String jobPosition String
jobPositionEN String jobPositionEN String
jobDescription String jobDescription String

View file

@ -137,27 +137,30 @@ export class CustomerBranchController extends Controller {
const where = { const where = {
OR: [ OR: [
{ nameEN: { contains: query }, zipCode, ...filterStatus(status) }, { registerName: { contains: query } },
{ name: { contains: query }, zipCode, ...filterStatus(status) }, { registerNameEN: { contains: query } },
{ email: { contains: query }, zipCode, ...filterStatus(status) }, { email: { contains: query } },
{ code: { contains: query }, zipCode, ...filterStatus(status) }, { code: { contains: query } },
{ address: { contains: query }, zipCode, ...filterStatus(status) }, { address: { contains: query } },
{ addressEN: { contains: query }, zipCode, ...filterStatus(status) }, { addressEN: { contains: query } },
{ province: { name: { contains: query } }, zipCode, ...filterStatus(status) }, { province: { name: { contains: query } } },
{ province: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) }, { province: { nameEN: { contains: query } } },
{ district: { name: { contains: query } }, zipCode, ...filterStatus(status) }, { district: { name: { contains: query } } },
{ district: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) }, { district: { nameEN: { contains: query } } },
{ subDistrict: { name: { contains: query } }, zipCode, ...filterStatus(status) }, { subDistrict: { name: { contains: query } } },
{ subDistrict: { nameEN: { contains: query } }, zipCode, ...filterStatus(status) }, { subDistrict: { nameEN: { contains: query } } },
{ {
customer: { customer: {
OR: [{ customerName: { contains: query } }, { customerNameEN: { contains: query } }], OR: [
{ firstName: { contains: query } },
{ firstNameEN: { contains: query } },
{ lastName: { contains: query } },
{ lastNameEN: { contains: query } },
],
}, },
zipCode,
status,
}, },
], ],
AND: { customerId }, AND: { customerId, subDistrict: zipCode ? { zipCode } : undefined, ...filterStatus(status) },
} satisfies Prisma.CustomerBranchWhereInput; } satisfies Prisma.CustomerBranchWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -263,7 +266,15 @@ export class CustomerBranchController extends Controller {
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
prisma.district.findFirst({ where: { id: body.districtId || undefined } }), prisma.district.findFirst({ where: { id: body.districtId || undefined } }),
prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }), prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }),
prisma.customer.findFirst({ where: { id: body.customerId || undefined } }), prisma.customer.findFirst({
where: { id: body.customerId || undefined },
include: {
branch: {
take: 1,
orderBy: { createdAt: "asc" },
},
},
}),
]); ]);
if (body.provinceId && !province) if (body.provinceId && !province)
throw new HttpError( throw new HttpError(
@ -294,48 +305,37 @@ export class CustomerBranchController extends Controller {
const record = await prisma.$transaction( const record = await prisma.$transaction(
async (tx) => { async (tx) => {
const conflict = await tx.customerBranch.findFirst({ // const last = await tx.runningNo.upsert({
where: { customerId, branchNo: rest.branchNo }, // where: {
}); // key: `CUSTOMER_${customer.code.slice(0, -6)}`,
if (conflict) { // },
throw new HttpError( // create: {
HttpStatus.BAD_REQUEST, // key: `CUSTOMER_${customer.code.slice(0, -6)}`,
"Branch with current no already exists.", // value: 1,
"branchSameNoExist", // },
); // update: { value: { increment: 1 } },
} // });
//
const last = await tx.runningNo.upsert({ // return await tx.customerBranch.create({
where: { // include: {
key: `CUSTOMER_${customer.code.slice(0, -6)}`, // province: true,
}, // district: true,
create: { // subDistrict: true,
key: `CUSTOMER_${customer.code.slice(0, -6)}`, // createdBy: true,
value: 1, // updatedBy: true,
}, // },
update: { value: { increment: 1 } }, // data: {
}); // ...rest,
// statusOrder: +(rest.status === "INACTIVE"),
return await tx.customerBranch.create({ // code: `${customer.code.slice(0, -6)}${`${last.value - 1}`.padStart(6, "0")}`,
include: { // customer: { connect: { id: customerId } },
province: true, // province: { connect: provinceId ? { id: provinceId } : undefined },
district: true, // district: { connect: districtId ? { id: districtId } : undefined },
subDistrict: true, // subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
createdBy: true, // createdBy: { connect: { id: req.user.sub } },
updatedBy: true, // updatedBy: { connect: { id: req.user.sub } },
}, // },
data: { // });
...rest,
statusOrder: +(rest.status === "INACTIVE"),
code: `${customer.code.slice(0, -6)}${`${last.value - 1}`.padStart(6, "0")}`,
customer: { connect: { id: customerId } },
province: { connect: provinceId ? { id: provinceId } : undefined },
district: { connect: districtId ? { id: districtId } : undefined },
subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined },
createdBy: { connect: { id: req.user.sub } },
updatedBy: { connect: { id: req.user.sub } },
},
});
}, },
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
); );
@ -352,6 +352,12 @@ export class CustomerBranchController extends Controller {
@Body() body: CustomerBranchUpdate, @Body() body: CustomerBranchUpdate,
@Path() branchId: string, @Path() branchId: string,
) { ) {
const branch = await prisma.customerBranch.findUnique({ where: { id: branchId } });
if (!branch) {
throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound");
}
if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) { if (body.provinceId || body.districtId || body.subDistrictId || body.customerId) {
const [province, district, subDistrict, customer] = await prisma.$transaction([ const [province, district, subDistrict, customer] = await prisma.$transaction([
prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), prisma.province.findFirst({ where: { id: body.provinceId || undefined } }),
@ -385,44 +391,41 @@ export class CustomerBranchController extends Controller {
); );
} }
const { provinceId, districtId, subDistrictId, customerId, ...rest } = body; // 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.", "branchNotFound"); // const record = await prisma.customerBranch.update({
} // where: { id: branchId },
// include: {
const record = await prisma.customerBranch.update({ // province: true,
where: { id: branchId }, // district: true,
include: { // subDistrict: true,
province: true, // createdBy: true,
district: true, // updatedBy: true,
subDistrict: true, // },
createdBy: true, // data: {
updatedBy: true, // ...rest,
}, // statusOrder: +(rest.status === "INACTIVE"),
data: { // customer: { connect: customerId ? { id: customerId } : undefined },
...rest, // province: {
statusOrder: +(rest.status === "INACTIVE"), // connect: provinceId ? { id: provinceId } : undefined,
customer: { connect: customerId ? { id: customerId } : undefined }, // disconnect: provinceId === null || undefined,
province: { // },
connect: provinceId ? { id: provinceId } : undefined, // district: {
disconnect: provinceId === null || undefined, // connect: districtId ? { id: districtId } : undefined,
}, // disconnect: districtId === null || undefined,
district: { // },
connect: districtId ? { id: districtId } : undefined, // subDistrict: {
disconnect: districtId === null || undefined, // connect: subDistrictId ? { id: subDistrictId } : undefined,
}, // disconnect: subDistrictId === null || undefined,
subDistrict: { // },
connect: subDistrictId ? { id: subDistrictId } : undefined, // updatedBy: { connect: { id: req.user.sub } },
disconnect: subDistrictId === null || undefined, // },
}, // });
updatedBy: { connect: { id: req.user.sub } }, //
}, // this.setStatus(HttpStatus.CREATED);
}); //
// return record;
this.setStatus(HttpStatus.CREATED);
return record;
} }
@Delete("{branchId}") @Delete("{branchId}")

View file

@ -15,7 +15,7 @@ import {
} from "tsoa"; } from "tsoa";
import { RequestWithUser } from "../interfaces/user"; import { RequestWithUser } from "../interfaces/user";
import prisma from "../db"; import prisma from "../db";
import minio, { presignedGetObjectIfExist } from "../services/minio"; import minio, { deleteFolder, presignedGetObjectIfExist } from "../services/minio";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
@ -37,95 +37,31 @@ const MANAGE_ROLES = [
export type CustomerCreate = { export type CustomerCreate = {
registeredBranchId?: string; registeredBranchId?: string;
code: string;
status?: Status; status?: Status;
personName: string;
personNameEN?: string;
customerType: CustomerType; customerType: CustomerType;
customerName: string; namePrefix: string;
customerNameEN: string; firstName: string;
taxNo?: string | null; firstNameEN?: string;
customerBranch?: { lastName: string;
status?: Status; lastNameEN?: string;
gender: string;
legalPersonNo: string; birthDate: Date;
branchNo: number;
taxNo: string | null;
name: string;
nameEN: string;
addressEN: string;
address: string;
zipCode: string;
email: string;
telephoneNo: string;
registerName: string;
registerDate: Date;
authorizedCapital: string;
employmentOffice: string;
bussinessType: string;
bussinessTypeEN: string;
jobPosition: string;
jobPositionEN: string;
jobDescription: string;
saleEmployee: string;
payDate: Date;
wageRate: number;
subDistrictId?: string | null;
districtId?: string | null;
provinceId?: string | null;
}[];
}; };
export type CustomerUpdate = { export type CustomerUpdate = {
registeredBranchId?: string; registeredBranchId?: string;
status?: "ACTIVE" | "INACTIVE"; status?: "ACTIVE" | "INACTIVE";
personName?: string;
personNameEN?: string;
customerType?: CustomerType;
customerName?: string;
customerNameEN?: string;
taxNo?: string | null;
customerBranch?: {
id?: string;
status?: Status; customerType: CustomerType;
namePrefix: string;
legalPersonNo: string; firstName: string;
firstNameEN?: string;
branchNo: number; lastName: string;
taxNo: string | null; lastNameEN?: string;
name: string; gender: string;
nameEN: string; birthDate: Date;
addressEN: string;
address: string;
zipCode: string;
email: string;
telephoneNo: string;
registerName: string;
registerDate: Date;
authorizedCapital: string;
employmentOffice: string;
bussinessType: string;
bussinessTypeEN: string;
jobPosition: string;
jobPositionEN: string;
jobDescription: string;
saleEmployee: string;
payDate: Date;
wageRate: number;
subDistrictId?: string | null;
districtId?: string | null;
provinceId?: string | null;
}[];
}; };
function imageLocation(id: string) { function imageLocation(id: string) {
@ -167,16 +103,17 @@ export class CustomerController extends Controller {
) { ) {
const filterStatus = (val?: Status) => { const filterStatus = (val?: Status) => {
if (!val) return {}; if (!val) return {};
return val !== Status.CREATED && val !== Status.ACTIVE return val !== Status.CREATED && val !== Status.ACTIVE
? { status: val } ? { status: val }
: { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] };
}; };
const where = { const where = {
OR: [ OR: [
{ customerName: { contains: query }, customerType, ...filterStatus(status) }, { namePrefix: { contains: query } },
{ customerNameEN: { contains: query }, customerType, ...filterStatus(status) }, { firstName: { contains: query } },
{ firstNameEN: { contains: query } },
], ],
AND: { customerType, ...filterStatus(status) },
} satisfies Prisma.CustomerWhereInput; } satisfies Prisma.CustomerWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -190,9 +127,7 @@ export class CustomerController extends Controller {
district: true, district: true,
subDistrict: true, subDistrict: true,
}, },
orderBy: { orderBy: { createdAt: "asc" },
branchNo: "asc",
},
} }
: undefined, : undefined,
createdBy: true, createdBy: true,
@ -206,21 +141,7 @@ export class CustomerController extends Controller {
prisma.customer.count({ where }), prisma.customer.count({ where }),
]); ]);
return { return { result, page, pageSize, total };
result: await Promise.all(
result.map(async (v) => ({
...v,
imageUrl: await presignedGetObjectIfExist(
MINIO_BUCKET,
imageLocation(v.id),
12 * 60 * 60,
),
})),
),
page,
pageSize,
total,
};
} }
@Get("{customerId}") @Get("{customerId}")
@ -234,71 +155,27 @@ export class CustomerController extends Controller {
district: true, district: true,
subDistrict: true, subDistrict: true,
}, },
orderBy: { branchNo: "asc" }, orderBy: { createdAt: "asc" },
}, },
createdBy: true, createdBy: true,
updatedBy: true, updatedBy: true,
}, },
where: { id: customerId }, where: { id: customerId },
}); });
if (!record)
if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound"); throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound");
return Object.assign(record, { }
imageUrl: await presignedGetObjectIfExist( return record;
MINIO_BUCKET,
imageLocation(record.id),
12 * 60 * 60,
),
});
} }
@Post() @Post()
@Security("keycloak", MANAGE_ROLES) @Security("keycloak", MANAGE_ROLES)
async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) { async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) {
const { customerBranch, ...payload } = body; const [branch] = await prisma.$transaction([
const provinceId = body.customerBranch?.reduce<string[]>((acc, cur) => {
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId);
return acc;
}, []);
const districtId = body.customerBranch?.reduce<string[]>((acc, cur) => {
if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId);
return acc;
}, []);
const subDistrictId = body.customerBranch?.reduce<string[]>((acc, cur) => {
if (cur.subDistrictId && !acc.includes(cur.subDistrictId))
return acc.concat(cur.subDistrictId);
return acc;
}, []);
const [province, district, subDistrict, branch] = await prisma.$transaction([
prisma.province.findMany({ where: { id: { in: provinceId } } }),
prisma.district.findMany({ where: { id: { in: districtId } } }),
prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }),
prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), prisma.branch.findFirst({ where: { id: body.registeredBranchId } }),
]); ]);
if (provinceId && province.length !== provinceId?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some province cannot be found.",
"relationProvinceNotFound",
);
}
if (districtId && district.length !== districtId?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some district cannot be found.",
"relationDistrictNotFound",
);
}
if (subDistrictId && subDistrict.length !== subDistrictId?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some sub district cannot be found.",
"relationSubDistrictNotFound",
);
}
if (!!body.registeredBranchId && !branch) { if (!!body.registeredBranchId && !branch) {
throw new HttpError( throw new HttpError(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
@ -306,27 +183,24 @@ export class CustomerController extends Controller {
"relationBranchNotFound", "relationBranchNotFound",
); );
} }
if (!body.registeredBranchId) { if (!body.registeredBranchId) {
body.registeredBranchId = undefined; body.registeredBranchId = undefined;
} }
const record = await prisma.$transaction( const record = await prisma.$transaction(
async (tx) => { async (tx) => {
body.code = body.code.toLocaleUpperCase(); await tx.branch.updateMany({
where: {
const exist = await tx.customer.findFirst({ id: body.registeredBranchId,
where: { code: body.code }, status: "CREATED",
},
data: {
status: "INACTIVE",
statusOrder: 1,
},
}); });
return await tx.customer.create({
if (exist) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Customer with same code already exists.",
"sameCustomerCodeExists",
);
}
return await prisma.customer.create({
include: { include: {
branch: { branch: {
include: { include: {
@ -339,20 +213,8 @@ export class CustomerController extends Controller {
updatedBy: true, updatedBy: true,
}, },
data: { data: {
...payload, ...body,
statusOrder: +(payload.status === "INACTIVE"), statusOrder: +(body.status === "INACTIVE"),
code: `${body.code}000000`,
branch: {
createMany: {
data:
customerBranch?.map((v) => ({
...v,
code: body.code,
createdByUserId: req.user.sub,
updatedByUserId: req.user.sub,
})) || [],
},
},
createdByUserId: req.user.sub, createdByUserId: req.user.sub,
updatedByUserId: req.user.sub, updatedByUserId: req.user.sub,
}, },
@ -363,18 +225,7 @@ export class CustomerController extends Controller {
this.setStatus(HttpStatus.CREATED); this.setStatus(HttpStatus.CREATED);
return Object.assign(record, { return record;
imageUrl: await presignedGetObjectIfExist(
MINIO_BUCKET,
imageLocation(record.id),
12 * 60 * 60,
),
imageUploadUrl: await minio.presignedPutObject(
MINIO_BUCKET,
imageLocation(record.id),
12 * 60 * 60,
),
});
} }
@Put("{customerId}") @Put("{customerId}")
@ -390,80 +241,25 @@ export class CustomerController extends Controller {
throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound"); throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound");
} }
const provinceId = body.customerBranch?.reduce<string[]>((acc, cur) => { const [branch] = await prisma.$transaction([
if (cur.provinceId && !acc.includes(cur.provinceId)) return acc.concat(cur.provinceId); prisma.customer.findUnique({ where: { id: customerId } }),
return acc; prisma.branch.findFirst({ where: { id: body.registeredBranchId } }),
}, []);
const districtId = body.customerBranch?.reduce<string[]>((acc, cur) => {
if (cur.districtId && !acc.includes(cur.districtId)) return acc.concat(cur.districtId);
return acc;
}, []);
const subDistrictId = body.customerBranch?.reduce<string[]>((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) { if (!!body.registeredBranchId && !branch) {
throw new HttpError( throw new HttpError(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
"Some province cannot be found.", "Branch cannot be found.",
"relationProvinceNotFound", "relationBranchNotFound",
);
}
if (districtId && district.length !== districtId?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some district cannot be found.",
"relationDistrictNotFound",
);
}
if (subDistrictId && subDistrict.length !== subDistrictId?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some sub district cannot be found.",
"relationSubDistrictNotFound",
); );
} }
const { customerBranch, ...payload } = body; if (!body.registeredBranchId) {
body.registeredBranchId = undefined;
const relation = await prisma.customerBranch.findMany({
where: {
customerId,
},
});
if (
customerBranch &&
relation.find((a) => !customerBranch.find((b) => a.id === b.id) && a.status !== "CREATED")
) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"One or more branch cannot be delete and is missing.",
"oneOrMoreBranchMissing",
);
} }
if ( const record = await prisma.$transaction(async (tx) => {
customerBranch && return await tx.customer.update({
relation.find((a) => customerBranch.find((b) => a.id !== b.id && a.branchNo === b.branchNo))
) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Branch cannot have same number.",
"oneOrMoreBranchNoExist",
);
}
const record = await prisma.customer
.update({
include: { include: {
branch: { branch: {
include: { include: {
@ -477,74 +273,14 @@ export class CustomerController extends Controller {
}, },
where: { id: customerId }, where: { id: customerId },
data: { data: {
...payload, ...body,
statusOrder: +(payload.status === "INACTIVE"), statusOrder: +(body.status === "INACTIVE"),
branch:
(customerBranch && {
deleteMany: {
id: {
notIn: customerBranch.map((v) => v.id).filter((v): v is string => !!v) || [],
},
status: Status.CREATED,
},
upsert: customerBranch.map((v) => ({
where: { id: v.id || "" },
create: {
...v,
code: `${customer.code}-${v.branchNo.toString().padStart(2, "0")}`,
createdByUserId: req.user.sub,
updatedByUserId: req.user.sub,
id: undefined,
},
update: {
...v,
code: undefined,
branchNo: undefined,
updatedByUserId: req.user.sub,
},
})),
}) ||
undefined,
updatedByUserId: req.user.sub, updatedByUserId: req.user.sub,
}, },
})
.then((v) => {
if (customerBranch) {
relation
.filter((a) => !customerBranch.find((b) => b.id === a.id))
.forEach((deleted) => {
new Promise<string[]>((resolve, reject) => {
const item: string[] = [];
const stream = minio.listObjectsV2(MINIO_BUCKET, `customer/${deleted.id}`);
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,
});
});
});
});
}
return v;
}); });
return Object.assign(record, {
imageUrl: await presignedGetObjectIfExist(
MINIO_BUCKET,
imageLocation(record.id),
12 * 60 * 60,
),
imageUploadUrl: await minio.presignedPutObject(
MINIO_BUCKET,
imageLocation(record.id),
12 * 60 * 60,
),
}); });
return record;
} }
@Delete("{customerId}") @Delete("{customerId}")
@ -560,24 +296,11 @@ export class CustomerController extends Controller {
throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "customerInUsed"); throw new HttpError(HttpStatus.FORBIDDEN, "Customer is in used.", "customerInUsed");
} }
return await prisma.customer.delete({ where: { id: customerId } }).then((v) => { return await prisma.customer
new Promise<string[]>((resolve, reject) => { .delete({ where: { id: customerId } })
const item: string[] = []; .then(
async (data) => await deleteFolder(MINIO_BUCKET, `customer/${customerId}`).then(() => data),
const stream = minio.listObjectsV2(MINIO_BUCKET, `customer/${customerId}`); );
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,
});
});
});
return v;
});
} }
@Get("{customerId}/image") @Get("{customerId}/image")