diff --git a/src/modules/scheduling/SuggestionDetail.tsx b/src/modules/scheduling/SuggestionDetail.tsx index 19b17e3..96f6ff3 100644 --- a/src/modules/scheduling/SuggestionDetail.tsx +++ b/src/modules/scheduling/SuggestionDetail.tsx @@ -229,7 +229,7 @@ export default function SuggestionDetail({ suggestion: s, onClose, onNotifySucce {c.region} {c.vehicleType} {c.targetName || '库存'} - 剩余{v.daysLeft}天 + 剩余{c.daysLeft}天 {c.canQualifyAfterSwap ? ( diff --git a/src/modules/scheduling/types.ts b/src/modules/scheduling/types.ts index a90bbbd..d7a0c18 100644 --- a/src/modules/scheduling/types.ts +++ b/src/modules/scheduling/types.ts @@ -25,6 +25,7 @@ export interface CandidateVehicle { totalMileage: number; completionRate: number; yearTarget: number | null; + daysLeft: number; region: string; province: string; mileageGap: number; diff --git a/src/server/routes/scheduling/algorithm.ts b/src/server/routes/scheduling/algorithm.ts index 4caa611..5dea032 100644 --- a/src/server/routes/scheduling/algorithm.ts +++ b/src/server/routes/scheduling/algorithm.ts @@ -90,13 +90,10 @@ export function generateSuggestions( // Among those, prefer the one with the smallest gap (easiest to finish). // Exclude already-qualified (>= 100%) — no value in swapping those. for (const vehicle of hopeless) { - const customerCanAdd = vehicle.customerAvgDaily * vehicle.daysLeft; - const candidates: CandidateVehicle[] = inventoryVehicles .filter((inv) => { if (!isTypeCompatible(vehicle.vehicleType, inv.vehicleType)) return false; if (inv.region !== vehicle.region) return false; - // Exclude already fully qualified const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; if (effectiveTarget > 0 && inv.totalMileage >= effectiveTarget) return false; return true; @@ -104,7 +101,9 @@ export function generateSuggestions( .map((inv) => { const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage); - const predictedAfterSwap = inv.totalMileage + customerCanAdd; + // Use candidate's own daysLeft for prediction + const candidateCanAdd = vehicle.customerAvgDaily * inv.daysLeft; + const predictedAfterSwap = inv.totalMileage + candidateCanAdd; const canQualifyAfterSwap = predictedAfterSwap >= effectiveTarget; return { plateNumber: inv.plateNumber, @@ -114,6 +113,7 @@ export function generateSuggestions( totalMileage: inv.totalMileage, completionRate: inv.completionRate, yearTarget: inv.yearTarget ?? vehicle.yearTarget, + daysLeft: inv.daysLeft, region: inv.region, province: inv.province, mileageGap, @@ -133,7 +133,7 @@ export function generateSuggestions( const gap = Math.max(0, vehicle.yearTarget - vehicle.currentYearMileage); const dailyReq = vehicle.daysLeft > 0 ? Math.round(gap / vehicle.daysLeft) : 0; - const predictedTotal = Math.round(vehicle.currentYearMileage + customerCanAdd); + const predictedTotal = Math.round(vehicle.currentYearMileage + vehicle.customerAvgDaily * vehicle.daysLeft); const reason = `客户日均 ${Math.round(vehicle.customerAvgDaily)} km | 考核周期剩余 ${vehicle.daysLeft} 天 · 日均需 ${fmtKmSimple(dailyReq)} km\n!!预估无法达标,需替换`; suggestions.push({ @@ -165,8 +165,9 @@ export function generateSuggestions( .map((inv) => { const effectiveTarget = inv.yearTarget ?? vehicle.yearTarget; const mileageGap = Math.max(0, effectiveTarget - inv.totalMileage); - const predictedAfterSwap = - inv.totalMileage + vehicle.customerAvgDaily * vehicle.daysLeft; + // Use candidate's own daysLeft for prediction + const candidateCanAdd = vehicle.customerAvgDaily * inv.daysLeft; + const predictedAfterSwap = inv.totalMileage + candidateCanAdd; const canQualifyAfterSwap = predictedAfterSwap >= effectiveTarget; return { plateNumber: inv.plateNumber, @@ -176,6 +177,7 @@ export function generateSuggestions( totalMileage: inv.totalMileage, completionRate: inv.completionRate, yearTarget: inv.yearTarget ?? vehicle.yearTarget, + daysLeft: inv.daysLeft, region: inv.region, province: inv.province, mileageGap, diff --git a/src/server/routes/scheduling/suggestions.ts b/src/server/routes/scheduling/suggestions.ts index d0b3924..5c9f892 100644 --- a/src/server/routes/scheduling/suggestions.ts +++ b/src/server/routes/scheduling/suggestions.ts @@ -250,12 +250,21 @@ app.get('/', async (c) => { // Cross-reference with assessment data const assessment = assessmentByPlate.get(plate); + // Compute this vehicle's own daysLeft from its assessment end date + let invDaysLeft = 0; + if (assessment?.current_year_assessment_end_date) { + const endDate = new Date(assessment.current_year_assessment_end_date); + invDaysLeft = Math.max(1, Math.ceil((endDate.getTime() - now.getTime()) / 86400000)); + } else { + invDaysLeft = Math.max(1, Math.ceil((yearEnd.getTime() - now.getTime()) / 86400000)); + } inventoryVehicles.push({ plateNumber: plate, vehicleType, region, province, totalMileage: assessment ? Number(assessment.vehicle_total_mileage) || 0 : 0, + daysLeft: invDaysLeft, targetId: assessment ? (assessment.target_id as number) : null, targetName: assessment ? (targetMap.get(assessment.target_id)?.targetName ?? null) : null, yearTarget: assessment ? Number(assessment.current_year_mileage_task) || null : null, diff --git a/src/server/routes/scheduling/types.ts b/src/server/routes/scheduling/types.ts index 5cf7b43..65a998b 100644 --- a/src/server/routes/scheduling/types.ts +++ b/src/server/routes/scheduling/types.ts @@ -25,6 +25,7 @@ export interface CandidateVehicle { totalMileage: number; completionRate: number; yearTarget: number | null; + daysLeft: number; region: string; province: string; mileageGap: number; @@ -97,6 +98,7 @@ export interface InventoryVehicle { region: string; province: string; totalMileage: number; + daysLeft: number; targetId: number | null; targetName: string | null; yearTarget: number | null;