From 5f499eeda5af9d6380b67e2e2dbbcb38d7c92239 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 10 Mar 2025 09:55:55 +0700 Subject: [PATCH 1/2] feat: customer dept --- src/controllers/00-stats-controller.ts | 125 ++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index afd03df..1f32345 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -5,16 +5,19 @@ import { ProductGroup, QuotationStatus, RequestWorkStatus, + PaymentStatus, User, + Invoice, + CustomerType, } from "@prisma/client"; import { Controller, Get, Query, Request, Route, Security, Tags } from "tsoa"; import prisma from "../db"; import { createPermCondition } from "../services/permission"; import { RequestWithUser } from "../interfaces/user"; -import { PaymentStatus } from "../generated/kysely/types"; import { precisionRound } from "../utils/arithmetic"; import dayjs from "dayjs"; import { json2csv } from "json-2-csv"; +import { isSystem } from "../utils/keycloak"; const permissionCondCompany = createPermCondition((_) => true); @@ -638,4 +641,124 @@ export class StatsController extends Controller { }); return data; } + + @Get("customer-dept") + async reportCustomerDept(@Request() req: RequestWithUser) { + let query = prisma.$kysely + .selectFrom("Invoice") + .leftJoin("Payment", "Invoice.id", "Payment.invoiceId") + .leftJoin("Quotation", "Quotation.id", "Invoice.quotationId") + .leftJoin("CustomerBranch", "CustomerBranch.id", "Quotation.customerBranchId") + .leftJoin("Customer", "Customer.id", "CustomerBranch.customerId") + .select([ + "CustomerBranch.id as customerBranchId", + "CustomerBranch.code as customerBranchCode", + "CustomerBranch.registerName as customerBranchRegisterName", + "CustomerBranch.registerNameEN as customerBranchRegisterNameEN", + "CustomerBranch.firstName as customerBranchFirstName", + "CustomerBranch.firstNameEN as customerBranchFirstNameEN", + "CustomerBranch.lastName as customerBranchFirstName", + "CustomerBranch.lastNameEN as customerBranchFirstNameEN", + "Customer.customerType", + "Quotation.id as quotationId", + "Quotation.finalPrice as quotationValue", + ]) + .select(["Payment.paymentStatus"]) + .selectAll(["Invoice"]) + .distinctOn("Invoice.id"); + + if (!isSystem(req.user)) { + query = query.where(({ eb, exists }) => + exists( + eb + .selectFrom("Branch") + .leftJoin("BranchUser", "BranchUser.branchId", "Branch.id") + .leftJoin("Branch as SubBranch", "SubBranch.headOfficeId", "Branch.id") + .leftJoin("BranchUser as SubBranchUser", "SubBranchUser.branchId", "SubBranch.id") + .leftJoin("Branch as HeadBranch", "HeadBranch.id", "Branch.id") + .leftJoin("BranchUser as HeadBranchUser", "HeadBranchUser.branchId", "HeadBranch.id") + .leftJoin("Branch as SubHeadBranch", "SubHeadBranch.headOfficeId", "HeadBranch.id") + .leftJoin( + "BranchUser as SubHeadBranchUser", + "SubHeadBranchUser.branchId", + "SubHeadBranch.id", + ) + .where((eb) => { + const cond = [ + eb("BranchUser.userId", "=", req.user.sub), // NOTE: if user belong to current branch. + eb("SubBranchUser.userId", "=", req.user.sub), // NOTE: if user belong to branch under current branch. + eb("HeadBranchUser.userId", "=", req.user.sub), // NOTE: if the current branch is under head branch user belong to. + eb("SubHeadBranchUser.userId", "=", req.user.sub), // NOTE: if the current branch is under the same head branch user belong to. + ]; + return eb.or(cond); + }) + .select("Branch.id"), + ), + ); + } + + const ret = await query.execute(); + const arr = ret.map((item) => { + const data: Record = {}; + for (const [key, val] of Object.entries(item)) { + if (key.startsWith("customerBranch")) { + if (!data["customerBranch"]) data["customerBranch"] = {}; + data["customerBranch"][key.slice(14).slice(0, 1).toLowerCase() + key.slice(14).slice(1)] = + val; + } else if (key.startsWith("customerType")) { + data["customerBranch"]["customer"] = { customerType: val }; + } else { + data[key as keyof typeof data] = val; + } + } + return data as Invoice & { + quotationId: string; + quotationValue: number; + paymentStatus: PaymentStatus; + customerBranch: CustomerBranch & { customer: { customerType: CustomerType } }; + }; + }); + + return arr + .reduce< + { + paid: number; + unpaid: number; + customerBranch: CustomerBranch & { customer: { customerType: CustomerType } }; + _quotation: { id: string; value: number }[]; + }[] + >((acc, item) => { + const exists = acc.find((v) => v.customerBranch.id === item.customerBranch.id); + + const quotation = { + id: item.quotationId, + value: + item.quotationValue - + (item.paymentStatus === "PaymentSuccess" && item.amount ? item.amount : 0), + }; + + if (!exists) { + return acc.concat({ + _quotation: [quotation], + customerBranch: item.customerBranch, + paid: item.paymentStatus === "PaymentSuccess" && item.amount ? item.amount : 0, + unpaid: quotation.value, + }); + } else { + const same = exists._quotation.find((v) => v.id === item.quotationId); + + if (item.paymentStatus === "PaymentSuccess" && item.amount) { + exists.paid += item.amount; + + if (same) same.value -= item.amount; + else exists._quotation.push(quotation); + } + + exists.unpaid = exists._quotation.reduce((a, c) => a + c.value, 0); + } + + return acc; + }, []) + .map((v) => ({ ...v, _quotation: undefined })); + } } From 250d69d122ea3f79c4bf597d4bfebeca18f52885 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:25:01 +0700 Subject: [PATCH 2/2] fix: wrong stats --- src/controllers/00-stats-controller.ts | 37 +++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index 1f32345..8cb5d53 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -645,9 +645,9 @@ export class StatsController extends Controller { @Get("customer-dept") async reportCustomerDept(@Request() req: RequestWithUser) { let query = prisma.$kysely - .selectFrom("Invoice") + .selectFrom("Quotation") + .leftJoin("Invoice", "Quotation.id", "Invoice.quotationId") .leftJoin("Payment", "Invoice.id", "Payment.invoiceId") - .leftJoin("Quotation", "Quotation.id", "Invoice.quotationId") .leftJoin("CustomerBranch", "CustomerBranch.id", "Quotation.customerBranchId") .leftJoin("Customer", "Customer.id", "CustomerBranch.customerId") .select([ @@ -661,11 +661,12 @@ export class StatsController extends Controller { "CustomerBranch.lastNameEN as customerBranchFirstNameEN", "Customer.customerType", "Quotation.id as quotationId", + "Quotation.code as quotationCode", "Quotation.finalPrice as quotationValue", ]) .select(["Payment.paymentStatus"]) .selectAll(["Invoice"]) - .distinctOn("Invoice.id"); + .distinctOn("Quotation.id"); if (!isSystem(req.user)) { query = query.where(({ eb, exists }) => @@ -713,6 +714,7 @@ export class StatsController extends Controller { } return data as Invoice & { quotationId: string; + quotationCode: string; quotationValue: number; paymentStatus: PaymentStatus; customerBranch: CustomerBranch & { customer: { customerType: CustomerType } }; @@ -725,13 +727,14 @@ export class StatsController extends Controller { paid: number; unpaid: number; customerBranch: CustomerBranch & { customer: { customerType: CustomerType } }; - _quotation: { id: string; value: number }[]; + _quotation: { id: string; code: string; value: number }[]; }[] >((acc, item) => { const exists = acc.find((v) => v.customerBranch.id === item.customerBranch.id); const quotation = { id: item.quotationId, + code: item.quotationCode, value: item.quotationValue - (item.paymentStatus === "PaymentSuccess" && item.amount ? item.amount : 0), @@ -744,21 +747,23 @@ export class StatsController extends Controller { paid: item.paymentStatus === "PaymentSuccess" && item.amount ? item.amount : 0, unpaid: quotation.value, }); - } else { - const same = exists._quotation.find((v) => v.id === item.quotationId); - - if (item.paymentStatus === "PaymentSuccess" && item.amount) { - exists.paid += item.amount; - - if (same) same.value -= item.amount; - else exists._quotation.push(quotation); - } - - exists.unpaid = exists._quotation.reduce((a, c) => a + c.value, 0); } + const same = exists._quotation.find((v) => v.id === item.quotationId); + + if (item.paymentStatus === "PaymentSuccess" && item.amount) { + exists.paid += item.amount; + if (same) same.value -= item.amount; + } + + if (!same) exists._quotation.push(quotation); + + exists.unpaid = exists._quotation.reduce((a, c) => a + c.value, 0); + return acc; }, []) - .map((v) => ({ ...v, _quotation: undefined })); + .map((v) => { + return { ...v, _quotation: undefined }; + }); } }