fix: 环比统计跟随筛选条件正确计算

每辆车缓存其昨日里程(yesterdayKm),筛选后的环比基于
相同筛选条件下的车辆计算,而非全局对比。
例如筛选"业务一部"后,今日和昨日都只统计一部的车辆。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-01 23:50:07 +08:00
parent 0b8bbbb063
commit 82dac759be

View File

@@ -39,11 +39,12 @@ interface CachedVehicle {
rentStatus: string | null; rentStatus: string | null;
entity: string | null; entity: string | null;
project: string | null; project: string | null;
yesterdayKm: number;
} }
interface MonitoringCache { interface MonitoringCache {
vehicles: CachedVehicle[]; vehicles: CachedVehicle[];
stats: { totalToday: number; totalAll: number; vehicleCount: number; yesterdayTotal: number }; stats: { totalToday: number; totalAll: number; vehicleCount: number };
filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] }; filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] };
updatedAt: string; updatedAt: string;
} }
@@ -72,10 +73,16 @@ async function refreshMonitoringCache() {
})(), })(),
(async () => { (async () => {
const [rows] = await mileagePool.execute( const [rows] = await mileagePool.execute(
`SELECT SUM(daily_km) as total FROM v_vehicle_daily_stats `SELECT plate, daily_km FROM v_vehicle_daily_stats
WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)` WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)`
) as any; ) as any;
return Number(rows[0]?.total) || 0; const map = new Map<string, number>();
for (const r of rows) {
const existing = map.get(r.plate) || 0;
const km = Number(r.daily_km) || 0;
if (km > existing) map.set(r.plate, km);
}
return map;
})(), })(),
pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]), pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]),
]); ]);
@@ -114,6 +121,7 @@ async function refreshMonitoringCache() {
rentStatus: info?.rent_status || null, rentStatus: info?.rent_status || null,
entity: info?.entity || null, entity: info?.entity || null,
project: info?.project || null, project: info?.project || null,
yesterdayKm: yesterdayResult.get(m.plate) || 0,
}; };
}); });
@@ -137,7 +145,7 @@ async function refreshMonitoringCache() {
monitoringCache = { monitoringCache = {
vehicles, vehicles,
stats: { totalToday, totalAll, vehicleCount: vehicles.length, yesterdayTotal: yesterdayResult }, stats: { totalToday, totalAll, vehicleCount: vehicles.length },
filters: { departments, customers, plates, projects, entities }, filters: { departments, customers, plates, projects, entities },
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
@@ -153,18 +161,20 @@ refreshMonitoringCache();
setInterval(refreshMonitoringCache, 2 * 60 * 1000); setInterval(refreshMonitoringCache, 2 * 60 * 1000);
// 查询指定日期的里程数据(非缓存) // 查询指定日期的里程数据(非缓存)
async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[]; yesterdayTotal: number }> { async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[] }> {
const [mileageRows] = await mileagePool.execute( const [mileageRows, yesterdayRows, infoRows] = await Promise.all([
`SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?`, mileagePool.execute(`SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?`, [dateStr]).then(([r]) => r as any[]),
[dateStr] mileagePool.execute(`SELECT plate, daily_km FROM v_vehicle_daily_stats WHERE stat_date = DATE_SUB(?, INTERVAL 1 DAY)`, [dateStr]).then(([r]) => r as any[]),
) as any; pool.execute(VEHICLE_INFO_SQL).then(([r]) => r as any[]),
const [yesterdayRows] = await mileagePool.execute( ]);
`SELECT SUM(daily_km) as total FROM v_vehicle_daily_stats WHERE stat_date = DATE_SUB(?, INTERVAL 1 DAY)`,
[dateStr]
) as any;
const [infoRows] = await pool.execute(VEHICLE_INFO_SQL) as any;
const infoMap = new Map<string, any>(); const infoMap = new Map<string, any>();
for (const row of infoRows) infoMap.set(row.plate, row); for (const row of infoRows) infoMap.set(row.plate, row);
const yesterdayMap = new Map<string, number>();
for (const r of yesterdayRows) {
const km = Number(r.daily_km) || 0;
const existing = yesterdayMap.get(r.plate) || 0;
if (km > existing) yesterdayMap.set(r.plate, km);
}
const mileageMap = new Map<string, any>(); const mileageMap = new Map<string, any>();
for (const row of mileageRows) { for (const row of mileageRows) {
const existing = mileageMap.get(row.plate); const existing = mileageMap.get(row.plate);
@@ -181,9 +191,10 @@ async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehi
customer: info?.customer || null, department: info?.department || null, customer: info?.customer || null, department: info?.department || null,
manager: info?.manager || null, rentStatus: info?.rent_status || null, manager: info?.manager || null, rentStatus: info?.rent_status || null,
entity: info?.entity || null, project: info?.project || null, entity: info?.entity || null, project: info?.project || null,
yesterdayKm: yesterdayMap.get(m.plate) || 0,
}; };
}); });
return { vehicles, yesterdayTotal: Number(yesterdayRows[0]?.total) || 0 }; return { vehicles };
} }
// GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页 // GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页
@@ -205,7 +216,6 @@ app.get('/monitoring', async (c) => {
const date = c.req.query('date') || ''; const date = c.req.query('date') || '';
let allVehicles: CachedVehicle[]; let allVehicles: CachedVehicle[];
let yesterdayTotal: number;
let filters: MonitoringCache['filters']; let filters: MonitoringCache['filters'];
if (date) { if (date) {
@@ -213,8 +223,6 @@ app.get('/monitoring', async (c) => {
try { try {
const result = await queryDateMileage(date); const result = await queryDateMileage(date);
allVehicles = result.vehicles; allVehicles = result.vehicles;
yesterdayTotal = result.yesterdayTotal;
// 从查询结果提取筛选选项
const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
filters = { filters = {
departments: (Array.from(new Set(allVehicles.map(v => v.department).filter(Boolean))) as string[]).sort((a, b) => { departments: (Array.from(new Set(allVehicles.map(v => v.department).filter(Boolean))) as string[]).sort((a, b) => {
@@ -231,10 +239,8 @@ app.get('/monitoring', async (c) => {
return c.json(emptyResponse, 500); return c.json(emptyResponse, 500);
} }
} else { } else {
// 默认:从缓存取
if (!monitoringCache) return c.json(emptyResponse); if (!monitoringCache) return c.json(emptyResponse);
allVehicles = monitoringCache.vehicles; allVehicles = monitoringCache.vehicles;
yesterdayTotal = monitoringCache.stats.yesterdayTotal;
filters = monitoringCache.filters; filters = monitoringCache.filters;
} }
@@ -259,12 +265,12 @@ app.get('/monitoring', async (c) => {
const total = vehicles.length; const total = vehicles.length;
// 基于筛选后的数据计算统计 // 基于筛选后的数据计算统计yesterdayTotal 也基于筛选后的车辆)
const filteredStats = { const filteredStats = {
totalToday: vehicles.reduce((sum, v) => sum + v.dailyKm, 0), totalToday: vehicles.reduce((sum, v) => sum + v.dailyKm, 0),
totalAll: vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0), totalAll: vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0),
vehicleCount: vehicles.length, vehicleCount: vehicles.length,
yesterdayTotal, yesterdayTotal: vehicles.reduce((sum, v) => sum + v.yesterdayKm, 0),
}; };
// 排序 // 排序