diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0fa9294..8faedfe 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -878,7 +878,7 @@ model QuotationServiceWorkProduct { product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String - vat Int + vat Float amount Int discount Float pricePerUnit Float diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 57f7853..d988a27 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -1,4 +1,4 @@ -import { PayCondition, Status } from "@prisma/client"; +import { PayCondition, Prisma, Status } from "@prisma/client"; import { Body, Controller, @@ -70,7 +70,16 @@ type QuotationCreate = { product: { id: string; amount: number; + /** + * @maximum 1 + * @minimum 0 + */ discount: number; + /** + * @maximum 1 + * @minimum 0 + */ + vat?: number; }[]; }[]; }[]; @@ -127,8 +136,20 @@ type QuotationUpdate = { // Name field will come from original data product: { id: string; + /** + * @isInt + */ amount: number; + /** + * @maximum 1 + * @minimum 0 + */ discount: number; + /** + * @maximum 1 + * @minimum 0 + */ + vat: number; }[]; }[]; }[]; @@ -216,9 +237,7 @@ export class QuotationController extends Controller { where: { id: { in: existingEmployee } }, }), prisma.service.findMany({ - include: { - work: true, - }, + include: { work: true }, where: { id: { in: serviceIdList } }, }), 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) throw new HttpError( HttpStatus.BAD_REQUEST, @@ -238,6 +271,12 @@ export class QuotationController extends Controller { "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; @@ -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 currentService = service.find((b) => b.id === a.id); @@ -286,6 +326,7 @@ export class QuotationController extends Controller { name: currentService.name, code: currentService.code, detail: currentService.detail, + attributes: currentService.attributes as Prisma.JsonObject, work: a.work.flatMap((c) => { const currentWork = currentService.work.find((d) => d.id === c.id); @@ -295,12 +336,27 @@ export class QuotationController extends Controller { 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 - 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({ + include: { + service: true, + }, data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), @@ -321,25 +380,14 @@ export class QuotationController extends Controller { })), }, }, - service: { - createMany: { - data: body.service.flatMap((a) => { - const src = service.find((b) => b.id == a.id); - return src - ? { - id: a.id, - name: src.name, - code: src.code, - detail: src.detail, - } - : []; // should not be possible to not found. - }), - }, - }, - totalPrice, - totalDiscount, + totalPrice: price.totalPrice, + totalDiscount: price.totalDiscount, + + vat: price.totalVat, vatExcluded: 0, - vat: 0, + + finalPrice: price.totalPrice - price.totalDiscount, + paySplit: { createMany: { 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(""); }); }