feat(assets): align flow statistics and separate abnormal inventory
ci/woodpecker/push/woodpecker Pipeline failed

Use operations signing times and delivery completion statuses; include replacement deliveries in delivery totals with range drilldowns and exports. Separate abnormal vehicles from inventory, add a date range picker, and enable CI for feature/asset-statistics.
This commit is contained in:
shishengliang
2026-09-16 10:35:50 +08:00
parent 6ea1b3d5dd
commit 0c26b973ef
21 changed files with 371 additions and 119 deletions
+46 -1
View File
@@ -110,7 +110,52 @@ test('keeps weekly detail SQL selection and flow date parameters unchanged', asy
{ sql: WEEKLY_DETAIL_SQL.new, values: undefined },
{
sql: FLOW_STATS_SQL,
values: ['2026-08-01', '2026-08-13', '2026-08-01', '2026-08-13', '2026-08-01', '2026-08-13'],
values: ['2026-08-01', '2026-08-13', '2026-08-01', '2026-08-13'],
},
]);
});
test('flow dates and inclusive day boundaries use operations signing times, retaining creation time only for display', () => {
for (const [alias, field] of [['dv', 'delivery_completion_processed_time'], ['r', 'return_completion_processed_time']]) {
assert.ok(FLOW_STATS_SQL.includes(`DATE_FORMAT(${alias}.${field}, '%Y-%m-%d') AS stat_date`));
assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} IS NOT NULL`));
assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} >= ?`));
assert.ok(FLOW_STATS_SQL.includes(`AND ${alias}.${field} < DATE_ADD(?, INTERVAL 1 DAY)`));
assert.ok(FLOW_STATS_SQL.includes(`DATE_FORMAT(${alias}.create_time, '%Y-%m-%d %H:%i:%s') AS submit_time`));
}
assert.doesNotMatch(FLOW_STATS_SQL, /AND \w+\.create_time/);
assert.ok(FLOW_STATS_SQL.includes('ORDER BY flow.event_time DESC, flow.id DESC'));
});
test('flow retains multiple business events for the same vehicle', async () => {
const events = [
{ id: 'delivered-1', truck_id: '1', type: 'delivered', stat_date: '2026-09-16' },
{ id: 'delivered-2', truck_id: '1', type: 'delivered', stat_date: '2026-09-16' },
{ id: 'returned-1', truck_id: '1', type: 'returned', stat_date: '2026-09-16' },
];
const repository = new VehicleRepository(new FakeDatabase([events]));
assert.deepEqual(await repository.getFlowDetailRows('2026-09-16', '2026-09-16'), events);
});
test('replacement trips come exclusively from linked delivery records', () => {
assert.ok(FLOW_STATS_SQL.includes("CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN 'replaced' ELSE 'delivered' END AS type"));
assert.ok(FLOW_STATS_SQL.includes("CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN '替换交车' ELSE '交车' END AS type_label"));
assert.equal((FLOW_STATS_SQL.match(/FROM delivery_vehicle/g) || []).length, 1);
assert.doesNotMatch(FLOW_STATS_SQL, /FROM vehicle_replacement|dv\.delivery_time|r\.arrival_time|vr\.replace_time/);
});
test('completed delivery and replacement trips use vehicle status and exclude suspended subjects', () => {
const [deliveries, returns] = FLOW_STATS_SQL.split('UNION ALL');
assert.ok(deliveries.includes('AND dv.delivery_status IN (2,3,20)'));
assert.ok(deliveries.includes('AND (dts.status IS NULL OR dts.status <> 10)'));
assert.doesNotMatch(deliveries, /dts\.status\s*(?:=|IN)|dv\.delivery_status IN \(2,3,5\)/);
assert.ok(returns.includes('AND r.status IN (2,3,5)'));
});
test('return history includes completed, signed and awaiting-signature statuses without arrival or settlement requirements', () => {
const returns = FLOW_STATS_SQL.split('UNION ALL')[1];
assert.ok(returns.includes('AND r.status IN (2,3,5)'));
assert.ok(returns.includes('AND r.return_completion_processed_time >= ?'));
assert.doesNotMatch(returns, /r\.is_arrived|r\.arrival_time|settlement|AND dts\.status|AND \(dts\.status/);
});