feat: create quotation endpoint (test required)

This commit is contained in:
Methapon2001 2024-07-23 10:28:47 +07:00
parent d2e7a89211
commit e509cd1fd5
2 changed files with 147 additions and 31 deletions

View file

@ -878,7 +878,7 @@ model QuotationServiceWorkProduct {
product Product @relation(fields: [productId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String productId String
vat Int vat Float
amount Int amount Int
discount Float discount Float
pricePerUnit Float pricePerUnit Float

View file

@ -1,4 +1,4 @@
import { PayCondition, Status } from "@prisma/client"; import { PayCondition, Prisma, Status } from "@prisma/client";
import { import {
Body, Body,
Controller, Controller,
@ -70,7 +70,16 @@ type QuotationCreate = {
product: { product: {
id: string; id: string;
amount: number; amount: number;
/**
* @maximum 1
* @minimum 0
*/
discount: number; discount: number;
/**
* @maximum 1
* @minimum 0
*/
vat?: number;
}[]; }[];
}[]; }[];
}[]; }[];
@ -127,8 +136,20 @@ type QuotationUpdate = {
// Name field will come from original data // Name field will come from original data
product: { product: {
id: string; id: string;
/**
* @isInt
*/
amount: number; amount: number;
/**
* @maximum 1
* @minimum 0
*/
discount: number; discount: number;
/**
* @maximum 1
* @minimum 0
*/
vat: number;
}[]; }[];
}[]; }[];
}[]; }[];
@ -216,9 +237,7 @@ export class QuotationController extends Controller {
where: { id: { in: existingEmployee } }, where: { id: { in: existingEmployee } },
}), }),
prisma.service.findMany({ prisma.service.findMany({
include: { include: { work: true },
work: true,
},
where: { id: { in: serviceIdList } }, where: { id: { in: serviceIdList } },
}), }),
prisma.product.findMany({ prisma.product.findMany({
@ -226,6 +245,20 @@ export class QuotationController extends Controller {
}), }),
]); ]);
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 (!customer) if (!customer)
throw new HttpError( throw new HttpError(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
@ -238,6 +271,12 @@ export class QuotationController extends Controller {
"Customer Branch cannot be found.", "Customer Branch cannot be found.",
"relationCustomerBranchNotFound", "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; const { service: _service, worker: _worker, ...rest } = body;
@ -275,7 +314,8 @@ export class QuotationController extends Controller {
} }
} }
const price = { totalPrice: 0, totalDiscount }; const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 };
const restructureService = body.service.flatMap((a) => { const restructureService = body.service.flatMap((a) => {
const currentService = service.find((b) => b.id === a.id); const currentService = service.find((b) => b.id === a.id);
@ -286,6 +326,7 @@ export class QuotationController extends Controller {
name: currentService.name, name: currentService.name,
code: currentService.code, code: currentService.code,
detail: currentService.detail, detail: currentService.detail,
attributes: currentService.attributes as Prisma.JsonObject,
work: a.work.flatMap((c) => { work: a.work.flatMap((c) => {
const currentWork = currentService.work.find((d) => d.id === c.id); const currentWork = currentService.work.find((d) => d.id === c.id);
@ -295,12 +336,27 @@ export class QuotationController extends Controller {
id: currentWork.id, id: currentWork.id,
order: currentWork.order, order: currentWork.order,
name: currentWork.name, name: currentWork.name,
attributes: currentWork.attributes as Prisma.JsonObject,
product: c.product.flatMap((e) => { product: c.product.flatMap((e) => {
const currentProduct = product.find((f) => f.id === e.id); const currentProduct = product.find((f) => f.id === e.id);
if (!currentProduct) return []; // should not possible if (!currentProduct) return []; // should not possible
return { ...e, pricePerUnit: currentProduct.price }; 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;
console.log(e.vat);
return {
...e,
vat: e.vat === undefined ? 0.07 : e.vat,
pricePerUnit: currentProduct.price,
};
}), }),
}; };
}), }),
@ -308,6 +364,9 @@ export class QuotationController extends Controller {
}); });
const quotation = await tx.quotation.create({ const quotation = await tx.quotation.create({
include: {
service: true,
},
data: { data: {
...rest, ...rest,
statusOrder: +(rest.status === "INACTIVE"), statusOrder: +(rest.status === "INACTIVE"),
@ -321,25 +380,14 @@ export class QuotationController extends Controller {
})), })),
}, },
}, },
service: { totalPrice: price.totalPrice,
createMany: { totalDiscount: price.totalDiscount,
data: body.service.flatMap((a) => {
const src = service.find((b) => b.id == a.id); vat: price.totalVat,
return src
? {
id: a.id,
name: src.name,
code: src.code,
detail: src.detail,
}
: []; // should not be possible to not found.
}),
},
},
totalPrice,
totalDiscount,
vatExcluded: 0, vatExcluded: 0,
vat: 0,
finalPrice: price.totalPrice - price.totalDiscount,
paySplit: { paySplit: {
createMany: { createMany: {
data: (rest.paySplit || []).map((v, i) => ({ data: (rest.paySplit || []).map((v, i) => ({
@ -348,15 +396,83 @@ export class QuotationController extends Controller {
})), })),
}, },
}, },
createdByUserId: req.user.sub,
updatedByUserId: req.user.sub,
}, },
}); });
// await tx.quotationServiceWork.createMany({
// data: service.flatMap((a) =>
// a.work.map((b) => ({ id: b.id, order: b.order, name: b.name, serviceId: a.id })),
// ),
// });
throw new Error("Test Quotation Structure"); await Promise.all(
restructureService.map(async (a) => {
await tx.quotationService.create({
data: {
id: a.id,
code: a.code,
name: a.name,
detail: a.detail,
attributes: a.attributes,
quotationId: quotation.id,
},
});
await Promise.all(
a.work.map(async (b) => {
await tx.quotationServiceWork.create({
data: {
id: b.id,
order: b.order,
name: b.name,
attributes: b.attributes,
serviceId: a.id,
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,
})),
},
},
},
});
}),
);
}),
);
const result = 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 },
});
console.log(JSON.stringify(result, null, 2));
// console.log("Re-Structure:", JSON.stringify(restructureService, null, 2));
// console.log("Price:", JSON.stringify(price, null, 2));
throw new Error("");
}); });
} }