feat: drill hydrogen trends into orders

This commit is contained in:
kkfluous
2026-08-07 16:29:26 +08:00
parent 263d8c6245
commit a8f7548314
10 changed files with 432 additions and 14 deletions
+112
View File
@@ -7,6 +7,8 @@ import {
parseHydrogenCustomerKind,
parseElectricVehicleScope,
parseHydrogenCustomerName,
parseHydrogenOrderLimit,
parseHydrogenOrderPage,
parseHydrogenStationId,
parseEnergyDate,
parseEtcLimit,
@@ -538,6 +540,116 @@ app.get('/hydrogen/daily', async (c) => {
return c.json(data);
});
// =========================================================
// 氢能订单下钻:指定自然日 + 车辆归属 + 可选站点/客户
// =========================================================
app.get('/hydrogen/orders', async (c) => {
const date = parseEnergyDate(c.req.query('date'));
if (date === null) return c.json({ error: 'date 必须是有效的 YYYY-MM-DD 日期' }, 400);
const customer = parseHydrogenCustomerKind(c.req.query('customer'));
const stationIdParam = c.req.query('stationId');
const customerNameParam = c.req.query('customerName');
const stationId = parseHydrogenStationId(stationIdParam);
const customerName = parseHydrogenCustomerName(customerNameParam);
if (stationIdParam !== undefined && stationId === null) {
return c.json({ error: 'stationId 必须是非负整数' }, 400);
}
if (customerNameParam !== undefined && customerName === null) {
return c.json({ error: 'customerName 必须是 1-128 个字符' }, 400);
}
const page = parseHydrogenOrderPage(c.req.query('page'));
const limit = parseHydrogenOrderLimit(c.req.query('limit'));
const offset = (page - 1) * limit;
const whereParts = [
HYDROGEN_BASE_WHERE_B,
`b.${HYDROGEN_LOCAL} >= ?`,
`b.${HYDROGEN_LOCAL} < DATE_ADD(?, INTERVAL 1 DAY)`,
customerClause(customer).replaceAll('customer_price', 'b.customer_price').replaceAll('fee_total', 'b.fee_total'),
];
const params: Array<string | number> = [date, date];
if (stationId !== null) {
whereParts.push('COALESCE(b.station_id, 0) = ?');
params.push(stationId);
}
if (customerName !== null) {
whereParts.push("COALESCE(NULLIF(TRIM(b.customer_name), ''), '未指定客户') = ?");
params.push(customerName);
}
const where = whereParts.join(' AND ');
const data = await cached(
`hydrogen/orders?date=${date}&customer=${customer}&station=${stationId ?? 'all'}&customerName=${encodeURIComponent(customerName ?? 'all')}&page=${page}&limit=${limit}`,
async () => {
const hydrogenPool = getHydrogenPool();
const [[summaryRows], [detailRows]] = await Promise.all([
hydrogenPool.query<RowDataPacket[]>(
`SELECT COUNT(*) AS recordCount,
SUM(b.amount_kg) AS totalKg,
SUM(b.cost_total) AS totalCost,
SUM(b.fee_total) AS totalRevenue
FROM ${HYDROGEN_TABLE} b
WHERE ${where}`,
params,
),
hydrogenPool.query<RowDataPacket[]>(
`SELECT DATE_FORMAT(b.${HYDROGEN_LOCAL}, '%Y-%m-%d %H:%i:%s') AS refuelTime,
COALESCE(NULLIF(TRIM(b.plate_number), ''), '未识别车辆') AS plate,
COALESCE(NULLIF(TRIM(b.customer_name), ''), '未指定客户') AS customerName,
COALESCE(s.station_short_name, s.station_name, NULLIF(TRIM(b.station_name), ''),
CASE WHEN b.station_id IS NULL THEN '未关联站点'
ELSE CONCAT('未知站点 #', b.station_id) END) AS stationName,
b.amount_kg AS amountKg,
b.cost_price AS costPrice,
b.cost_total AS costTotal,
b.customer_price AS customerPrice,
b.fee_total AS revenue
FROM ${HYDROGEN_TABLE} b
LEFT JOIN hydrogen_station s ON s.id = b.station_id AND s.del_flag = '0'
WHERE ${where}
ORDER BY b.${HYDROGEN_LOCAL} DESC,
b.plate_number ASC,
b.station_id ASC,
b.amount_kg DESC
LIMIT ? OFFSET ?`,
[...params, limit, offset],
),
]);
const summary = summaryRows[0] ?? {};
const recordCount = Number(summary.recordCount) || 0;
return {
date,
vehicleScope: customer,
stationId,
customerName,
page,
limit,
recordCount,
totalPages: Math.max(1, Math.ceil(recordCount / limit)),
totalKg: Math.round((Number(summary.totalKg) || 0) * 100) / 100,
totalCost: Math.round((Number(summary.totalCost) || 0) * 100) / 100,
totalRevenue: Math.round((Number(summary.totalRevenue) || 0) * 100) / 100,
items: detailRows.map((row, index) => ({
rowNumber: offset + index + 1,
refuelTime: String(row.refuelTime),
plate: String(row.plate),
customerName: String(row.customerName),
stationName: String(row.stationName),
amountKg: Math.round((Number(row.amountKg) || 0) * 100) / 100,
costPrice: Math.round((Number(row.costPrice) || 0) * 100) / 100,
costTotal: Math.round((Number(row.costTotal) || 0) * 100) / 100,
customerPrice: Math.round((Number(row.customerPrice) || 0) * 100) / 100,
revenue: Math.round((Number(row.revenue) || 0) * 100) / 100,
})),
};
},
{ source: 'hydrogenDatabase' },
);
return c.json(data);
});
// =========================================================
// 电能 总览:KPI + 本月每日柱图数据 —— 数据源:bi_ele_charge_record
// =========================================================