|
|
/**
|
|
|
* 统计分析控制器
|
|
|
*
|
|
|
* 功能:处理数据分析和报表的业务逻辑
|
|
|
*/
|
|
|
|
|
|
const Analytics = require('../models/Analytics');
|
|
|
const { success } = require('../utils/response');
|
|
|
const { ValidationError } = require('../middleware/errorHandler');
|
|
|
|
|
|
/**
|
|
|
* 获取综合仪表盘数据
|
|
|
* GET /api/analytics/dashboard
|
|
|
*/
|
|
|
const getDashboard = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const dashboard = await Analytics.getDashboard(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success(dashboard, '获取仪表盘数据成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取销售趋势
|
|
|
* GET /api/analytics/sales-trend
|
|
|
*/
|
|
|
const getSalesTrend = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
if (!start_date || !end_date) {
|
|
|
throw new ValidationError('请提供开始日期和结束日期');
|
|
|
}
|
|
|
|
|
|
const trend = await Analytics.getSalesTrend(start_date, end_date);
|
|
|
|
|
|
res.json(success({
|
|
|
period: { start_date, end_date },
|
|
|
data: trend
|
|
|
}, '获取销售趋势成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取热门菜品排行
|
|
|
* GET /api/analytics/top-dishes
|
|
|
*/
|
|
|
const getTopDishes = async (req, res, next) => {
|
|
|
try {
|
|
|
const { limit = 10, start_date, end_date } = req.query;
|
|
|
|
|
|
const dishes = await Analytics.getTopDishes(
|
|
|
parseInt(limit),
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
count: dishes.length,
|
|
|
dishes
|
|
|
}, '获取热门菜品成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取分类销售统计
|
|
|
* GET /api/analytics/category-sales
|
|
|
*/
|
|
|
const getCategorySales = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const categories = await Analytics.getCategorySales(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
count: categories.length,
|
|
|
categories
|
|
|
}, '获取分类销售统计成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取营业时段分析
|
|
|
* GET /api/analytics/hourly-analysis
|
|
|
*/
|
|
|
const getHourlyAnalysis = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const hourlyData = await Analytics.getHourlyAnalysis(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
data: hourlyData
|
|
|
}, '获取时段分析成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取会员消费排行
|
|
|
* GET /api/analytics/top-members
|
|
|
*/
|
|
|
const getTopMembers = async (req, res, next) => {
|
|
|
try {
|
|
|
const { limit = 10, start_date, end_date } = req.query;
|
|
|
|
|
|
const members = await Analytics.getTopMembers(
|
|
|
parseInt(limit),
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
count: members.length,
|
|
|
members
|
|
|
}, '获取会员排行成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取订单类型统计
|
|
|
* GET /api/analytics/order-types
|
|
|
*/
|
|
|
const getOrderTypeStats = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const orderTypes = await Analytics.getOrderTypeStats(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
data: orderTypes
|
|
|
}, '获取订单类型统计成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取支付方式统计
|
|
|
* GET /api/analytics/payment-methods
|
|
|
*/
|
|
|
const getPaymentMethodStats = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const paymentMethods = await Analytics.getPaymentMethodStats(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
data: paymentMethods
|
|
|
}, '获取支付方式统计成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取月度对比报表
|
|
|
* GET /api/analytics/monthly-comparison
|
|
|
*/
|
|
|
const getMonthlyComparison = async (req, res, next) => {
|
|
|
try {
|
|
|
const { months = 6 } = req.query;
|
|
|
|
|
|
const monthlyData = await Analytics.getMonthlyComparison(parseInt(months));
|
|
|
|
|
|
res.json(success({
|
|
|
months: parseInt(months),
|
|
|
data: monthlyData
|
|
|
}, '获取月度对比成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取库存价值报表
|
|
|
* GET /api/analytics/inventory-value
|
|
|
*/
|
|
|
const getInventoryValue = async (req, res, next) => {
|
|
|
try {
|
|
|
const inventoryValue = await Analytics.getInventoryValue();
|
|
|
|
|
|
res.json(success(inventoryValue, '获取库存价值报表成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取优惠券使用统计
|
|
|
* GET /api/analytics/coupon-stats
|
|
|
*/
|
|
|
const getCouponStats = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const couponStats = await Analytics.getCouponStats(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success({
|
|
|
count: couponStats.length,
|
|
|
coupons: couponStats
|
|
|
}, '获取优惠券统计成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取预订转化率
|
|
|
* GET /api/analytics/reservation-conversion
|
|
|
*/
|
|
|
const getReservationConversion = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date } = req.query;
|
|
|
|
|
|
const conversion = await Analytics.getReservationConversion(
|
|
|
start_date || null,
|
|
|
end_date || null
|
|
|
);
|
|
|
|
|
|
res.json(success(conversion, '获取预订转化率成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 导出综合报表(CSV格式)
|
|
|
* GET /api/analytics/export
|
|
|
*/
|
|
|
const exportReport = async (req, res, next) => {
|
|
|
try {
|
|
|
const { start_date, end_date, type = 'sales' } = req.query;
|
|
|
|
|
|
if (!start_date || !end_date) {
|
|
|
throw new ValidationError('请提供开始日期和结束日期');
|
|
|
}
|
|
|
|
|
|
let data;
|
|
|
let filename;
|
|
|
|
|
|
switch (type) {
|
|
|
case 'sales':
|
|
|
data = await Analytics.getSalesTrend(start_date, end_date);
|
|
|
filename = `sales_report_${start_date}_${end_date}.csv`;
|
|
|
break;
|
|
|
case 'dishes':
|
|
|
data = await Analytics.getTopDishes(100, start_date, end_date);
|
|
|
filename = `dishes_report_${start_date}_${end_date}.csv`;
|
|
|
break;
|
|
|
case 'members':
|
|
|
data = await Analytics.getTopMembers(100, start_date, end_date);
|
|
|
filename = `members_report_${start_date}_${end_date}.csv`;
|
|
|
break;
|
|
|
default:
|
|
|
throw new ValidationError('无效的报表类型');
|
|
|
}
|
|
|
|
|
|
// 生成CSV内容
|
|
|
if (data.length === 0) {
|
|
|
throw new ValidationError('该时间段内没有数据');
|
|
|
}
|
|
|
|
|
|
const headers = Object.keys(data[0]).join(',');
|
|
|
const rows = data.map(row => Object.values(row).join(',')).join('\n');
|
|
|
const csv = `${headers}\n${rows}`;
|
|
|
|
|
|
// 设置响应头
|
|
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
|
|
|
|
res.send('\ufeff' + csv); // 添加BOM以支持中文
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 导出所有控制器函数
|
|
|
module.exports = {
|
|
|
getDashboard,
|
|
|
getSalesTrend,
|
|
|
getTopDishes,
|
|
|
getCategorySales,
|
|
|
getHourlyAnalysis,
|
|
|
getTopMembers,
|
|
|
getOrderTypeStats,
|
|
|
getPaymentMethodStats,
|
|
|
getMonthlyComparison,
|
|
|
getInventoryValue,
|
|
|
getCouponStats,
|
|
|
getReservationConversion,
|
|
|
exportReport
|
|
|
};
|