From 23fa3e1531eb501aee869a0ed8bda5cab8d8d688 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Thu, 26 Mar 2026 14:47:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B5=84=E4=BA=A7=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E8=A1=A8=E6=8C=89=E5=93=81=E7=89=8C=E5=9E=8B=E5=8F=B7=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E4=BA=A4=E8=BD=A6/=E8=BF=98=E8=BD=A6/=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2/=E5=BE=85=E4=BA=A4=E8=BD=A6=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- src/server/routes/vehicles.ts | 68 +++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/src/server/routes/vehicles.ts b/src/server/routes/vehicles.ts index 66a6d96..79340e1 100644 --- a/src/server/routes/vehicles.ts +++ b/src/server/routes/vehicles.ts @@ -267,7 +267,56 @@ function getRegionCounts(vehicles: Vehicle[], regions: readonly string[]): Recor }, {} as Record); } -function getStats(list: Vehicle[]) { +// Weekly truck ID sets, cached +interface WeeklyTruckIds { + pending: Set; + delivered: Set; + returned: Set; + replaced: Set; +} +let cachedWeeklyTruckIds: WeeklyTruckIds | null = null; +let weeklyTruckIdsLastFetch = 0; + +async function getWeeklyTruckIds(): Promise { + const now = Date.now(); + if (cachedWeeklyTruckIds && now - weeklyTruckIdsLastFetch < CACHE_TTL) { + return cachedWeeklyTruckIds; + } + + const [[pendingRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([ + pool.query(`SELECT id AS truck_id FROM tab_truck WHERE is_deleted=0 AND is_operation=1 AND truck_rent_status=7`), + pool.query(`SELECT rent_truck.truck_id FROM tab_truck_rent_take take + LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id + LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id + WHERE take.is_deleted=0 AND take.take_name IS NOT NULL + AND task.task_type=1 AND task.task_status=1 AND take.update_time IS NOT NULL + AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`), + pool.query(`SELECT rent_truck.truck_id FROM tab_truck_rent_return r + LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id + LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id + WHERE r.is_deleted=0 AND r.return_date IS NOT NULL + AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL}`), + pool.query(`SELECT rent_truck.truck_id FROM tab_truck_rent_take take + LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id + LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id + WHERE take.is_deleted=0 AND take.take_name IS NOT NULL + AND task.task_type=3 AND task.task_status=1 AND take.update_time IS NOT NULL + AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`), + ]); + + const toSet = (rows: any[]) => new Set((rows as any[]).map((r) => Number(r.truck_id)).filter(Boolean)); + cachedWeeklyTruckIds = { + pending: toSet(pendingRows as any[]), + delivered: toSet(deliveredRows as any[]), + returned: toSet(returnedRows as any[]), + replaced: toSet(replacedRows as any[]), + }; + weeklyTruckIdsLastFetch = now; + return cachedWeeklyTruckIds; +} + +function getStats(list: Vehicle[], weeklyIds?: WeeklyTruckIds) { + const ids = list.map((v) => v.id); return { total: list.length, inventory: list.filter((v) => v.status === 'Inventory').length, @@ -275,11 +324,11 @@ function getStats(list: Vehicle[]) { list.filter((v) => v.status === 'Inventory'), REGIONS, ), - pending: 0, + pending: weeklyIds ? ids.filter((id) => weeklyIds.pending.has(id)).length : 0, operating: list.filter((v) => v.status === 'Operating').length, - weeklyDelivered: 0, - weeklyReturned: 0, - weeklyReplaced: 0, + weeklyDelivered: weeklyIds ? ids.filter((id) => weeklyIds.delivered.has(id)).length : 0, + weeklyReturned: weeklyIds ? ids.filter((id) => weeklyIds.returned.has(id)).length : 0, + weeklyReplaced: weeklyIds ? ids.filter((id) => weeklyIds.replaced.has(id)).length : 0, }; } @@ -419,7 +468,7 @@ app.get('/summary', async (c) => { // GET /api/vehicles/by-type app.get('/by-type', async (c) => { - const vehicles = await getVehicles(); + const [vehicles, weeklyIds] = await Promise.all([getVehicles(), getWeeklyTruckIds()]); const typeFilters = [ { name: '4.5T普货', filter: (v: Vehicle) => v.type === '4.5T' && !v.model.includes('冷链') }, @@ -435,20 +484,19 @@ app.get('/by-type', async (c) => { const modelSummaries: ModelSummary[] = models.map((model) => { const modelVehicles = typeVehicles.filter((v) => v.model === model); - // Use contractNo as batch identifier const batches = Array.from(new Set(modelVehicles.map((v) => v.contractNo || '未知'))).filter(Boolean); return { model, - ...getStats(modelVehicles), + ...getStats(modelVehicles, weeklyIds), batches: batches.map((batch) => ({ batch, - ...getStats(modelVehicles.filter((v) => (v.contractNo || '未知') === batch)), + ...getStats(modelVehicles.filter((v) => (v.contractNo || '未知') === batch), weeklyIds), })), }; }); - const typeStats = getStats(typeVehicles); + const typeStats = getStats(typeVehicles, weeklyIds); return { type: t.name, totalAssets: typeVehicles.length,