diff --git a/src/modules/mileage/StatisticsView.tsx b/src/modules/mileage/StatisticsView.tsx index c82fa0f..1036b6e 100644 --- a/src/modules/mileage/StatisticsView.tsx +++ b/src/modules/mileage/StatisticsView.tsx @@ -381,16 +381,12 @@ export default function StatisticsView() {

{fmtKm(assessment.target)} km

-

{assessment.label}累计里程

-

{fmtKm(assessment.actualMileage)} km

+

{assessment.label}已完成

+

{fmtKm(assessment.completed)} km

数据截至 {assessment.daysLeft === 0 ? fmtDateLabel(assessment.endDate) : currentDateLabel}

-
-

{assessment.label}计入完成

-

{fmtKm(assessment.completed)} km

-

{assessment.label}完成率

{fmtPercent(assessment.completionRate)}

diff --git a/src/modules/mileage/types.ts b/src/modules/mileage/types.ts index fb8819b..e7371ca 100644 --- a/src/modules/mileage/types.ts +++ b/src/modules/mileage/types.ts @@ -83,7 +83,6 @@ export interface TargetYearlyAssessment { label: string; vehicleCount: number; target: number; - actualMileage: number; completed: number; remaining: number; completionRate: number; diff --git a/src/server/routes/mileage/targets.ts b/src/server/routes/mileage/targets.ts index 93dc128..edcc864 100644 --- a/src/server/routes/mileage/targets.ts +++ b/src/server/routes/mileage/targets.ts @@ -1,7 +1,7 @@ import { Hono } from 'hono'; import pool from '../../db.js'; import mileagePool from '../../mileage-db.js'; -import { getCache, queryDateMileage } from './cache.js'; +import { getCache } from './cache.js'; import { fetchVehicleInfoByPlates } from './vehicle-info.js'; import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js'; @@ -183,18 +183,28 @@ app.get('/', async (c) => { } } - const cutoffMileageMapByDate = new Map>(); - for (const date of Array.from(new Set(endedPeriodDates))) { - const vehicles = await queryDateMileage(date); - const map = new Map(); - for (const vehicle of vehicles) { - if (vehicle.totalKm != null) map.set(vehicle.plate, vehicle.totalKm); + const postPeriodDailyMap = new Map(); + const allTargetPlates = Array.from(new Set(targetVehicleRows.map(row => row.plate_number))); + const minEndedDate = endedPeriodDates.sort()[0]; + if (minEndedDate && allTargetPlates.length > 0) { + const [postPeriodDailyRows] = await mileagePool.execute( + `SELECT plate, + DATE_FORMAT(stat_date, '%Y-%m-%d') as stat_date, + MAX(GREATEST(daily_km, 0)) as daily_km + FROM v_vehicle_daily_stats + WHERE stat_date > ? AND plate IN (${allTargetPlates.map(() => '?').join(',')}) + GROUP BY plate, stat_date`, + [minEndedDate, ...allTargetPlates] + ) as [{ plate: string; stat_date: string; daily_km: string | number | null }[], unknown]; + + for (const row of postPeriodDailyRows) { + const list = postPeriodDailyMap.get(row.plate) || []; + list.push({ date: row.stat_date, km: Number(row.daily_km) || 0 }); + postPeriodDailyMap.set(row.plate, list); } - cutoffMileageMapByDate.set(date, map); } const yearlyMetricMap = new Map { }>(); const yearlyMetricDraftMap = new Map { const target = targetRuleMap.get(row.target_id); const annualMileage = Number(target?.annual_mileage_per_vehicle) || 0; const maxYear = Math.min(Number(target?.assessment_years) || 0, Number(row.current_year_number) || 0); + const postDailyRows = postPeriodDailyMap.get(row.plate_number) || []; for (let year = 1; year <= maxYear; year++) { const key = `${row.target_id}-${year}`; const goal = annualMileage * year; const endDate = addYearsMinusOneDay(row.assessment_start_date, year); - const cutoffMap = endDate < todayStr ? cutoffMileageMapByDate.get(endDate) : undefined; - const mileageAtCutoff = Math.max(0, cutoffMap?.get(row.plate_number) ?? (Number(row.current_mileage) || 0)); + const postPeriodMileage = endDate < todayStr + ? postDailyRows.reduce((sum, item) => item.date > endDate ? sum + item.km : sum, 0) + : 0; + const mileageAtCutoff = Math.max(0, (Number(row.current_mileage) || 0) - postPeriodMileage); const completed = Math.min(mileageAtCutoff, goal); const draft = yearlyMetricDraftMap.get(key) || { target: 0, - actualMileage: 0, completed: 0, remaining: 0, vehicleCount: 0, @@ -235,7 +246,6 @@ app.get('/', async (c) => { }; draft.target += goal; - draft.actualMileage += mileageAtCutoff; draft.completed += completed; draft.remaining += Math.max(goal - mileageAtCutoff, 0); draft.vehicleCount += 1; @@ -247,7 +257,6 @@ app.get('/', async (c) => { for (const [key, draft] of yearlyMetricDraftMap) { yearlyMetricMap.set(key, { - actualMileage: round2(draft.actualMileage), completed: round2(draft.completed), remaining: round2(draft.remaining), completionRate: round2(draft.target > 0 ? (draft.completed / draft.target) * 100 : 0), @@ -286,7 +295,6 @@ app.get('/', async (c) => { label: `第${yearNumber}年`, vehicleCount, target: Number(row.target_mileage) || 0, - actualMileage: cutoffMetrics?.actualMileage ?? (Number(row.completed_mileage) || 0), completed: cutoffMetrics?.completed ?? (Number(row.completed_mileage) || 0), remaining: cutoffMetrics?.remaining ?? remainingMileage, completionRate: cutoffMetrics?.completionRate ?? ((Number(row.completion_rate) || 0) * 100),