feat: update quotation

This commit is contained in:
Methapon2001 2024-07-24 13:33:46 +07:00
parent 67c3ead7ec
commit 7d96d1afb6

View file

@ -89,7 +89,7 @@ type QuotationCreate = {
type QuotationUpdate = {
status?: "ACTIVE" | "INACTIVE";
payCondition: PayCondition;
payCondition?: PayCondition;
paySplitCount?: number;
paySplit?: Date[];
@ -124,16 +124,17 @@ type QuotationUpdate = {
}
)[];
customerBranchId: string;
customerId: string;
customerBranchId?: string;
customerId?: string;
urgent?: boolean;
service: {
service?: {
id: string;
// Other fields will come from original data
work: {
id: string;
excluded?: boolean;
// Name field will come from original data
product: {
id: string;
@ -485,12 +486,295 @@ export class QuotationController extends Controller {
@Body() body: QuotationUpdate,
) {
const record = await prisma.quotation.findUnique({
include: { customer: true },
where: { id: quotationId },
});
if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound");
}
const existingEmployee = body.worker?.filter((v) => typeof v === "string");
const serviceIdList = body.service?.map((v) => v.id);
const productIdList = body.service?.flatMap((a) =>
a.work.flatMap((b) => b.product.map((c) => c.id)),
);
const [customer, customerBranch, employee, service, product] = await prisma.$transaction(
async (tx) =>
await Promise.all([
tx.customer.findUnique({
where: { id: body.customerId },
}),
tx.customerBranch.findUnique({
include: { customer: true },
where: { id: body.customerBranchId },
}),
body.worker
? tx.employee.findMany({
where: { id: { in: existingEmployee } },
})
: null,
body.service
? tx.service.findMany({
include: { work: true },
where: { id: { in: serviceIdList } },
})
: null,
body.service
? tx.product.findMany({
where: { id: { in: productIdList } },
})
: null,
]),
);
if (serviceIdList?.length !== service?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some service cannot be found.",
"relationServiceNotFound",
);
}
if (productIdList?.length !== product?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some product cannot be found.",
"relationProductNotFound",
);
}
if (existingEmployee?.length !== employee?.length) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Some worker(employee) cannot be found.",
"relationWorkerNotFound",
);
}
if (!customer)
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Customer cannot be found.",
"relationCustomerNotFound",
);
if (!customerBranch)
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Customer Branch cannot be found.",
"relationCustomerBranchNotFound",
);
if (customerBranch.customerId !== customer.id)
throw new HttpError(
HttpStatus.BAD_REQUEST,
"Customer conflict with customer branch.",
"customerConflictCustomerBranch",
);
const { service: _service, worker: _worker, ...rest } = body;
return await prisma.$transaction(async (tx) => {
const sortedEmployeeId: string[] = [];
if (body.worker) {
const nonExistEmployee = body.worker.filter((v) => typeof v !== "string");
const lastEmployee = await tx.runningNo.upsert({
where: {
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
},
create: {
key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`,
value: 1,
},
update: { value: { increment: nonExistEmployee.length } },
});
const newEmployee = await Promise.all(
nonExistEmployee.map(async (v, i) =>
tx.employee.create({
data: {
...v,
code: `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${(lastEmployee.value + i).toString().padStart(4, "0")}`,
customerBranchId: customerBranch.id,
},
}),
),
);
while (body.worker.length > 0) {
const popExist = body.worker.shift();
if (typeof popExist === "string") sortedEmployeeId.push(popExist);
else {
const popNew = newEmployee.shift();
popNew && sortedEmployeeId.push(popNew.id);
}
}
}
const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 };
const restructureService = body.service?.flatMap((a) => {
const currentService = service?.find((b) => b.id === a.id);
if (!currentService) return []; // should not possible
return {
id: currentService.id,
name: currentService.name,
code: currentService.code,
detail: currentService.detail,
attributes: currentService.attributes as Prisma.JsonObject,
work: a.work.flatMap((c) => {
if (c.excluded) return [];
const currentWork = currentService.work.find((d) => d.id === c.id);
if (!currentWork) return []; // additional will get stripped
return {
id: currentWork.id,
order: currentWork.order,
name: currentWork.name,
attributes: currentWork.attributes as Prisma.JsonObject,
product: c.product.flatMap((e) => {
const currentProduct = product?.find((f) => f.id === e.id);
if (!currentProduct) return []; // should not possible
price.totalPrice += currentProduct.price;
price.totalDiscount += Math.round(currentProduct.price * e.discount * 100) / 100;
price.totalVat +=
Math.round(
(currentProduct.price - currentProduct.price * e.discount) *
(e.vat === undefined ? 0.07 : e.vat) *
100,
) / 100;
return {
...e,
vat: e.vat === undefined ? 0.07 : e.vat,
pricePerUnit: currentProduct.price,
};
}),
};
}),
};
});
const quotation = await tx.quotation.update({
include: {
worker: {
include: { employee: true },
},
},
where: { id: quotationId },
data: {
...rest,
statusOrder: +(rest.status === "INACTIVE"),
code: "",
worker:
sortedEmployeeId.length > 0
? {
deleteMany: { id: { notIn: sortedEmployeeId } },
createMany: {
skipDuplicates: true,
data: sortedEmployeeId.map((v, i) => ({
no: i,
code: "",
employeeId: v,
})),
},
}
: undefined,
totalPrice: body.service ? price.totalPrice : undefined,
totalDiscount: body.service ? price.totalDiscount : undefined,
vat: body.service ? price.totalVat : undefined,
vatExcluded: body.service ? 0 : undefined,
finalPrice: body.service ? price.totalPrice - price.totalDiscount : undefined,
paySplit: rest.paySplit
? {
deleteMany: {},
createMany: {
data: (rest.paySplit || []).map((v, i) => ({
no: i + 1,
date: v,
})),
},
}
: undefined,
service: body.service ? { deleteMany: {} } : undefined,
updatedByUserId: req.user.sub,
},
});
if (restructureService)
await Promise.all(
restructureService.map(async (a) => {
const { id: _currentServiceId } = await tx.quotationService.create({
data: {
code: a.code,
name: a.name,
detail: a.detail,
attributes: a.attributes,
quotationId: quotation.id,
refServiceId: a.id,
},
});
await Promise.all(
a.work.map(async (b) => {
await tx.quotationServiceWork.create({
data: {
order: b.order,
name: b.name,
attributes: b.attributes,
serviceId: _currentServiceId,
productOnWork: {
createMany: {
data: b.product.map((v, i) => ({
productId: v.id,
order: i + 1,
vat: v.vat,
amount: v.amount,
discount: v.discount,
pricePerUnit: v.pricePerUnit,
})),
},
},
},
});
}),
);
}),
);
return await tx.quotation.findUnique({
include: {
service: {
include: {
work: {
include: {
productOnWork: {
include: { product: true },
},
},
},
},
},
paySplit: true,
worker: true,
customerBranch: {
include: { customer: true },
},
_count: {
select: { service: true },
},
},
where: { id: quotation.id },
});
});
}
@Delete("{quotationId}")