feat: profit report
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 6s
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 6s
This commit is contained in:
parent
54dba11dc4
commit
b4df1a5d4e
1 changed files with 157 additions and 83 deletions
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue