diff --git a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-warehouse-config.service.test.ts b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-warehouse-config.service.test.ts index 6f1d0e24..ec600c50 100644 --- a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-warehouse-config.service.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-warehouse-config.service.test.ts @@ -29,7 +29,7 @@ function jstWarehouse( name: string wmsCoId: number | null isMain: boolean - attribute: string | null + attribute?: string | null createdAt?: string | null updatedAt?: string | null status?: string | null @@ -54,10 +54,6 @@ function repository( } } -function jstWarehouse(records: Array<{ name: string; wmsCoId: number | null; isMain: boolean; status?: string | null }> = []) { - return { listWarehouses: jest.fn().mockResolvedValue(records) } -} - describe('SupplyChainProductArchiveWarehouseConfigService', () => { it('infers ownership from the storage type and the warehouse name', () => { // 名称含「工厂-」「配件-」→ 工厂 @@ -70,39 +66,48 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { expect(inferProductArchiveWarehouseOwnership('横林二仓')).toBe('JIANSHANG') }) - it('combines archive warehouse names with saved configurations', async () => { + it('lists only warehouses that exist in the jst warehouse master', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: ['杭州仓', '上海仓'], + warehouseNames: ['上海仓', '北京仓'], configurations: [configuration()], - }), jstWarehouse()) + }), jstWarehouse([ + { name: '杭州仓', wmsCoId: 11, isMain: false }, + ])) const result = await service.list({}) - expect(result.items).toEqual([ - expect.objectContaining({ - id: null, - warehouseName: '上海仓', - // 未配置的仓库按名称给默认值:不含「代发」= 入库 - storageType: 'INBOUND', - status: null, - configured: false, - }), - expect.objectContaining({ - id: 'config-1', - warehouseName: '杭州仓', - storageType: 'INBOUND', - status: 'ACTIVE', - configured: true, - }), - ]) + // 档案历史名称与配置表里聚水潭查无此仓的条目都不展示。 + expect(result.items.map((item) => item.warehouseName)).toEqual(['杭州仓']) + expect(result.items[0]).toEqual(expect.objectContaining({ + id: 'config-1', + warehouseName: '杭州仓', + storageType: 'INBOUND', + status: 'ACTIVE', + configured: true, + })) + expect(result.pageInfo.total).toBe(1) + }) + + it('falls back to archive warehouse names when the jst master read fails', async () => { + const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ + warehouseNames: ['上海仓'], + configurations: [configuration()], + }), { listWarehouses: jest.fn().mockRejectedValue(new Error('source down')) }) + + const result = await service.list({}) + + // 聚水潭读取失败时降级为旧行为:档案历史名称兜底,配置照常展示,避免整页空白。 + expect(result.items.map((item) => item.warehouseName)).toEqual(['上海仓', '杭州仓']) expect(result.pageInfo.total).toBe(2) }) - it('matches saved configurations to archive warehouses ignoring case and spaces', async () => { + it('matches saved configurations to jst warehouses ignoring case and spaces', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: [' HangZhou '], + warehouseNames: ['历史仓'], configurations: [configuration({ warehouseName: 'hangzhou' })], - }), jstWarehouse()) + }), jstWarehouse([ + { name: ' HangZhou ', wmsCoId: 11, isMain: false }, + ])) const result = await service.list({}) @@ -111,14 +116,19 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { id: 'config-1', warehouseName: 'hangzhou', configured: true, - }), jstWarehouse()) + wmsCoId: 11, + })) }) it('can filter to unconfigured warehouse names before pagination', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: ['杭州仓', '上海仓', '北京仓'], + warehouseNames: ['历史仓'], configurations: [configuration()], - }), jstWarehouse()) + }), jstWarehouse([ + { name: '杭州仓', wmsCoId: 11, isMain: false }, + { name: '上海仓', wmsCoId: 22, isMain: false }, + { name: '北京仓', wmsCoId: 33, isMain: false }, + ])) const result = await service.list({ status: 'UNCONFIGURED', page: 1, pageSize: 1 }) @@ -129,9 +139,12 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { it('filters by config status codes and source status text together', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: ['杭州仓', '上海仓'], + warehouseNames: [], configurations: [configuration({ status: 'INACTIVE' })], - }), jstWarehouse()) + }), jstWarehouse([ + { name: '杭州仓', wmsCoId: 11, isMain: false }, + { name: '上海仓', wmsCoId: 22, isMain: false }, + ])) // 配置状态多选:表头下拉的 启用/停用/未配置 走这一支。 expect((await service.list({ statuses: ['ACTIVE'] })).items).toHaveLength(0) @@ -145,9 +158,12 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { it('shows inferred ownership for unconfigured warehouse rows', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: ['工厂-常州仓', '横林二仓'], + warehouseNames: [], configurations: [], - }), jstWarehouse()) + }), jstWarehouse([ + { name: '工厂-常州仓', wmsCoId: 11, isMain: false }, + { name: '横林二仓', wmsCoId: 22, isMain: false }, + ])) const result = await service.list({}) @@ -162,9 +178,12 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { it('defaults the inbound/dropship flag from the warehouse name', async () => { const service = new SupplyChainProductArchiveWarehouseConfigService(repository({ - warehouseNames: ['工厂-常州博一代发仓', '简尚-横林三仓'], + warehouseNames: [], configurations: [], - }), jstWarehouse()) + }), jstWarehouse([ + { name: '工厂-常州博一代发仓', wmsCoId: 11, isMain: false }, + { name: '简尚-横林三仓', wmsCoId: 22, isMain: false }, + ])) const result = await service.list({}) const byName = new Map(result.items.map((item) => [item.warehouseName, item])) @@ -217,6 +236,7 @@ describe('SupplyChainProductArchiveWarehouseConfigService', () => { }, jstWarehouse([ { name: '工厂-常州博一代发仓', wmsCoId: 1, isMain: false, attribute: null }, + { name: '杭州仓', wmsCoId: 2, isMain: false, attribute: null }, ]), ) diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive-warehouse-config.service.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive-warehouse-config.service.ts index 06921298..e8dd49f8 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive-warehouse-config.service.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive-warehouse-config.service.ts @@ -243,15 +243,15 @@ export class SupplyChainProductArchiveWarehouseConfigService { ) {} /** - * 仓库名称以聚水潭仓库档案(jst_warehouseInfo.name)为准, - * 读取失败时不阻断页面,退回到产品档案里的仓库名。 + * 仓库名称只取聚水潭仓库档案(jst_warehouseInfo.name)。 + * 读取失败时返回 null:调用方降级为产品档案历史名称兜底,避免数据源抖动把整页清空。 */ - private async loadJstWarehouses(): Promise { + private async loadJstWarehouses(): Promise { try { return await this.jstWarehouse.listWarehouses() } catch (cause) { console.error('[warehouse-config] jst warehouse read failed', cause) - return [] + return null } } @@ -262,7 +262,7 @@ export class SupplyChainProductArchiveWarehouseConfigService { async refreshWarehouseSource() { this.jstWarehouse.invalidateCache() const warehouses = await this.loadJstWarehouses() - return { warehouseCount: warehouses.length } + return { warehouseCount: (warehouses ?? []).length } } /** 产品档案的仓库→采购对照,用于工厂仓的默认负责人;读取失败不影响页面。 */ @@ -324,7 +324,7 @@ export class SupplyChainProductArchiveWarehouseConfigService { attribute: string | null status: string | null }>() - for (const warehouse of jstWarehouses) { + for (const warehouse of jstWarehouses ?? []) { options.set(normalizeWarehouseName(warehouse.name), { name: warehouse.name, wmsCoId: warehouse.wmsCoId, @@ -386,7 +386,7 @@ export class SupplyChainProductArchiveWarehouseConfigService { this.loadPurchaserByWarehouse(), ]) const jstWarehouseByName = new Map( - jstWarehouses.map((warehouse) => [normalizeWarehouseName(warehouse.name), warehouse]), + (jstWarehouses ?? []).map((warehouse) => [normalizeWarehouseName(warehouse.name), warehouse]), ) const statuses = input.statuses ?? [] const ownerships = input.ownerships ?? [] @@ -398,16 +398,19 @@ export class SupplyChainProductArchiveWarehouseConfigService { const createdDates = input.createdDates ?? [] const updatedDates = input.updatedDates ?? [] const merged = new Map() - // 仓库名称以聚水潭仓库档案为准,产品档案里的历史名称作为补充保留。 - for (const warehouseName of [ - ...jstWarehouses.map((warehouse) => warehouse.name), - ...result.warehouseNames, - ]) { + // 仓库名称只取聚水潭仓库档案;产品档案里的历史名称聚水潭查无此仓,不再展示。 + // 聚水潭读取失败时降级为档案历史名称兜底,避免数据源抖动把整页清空。 + for (const warehouseName of jstWarehouses === null + ? result.warehouseNames + : jstWarehouses.map((warehouse) => warehouse.name)) { const key = normalizeWarehouseName(warehouseName) if (key && !merged.has(key)) merged.set(key, mapUnconfigured(warehouseName)) } for (const configuration of result.configurations) { - merged.set(normalizeWarehouseName(configuration.warehouseName), mapRecord(configuration)) + const key = normalizeWarehouseName(configuration.warehouseName) + // 以聚水潭档案为基准时,配置表里聚水潭查无此仓的条目同样不展示。 + if (jstWarehouses !== null && !jstWarehouseByName.has(key)) continue + merged.set(key, mapRecord(configuration)) } // 仓库编号与仓库属性统一以聚水潭仓库档案为准,配置表里不重复存一份。 for (const [key, item] of merged) {