diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index afd03df..8cb5d53 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,129 @@ export class StatsController extends Controller { }); return data; } + + @Get("customer-dept") + async reportCustomerDept(@Request() req: RequestWithUser) { + let query = prisma.$kysely + .selectFrom("Quotation") + .leftJoin("Invoice", "Quotation.id", "Invoice.quotationId") + .leftJoin("Payment", "Invoice.id", "Payment.invoiceId") + .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.code as quotationCode", + "Quotation.finalPrice as quotationValue", + ]) + .select(["Payment.paymentStatus"]) + .selectAll(["Invoice"]) + .distinctOn("Quotation.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; + quotationCode: 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; 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), + }; + + if (!exists) { + return acc.concat({ + _quotation: [quotation], + customerBranch: item.customerBranch, + paid: item.paymentStatus === "PaymentSuccess" && item.amount ? item.amount : 0, + unpaid: quotation.value, + }); + } + + 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) => { + return { ...v, _quotation: undefined }; + }); + } }