feat(energy): integrate self-operated station customers and receipts
ci/woodpecker/push/woodpecker Pipeline was canceled

Co-authored-by: HiFox Agent <agents-noreply@hifox.com>
This commit is contained in:
kfluous
2026-09-05 17:03:15 +08:00
co-authored by HiFox Agent
parent 637a72c1e9
commit a177781fe7
14 changed files with 380 additions and 71 deletions
+105 -9
View File
@@ -588,6 +588,99 @@ test("单站看板过滤非指定零业务站并合并区间现结流水", async
assert.match(calls[4].sql, /GROUP BY DATE_FORMAT\(payment_date/);
});
test("单站外部客户按唯一标准化车牌聚合,进账保持租户客户级口径", async () => {
const calls: QueryCall[] = [];
const stationBoardApp = new Hono();
registerHydrogenStationBoardRoute(stationBoardApp, {
hydrogenPool: {
query: createQueryMock(
[
[{ id: 341, name: "佛山南海羚牛加氢站", province: "广东省", city: "佛山市", kg: 10, fee: 300, recordCount: 1 }],
[], [], [], [],
[], [], [],
[
{
month: "2026-09",
customerName: "外部客户 A",
kg: null,
fee: null,
recordCount: 1,
},
],
[
{
id: "auto:9007199254740993",
date: "2026-09-05",
customerName: "外部客户 A",
amount: 200,
payMethod: "wechat",
source: "spot_daily_auto",
sourceRecordCount: 2,
updatedAt: "2026-09-05 23:59:00",
},
{
id: "manual:1",
date: "2026-09-04",
customerName: "外部客户 B",
amount: 0,
payMethod: "corporate",
source: "external_recharge_manual",
sourceRecordCount: 1,
updatedAt: null,
},
],
],
calls,
),
},
cached: createCachedMock([]),
} as unknown as HydrogenStationBoardDependencies);
const response = await stationBoardApp.request(
"/hydrogen/station-board?stationId=341&startDate=2026-09-01&endDate=2026-09-05",
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.deepEqual(payload.selected.externalCustomerMonths, [{
month: "2026-09", customerName: "外部客户 A", kg: 0, fee: 0, recordCount: 1,
}]);
assert.deepEqual(payload.selected.externalReceipts, {
scope: "customer",
reason: "当前租户全部外部客户进账,不按站点归属,不计入本单站经营收益或现结 KPI。",
rows: [
{
id: "auto:9007199254740993", date: "2026-09-05", customerName: "外部客户 A", amount: 200,
payMethod: "wechat", source: "spot_daily_auto", sourceRecordCount: 2, updatedAt: "2026-09-05 23:59:00",
},
{
id: "manual:1", date: "2026-09-04", customerName: "外部客户 B", amount: 0,
payMethod: "corporate", source: "external_recharge_manual", sourceRecordCount: 1, updatedAt: null,
},
],
});
assert.equal(calls.length, 10);
for (const index of [0, 2, 3, 5, 7, 8, 9]) {
assert.match(calls[index].sql, /tenant_id\s*=\s*\?/);
assert.ok((calls[index].params as unknown[]).includes("000000"));
}
for (const index of [0, 2, 3, 5, 7, 8]) {
assert.match(calls[index].sql, /record_source, ''\) <> \?/);
assert.ok((calls[index].params as unknown[]).includes("external_recharge_manual"));
}
assert.match(calls[8].sql, /customerCount = 1/);
// 自营站客户月度保留全部加氢事实:映射仅做补充,不过滤未匹配或内部车辆。
assert.match(calls[7].sql, /LEFT JOIN external_vehicle_map/);
assert.match(calls[7].sql, /COALESCE\(m.customerName/);
assert.doesNotMatch(calls[7].sql.split('WHERE b.del_flag')[1], /vehicle_id IS NULL/);
assert.match(calls[7].sql, /GROUP BY[\s\S]*customerName/);
assert.match(calls[8].sql, /CONVERT\(UPPER\(REPLACE/);
assert.match(calls[8].sql, /b\.vehicle_id IS NULL OR b\.vehicle_id = 0/);
assert.match(calls[9].sql, /UNION ALL/);
assert.match(calls[9].sql, /fee_total AS amount/);
assert.match(calls[9].sql, /summary_date >= \?/);
assert.match(calls[9].sql, /refuel_time < DATE_ADD\(\?, INTERVAL 1 DAY\)/);
});
test("站日现结台账仅读取付款流水,并按最近有数据的 15 天聚合", async () => {
const calls: QueryCall[] = [];
const cacheCalls: CacheCall[] = [];
@@ -807,18 +900,18 @@ test("四个处理器继续使用原缓存语义、数据库、SQL 与参数", a
},
]);
// 指纹来自拆分前的 13 条 SQL,连空白符变化也会使测试失败。
// 指纹覆盖加氢事实专用过滤与既有查询结构,避免手工充值重新混入统计。
assert.deepEqual(
[...hydrogenCalls, ...electricCalls].map((call) => sqlHash(call.sql)),
[
"515acf4dbb2a",
"de16af670e96",
"1367eddb3b7f",
"f125d4dde376",
"c547b8e1318e",
"0e3dfd8f4b81",
"f1f69f275993",
"688cfdb7a2bf",
"4fa77c56a990",
"ac6329507bc2",
"e46a92ba851e",
"0295b2deee31",
"852b5b0b9fe1",
"abad6dcf0b2f",
"41cc73632c26",
"1224ee38f710",
"03fd669f8077",
"6e4031b10926",
"4fc77463294c",
@@ -859,6 +952,9 @@ test("四个处理器继续使用原缓存语义、数据库、SQL 与参数", a
],
);
assert.match(hydrogenCalls[7].sql, /b\.vehicle_id IS NULL/);
assert.ok(hydrogenCalls.every((call) =>
call.sql.includes("external_recharge_manual"),
));
assert.deepEqual(
electricCalls.map((call) => call.params),
[undefined, undefined, undefined, undefined, ["2025-01-01", "2025-01-02"]],