From 24984fe0338a4aead3109110b9208ffa36b64b79 Mon Sep 17 00:00:00 2001 From: shishengliang Date: Sat, 10 Oct 2026 21:43:03 +0800 Subject: [PATCH] fix(assets): show inventory operations managers and bump v1.1.19 --- package-lock.json | 4 ++-- package.json | 2 +- src/modules/assets/analysis-export.test.ts | 10 ++++++++++ src/modules/assets/analysis-export.ts | 4 ++-- .../assets/components/analysis/AssetAnalysisModal.tsx | 8 ++++---- src/modules/assets/types.ts | 1 + src/server/routes/vehicles.ts | 1 + src/server/routes/vehicles/model.test.ts | 1 + src/server/routes/vehicles/model.ts | 1 + src/server/routes/vehicles/repository.ts | 5 +++++ src/server/types.ts | 2 ++ 11 files changed, 30 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6de1da..cb536e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ln-bi", - "version": "1.1.18", + "version": "1.1.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ln-bi", - "version": "1.1.18", + "version": "1.1.19", "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", "@hono/node-server": "^1.13.0", diff --git a/package.json b/package.json index 52ecf03..a21fffb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ln-bi", "private": true, - "version": "1.1.18", + "version": "1.1.19", "type": "module", "scripts": { "dev": "concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"", diff --git a/src/modules/assets/analysis-export.test.ts b/src/modules/assets/analysis-export.test.ts index 26dde47..3da8e13 100644 --- a/src/modules/assets/analysis-export.test.ts +++ b/src/modules/assets/analysis-export.test.ts @@ -41,3 +41,13 @@ test('one Excel workbook contains statistics and all details in two named sheets const empty = await createAnalysisWorkbook([], []); assert.deepEqual(empty.SheetNames, ['统计表', '详情表']); }); + +test('inventory export uses assigned operations managers and a dash when unset', () => { + const vehicles = ['张三、李四', null, ' '].map(opsManagers => ({ + opsManagers, customerManager: '业务负责人', customerName: '客户', status: 'Inventory', + } as VehicleListItem)); + const rows = vehicleExportRows(vehicles, true); + assert.deepEqual(rows.map(row => row.运维负责人), ['张三、李四', '-', '-']); + assert.ok(rows.every(row => !('业务负责人' in row) && !('客户' in row))); + assert.equal(vehicleExportRows(vehicles)[0].业务负责人, '业务负责人'); +}); diff --git a/src/modules/assets/analysis-export.ts b/src/modules/assets/analysis-export.ts index 00f9c44..6748a27 100644 --- a/src/modules/assets/analysis-export.ts +++ b/src/modules/assets/analysis-export.ts @@ -12,7 +12,7 @@ export function statisticsExportRows(labels: string[], groups: { values: string[ })); } -export function vehicleExportRows(vehicles: VehicleListItem[]): ExportRow[] { +export function vehicleExportRows(vehicles: VehicleListItem[], inventory = false): ExportRow[] { const status: Record = { Abnormal: '异动', Pending: '待交车', Inventory: '在库', Operating: '运营中' }; const operation: Record = { '1': '租赁', '2': '自营', '3': '可运营', '4': '待运营' }; const hasPending = vehicles.some(v => v.pendingDelivery); @@ -21,7 +21,7 @@ export function vehicleExportRows(vehicles: VehicleListItem[]): ExportRow[] { 车牌: v.plateNumber, VIN: v.vin, 品牌: v.brandLabel, 车型: v.type, 型号: v.model, 省份: v.province, 城市: v.city || v.location, 状态: status[v.status] || v.status, 运营状态: operation[v.operationStatus || ''] || v.operationStatus, - 客户: v.customerName, 签约公司: v.orgName, 部门: v.departmentName, 业务负责人: v.customerManager, + ...(inventory ? { 运维负责人: v.opsManagers?.trim() || '-' } : { 客户: v.customerName, 签约公司: v.orgName, 部门: v.departmentName, 业务负责人: v.customerManager }), 所属公司: v.subjectOrg, 合同: v.contractNo, ...(hasPending ? { 待交车合同: v.pendingDelivery?.contractNo, 待交车客户: v.pendingDelivery?.customerName } : {}), ...(hasMovement ? { diff --git a/src/modules/assets/components/analysis/AssetAnalysisModal.tsx b/src/modules/assets/components/analysis/AssetAnalysisModal.tsx index c151142..1e7be3b 100644 --- a/src/modules/assets/components/analysis/AssetAnalysisModal.tsx +++ b/src/modules/assets/components/analysis/AssetAnalysisModal.tsx @@ -128,7 +128,7 @@ function AnalysisDialog({ selection, subject, close }: Props & { selection: Vehi extra: view === 'inventory' ? { 待交车: row.totals.pending, 异动: row.totals.abnormal } : undefined })), showDetails ? rows.length : analysis.total, ); - await downloadAnalysisWorkbook(statistics, vehicleExportRows(rows), `${title}-${scopeLabel}${detailGroup ? `-${detailGroup.label}` : ''}`); + await downloadAnalysisWorkbook(statistics, vehicleExportRows(rows, view === 'inventory'), `${title}-${scopeLabel}${detailGroup ? `-${detailGroup.label}` : ''}`); }; return
@@ -164,7 +164,7 @@ function AnalysisDialog({ selection, subject, close }: Props & { selection: Vehi

车辆明细 {number(visibleVehicles.length)} 台

{detailBusy ?
正在加载车辆明细…
: detailError ?

{detailError}

: <> {vehicles.length !== detailGroup.count &&

车辆状态可能已更新:统计快照 {detailGroup.count} 台,当前明细 {vehicles.length} 台。

} - {(view === 'abnormal' || detailGroup.values.inventoryState === 'abnormal' || (filterValues.inventoryState?.length === 1 && filterValues.inventoryState[0] === 'abnormal')) ? : (view === 'pending' || detailGroup.values.inventoryState === 'pending' || (filterValues.inventoryState?.length === 1 && filterValues.inventoryState[0] === 'pending')) ? : } + {(view === 'abnormal' || detailGroup.values.inventoryState === 'abnormal' || (filterValues.inventoryState?.length === 1 && filterValues.inventoryState[0] === 'abnormal')) ? : (view === 'pending' || detailGroup.values.inventoryState === 'pending' || (filterValues.inventoryState?.length === 1 && filterValues.inventoryState[0] === 'pending')) ? : } }
} @@ -175,14 +175,14 @@ function AnalysisDialog({ selection, subject, close }: Props & { selection: Vehi
; } -function VehicleRows({ vehicles }: { vehicles: VehicleListItem[] }) { +function VehicleRows({ vehicles, inventory = false }: { vehicles: VehicleListItem[]; inventory?: boolean }) { if (!vehicles.length) return
暂无匹配车辆
; const stateLabel = (vehicle: VehicleListItem) => { const operation = ({ '1': '租赁', '2': '自营', '3': '可运营', '4': '待运营' } as Record)[vehicle.operationStatus || ''] || vehicle.operationStatus; const business = ({ Abnormal: '异动', Pending: '待交车', Inventory: '在库', Operating: '运营中' } as Record)[vehicle.status] || vehicle.status; return [business, operation].filter(Boolean).join(' · '); }; - return
{['车牌 / VIN', '品牌 / 车型 / 型号', '省份 / 城市', '状态', '客户 / 签约公司', '部门 / 业务负责人', '所属公司'].map(label => )}{vehicles.map(vehicle => )}
{label}
{vehicle.plateNumber || vehicle.vin || '未提供'}{vehicle.brandLabel || '—'} · {vehicle.type}{vehicle.model}{vehicle.province || '未标注'} / {vehicle.city || vehicle.location || '未标注'}{stateLabel(vehicle)}{vehicle.customerName || '—'}{vehicle.orgName || '—'}{vehicle.departmentName || '—'}{vehicle.customerManager || '—'}{vehicle.subjectOrg || '—'}
; + return
{['车牌 / VIN', '品牌 / 车型 / 型号', '省份 / 城市', '状态', ...(inventory ? ['运维负责人'] : ['客户 / 签约公司', '部门 / 业务负责人']), '所属公司'].map(label => )}{vehicles.map(vehicle => {inventory ? : <>})}
{label}
{vehicle.plateNumber || vehicle.vin || '未提供'}{vehicle.brandLabel || '—'} · {vehicle.type}{vehicle.model}{vehicle.province || '未标注'} / {vehicle.city || vehicle.location || '未标注'}{stateLabel(vehicle)}{vehicle.opsManagers?.trim() || '-'}{vehicle.customerName || '—'}{vehicle.orgName || '—'}{vehicle.departmentName || '—'}{vehicle.customerManager || '—'}{vehicle.subjectOrg || '—'}
; } type WeeklyDimension = 'date' | 'contract_type'; diff --git a/src/modules/assets/types.ts b/src/modules/assets/types.ts index 52c82b7..93e9812 100644 --- a/src/modules/assets/types.ts +++ b/src/modules/assets/types.ts @@ -104,6 +104,7 @@ export interface InventoryTypeSummary { } export interface VehicleListItem { + opsManagers?: string | null; color?: string; operationStatus?: string | null; inventoryKey?: string; diff --git a/src/server/routes/vehicles.ts b/src/server/routes/vehicles.ts index 3b847f7..7ed70dc 100644 --- a/src/server/routes/vehicles.ts +++ b/src/server/routes/vehicles.ts @@ -544,6 +544,7 @@ function vehicleListItem(v: Vehicle) { subjectOrg: v.subjectOrg, departmentName: v.departmentName, customerManager: v.customerManager, + opsManagers: v.opsManagers || null, brandLabel: v.brandLabel, orgName: v.orgName, }; diff --git a/src/server/routes/vehicles/model.test.ts b/src/server/routes/vehicles/model.test.ts index a16edfa..e47ad67 100644 --- a/src/server/routes/vehicles/model.test.ts +++ b/src/server/routes/vehicles/model.test.ts @@ -199,6 +199,7 @@ test('transforms database rows without changing the route model contract', () => subjectOrg: '主体', projectName: '项目', customerManager: '经理', + opsManagers: null, managerId: '9', brandLabel: '现代', }); diff --git a/src/server/routes/vehicles/model.ts b/src/server/routes/vehicles/model.ts index c1cc515..95ec822 100644 --- a/src/server/routes/vehicles/model.ts +++ b/src/server/routes/vehicles/model.ts @@ -255,6 +255,7 @@ export function transformRow(row: VehicleRow): Vehicle { subjectOrg: row.主体, projectName: row.项目名称, customerManager: row.客户经理, + opsManagers: row.ops_managers?.trim() || null, managerId: row.经理ID || null, brandLabel: row.车辆品牌Label, }; diff --git a/src/server/routes/vehicles/repository.ts b/src/server/routes/vehicles/repository.ts index b6d4dbb..c56260a 100644 --- a/src/server/routes/vehicles/repository.ts +++ b/src/server/routes/vehicles/repository.ts @@ -100,6 +100,11 @@ export const MAIN_SQL = `SELECT NULLIF(vi.registered_ownership, '') AS 主体, vor.project_name AS 项目名称, vor.business_manager AS 客户经理, + (SELECT GROUP_CONCAT( + COALESCE(NULLIF(TRIM(ops.nick_name), ''), NULLIF(TRIM(ops.user_name), ''), CAST(ops.user_id AS CHAR)) + ORDER BY ops.id SEPARATOR '、') + FROM vehicle_ops_manager_rel ops + WHERE ops.vehicle_id = vi.id AND ops.del_flag = '0') AS ops_managers, CAST(COALESCE(c.business_manager_id, vi.business_id) AS CHAR) AS 经理ID FROM vehicle_info vi LEFT JOIN vehicle_status vs diff --git a/src/server/types.ts b/src/server/types.ts index 277a623..933858c 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -1,6 +1,7 @@ import type { AbnormalMoveDetail } from '../shared/assets/abnormal-move.js'; export interface VehicleRow { + ops_managers?: string | null; pending_delivery_id?: string | null; pending_contract_no?: string | null; pending_customer_name?: string | null; @@ -42,6 +43,7 @@ export interface VehicleRow { } export interface Vehicle { + opsManagers?: string | null; pendingDelivery?: { contractNo: string | null; customerName: string | null } | null; abnormalMove?: AbnormalMoveDetail | null; id: string;