Files
ylt_diy/src/lib/report-generator.ts

254 lines
7.8 KiB
TypeScript
Raw Normal View History

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;
}
}