diff --git a/src/modules/mileage/StatisticsView.tsx b/src/modules/mileage/StatisticsView.tsx
index 1036b6e..c82fa0f 100644
--- a/src/modules/mileage/StatisticsView.tsx
+++ b/src/modules/mileage/StatisticsView.tsx
@@ -381,12 +381,16 @@ export default function StatisticsView() {
{fmtKm(assessment.target)} km
-
{assessment.label}已完成
-
{fmtKm(assessment.completed)} km
+
{assessment.label}累计里程
+
{fmtKm(assessment.actualMileage)} 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 e7371ca..fb8819b 100644
--- a/src/modules/mileage/types.ts
+++ b/src/modules/mileage/types.ts
@@ -83,6 +83,7 @@ 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 edcc864..93dc128 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 } from './cache.js';
+import { getCache, queryDateMileage } from './cache.js';
import { fetchVehicleInfoByPlates } from './vehicle-info.js';
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
@@ -183,28 +183,18 @@ app.get('/', async (c) => {
}
}
- 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);
+ 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);
}
+ 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 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 cutoffMap = endDate < todayStr ? cutoffMileageMapByDate.get(endDate) : undefined;
+ const mileageAtCutoff = Math.max(0, cutoffMap?.get(row.plate_number) ?? (Number(row.current_mileage) || 0));
const completed = Math.min(mileageAtCutoff, goal);
const draft = yearlyMetricDraftMap.get(key) || {
target: 0,
+ actualMileage: 0,
completed: 0,
remaining: 0,
vehicleCount: 0,
@@ -246,6 +235,7 @@ app.get('/', async (c) => {
};
draft.target += goal;
+ draft.actualMileage += mileageAtCutoff;
draft.completed += completed;
draft.remaining += Math.max(goal - mileageAtCutoff, 0);
draft.vehicleCount += 1;
@@ -257,6 +247,7 @@ 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),
@@ -295,6 +286,7 @@ 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),