254 lines
7.8 KiB
TypeScript
254 lines
7.8 KiB
TypeScript
|
|
import pool from '@/lib/db';
|
|||
|
|
import { FIELD_MAPPING, STATE_MAPPING, ORDER_TYPE_MAPPING, PAY_METHOD_MAPPING, PAY_STATUS_MAPPING } from '@/lib/field-mapping';
|
|||
|
|
import * as XLSX from 'xlsx';
|
|||
|
|
import { writeFileSync, mkdirSync } from 'fs';
|
|||
|
|
import path from 'path';
|
|||
|
|
|
|||
|
|
// 日报生成配置
|
|||
|
|
interface ReportConfig {
|
|||
|
|
config_id: number;
|
|||
|
|
split_type: string; // 'company_id' 或 'user_id'
|
|||
|
|
selected_fields: string[]; // 用户选择的字段
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 日报生成结果
|
|||
|
|
interface ReportResult {
|
|||
|
|
success: boolean;
|
|||
|
|
reportDate: string;
|
|||
|
|
startTime: string;
|
|||
|
|
endTime: string;
|
|||
|
|
splitType: string;
|
|||
|
|
splitValue: string;
|
|||
|
|
totalOrders: number;
|
|||
|
|
totalAmount: number;
|
|||
|
|
filePath?: string;
|
|||
|
|
error?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成日报
|
|||
|
|
export async function generateDailyReport(
|
|||
|
|
splitType: string,
|
|||
|
|
splitValue: string,
|
|||
|
|
selectedFields: string[],
|
|||
|
|
reportDate?: string // 可选的报表日期,默认为昨天
|
|||
|
|
): Promise<ReportResult> {
|
|||
|
|
try {
|
|||
|
|
// 计算时间范围:前一天早8点到当天早8点
|
|||
|
|
const targetDate = reportDate ? new Date(reportDate) : new Date();
|
|||
|
|
targetDate.setDate(targetDate.getDate() - 1); // 昨天
|
|||
|
|
|
|||
|
|
const startTime = new Date(targetDate);
|
|||
|
|
startTime.setHours(8, 0, 0, 0);
|
|||
|
|
|
|||
|
|
const endTime = new Date(targetDate);
|
|||
|
|
endTime.setDate(endTime.getDate() + 1);
|
|||
|
|
endTime.setHours(8, 0, 0, 0);
|
|||
|
|
|
|||
|
|
// 查询订单数据
|
|||
|
|
const queryField = splitType === 'company_id' ? 'company_id' : 'user_id';
|
|||
|
|
const fieldList = selectedFields.join(', ');
|
|||
|
|
|
|||
|
|
const [orders] = await pool.execute<any[]>(
|
|||
|
|
`SELECT ${fieldList} FROM t_equipment_charge_order
|
|||
|
|
WHERE state = 3
|
|||
|
|
AND report_time >= ?
|
|||
|
|
AND report_time < ?
|
|||
|
|
AND ${queryField} = ?
|
|||
|
|
ORDER BY report_time ASC`,
|
|||
|
|
[startTime, endTime, splitValue]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(orders) || orders.length === 0) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
reportDate: targetDate.toISOString().split('T')[0],
|
|||
|
|
startTime: startTime.toISOString(),
|
|||
|
|
endTime: endTime.toISOString(),
|
|||
|
|
splitType,
|
|||
|
|
splitValue,
|
|||
|
|
totalOrders: 0,
|
|||
|
|
totalAmount: 0,
|
|||
|
|
error: '该时间段内没有订单数据'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理数据:将字段名转换为中文,并转换特殊字段的值
|
|||
|
|
const processedOrders = orders.map(order => {
|
|||
|
|
const processed: Record<string, any> = {};
|
|||
|
|
|
|||
|
|
for (const [key, value] of Object.entries(order)) {
|
|||
|
|
// 使用中文字段名
|
|||
|
|
const chineseName = FIELD_MAPPING[key] || key;
|
|||
|
|
|
|||
|
|
// 特殊字段值转换
|
|||
|
|
if (key === 'state' && typeof value === 'number') {
|
|||
|
|
processed[chineseName] = STATE_MAPPING[value] || value;
|
|||
|
|
} else if (key === 'order_type' && typeof value === 'number') {
|
|||
|
|
processed[chineseName] = ORDER_TYPE_MAPPING[value] || value;
|
|||
|
|
} else if (key === 'pay_method' && typeof value === 'number') {
|
|||
|
|
processed[chineseName] = PAY_METHOD_MAPPING[value] || value;
|
|||
|
|
} else if (key === 'pay_status' && typeof value === 'number') {
|
|||
|
|
processed[chineseName] = PAY_STATUS_MAPPING[value] || value;
|
|||
|
|
} else {
|
|||
|
|
processed[chineseName] = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return processed;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 生成Excel文件
|
|||
|
|
const workbook = XLSX.utils.book_new();
|
|||
|
|
const worksheet = XLSX.utils.json_to_sheet(processedOrders);
|
|||
|
|
|
|||
|
|
// 设置列宽
|
|||
|
|
const columnWidths = selectedFields.map(field => ({
|
|||
|
|
wch: Math.max(FIELD_MAPPING[field]?.length || 10, 15)
|
|||
|
|
}));
|
|||
|
|
worksheet['!cols'] = columnWidths;
|
|||
|
|
|
|||
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, '日报');
|
|||
|
|
|
|||
|
|
// 生成文件名
|
|||
|
|
const fileName = `日报_${splitType === 'company_id' ? '企业' : '用户'}_${splitValue}_${targetDate.toISOString().split('T')[0]}.xlsx`;
|
|||
|
|
const reportsDir = path.join(process.cwd(), 'public', 'reports');
|
|||
|
|
|
|||
|
|
// 确保目录存在
|
|||
|
|
mkdirSync(reportsDir, { recursive: true });
|
|||
|
|
|
|||
|
|
const filePath = path.join(reportsDir, fileName);
|
|||
|
|
|
|||
|
|
// 写入文件
|
|||
|
|
writeFileSync(filePath, XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }));
|
|||
|
|
|
|||
|
|
// 计算统计信息
|
|||
|
|
const totalOrders = orders.length;
|
|||
|
|
const totalAmount = orders.reduce((sum: number, order: any) => {
|
|||
|
|
return sum + (parseFloat(order.actual_pay_amount) || 0);
|
|||
|
|
}, 0);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
reportDate: targetDate.toISOString().split('T')[0],
|
|||
|
|
startTime: startTime.toISOString(),
|
|||
|
|
endTime: endTime.toISOString(),
|
|||
|
|
splitType,
|
|||
|
|
splitValue,
|
|||
|
|
totalOrders,
|
|||
|
|
totalAmount,
|
|||
|
|
filePath: `/reports/${fileName}`
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
reportDate: '',
|
|||
|
|
startTime: '',
|
|||
|
|
endTime: '',
|
|||
|
|
splitType,
|
|||
|
|
splitValue,
|
|||
|
|
totalOrders: 0,
|
|||
|
|
totalAmount: 0,
|
|||
|
|
error: error instanceof Error ? error.message : '未知错误'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量生成日报(用于定时任务)
|
|||
|
|
export async function generateBatchReports(configId: number): Promise<ReportResult[]> {
|
|||
|
|
try {
|
|||
|
|
// 获取配置
|
|||
|
|
const [configs] = await pool.execute<any[]>(
|
|||
|
|
'SELECT * FROM t_daily_report_config WHERE id = ? AND is_active = 1',
|
|||
|
|
[configId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(configs) || configs.length === 0) {
|
|||
|
|
throw new Error('配置不存在或已禁用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const config = configs[0];
|
|||
|
|
const splitType = config.split_type;
|
|||
|
|
const selectedFields = JSON.parse(config.selected_fields);
|
|||
|
|
|
|||
|
|
// 获取所有需要生成日报的企业/用户列表
|
|||
|
|
const queryField = splitType === 'company_id' ? 'company_id' : 'user_id';
|
|||
|
|
|
|||
|
|
// 计算时间范围
|
|||
|
|
const targetDate = new Date();
|
|||
|
|
targetDate.setDate(targetDate.getDate() - 1);
|
|||
|
|
|
|||
|
|
const startTime = new Date(targetDate);
|
|||
|
|
startTime.setHours(8, 0, 0, 0);
|
|||
|
|
|
|||
|
|
const endTime = new Date(targetDate);
|
|||
|
|
endTime.setDate(endTime.getDate() + 1);
|
|||
|
|
endTime.setHours(8, 0, 0, 0);
|
|||
|
|
|
|||
|
|
// 获取该时间段内的所有企业/用户
|
|||
|
|
const [entities] = await pool.execute<any[]>(
|
|||
|
|
`SELECT DISTINCT ${queryField} as entity_id
|
|||
|
|
FROM t_equipment_charge_order
|
|||
|
|
WHERE state = 3
|
|||
|
|
AND report_time >= ?
|
|||
|
|
AND report_time < ?
|
|||
|
|
AND ${queryField} IS NOT NULL`,
|
|||
|
|
[startTime, endTime]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(entities) || entities.length === 0) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量生成日报
|
|||
|
|
const results: ReportResult[] = [];
|
|||
|
|
|
|||
|
|
for (const entity of entities) {
|
|||
|
|
const entityId = entity.entity_id;
|
|||
|
|
const result = await generateDailyReport(splitType, String(entityId), selectedFields);
|
|||
|
|
|
|||
|
|
// 保存到历史记录表
|
|||
|
|
if (result.success) {
|
|||
|
|
await pool.execute(
|
|||
|
|
`INSERT INTO t_daily_report_history
|
|||
|
|
(report_date, start_time, end_time, split_type, split_value, config_id, total_orders, total_amount, file_path, status)
|
|||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|||
|
|
[
|
|||
|
|
result.reportDate,
|
|||
|
|
result.startTime,
|
|||
|
|
result.endTime,
|
|||
|
|
result.splitType,
|
|||
|
|
result.splitValue,
|
|||
|
|
configId,
|
|||
|
|
result.totalOrders,
|
|||
|
|
result.totalAmount,
|
|||
|
|
result.filePath || null,
|
|||
|
|
1
|
|||
|
|
]
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
await pool.execute(
|
|||
|
|
`INSERT INTO t_daily_report_history
|
|||
|
|
(report_date, start_time, end_time, split_type, split_value, config_id, total_orders, total_amount, status)
|
|||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|||
|
|
[
|
|||
|
|
result.reportDate,
|
|||
|
|
result.startTime,
|
|||
|
|
result.endTime,
|
|||
|
|
result.splitType,
|
|||
|
|
result.splitValue,
|
|||
|
|
configId,
|
|||
|
|
result.totalOrders,
|
|||
|
|
result.totalAmount,
|
|||
|
|
0
|
|||
|
|
]
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
results.push(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return results;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|