feat: 完善日报系统功能
主要更新: 1. 【核心变更】从批量拆分改为单个企业/用户选择生成 - 用户现在可以选择特定的企业或用户生成日报 - 企业列表显示企业名称(如"国营-西南-79路") - 用户列表显示用户姓名,并标注用户类型(企业用户/普通用户) 2. 【新增功能】求和功能 - 支持选择多个字段进行求和 - 求和结果显示在Excel表格底部 - 可对金额、电量等多列分别求和 3. 【数据库表结构更新】 - t_daily_report_config 新增字段:split_name(企业/用户名称)、sum_fields(求和字段列表) - t_daily_report_history 新增字段:split_name、sum_results(求和结果JSON) 4. 【前端页面优化】 - 配置管理:选择企业/用户时显示名称和订单数量 - 日报生成:支持选择单个企业/用户、字段和求和字段 - 历史记录:显示企业/用户名称和求和结果 5. 【API接口优化】 - 企业列表API:返回company_name、company_no、company_group等信息 - 用户列表API:返回user_name和用户类型标签 - 日报生成API:支持sum_fields参数 6. 【删除临时文件】清理debug调试API接口 所有代码静态检查通过,企业列表和用户列表API测试成功 Coze-Commit-Type: user Coze-User-ID: 3722323274763196 Coze-Conversation-ID: 9894087
This commit is contained in:
@@ -11,10 +11,12 @@ export async function GET() {
|
|||||||
// 尝试查询配置(如果表不存在会失败)
|
// 尝试查询配置(如果表不存在会失败)
|
||||||
let configs: any[] = [];
|
let configs: any[] = [];
|
||||||
try {
|
try {
|
||||||
const [configRows] = await pool.execute<any[]>(
|
const connection = await pool.getConnection();
|
||||||
|
const [configRows] = await connection.query<any[]>(
|
||||||
'SELECT * FROM t_daily_report_config WHERE is_active = 1 ORDER BY create_time DESC'
|
'SELECT * FROM t_daily_report_config WHERE is_active = 1 ORDER BY create_time DESC'
|
||||||
);
|
);
|
||||||
configs = configRows || [];
|
configs = configRows || [];
|
||||||
|
connection.release();
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
// 如果表不存在,返回空数组
|
// 如果表不存在,返回空数组
|
||||||
console.log('配置表查询失败,可能是表未初始化:', dbError);
|
console.log('配置表查询失败,可能是表未初始化:', dbError);
|
||||||
@@ -39,13 +41,20 @@ export async function GET() {
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { config_name, split_type, selected_fields } = body;
|
const {
|
||||||
|
config_name,
|
||||||
|
split_type,
|
||||||
|
split_value,
|
||||||
|
split_name,
|
||||||
|
selected_fields,
|
||||||
|
sum_fields
|
||||||
|
} = body;
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!config_name || !split_type || !selected_fields || selected_fields.length === 0) {
|
if (!config_name || !split_type || !split_value || !selected_fields || selected_fields.length === 0) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '缺少必填字段'
|
message: '缺少必填字段(配置名称、拆分方式、拆分值、选择的字段)'
|
||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +66,29 @@ export async function POST(request: NextRequest) {
|
|||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取连接
|
||||||
|
const connection = await pool.getConnection();
|
||||||
|
|
||||||
// 插入配置
|
// 插入配置
|
||||||
const [result] = await pool.execute(
|
await connection.query(
|
||||||
`INSERT INTO t_daily_report_config (config_name, split_type, selected_fields)
|
`INSERT INTO t_daily_report_config
|
||||||
VALUES (?, ?, ?)`,
|
(config_name, split_type, split_value, split_name, selected_fields, sum_fields)
|
||||||
[config_name, split_type, JSON.stringify(selected_fields)]
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
|
[
|
||||||
|
config_name,
|
||||||
|
split_type,
|
||||||
|
split_value,
|
||||||
|
split_name || null,
|
||||||
|
JSON.stringify(selected_fields),
|
||||||
|
sum_fields ? JSON.stringify(sum_fields) : null
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
connection.release();
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '配置创建成功',
|
message: '配置创建成功'
|
||||||
data: result
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -1,55 +1,68 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import pool from '@/lib/db';
|
import pool from '@/lib/db';
|
||||||
|
|
||||||
// GET - 获取企业列表或用户列表
|
// GET - 获取企业列表或用户列表(带名称)
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const type = searchParams.get('type'); // 'company' 或 'user'
|
const type = searchParams.get('type'); // 'company' 或 'user'
|
||||||
|
|
||||||
|
// 获取连接
|
||||||
|
const connection = await pool.getConnection();
|
||||||
|
|
||||||
if (type === 'company') {
|
if (type === 'company') {
|
||||||
// 获取企业列表(根据company_id分组,并统计订单数量)
|
// 获取企业列表(从 t_company 表读取企业名称)
|
||||||
const [companies] = await pool.execute(`
|
const [companies] = await connection.query<any[]>(`
|
||||||
SELECT
|
SELECT
|
||||||
company_id,
|
c.id as company_id,
|
||||||
COUNT(*) as order_count,
|
c.company_name,
|
||||||
SUM(actual_pay_amount) as total_amount
|
c.company_no,
|
||||||
FROM t_equipment_charge_order
|
c.company_group,
|
||||||
WHERE state = 3 AND company_id IS NOT NULL
|
COUNT(o.id) as order_count,
|
||||||
GROUP BY company_id
|
COALESCE(SUM(o.actual_pay_amount), 0) as total_amount
|
||||||
|
FROM t_company c
|
||||||
|
LEFT JOIN t_equipment_charge_order o ON c.id = o.company_id AND o.state = 3
|
||||||
|
GROUP BY c.id, c.company_name, c.company_no, c.company_group
|
||||||
ORDER BY order_count DESC
|
ORDER BY order_count DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
connection.release();
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: companies,
|
data: companies,
|
||||||
type: '企业'
|
type: '企业'
|
||||||
});
|
});
|
||||||
} else if (type === 'user') {
|
} else if (type === 'user') {
|
||||||
// 获取用户列表(根据user_id分组,区分企业用户和普通用户)
|
// 获取用户列表(从 t_user 表读取用户姓名)
|
||||||
const [users] = await pool.execute(`
|
const [users] = await connection.query<any[]>(`
|
||||||
SELECT
|
SELECT
|
||||||
user_id,
|
u.id as user_id,
|
||||||
order_type,
|
u.user_name,
|
||||||
COUNT(*) as order_count,
|
u.phone,
|
||||||
SUM(actual_pay_amount) as total_amount,
|
o.order_type,
|
||||||
|
COUNT(o.id) as order_count,
|
||||||
|
COALESCE(SUM(o.actual_pay_amount), 0) as total_amount,
|
||||||
CASE
|
CASE
|
||||||
WHEN order_type = 3 THEN '企业用户'
|
WHEN o.order_type = 3 THEN '企业用户'
|
||||||
WHEN order_type = 1 THEN '普通用户'
|
WHEN o.order_type = 1 THEN '普通用户'
|
||||||
ELSE '其他类型'
|
ELSE '其他类型'
|
||||||
END as user_type_label
|
END as user_type_label
|
||||||
FROM t_equipment_charge_order
|
FROM t_user u
|
||||||
WHERE state = 3
|
LEFT JOIN t_equipment_charge_order o ON u.id = o.user_id AND o.state = 3
|
||||||
GROUP BY user_id, order_type
|
GROUP BY u.id, u.user_name, u.phone, o.order_type
|
||||||
ORDER BY order_count DESC
|
ORDER BY order_count DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
connection.release();
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: users,
|
data: users,
|
||||||
type: '用户'
|
type: '用户'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
connection.release();
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '类型参数必须为 company 或 user'
|
message: '类型参数必须为 company 或 user'
|
||||||
|
|||||||
@@ -5,14 +5,22 @@ import { generateDailyReport, generateBatchReports } from '@/lib/report-generato
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { split_type, split_value, selected_fields, report_date, config_id } = body;
|
const {
|
||||||
|
split_type,
|
||||||
|
split_value,
|
||||||
|
split_name,
|
||||||
|
selected_fields,
|
||||||
|
sum_fields,
|
||||||
|
report_date,
|
||||||
|
config_id
|
||||||
|
} = body;
|
||||||
|
|
||||||
// 如果提供了config_id,则批量生成所有企业/用户的日报
|
// 如果提供了config_id,则根据配置生成日报
|
||||||
if (config_id) {
|
if (config_id) {
|
||||||
const results = await generateBatchReports(config_id);
|
const results = await generateBatchReports(config_id);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '批量日报生成完成',
|
message: '日报生成完成',
|
||||||
data: results
|
data: results
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -21,11 +29,18 @@ export async function POST(request: NextRequest) {
|
|||||||
if (!split_type || !split_value || !selected_fields || selected_fields.length === 0) {
|
if (!split_type || !split_value || !selected_fields || selected_fields.length === 0) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
message: '缺少必填字段'
|
message: '缺少必填字段(拆分方式、拆分值、选择的字段)'
|
||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await generateDailyReport(split_type, split_value, selected_fields, report_date);
|
const result = await generateDailyReport(
|
||||||
|
split_type,
|
||||||
|
split_value,
|
||||||
|
split_name,
|
||||||
|
selected_fields,
|
||||||
|
sum_fields,
|
||||||
|
report_date
|
||||||
|
);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: result.success,
|
success: result.success,
|
||||||
|
|||||||
196
src/app/page.tsx
196
src/app/page.tsx
@@ -24,12 +24,17 @@ export default function Home() {
|
|||||||
// 配置表单
|
// 配置表单
|
||||||
const [configName, setConfigName] = useState('');
|
const [configName, setConfigName] = useState('');
|
||||||
const [splitType, setSplitType] = useState('company_id');
|
const [splitType, setSplitType] = useState('company_id');
|
||||||
|
const [splitValue, setSplitValue] = useState('');
|
||||||
|
const [splitName, setSplitName] = useState('');
|
||||||
const [selectedFields, setSelectedFields] = useState<string[]>([]);
|
const [selectedFields, setSelectedFields] = useState<string[]>([]);
|
||||||
|
const [sumFields, setSumFields] = useState<string[]>([]);
|
||||||
|
|
||||||
// 日报生成表单
|
// 日报生成表单
|
||||||
const [reportSplitType, setReportSplitType] = useState('company_id');
|
const [reportSplitType, setReportSplitType] = useState('company_id');
|
||||||
const [reportSplitValue, setReportSplitValue] = useState('');
|
const [reportSplitValue, setReportSplitValue] = useState('');
|
||||||
|
const [reportSplitName, setReportSplitName] = useState('');
|
||||||
const [reportSelectedFields, setReportSelectedFields] = useState<string[]>([]);
|
const [reportSelectedFields, setReportSelectedFields] = useState<string[]>([]);
|
||||||
|
const [reportSumFields, setReportSumFields] = useState<string[]>([]);
|
||||||
const [reportDate, setReportDate] = useState('');
|
const [reportDate, setReportDate] = useState('');
|
||||||
|
|
||||||
// 历史查询
|
// 历史查询
|
||||||
@@ -46,6 +51,8 @@ export default function Home() {
|
|||||||
} else {
|
} else {
|
||||||
loadUsers();
|
loadUsers();
|
||||||
}
|
}
|
||||||
|
setSplitValue('');
|
||||||
|
setSplitName('');
|
||||||
}, [splitType]);
|
}, [splitType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -54,6 +61,8 @@ export default function Home() {
|
|||||||
} else {
|
} else {
|
||||||
loadUsers();
|
loadUsers();
|
||||||
}
|
}
|
||||||
|
setReportSplitValue('');
|
||||||
|
setReportSplitName('');
|
||||||
}, [reportSplitType]);
|
}, [reportSplitType]);
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -112,10 +121,10 @@ export default function Home() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateConfig = async () => {
|
const handleCreateConfig = async () => {
|
||||||
if (!configName || selectedFields.length === 0) {
|
if (!configName || !splitValue || selectedFields.length === 0) {
|
||||||
toast({
|
toast({
|
||||||
title: '错误',
|
title: '错误',
|
||||||
description: '请填写配置名称并选择至少一个字段',
|
description: '请填写配置名称并选择企业和字段',
|
||||||
variant: 'destructive'
|
variant: 'destructive'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -129,7 +138,10 @@ export default function Home() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
config_name: configName,
|
config_name: configName,
|
||||||
split_type: splitType,
|
split_type: splitType,
|
||||||
selected_fields: selectedFields
|
split_value: splitValue,
|
||||||
|
split_name: splitName,
|
||||||
|
selected_fields: selectedFields,
|
||||||
|
sum_fields: sumFields
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,7 +153,10 @@ export default function Home() {
|
|||||||
});
|
});
|
||||||
loadData();
|
loadData();
|
||||||
setConfigName('');
|
setConfigName('');
|
||||||
|
setSplitValue('');
|
||||||
|
setSplitName('');
|
||||||
setSelectedFields([]);
|
setSelectedFields([]);
|
||||||
|
setSumFields([]);
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: '错误',
|
title: '错误',
|
||||||
@@ -177,7 +192,9 @@ export default function Home() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
split_type: reportSplitType,
|
split_type: reportSplitType,
|
||||||
split_value: reportSplitValue,
|
split_value: reportSplitValue,
|
||||||
|
split_name: reportSplitName,
|
||||||
selected_fields: reportSelectedFields,
|
selected_fields: reportSelectedFields,
|
||||||
|
sum_fields: reportSumFields,
|
||||||
report_date: reportDate
|
report_date: reportDate
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -237,13 +254,19 @@ export default function Home() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFieldChange = (field: string, checked: boolean, type: 'config' | 'report') => {
|
const handleFieldChange = (field: string, checked: boolean, type: 'config' | 'report' | 'sum') => {
|
||||||
if (type === 'config') {
|
if (type === 'config') {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSelectedFields([...selectedFields, field]);
|
setSelectedFields([...selectedFields, field]);
|
||||||
} else {
|
} else {
|
||||||
setSelectedFields(selectedFields.filter(f => f !== field));
|
setSelectedFields(selectedFields.filter(f => f !== field));
|
||||||
}
|
}
|
||||||
|
} else if (type === 'sum') {
|
||||||
|
if (checked) {
|
||||||
|
setSumFields([...sumFields, field]);
|
||||||
|
} else {
|
||||||
|
setSumFields(sumFields.filter(f => f !== field));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setReportSelectedFields([...reportSelectedFields, field]);
|
setReportSelectedFields([...reportSelectedFields, field]);
|
||||||
@@ -253,6 +276,24 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReportSumFieldChange = (field: string, checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setReportSumFields([...reportSumFields, field]);
|
||||||
|
} else {
|
||||||
|
setReportSumFields(reportSumFields.filter(f => f !== field));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSplitValueChange = (value: string, name: string) => {
|
||||||
|
setSplitValue(value);
|
||||||
|
setSplitName(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReportSplitValueChange = (value: string, name: string) => {
|
||||||
|
setReportSplitValue(value);
|
||||||
|
setReportSplitName(name);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 p-8">
|
<div className="min-h-screen bg-gray-50 p-8">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
@@ -260,7 +301,7 @@ export default function Home() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-2xl">订单日报系统</CardTitle>
|
<CardTitle className="text-2xl">订单日报系统</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
每天早8点自动生成前一天的订单日报(早8点到当天早8点)
|
为选定的企业或用户生成订单日报(前一天早8点到当天早8点)
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -287,26 +328,58 @@ export default function Home() {
|
|||||||
id="configName"
|
id="configName"
|
||||||
value={configName}
|
value={configName}
|
||||||
onChange={(e) => setConfigName(e.target.value)}
|
onChange={(e) => setConfigName(e.target.value)}
|
||||||
placeholder="例如:企业日报配置"
|
placeholder="例如:国营-西南-79路日报"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Label htmlFor="splitType">拆分方式</Label>
|
<div>
|
||||||
<Select value={splitType} onValueChange={setSplitType}>
|
<Label htmlFor="splitType">拆分方式</Label>
|
||||||
<SelectTrigger>
|
<Select value={splitType} onValueChange={setSplitType}>
|
||||||
<SelectValue />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="company_id">按企业ID拆分</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="user_id">按用户ID拆分</SelectItem>
|
<SelectItem value="company_id">按企业ID</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="user_id">按用户ID</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="splitValue">
|
||||||
|
{splitType === 'company_id' ? '选择企业' : '选择用户'}
|
||||||
|
</Label>
|
||||||
|
<Select value={splitValue} onValueChange={(value) => {
|
||||||
|
const selectedItem = splitType === 'company_id'
|
||||||
|
? companies.find(c => String(c.company_id) === value)
|
||||||
|
: users.find(u => String(u.user_id) === value);
|
||||||
|
handleSplitValueChange(value, selectedItem?.company_name || selectedItem?.user_name || '');
|
||||||
|
}}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="请选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{splitType === 'company_id'
|
||||||
|
? companies.map((company) => (
|
||||||
|
<SelectItem key={company.company_id} value={String(company.company_id)}>
|
||||||
|
{company.company_name} ({company.order_count} 单)
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
: users.map((user) => (
|
||||||
|
<SelectItem key={`${user.user_id}-${user.order_type}`} value={String(user.user_id)}>
|
||||||
|
{user.user_name || `用户${user.user_id}`} ({user.user_type_label}, {user.order_count} 单)
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>选择字段(已选 {selectedFields.length} 个)</Label>
|
<Label>选择字段(已选 {selectedFields.length} 个)</Label>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mt-2 h-[300px] overflow-y-auto border rounded p-2">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mt-2 h-[200px] overflow-y-auto border rounded p-2">
|
||||||
{availableFields.map((field) => (
|
{availableFields.map((field) => (
|
||||||
<div key={field.field} className="flex items-center space-x-2">
|
<div key={field.field} className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -324,6 +397,30 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>求和字段(已选 {sumFields.length} 个)- 将在表格底部显示求和结果</Label>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mt-2 h-[150px] overflow-y-auto border rounded p-2">
|
||||||
|
{availableFields.filter(f =>
|
||||||
|
['charge_degree', 'charge_ah', 'receivable_electric_fee', 'receivable_service_fee',
|
||||||
|
'receivable_total_fee', 'charge_elecfee_amount', 'charge_service_amount',
|
||||||
|
'actual_pay_amount', 'pay_amount', 'total_amount'].some(numField => f.field.includes(numField))
|
||||||
|
).map((field) => (
|
||||||
|
<div key={`sum-${field.field}`} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`sum-config-${field.field}`}
|
||||||
|
checked={sumFields.includes(field.field)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleFieldChange(field.field, checked as boolean, 'sum')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`sum-config-${field.field}`} className="text-sm">
|
||||||
|
{field.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateConfig}
|
onClick={handleCreateConfig}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -346,7 +443,9 @@ export default function Home() {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>配置名称</TableHead>
|
<TableHead>配置名称</TableHead>
|
||||||
<TableHead>拆分方式</TableHead>
|
<TableHead>拆分方式</TableHead>
|
||||||
<TableHead>字段数量</TableHead>
|
<TableHead>企业/用户</TableHead>
|
||||||
|
<TableHead>字段数</TableHead>
|
||||||
|
<TableHead>求和字段</TableHead>
|
||||||
<TableHead>操作</TableHead>
|
<TableHead>操作</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -359,9 +458,13 @@ export default function Home() {
|
|||||||
{config.split_type === 'company_id' ? '企业' : '用户'}
|
{config.split_type === 'company_id' ? '企业' : '用户'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>{config.split_name || config.split_value}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{JSON.parse(config.selected_fields).length} 个
|
{JSON.parse(config.selected_fields).length} 个
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{config.sum_fields ? JSON.parse(config.sum_fields).length : 0} 个
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -386,7 +489,7 @@ export default function Home() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>手动生成日报</CardTitle>
|
<CardTitle>手动生成日报</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
选择企业/用户和字段,手动生成指定日期的日报
|
选择企业/用户和字段,生成指定日期的日报
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
@@ -404,16 +507,21 @@ export default function Home() {
|
|||||||
</Select>
|
</Select>
|
||||||
{reportSplitType === 'user_id' && (
|
{reportSplitType === 'user_id' && (
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
注意:用户ID可能对应企业用户或普通用户
|
注意:用户ID会显示用户类型(企业用户/普通用户)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="reportSplitValue">
|
<Label htmlFor="reportSplitValue">
|
||||||
{reportSplitType === 'company_id' ? '企业ID' : '用户ID'}
|
{reportSplitType === 'company_id' ? '选择企业' : '选择用户'}
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={reportSplitValue} onValueChange={setReportSplitValue}>
|
<Select value={reportSplitValue} onValueChange={(value) => {
|
||||||
|
const selectedItem = reportSplitType === 'company_id'
|
||||||
|
? companies.find(c => String(c.company_id) === value)
|
||||||
|
: users.find(u => String(u.user_id) === value);
|
||||||
|
handleReportSplitValueChange(value, selectedItem?.company_name || selectedItem?.user_name || '');
|
||||||
|
}}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="请选择" />
|
<SelectValue placeholder="请选择" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -421,12 +529,12 @@ export default function Home() {
|
|||||||
{reportSplitType === 'company_id'
|
{reportSplitType === 'company_id'
|
||||||
? companies.map((company) => (
|
? companies.map((company) => (
|
||||||
<SelectItem key={company.company_id} value={String(company.company_id)}>
|
<SelectItem key={company.company_id} value={String(company.company_id)}>
|
||||||
企业ID: {company.company_id} ({company.order_count} 单)
|
{company.company_name} ({company.order_count} 单)
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
: users.map((user) => (
|
: users.map((user) => (
|
||||||
<SelectItem key={`${user.user_id}-${user.order_type}`} value={String(user.user_id)}>
|
<SelectItem key={`${user.user_id}-${user.order_type}`} value={String(user.user_id)}>
|
||||||
用户ID: {user.user_id} ({user.user_type_label}, {user.order_count} 单)
|
{user.user_name || `用户${user.user_id}`} ({user.user_type_label}, {user.order_count} 单)
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -465,6 +573,30 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>求和字段(已选 {reportSumFields.length} 个)- 将在表格底部显示求和结果</Label>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mt-2 h-[150px] overflow-y-auto border rounded p-2">
|
||||||
|
{availableFields.filter(f =>
|
||||||
|
['charge_degree', 'charge_ah', 'receivable_electric_fee', 'receivable_service_fee',
|
||||||
|
'receivable_total_fee', 'charge_elecfee_amount', 'charge_service_amount',
|
||||||
|
'actual_pay_amount', 'pay_amount', 'total_amount'].some(numField => f.field.includes(numField))
|
||||||
|
).map((field) => (
|
||||||
|
<div key={`sum-report-${field.field}`} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`sum-report-${field.field}`}
|
||||||
|
checked={reportSumFields.includes(field.field)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleReportSumFieldChange(field.field, checked as boolean)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`sum-report-${field.field}`} className="text-sm">
|
||||||
|
{field.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleGenerateReport}
|
onClick={handleGenerateReport}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -489,9 +621,10 @@ export default function Home() {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>报表日期</TableHead>
|
<TableHead>报表日期</TableHead>
|
||||||
<TableHead>拆分方式</TableHead>
|
<TableHead>拆分方式</TableHead>
|
||||||
<TableHead>拆分值</TableHead>
|
<TableHead>企业/用户</TableHead>
|
||||||
<TableHead>订单数</TableHead>
|
<TableHead>订单数</TableHead>
|
||||||
<TableHead>总金额</TableHead>
|
<TableHead>总金额</TableHead>
|
||||||
|
<TableHead>求和结果</TableHead>
|
||||||
<TableHead>状态</TableHead>
|
<TableHead>状态</TableHead>
|
||||||
<TableHead>生成时间</TableHead>
|
<TableHead>生成时间</TableHead>
|
||||||
<TableHead>操作</TableHead>
|
<TableHead>操作</TableHead>
|
||||||
@@ -506,9 +639,18 @@ export default function Home() {
|
|||||||
{record.split_type === 'company_id' ? '企业' : '用户'}
|
{record.split_type === 'company_id' ? '企业' : '用户'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{record.split_value}</TableCell>
|
<TableCell>{record.split_name || record.split_value}</TableCell>
|
||||||
<TableCell>{record.total_orders}</TableCell>
|
<TableCell>{record.total_orders}</TableCell>
|
||||||
<TableCell>¥{record.total_amount}</TableCell>
|
<TableCell>¥{record.total_amount}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{record.sum_results ? (
|
||||||
|
<div className="text-xs">
|
||||||
|
{Object.entries(JSON.parse(record.sum_results) as Record<string, string | number>).slice(0, 2).map(([key, value]) => (
|
||||||
|
<div key={key}>{key}: {value}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : '-'}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={record.status === 1 ? 'default' : 'destructive'}>
|
<Badge variant={record.status === 1 ? 'default' : 'destructive'}>
|
||||||
{record.status === 1 ? '成功' : '失败'}
|
{record.status === 1 ? '成功' : '失败'}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ const dbConfig = {
|
|||||||
password: 'DsideaL147258369',
|
password: 'DsideaL147258369',
|
||||||
database: 'yltcharge',
|
database: 'yltcharge',
|
||||||
waitForConnections: true,
|
waitForConnections: true,
|
||||||
connectionLimit: 10,
|
connectionLimit: 5,
|
||||||
queueLimit: 0,
|
queueLimit: 0,
|
||||||
|
enableKeepAlive: true,
|
||||||
|
keepAliveInitialDelay: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建连接池
|
// 创建连接池
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ CREATE TABLE IF NOT EXISTS t_daily_report_config (
|
|||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
|
||||||
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
||||||
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式:company_id 或 user_id',
|
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式:company_id 或 user_id',
|
||||||
|
split_value VARCHAR(100) NOT NULL COMMENT '拆分值(企业ID或用户ID)',
|
||||||
|
split_name VARCHAR(200) COMMENT '拆分显示名称(企业名称或用户姓名)',
|
||||||
selected_fields JSON NOT NULL COMMENT '用户选择的字段列表(JSON数组)',
|
selected_fields JSON NOT NULL COMMENT '用户选择的字段列表(JSON数组)',
|
||||||
|
sum_fields JSON COMMENT '需要求和的字段列表(JSON数组)',
|
||||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
is_active TINYINT DEFAULT 1 COMMENT '是否启用:0禁用 1启用'
|
is_active TINYINT DEFAULT 1 COMMENT '是否启用:0禁用 1启用'
|
||||||
@@ -21,9 +24,11 @@ CREATE TABLE IF NOT EXISTS t_daily_report_history (
|
|||||||
end_time DATETIME NOT NULL COMMENT '报表结束时间',
|
end_time DATETIME NOT NULL COMMENT '报表结束时间',
|
||||||
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式:company_id 或 user_id',
|
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式:company_id 或 user_id',
|
||||||
split_value VARCHAR(100) NOT NULL COMMENT '拆分值(企业ID或用户ID)',
|
split_value VARCHAR(100) NOT NULL COMMENT '拆分值(企业ID或用户ID)',
|
||||||
|
split_name VARCHAR(200) COMMENT '拆分显示名称',
|
||||||
config_id BIGINT COMMENT '使用的配置ID',
|
config_id BIGINT COMMENT '使用的配置ID',
|
||||||
total_orders INT DEFAULT 0 COMMENT '订单总数',
|
total_orders INT DEFAULT 0 COMMENT '订单总数',
|
||||||
total_amount DECIMAL(15,2) DEFAULT 0 COMMENT '总金额',
|
total_amount DECIMAL(15,2) DEFAULT 0 COMMENT '总金额',
|
||||||
|
sum_results JSON COMMENT '求和结果(JSON对象)',
|
||||||
file_path VARCHAR(500) COMMENT '生成的文件路径',
|
file_path VARCHAR(500) COMMENT '生成的文件路径',
|
||||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
|
||||||
status TINYINT DEFAULT 1 COMMENT '状态:0失败 1成功'
|
status TINYINT DEFAULT 1 COMMENT '状态:0失败 1成功'
|
||||||
@@ -39,15 +44,15 @@ export async function initDatabaseTables(): Promise<void> {
|
|||||||
const statements = createTablesSQL.split(';').filter(stmt => stmt.trim());
|
const statements = createTablesSQL.split(';').filter(stmt => stmt.trim());
|
||||||
for (const statement of statements) {
|
for (const statement of statements) {
|
||||||
if (statement.trim()) {
|
if (statement.trim()) {
|
||||||
await connection.execute(statement);
|
await connection.query(statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.release();
|
connection.release();
|
||||||
console.log('数据库表初始化成功');
|
console.log('数据库表初始化成功');
|
||||||
console.log('创建了以下表:');
|
console.log('创建了以下表:');
|
||||||
console.log('1. t_daily_report_config - 日报字段配置表');
|
console.log('1. t_daily_report_config - 日报字段配置表(包含拆分值和显示名称)');
|
||||||
console.log('2. t_daily_report_history - 日报生成历史表');
|
console.log('2. t_daily_report_history - 日报生成历史表(包含求和结果)');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('数据库表初始化失败:', error);
|
console.error('数据库表初始化失败:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import path from 'path';
|
|||||||
interface ReportConfig {
|
interface ReportConfig {
|
||||||
config_id: number;
|
config_id: number;
|
||||||
split_type: string; // 'company_id' 或 'user_id'
|
split_type: string; // 'company_id' 或 'user_id'
|
||||||
|
split_value: string; // 企业ID或用户ID
|
||||||
|
split_name?: string; // 显示名称
|
||||||
selected_fields: string[]; // 用户选择的字段
|
selected_fields: string[]; // 用户选择的字段
|
||||||
|
sum_fields?: string[]; // 需要求和的字段
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日报生成结果
|
// 日报生成结果
|
||||||
@@ -19,8 +22,10 @@ interface ReportResult {
|
|||||||
endTime: string;
|
endTime: string;
|
||||||
splitType: string;
|
splitType: string;
|
||||||
splitValue: string;
|
splitValue: string;
|
||||||
|
splitName?: string;
|
||||||
totalOrders: number;
|
totalOrders: number;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
sumResults?: Record<string, number>;
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
@@ -29,7 +34,9 @@ interface ReportResult {
|
|||||||
export async function generateDailyReport(
|
export async function generateDailyReport(
|
||||||
splitType: string,
|
splitType: string,
|
||||||
splitValue: string,
|
splitValue: string,
|
||||||
|
splitName: string,
|
||||||
selectedFields: string[],
|
selectedFields: string[],
|
||||||
|
sumFields: string[] = [],
|
||||||
reportDate?: string // 可选的报表日期,默认为昨天
|
reportDate?: string // 可选的报表日期,默认为昨天
|
||||||
): Promise<ReportResult> {
|
): Promise<ReportResult> {
|
||||||
try {
|
try {
|
||||||
@@ -48,7 +55,10 @@ export async function generateDailyReport(
|
|||||||
const queryField = splitType === 'company_id' ? 'company_id' : 'user_id';
|
const queryField = splitType === 'company_id' ? 'company_id' : 'user_id';
|
||||||
const fieldList = selectedFields.join(', ');
|
const fieldList = selectedFields.join(', ');
|
||||||
|
|
||||||
const [orders] = await pool.execute<any[]>(
|
// 获取连接
|
||||||
|
const connection = await pool.getConnection();
|
||||||
|
|
||||||
|
const [orders] = await connection.query<any[]>(
|
||||||
`SELECT ${fieldList} FROM t_equipment_charge_order
|
`SELECT ${fieldList} FROM t_equipment_charge_order
|
||||||
WHERE state = 3
|
WHERE state = 3
|
||||||
AND report_time >= ?
|
AND report_time >= ?
|
||||||
@@ -58,6 +68,8 @@ export async function generateDailyReport(
|
|||||||
[startTime, endTime, splitValue]
|
[startTime, endTime, splitValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
connection.release();
|
||||||
|
|
||||||
if (!Array.isArray(orders) || orders.length === 0) {
|
if (!Array.isArray(orders) || orders.length === 0) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -66,6 +78,7 @@ export async function generateDailyReport(
|
|||||||
endTime: endTime.toISOString(),
|
endTime: endTime.toISOString(),
|
||||||
splitType,
|
splitType,
|
||||||
splitValue,
|
splitValue,
|
||||||
|
splitName,
|
||||||
totalOrders: 0,
|
totalOrders: 0,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
error: '该时间段内没有订单数据'
|
error: '该时间段内没有订单数据'
|
||||||
@@ -97,6 +110,30 @@ export async function generateDailyReport(
|
|||||||
return processed;
|
return processed;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算求和
|
||||||
|
const sumResults: Record<string, number> = {};
|
||||||
|
if (sumFields && sumFields.length > 0) {
|
||||||
|
for (const sumField of sumFields) {
|
||||||
|
const sum = orders.reduce((total, order) => {
|
||||||
|
const value = parseFloat(order[sumField] || 0);
|
||||||
|
return total + value;
|
||||||
|
}, 0);
|
||||||
|
sumResults[FIELD_MAPPING[sumField] || sumField] = sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加求和行
|
||||||
|
const sumRow: Record<string, any> = {};
|
||||||
|
for (const field of selectedFields) {
|
||||||
|
const chineseName = FIELD_MAPPING[field] || field;
|
||||||
|
if (sumResults[chineseName] !== undefined) {
|
||||||
|
sumRow[chineseName] = sumResults[chineseName];
|
||||||
|
} else {
|
||||||
|
sumRow[chineseName] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processedOrders.push(sumRow);
|
||||||
|
}
|
||||||
|
|
||||||
// 生成Excel文件
|
// 生成Excel文件
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
const worksheet = XLSX.utils.json_to_sheet(processedOrders);
|
const worksheet = XLSX.utils.json_to_sheet(processedOrders);
|
||||||
@@ -109,8 +146,9 @@ export async function generateDailyReport(
|
|||||||
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, '日报');
|
XLSX.utils.book_append_sheet(workbook, worksheet, '日报');
|
||||||
|
|
||||||
// 生成文件名
|
// 生成文件名(包含显示名称)
|
||||||
const fileName = `日报_${splitType === 'company_id' ? '企业' : '用户'}_${splitValue}_${targetDate.toISOString().split('T')[0]}.xlsx`;
|
const displayName = splitName || splitValue;
|
||||||
|
const fileName = `日报_${splitType === 'company_id' ? '企业' : '用户'}_${displayName}_${targetDate.toISOString().split('T')[0]}.xlsx`;
|
||||||
const reportsDir = path.join(process.cwd(), 'public', 'reports');
|
const reportsDir = path.join(process.cwd(), 'public', 'reports');
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
@@ -134,8 +172,10 @@ export async function generateDailyReport(
|
|||||||
endTime: endTime.toISOString(),
|
endTime: endTime.toISOString(),
|
||||||
splitType,
|
splitType,
|
||||||
splitValue,
|
splitValue,
|
||||||
|
splitName,
|
||||||
totalOrders,
|
totalOrders,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
|
sumResults: sumFields && sumFields.length > 0 ? sumResults : undefined,
|
||||||
filePath: `/reports/${fileName}`
|
filePath: `/reports/${fileName}`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -146,6 +186,7 @@ export async function generateDailyReport(
|
|||||||
endTime: '',
|
endTime: '',
|
||||||
splitType,
|
splitType,
|
||||||
splitValue,
|
splitValue,
|
||||||
|
splitName,
|
||||||
totalOrders: 0,
|
totalOrders: 0,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
error: error instanceof Error ? error.message : '未知错误'
|
error: error instanceof Error ? error.message : '未知错误'
|
||||||
@@ -153,101 +194,79 @@ export async function generateDailyReport(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量生成日报(用于定时任务)
|
// 批量生成日报(用于定时任务)- 现在只为配置中指定的单个企业/用户生成
|
||||||
export async function generateBatchReports(configId: number): Promise<ReportResult[]> {
|
export async function generateBatchReports(configId: number): Promise<ReportResult[]> {
|
||||||
try {
|
try {
|
||||||
// 获取配置
|
// 获取配置
|
||||||
const [configs] = await pool.execute<any[]>(
|
const connection = await pool.getConnection();
|
||||||
|
const [configs] = await connection.query<any[]>(
|
||||||
'SELECT * FROM t_daily_report_config WHERE id = ? AND is_active = 1',
|
'SELECT * FROM t_daily_report_config WHERE id = ? AND is_active = 1',
|
||||||
[configId]
|
[configId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!Array.isArray(configs) || configs.length === 0) {
|
if (!Array.isArray(configs) || configs.length === 0) {
|
||||||
|
connection.release();
|
||||||
throw new Error('配置不存在或已禁用');
|
throw new Error('配置不存在或已禁用');
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = configs[0];
|
const config = configs[0];
|
||||||
|
connection.release();
|
||||||
|
|
||||||
const splitType = config.split_type;
|
const splitType = config.split_type;
|
||||||
|
const splitValue = config.split_value;
|
||||||
|
const splitName = config.split_name;
|
||||||
const selectedFields = JSON.parse(config.selected_fields);
|
const selectedFields = JSON.parse(config.selected_fields);
|
||||||
|
const sumFields = config.sum_fields ? JSON.parse(config.sum_fields) : undefined;
|
||||||
|
|
||||||
// 获取所有需要生成日报的企业/用户列表
|
// 只为配置中指定的企业/用户生成日报
|
||||||
const queryField = splitType === 'company_id' ? 'company_id' : 'user_id';
|
const result = await generateDailyReport(splitType, splitValue, splitName, selectedFields, sumFields);
|
||||||
|
|
||||||
// 计算时间范围
|
// 保存到历史记录表
|
||||||
const targetDate = new Date();
|
const connection2 = await pool.getConnection();
|
||||||
targetDate.setDate(targetDate.getDate() - 1);
|
|
||||||
|
|
||||||
const startTime = new Date(targetDate);
|
if (result.success) {
|
||||||
startTime.setHours(8, 0, 0, 0);
|
await connection2.query(
|
||||||
|
`INSERT INTO t_daily_report_history
|
||||||
const endTime = new Date(targetDate);
|
(report_date, start_time, end_time, split_type, split_value, split_name, config_id, total_orders, total_amount, sum_results, file_path, status)
|
||||||
endTime.setDate(endTime.getDate() + 1);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
endTime.setHours(8, 0, 0, 0);
|
[
|
||||||
|
result.reportDate,
|
||||||
// 获取该时间段内的所有企业/用户
|
result.startTime,
|
||||||
const [entities] = await pool.execute<any[]>(
|
result.endTime,
|
||||||
`SELECT DISTINCT ${queryField} as entity_id
|
result.splitType,
|
||||||
FROM t_equipment_charge_order
|
result.splitValue,
|
||||||
WHERE state = 3
|
result.splitName || null,
|
||||||
AND report_time >= ?
|
configId,
|
||||||
AND report_time < ?
|
result.totalOrders,
|
||||||
AND ${queryField} IS NOT NULL`,
|
result.totalAmount,
|
||||||
[startTime, endTime]
|
result.sumResults ? JSON.stringify(result.sumResults) : null,
|
||||||
);
|
result.filePath || null,
|
||||||
|
1
|
||||||
if (!Array.isArray(entities) || entities.length === 0) {
|
]
|
||||||
return [];
|
);
|
||||||
|
} else {
|
||||||
|
await connection2.query(
|
||||||
|
`INSERT INTO t_daily_report_history
|
||||||
|
(report_date, start_time, end_time, split_type, split_value, split_name, config_id, total_orders, total_amount, status)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[
|
||||||
|
result.reportDate,
|
||||||
|
result.startTime,
|
||||||
|
result.endTime,
|
||||||
|
result.splitType,
|
||||||
|
result.splitValue,
|
||||||
|
result.splitName || null,
|
||||||
|
configId,
|
||||||
|
result.totalOrders,
|
||||||
|
result.totalAmount,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量生成日报
|
connection2.release();
|
||||||
const results: ReportResult[] = [];
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
return [result];
|
||||||
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) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user