feat: drill hydrogen trends into orders
This commit is contained in:
@@ -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
|
||||
// =========================================================
|
||||
|
||||
Reference in New Issue
Block a user