diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index 0271acb..e739d44 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -1,3 +1,4 @@ +import config from "../config.json"; import { Customer, CustomerBranch, @@ -11,9 +12,12 @@ 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"; const permissionCondCompany = createPermCondition((_) => true); +const VAT_DEFAULT = config.vat; + @Route("/api/v1/report") @Security("keycloak") @Tags("Report") @@ -127,30 +131,73 @@ export class StatsController extends Controller { @Query() startDate?: Date, @Query() endDate?: Date, ) { - const record = await prisma.product.findMany({ - include: { - quotationProductServiceList: { - include: { - quotation: true, + await prisma.$transaction(async (tx) => { + const record = await tx.product.findMany({ + include: { + quotationProductServiceList: { + include: { + quotation: true, + }, }, }, - }, - select: { - id: true, - code: true, - name: true, - createdAt: true, - updatedAt: true, - _count: { - select: { - quotationProductServiceList: { - where: { - quotation: { - quotationStatus: { + select: { + id: true, + code: true, + name: true, + createdAt: true, + updatedAt: true, + _count: { + select: { + quotationProductServiceList: { + where: { + quotation: { + quotationStatus: { + in: [ + QuotationStatus.PaymentInProcess, + QuotationStatus.PaymentSuccess, + QuotationStatus.ProcessComplete, + ], + }, + }, + }, + }, + }, + }, + }, + where: { + quotationProductServiceList: { + some: { + quotation: { createdAt: { gte: startDate, lte: endDate } }, + }, + }, + productGroup: { registeredBranch: { OR: permissionCondCompany(req.user) } }, + }, + orderBy: { + quotationProductServiceList: { _count: "desc" }, + }, + take: limit, + }); + + const doing = await tx.quotationProductServiceList.groupBy({ + _count: true, + by: "productId", + where: { + quotation: { + createdAt: { gte: startDate, lte: endDate }, + registeredBranch: { OR: permissionCondCompany(req.user) }, + }, + productId: { in: record.map((v) => v.id) }, + requestWork: { + some: { + stepStatus: { + some: { + workStatus: { in: [ - QuotationStatus.PaymentInProcess, - QuotationStatus.PaymentSuccess, - QuotationStatus.ProcessComplete, + RequestWorkStatus.Pending, + RequestWorkStatus.InProgress, + RequestWorkStatus.Validate, + RequestWorkStatus.Completed, + RequestWorkStatus.Ended, ], }, }, @@ -158,72 +205,31 @@ export class StatsController extends Controller { }, }, }, - }, - where: { - quotationProductServiceList: { - some: { - quotation: { createdAt: { gte: startDate, lte: endDate } }, + }); + + const order = await tx.quotationProductServiceList.groupBy({ + _count: true, + by: "productId", + where: { + quotation: { + createdAt: { gte: startDate, lte: endDate }, + registeredBranch: { OR: permissionCondCompany(req.user) }, }, + productId: { in: record.map((v) => v.id) }, }, - productGroup: { registeredBranch: { OR: permissionCondCompany(req.user) } }, - }, - orderBy: { - quotationProductServiceList: { _count: "desc" }, - }, - take: limit, - }); + }); - const doing = await prisma.quotationProductServiceList.groupBy({ - _count: true, - by: "productId", - where: { - quotation: { - createdAt: { gte: startDate, lte: endDate }, - registeredBranch: { OR: permissionCondCompany(req.user) }, - }, - productId: { in: record.map((v) => v.id) }, - requestWork: { - some: { - stepStatus: { - some: { - workStatus: { - in: [ - RequestWorkStatus.Pending, - RequestWorkStatus.InProgress, - RequestWorkStatus.Validate, - RequestWorkStatus.Completed, - RequestWorkStatus.Ended, - ], - }, - }, - }, - }, - }, - }, + return record.map((v) => ({ + document: "product", + code: v.code, + name: v.name, + sale: v._count.quotationProductServiceList, + did: doing.find((item) => item.productId === v.id)?._count || 0, + order: order.find((item) => item.productId === v.id)?._count || 0, + createdAt: v.createdAt, + updatedAt: v.updatedAt, + })); }); - - const order = await prisma.quotationProductServiceList.groupBy({ - _count: true, - by: "productId", - where: { - quotation: { - createdAt: { gte: startDate, lte: endDate }, - registeredBranch: { OR: permissionCondCompany(req.user) }, - }, - productId: { in: record.map((v) => v.id) }, - }, - }); - - return record.map((v) => ({ - document: "product", - code: v.code, - name: v.name, - sale: v._count.quotationProductServiceList, - did: doing.find((item) => item.productId === v.id)?._count || 0, - order: order.find((item) => item.productId === v.id)?._count || 0, - createdAt: v.createdAt, - updatedAt: v.updatedAt, - })); } @Get("sale") @@ -306,4 +312,72 @@ export class StatsController extends Controller { { byProductGroup: [], bySale: [], byCustomer: [] }, ); } + + @Get("profit") + async profit( + @Request() req: RequestWithUser, + @Query() limit?: number, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const record = await prisma.quotationProductServiceList.findMany({ + include: { + product: { + select: { + agentPrice: true, + agentPriceCalcVat: true, + agentPriceVatIncluded: true, + serviceCharge: true, + serviceChargeCalcVat: true, + serviceChargeVatIncluded: true, + price: true, + calcVat: true, + vatIncluded: true, + }, + }, + quotation: { + select: { + agentPrice: true, + }, + }, + }, + where: { + quotation: { + quotationStatus: { + in: [ + QuotationStatus.PaymentInProcess, + QuotationStatus.PaymentSuccess, + QuotationStatus.ProcessComplete, + ], + }, + registeredBranch: { + OR: permissionCondCompany(req.user), + }, + }, + }, + take: limit, + }); + + let income = 0; + let expenses = 0; + let netProfit = 0; + + record.forEach((v) => { + const originalPrice = v.product.serviceCharge; + const productExpenses = precisionRound( + originalPrice + (v.product.serviceChargeVatIncluded ? 0 : originalPrice * VAT_DEFAULT), + ); + const finalPrice = v.pricePerUnit * v.amount * (1 + config.vat); + + income += finalPrice; + expenses += productExpenses; + netProfit += finalPrice - productExpenses; + }); + + return { + income: precisionRound(income), + expenses: precisionRound(expenses), + netProfit: precisionRound(netProfit), + }; + } }