fix(mileage): totalKm 当日为空时回填该车 ln_vehicle_day_total_pg 最近一条
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
视图 v_vehicle_daily_stats.total_km 仅当日有 TBOX 记录才有值, 否则原样为 NULL。新增 fetchLatestPgTotalMileageMap 在应用层按车牌 取最近一条非空 total_mileage(queryDateMileage 限定 dates <= 查询日, 保证历史日不取未来值),插入 gpsTotal → latestPgTotal → bizTotal 的 回退链,让 totalKm 显示连续。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,11 +80,39 @@ async function fetchBizTotalMileageMap(): Promise<Map<string, number>> {
|
||||
return map;
|
||||
}
|
||||
|
||||
async function fetchLatestPgTotalMileageMap(asOf?: string): Promise<Map<string, number>> {
|
||||
// 当日 ln_vehicle_day_total_pg 无记录或 total_mileage 为 NULL 时,
|
||||
// 回填该车 dates <= asOf 的最近一条非空 total_mileage(÷1000 转 km),
|
||||
// 让视图 total_km 为 NULL 的车也能显示历史累计。
|
||||
const sql = `
|
||||
SELECT plate_number, total_mileage
|
||||
FROM (
|
||||
SELECT plate_number, total_mileage,
|
||||
ROW_NUMBER() OVER (PARTITION BY plate_number ORDER BY dates DESC) AS rn
|
||||
FROM ln_vehicle_day_total_pg
|
||||
WHERE total_mileage IS NOT NULL
|
||||
${asOf ? 'AND dates <= ?' : ''}
|
||||
) x
|
||||
WHERE rn = 1`;
|
||||
const params = asOf ? [asOf] : [];
|
||||
const [rows] = await mileagePool.execute(sql, params) as [
|
||||
{ plate_number: string; total_mileage: string | number | null }[],
|
||||
unknown,
|
||||
];
|
||||
const map = new Map<string, number>();
|
||||
for (const r of rows) {
|
||||
const km = Number(r.total_mileage) / 1000;
|
||||
if (Number.isFinite(km) && km > 0) map.set(r.plate_number, km);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function mergeVehicles(
|
||||
mileageRows: MileageRow[],
|
||||
infoMap: Map<string, VehicleInfoRow>,
|
||||
yesterdayMap: Map<string, number>,
|
||||
bizTotalMap: Map<string, number>,
|
||||
latestPgTotalMap: Map<string, number>,
|
||||
): CachedVehicle[] {
|
||||
const mileageMap = new Map<string, MileageRow>();
|
||||
for (const row of mileageRows) {
|
||||
@@ -99,12 +127,13 @@ function mergeVehicles(
|
||||
const dailyKm = Number(m.daily_km) || 0;
|
||||
const source = m.source || 'NONE';
|
||||
const gpsTotal = m.total_km !== null ? Number(m.total_km) : null;
|
||||
const latestPgTotal = latestPgTotalMap.get(m.plate);
|
||||
const bizTotal = bizTotalMap.get(m.plate);
|
||||
return {
|
||||
plate: m.plate,
|
||||
vin: m.vin,
|
||||
dailyKm,
|
||||
totalKm: gpsTotal !== null ? gpsTotal : (bizTotal ?? null),
|
||||
totalKm: gpsTotal !== null ? gpsTotal : (latestPgTotal ?? bizTotal ?? null),
|
||||
source,
|
||||
isOnline: source !== 'NONE' && dailyKm > 0,
|
||||
isDataSynced: source !== 'NONE',
|
||||
@@ -126,7 +155,7 @@ export async function refreshMonitoringCache(): Promise<void> {
|
||||
console.log('[mileage] refreshing monitoring cache...');
|
||||
const start = Date.now();
|
||||
|
||||
const [mileageRows, yesterdayMap, infoMap, targetRows, bizTotalMap] = await Promise.all([
|
||||
const [mileageRows, yesterdayMap, infoMap, targetRows, bizTotalMap, latestPgTotalMap] = await Promise.all([
|
||||
(async () => {
|
||||
const [dateRows] = await mileagePool.execute(
|
||||
'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats'
|
||||
@@ -160,6 +189,7 @@ export async function refreshMonitoringCache(): Promise<void> {
|
||||
WHERE t.is_deleted = 0`
|
||||
).then(([rows]) => rows as { id: number; target_name: string; plate_number: string }[]),
|
||||
fetchBizTotalMileageMap(),
|
||||
fetchLatestPgTotalMileageMap(),
|
||||
]);
|
||||
|
||||
const targetPlatesMap = new Map<string, Set<string>>();
|
||||
@@ -170,7 +200,7 @@ export async function refreshMonitoringCache(): Promise<void> {
|
||||
}
|
||||
const targetNames = Array.from(targetPlatesMap.keys());
|
||||
|
||||
const vehicles = mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap);
|
||||
const vehicles = mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap, latestPgTotalMap);
|
||||
const totalToday = vehicles.reduce((sum, v) => sum + v.dailyKm, 0);
|
||||
const totalAll = vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0);
|
||||
|
||||
@@ -189,7 +219,7 @@ export async function refreshMonitoringCache(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function queryDateMileage(dateStr: string): Promise<CachedVehicle[]> {
|
||||
const [mileageRows, yesterdayRows, infoMap, bizTotalMap] = await Promise.all([
|
||||
const [mileageRows, yesterdayRows, infoMap, bizTotalMap, latestPgTotalMap] = await Promise.all([
|
||||
mileagePool.execute(
|
||||
'SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?',
|
||||
[dateStr]
|
||||
@@ -200,6 +230,7 @@ export async function queryDateMileage(dateStr: string): Promise<CachedVehicle[]
|
||||
).then(([r]) => r as { plate: string; daily_km: string }[]),
|
||||
fetchVehicleInfoMap(),
|
||||
fetchBizTotalMileageMap(),
|
||||
fetchLatestPgTotalMileageMap(dateStr),
|
||||
]);
|
||||
|
||||
const yesterdayMap = new Map<string, number>();
|
||||
@@ -209,7 +240,7 @@ export async function queryDateMileage(dateStr: string): Promise<CachedVehicle[]
|
||||
if (km > existing) yesterdayMap.set(r.plate, km);
|
||||
}
|
||||
|
||||
return mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap);
|
||||
return mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap, latestPgTotalMap);
|
||||
}
|
||||
|
||||
export function buildDateFilters(vehicles: CachedVehicle[]): MonitoringFilters {
|
||||
|
||||
Reference in New Issue
Block a user