forked from admin/deShanXiao
初始版本,目前线上可用
This commit is contained in:
121
backEnd/src/router/stats/controller/dayIncome.ts
Normal file
121
backEnd/src/router/stats/controller/dayIncome.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
// controllers/paymentStats.ts
|
||||
import { Request, Response } from "express";
|
||||
import { getRepository } from "typeorm";
|
||||
import dayjs from "dayjs";
|
||||
import CheckoutPaymentRecords from "@/entity/CheckoutPayment";
|
||||
import PaymentRecord from "@/entity/Payment";
|
||||
import DeceasedRetail from "@/entity/DeceasedRetail";
|
||||
import parseRangDate, { handleError } from "@/util/globalMethods";
|
||||
|
||||
// 类型定义
|
||||
type PaymentStats = {
|
||||
cashAmount: number;
|
||||
unionPayAmount: number;
|
||||
cardAmount: number;
|
||||
publicTransferAmount: number;
|
||||
workshopPayment: number;
|
||||
};
|
||||
|
||||
type StatsResponse = {
|
||||
retail: PaymentStats; // 零售单统计(来自PaymentRecord)
|
||||
service: PaymentStats; // 服务单统计(来自CheckoutPaymentRecords)
|
||||
total: PaymentStats; // 合并统计
|
||||
};
|
||||
|
||||
export const getPaymentStats = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// 参数处理
|
||||
const { startDate, endDate } = parseRangDate(req);
|
||||
|
||||
// 并行获取统计结果
|
||||
const [retailStats, serviceStats] = await Promise.all([
|
||||
getRetailStats(startDate, endDate),
|
||||
getServiceStats(startDate, endDate),
|
||||
]);
|
||||
|
||||
// 构建响应
|
||||
const response: StatsResponse = {
|
||||
retail: retailStats,
|
||||
service: serviceStats,
|
||||
total: {
|
||||
cashAmount: retailStats.cashAmount + serviceStats.cashAmount,
|
||||
unionPayAmount:
|
||||
retailStats.unionPayAmount + serviceStats.unionPayAmount,
|
||||
cardAmount: retailStats.cardAmount + serviceStats.cardAmount,
|
||||
publicTransferAmount:
|
||||
retailStats.publicTransferAmount + serviceStats.publicTransferAmount,
|
||||
workshopPayment:
|
||||
retailStats.workshopPayment + serviceStats.workshopPayment,
|
||||
},
|
||||
};
|
||||
|
||||
res.json({ code: 200, data: response });
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
// 修改后的获取零售单统计
|
||||
const getRetailStats = async (
|
||||
start: string,
|
||||
end: string
|
||||
): Promise<PaymentStats> => {
|
||||
const result = await getRepository(PaymentRecord)
|
||||
.createQueryBuilder("payment")
|
||||
.select([
|
||||
"SUM(payment.cash_amount) AS cashAmount",
|
||||
"SUM(payment.union_pay_amount) AS unionPayAmount",
|
||||
"SUM(payment.card_amount) AS cardAmount",
|
||||
"SUM(payment.public_transfer_amount) AS publicTransferAmount",
|
||||
"SUM(payment.workshop_payment) AS workshopPayment",
|
||||
])
|
||||
.where("payment.checkout_date BETWEEN :start AND :end", { start, end })
|
||||
.andWhere(
|
||||
`(payment.deceased_retail_id IN (
|
||||
SELECT id
|
||||
FROM deceased_retail
|
||||
WHERE retail_state = 1
|
||||
) OR payment.no_deceased_retail_id IN (
|
||||
SELECT id
|
||||
FROM deceased_retail
|
||||
WHERE retail_state = 1
|
||||
))`
|
||||
)
|
||||
.getRawOne();
|
||||
|
||||
return formatStats(result);
|
||||
};
|
||||
|
||||
// 修改后的获取服务单统计
|
||||
const getServiceStats = async (
|
||||
start: string,
|
||||
end: string
|
||||
): Promise<PaymentStats> => {
|
||||
const result = await getRepository(CheckoutPaymentRecords)
|
||||
.createQueryBuilder("cpr")
|
||||
.innerJoin(
|
||||
DeceasedRetail,
|
||||
"dr",
|
||||
"dr.id = cpr.checkout_retail_id AND dr.retail_state = 1"
|
||||
)
|
||||
.select([
|
||||
"SUM(cpr.cash_amount) AS cashAmount",
|
||||
"SUM(cpr.union_pay_amount) AS unionPayAmount",
|
||||
"SUM(cpr.card_amount) AS cardAmount",
|
||||
"SUM(cpr.public_transfer_amount) AS publicTransferAmount",
|
||||
"SUM(cpr.workshop_payment) AS workshopPayment",
|
||||
])
|
||||
.where("cpr.checkout_date BETWEEN :start AND :end", { start, end })
|
||||
.getRawOne();
|
||||
|
||||
return formatStats(result);
|
||||
};
|
||||
|
||||
/** 格式化统计结果 */
|
||||
const formatStats = (raw: any): PaymentStats => ({
|
||||
cashAmount: Number(raw?.cashAmount || 0),
|
||||
unionPayAmount: Number(raw?.unionPayAmount || 0),
|
||||
cardAmount: Number(raw?.cardAmount || 0),
|
||||
publicTransferAmount: Number(raw?.publicTransferAmount || 0),
|
||||
workshopPayment: Number(raw?.workshopPayment || 0),
|
||||
});
|
||||
199
backEnd/src/router/stats/controller/salesDetail.ts
Normal file
199
backEnd/src/router/stats/controller/salesDetail.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import { Request, Response } from "express";
|
||||
import { getRepository } from "typeorm";
|
||||
import dayjs from "dayjs";
|
||||
import DeceasedRetail from "@/entity/DeceasedRetail";
|
||||
import SeletedServiceList from "@/entity/SeletedServiceList";
|
||||
import Deceased from "@/entity/Deceased";
|
||||
import { handleError } from "@/util/globalMethods";
|
||||
|
||||
interface QueryParams {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
serviceName?: string;
|
||||
deceasedName?: string;
|
||||
pageSize?: number;
|
||||
pageNumber?: number;
|
||||
guide?: string;
|
||||
familyName: string;
|
||||
}
|
||||
|
||||
interface SalesDetailItem {
|
||||
id: string;
|
||||
checkoutDate: string;
|
||||
deceasedName: string;
|
||||
gender: string;
|
||||
age: number;
|
||||
idCard: string;
|
||||
contactPerson: string;
|
||||
contactPhone: string;
|
||||
serviceName: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
amount: number;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export const getSalesDetails = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const params = parseQueryParams(
|
||||
req.method === "GET" ? req.query : req.body
|
||||
);
|
||||
const pageSize = Number(params.pageSize) || 10;
|
||||
const pageNumber = Number(params.pageNumber) || 1;
|
||||
|
||||
// 第一步:查询 SeletedServiceList 和 DeceasedRetail
|
||||
const initialQuery = getRepository(SeletedServiceList)
|
||||
.createQueryBuilder("ssl")
|
||||
.innerJoinAndMapOne(
|
||||
"ssl.deceasedRetail",
|
||||
DeceasedRetail,
|
||||
"dr",
|
||||
"FIND_IN_SET(ssl.id, dr.serviceItems) AND dr.retailState = 1 AND dr.cancelState = 0 "
|
||||
);
|
||||
|
||||
if (pageSize !== 999999999) {
|
||||
// 如果还没有连接 Deceased 表,需要确保连接
|
||||
if (
|
||||
(params.deceasedName || params.familyName) &&
|
||||
!initialQuery.expressionMap.joinAttributes.some(
|
||||
(join) => join.alias.name === "d"
|
||||
)
|
||||
) {
|
||||
initialQuery.innerJoin(Deceased, "d", "d.id = dr.deceasedId");
|
||||
}
|
||||
if (params.startDate || params.endDate) {
|
||||
initialQuery.andWhere("dr.checkoutDate BETWEEN :start AND :end", {
|
||||
start:
|
||||
params.startDate ||
|
||||
dayjs().startOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
||||
end: params.endDate || dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||||
});
|
||||
}
|
||||
|
||||
// 服务名称条件
|
||||
if (params.serviceName) {
|
||||
initialQuery.andWhere("ssl.name LIKE :serviceName", {
|
||||
serviceName: `%${params.serviceName}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (params.guide) {
|
||||
initialQuery.andWhere("dr.guide LIKE :guide", {
|
||||
guide: `%${params.guide}%`,
|
||||
});
|
||||
}
|
||||
|
||||
// 逝者姓名条件
|
||||
if (params.deceasedName) {
|
||||
initialQuery.andWhere(
|
||||
"( d.name LIKE :deceasedName OR (dr.deceasedId = 0 AND dr.deceasedName LIKE :deceasedName))",
|
||||
{
|
||||
deceasedName: `%${params.deceasedName}%`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 逝者家属姓名条件
|
||||
if (params.familyName) {
|
||||
initialQuery.andWhere(
|
||||
"( d.familyName LIKE :familyName OR (dr.deceasedId = 0 AND dr.familyName LIKE :familyName) )",
|
||||
{
|
||||
familyName: `%${params.familyName}%`,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初步查询
|
||||
const initialResult = await initialQuery
|
||||
.orderBy("dr.checkoutDate", "DESC")
|
||||
.skip((pageNumber - 1) * pageSize)
|
||||
.take(pageSize)
|
||||
.getMany();
|
||||
|
||||
// 第二步:根据初步查询结果查询绑定 Deceased
|
||||
|
||||
const deceasedIds = initialResult
|
||||
//@ts-ignore
|
||||
.map((item) => item.deceasedRetail?.deceasedId)
|
||||
.filter((id) => id !== undefined && id !== 0);
|
||||
|
||||
const deceasedMap = new Map();
|
||||
if (deceasedIds.length > 0) {
|
||||
const deceasedList = await getRepository(Deceased)
|
||||
.createQueryBuilder("d")
|
||||
.where("d.id IN (:...ids)", { ids: deceasedIds })
|
||||
.getMany();
|
||||
|
||||
deceasedList.forEach((deceased) => {
|
||||
deceasedMap.set(deceased.id, deceased);
|
||||
});
|
||||
}
|
||||
|
||||
// 合并结果
|
||||
const result = initialResult.map((item) => {
|
||||
//@ts-ignore
|
||||
if (item.deceasedRetail?.deceasedId !== 0) {
|
||||
//@ts-ignore
|
||||
item.deceased = deceasedMap.get(item.deceasedRetail.deceasedId) || {};
|
||||
} else {
|
||||
//@ts-ignore
|
||||
item.deceased = { ...item.deceasedRetail };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// 获取总记录数
|
||||
const total = await initialQuery.getCount();
|
||||
res.json({
|
||||
code: 200,
|
||||
data: {
|
||||
list: result.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// @ts-ignore
|
||||
sum: Number(item.price).toFixed(2) * Number(item.quantity),
|
||||
};
|
||||
}),
|
||||
pageSize,
|
||||
pageNumber,
|
||||
total,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
const parseQueryParams = (query: any): QueryParams => {
|
||||
const {
|
||||
startDate,
|
||||
endDate,
|
||||
serviceName,
|
||||
deceasedName,
|
||||
guide,
|
||||
pageSize,
|
||||
pageNumber,
|
||||
familyName,
|
||||
} = query;
|
||||
|
||||
const validateDate = (date: string, field: string) => {
|
||||
if (date && !dayjs(date).isValid()) {
|
||||
throw new Error(`${field}格式错误,请使用YYYY-MM-DD格式`);
|
||||
}
|
||||
};
|
||||
|
||||
validateDate(startDate, "开始日期");
|
||||
validateDate(endDate, "结束日期");
|
||||
|
||||
return {
|
||||
startDate: startDate?.trim(),
|
||||
endDate: endDate?.trim(),
|
||||
serviceName: serviceName?.trim(),
|
||||
deceasedName: deceasedName?.trim(),
|
||||
guide: guide?.trim(),
|
||||
pageSize: pageSize ? parseInt(pageSize) : undefined,
|
||||
pageNumber: pageNumber ? parseInt(pageNumber) : undefined,
|
||||
familyName: familyName,
|
||||
};
|
||||
};
|
||||
272
backEnd/src/router/stats/controller/serviceStats.ts
Normal file
272
backEnd/src/router/stats/controller/serviceStats.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
// controllers/serviceStats.ts
|
||||
import { Request, Response } from "express";
|
||||
import { getRepository, In } from "typeorm";
|
||||
import dayjs from "dayjs";
|
||||
import DeceasedRetail from "@/entity/DeceasedRetail";
|
||||
import SeletedServiceList from "@/entity/SeletedServiceList";
|
||||
import ServiceItem from "@/entity/ServiceItem";
|
||||
import ServiceCategory from "@/entity/ServiceCategory";
|
||||
import { handleError } from "@/util/globalMethods";
|
||||
|
||||
// 类型定义
|
||||
interface ServiceStatsItem {
|
||||
serviceName: string;
|
||||
price: number;
|
||||
unit: string;
|
||||
quantity: number;
|
||||
subtotal: number;
|
||||
}
|
||||
|
||||
interface CategoryStats {
|
||||
categoryName: string;
|
||||
services: ServiceStatsItem[];
|
||||
totalQuantity: number;
|
||||
totalAmount: number;
|
||||
}
|
||||
|
||||
interface StatsResponse {
|
||||
categories: CategoryStats[];
|
||||
otherCategory: CategoryStats;
|
||||
grandTotalQuantity: number;
|
||||
grandTotalAmount: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
serviceName?: string;
|
||||
categoryName?: string;
|
||||
}
|
||||
|
||||
export const getServiceStats = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// 1. 参数解析和验证
|
||||
const params = parseQueryParams(
|
||||
req.method === "GET" ? req.query : req.body
|
||||
);
|
||||
// 2. 查询符合条件的零售单(按结账时间)
|
||||
const retailRecords = await getRepository(DeceasedRetail)
|
||||
.createQueryBuilder("dr")
|
||||
.where("dr.checkoutDate BETWEEN :start AND :end", {
|
||||
start:
|
||||
params.startDate ||
|
||||
dayjs().startOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
||||
end: params.endDate || dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"),
|
||||
})
|
||||
.andWhere("dr.retailState = 1 AND dr.cancelState = 0") // 只统计已结账的
|
||||
.getMany();
|
||||
|
||||
// 3. 收集所有服务项目ID(从serviceItems字段)
|
||||
const allServiceItemIds = retailRecords
|
||||
.map((record) => {
|
||||
try {
|
||||
return record.serviceItems.split(",").map(Number);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
.filter((id) => id > 0); // 过滤无效ID
|
||||
|
||||
if (allServiceItemIds.length === 0) {
|
||||
return res.json(emptyResponse());
|
||||
}
|
||||
|
||||
// 4. 查询这些服务项目在SeletedServiceList中的记录
|
||||
const selectedServices = await getRepository(SeletedServiceList)
|
||||
.createQueryBuilder("ssl")
|
||||
.where("ssl.id IN (:...ids)", { ids: allServiceItemIds })
|
||||
.getMany();
|
||||
|
||||
// 5. 收集所有不重复的服务名称
|
||||
const serviceNames = Array.from(
|
||||
new Set(selectedServices.map((s) => s.name))
|
||||
);
|
||||
if (serviceNames.length === 0) {
|
||||
return res.json(emptyResponse());
|
||||
}
|
||||
|
||||
// 6. 分步查询分类信息
|
||||
// 第一步:查询ServiceItem获取parentIds
|
||||
const serviceItems = await getRepository(ServiceItem)
|
||||
.createQueryBuilder("si")
|
||||
.select(["si.name", "si.parentId"])
|
||||
.where("si.name IN (:...names)", { names: serviceNames })
|
||||
.getMany();
|
||||
|
||||
// 第二步:收集所有parentIds
|
||||
const parentIds = Array.from(
|
||||
new Set(serviceItems.map((item) => item.parentId))
|
||||
);
|
||||
|
||||
// 第三步:批量查询分类名称
|
||||
const categories = await getRepository(ServiceCategory)
|
||||
.createQueryBuilder("sc")
|
||||
.select(["sc.id", "sc.name"])
|
||||
.where({ id: In(parentIds) })
|
||||
.getMany();
|
||||
|
||||
// 第四步:构建分类ID到名称的映射
|
||||
const categoryIdToName = new Map<number, string>();
|
||||
categories.forEach((cat) => categoryIdToName.set(cat.id, cat.name));
|
||||
|
||||
// 第五步:构建服务名称到分类的映射
|
||||
const nameToCategoryMap = new Map<string, string>();
|
||||
serviceItems.forEach((item) => {
|
||||
const categoryName = categoryIdToName.get(item.parentId) || "其他服务";
|
||||
nameToCategoryMap.set(item.name, categoryName);
|
||||
});
|
||||
|
||||
// 7. 初始化统计结构
|
||||
const categoryStats = new Map<string, ServiceStatsItem[]>();
|
||||
const otherServices: ServiceStatsItem[] = [];
|
||||
|
||||
// 8. 处理每个服务项的统计
|
||||
const serviceNameToStats = new Map<string, ServiceStatsItem>();
|
||||
|
||||
selectedServices.forEach((service) => {
|
||||
// 应用服务名称过滤条件
|
||||
if (params.serviceName && !service.name.includes(params.serviceName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingItem = serviceNameToStats.get(service.name);
|
||||
if (existingItem) {
|
||||
// 如果已存在同名服务,则合并数量和金额
|
||||
existingItem.quantity += service.quantity;
|
||||
existingItem.subtotal += Number(service.price) * service.quantity;
|
||||
} else {
|
||||
// 否则创建新条目
|
||||
serviceNameToStats.set(service.name, {
|
||||
serviceName: service.name,
|
||||
price: Number(service.price),
|
||||
unit: service.unit,
|
||||
quantity: service.quantity,
|
||||
subtotal: Number(service.price) * service.quantity,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// // 9. 将合并后的服务项按分类分组
|
||||
// const categoryStats = new Map<string, ServiceStatsItem[]>();
|
||||
// const otherServices: ServiceStatsItem[] = [];
|
||||
|
||||
serviceNameToStats.forEach((statsItem, serviceName) => {
|
||||
// 获取分类名称,默认为"其他服务"
|
||||
const categoryName = nameToCategoryMap.get(serviceName) || "其他服务";
|
||||
|
||||
// 分类到对应分组
|
||||
if (categoryName === "其他服务") {
|
||||
otherServices.push(statsItem);
|
||||
} else {
|
||||
if (!categoryStats.has(categoryName)) {
|
||||
categoryStats.set(categoryName, []);
|
||||
}
|
||||
categoryStats.get(categoryName)?.push(statsItem);
|
||||
}
|
||||
});
|
||||
|
||||
// 9. 应用分类过滤条件
|
||||
if (params.categoryName) {
|
||||
if (params.categoryName !== "其他服务") {
|
||||
categoryStats.forEach((_, key) => {
|
||||
if (key !== params.categoryName) {
|
||||
categoryStats.delete(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
categoryStats.clear(); // 只保留其他服务
|
||||
}
|
||||
}
|
||||
|
||||
// 10. 构建最终响应
|
||||
const response = buildFinalResponse(categoryStats, otherServices);
|
||||
res.json({ code: 200, data: response });
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
// 辅助函数
|
||||
const emptyResponse = (): StatsResponse => ({
|
||||
categories: [],
|
||||
otherCategory: {
|
||||
categoryName: "其他服务",
|
||||
services: [],
|
||||
totalQuantity: 0,
|
||||
totalAmount: 0,
|
||||
},
|
||||
grandTotalQuantity: 0,
|
||||
grandTotalAmount: 0,
|
||||
});
|
||||
|
||||
const buildFinalResponse = (
|
||||
categoryMap: Map<string, ServiceStatsItem[]>,
|
||||
otherItems: ServiceStatsItem[]
|
||||
): StatsResponse => {
|
||||
const categories: CategoryStats[] = [];
|
||||
let grandTotalQuantity = 0;
|
||||
let grandTotalAmount = 0;
|
||||
|
||||
// 处理分类数据
|
||||
categoryMap.forEach((items, categoryName) => {
|
||||
const totalQuantity = items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
const totalAmount = items.reduce((sum, item) => sum + item.subtotal, 0);
|
||||
|
||||
categories.push({
|
||||
categoryName,
|
||||
services: items,
|
||||
totalQuantity,
|
||||
totalAmount,
|
||||
});
|
||||
|
||||
grandTotalQuantity += totalQuantity;
|
||||
grandTotalAmount += totalAmount;
|
||||
});
|
||||
|
||||
// 处理其他分类
|
||||
const otherTotalQuantity = otherItems.reduce(
|
||||
(sum, item) => sum + item.quantity,
|
||||
0
|
||||
);
|
||||
const otherTotalAmount = otherItems.reduce(
|
||||
(sum, item) => sum + item.subtotal,
|
||||
0
|
||||
);
|
||||
|
||||
grandTotalQuantity += otherTotalQuantity;
|
||||
grandTotalAmount += otherTotalAmount;
|
||||
|
||||
return {
|
||||
categories,
|
||||
otherCategory: {
|
||||
categoryName: "其他服务",
|
||||
services: otherItems,
|
||||
totalQuantity: otherTotalQuantity,
|
||||
totalAmount: otherTotalAmount,
|
||||
},
|
||||
grandTotalQuantity,
|
||||
grandTotalAmount,
|
||||
};
|
||||
};
|
||||
|
||||
const parseQueryParams = (query: any): QueryParams => {
|
||||
const { startDate, endDate, serviceName, categoryName } = query;
|
||||
|
||||
// 日期格式验证
|
||||
const validateDate = (date: string, field: string) => {
|
||||
if (date && !dayjs(date).isValid()) {
|
||||
throw new Error(`${field}格式错误,请使用YYYY-MM-DD格式`);
|
||||
}
|
||||
};
|
||||
|
||||
validateDate(startDate, "开始日期");
|
||||
validateDate(endDate, "结束日期");
|
||||
|
||||
return {
|
||||
startDate: startDate?.trim(),
|
||||
endDate: endDate?.trim(),
|
||||
serviceName: serviceName?.trim(),
|
||||
categoryName: categoryName?.trim(),
|
||||
};
|
||||
};
|
||||
13
backEnd/src/router/stats/stats.ts
Normal file
13
backEnd/src/router/stats/stats.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// controllers/paymentStats.ts
|
||||
import { Router } from "express";
|
||||
import { getPaymentStats } from "./controller/dayIncome";
|
||||
import { getServiceStats } from "./controller/serviceStats";
|
||||
import { getSalesDetails } from "./controller/salesDetail";
|
||||
|
||||
const router = Router();
|
||||
router.get("/dayIncome", getPaymentStats);
|
||||
router.get("/servicesStats", getServiceStats);
|
||||
router.get("/salesDetails", getSalesDetails);
|
||||
router.post("/salesDetails", getSalesDetails);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user