diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx index a2b44178..f3cec014 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx @@ -237,11 +237,13 @@ describe('限价管理', () => { const bulkRequest = productRequests().at(-1) ?? '' expect(brandFilterOf(bulkRequest)).toEqual(['自有品牌-亿家达', '其他品牌-普通大货']) + expect(JSON.parse(new URL(bulkRequest, 'http://localhost').searchParams.get('filters') ?? '{}').limitProductKind).toBe('bulk') fireEvent.click(screen.getByRole('button', { name: '品牌商品' })) await waitFor(() => { const brandRequest = productRequests().at(-1) ?? '' expect(brandFilterOf(brandRequest)).toEqual(['代理品牌-顾家', '代理品牌-索菲亚', '自有品牌-朴美']) + expect(JSON.parse(new URL(brandRequest, 'http://localhost').searchParams.get('filters') ?? '{}').limitProductKind).toBe('brand') }) }) diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx index ba50b8b0..a4fd750d 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx @@ -206,6 +206,9 @@ export function LimitPriceTab({ userId, canExport = false }: { userId: string; c const effectiveFilters = useMemo(() => { const next: ProductArchiveFilters = { ...filters } const tabBrands = productKind === 'combo' ? [] : (brandPartition?.[productKind] ?? []) + if (productKind === 'bulk' || productKind === 'brand') { + next.limitProductKind = productKind + } if (tabBrands.length) { const selected = filters.brand ?? [] const intersection = selected.length diff --git a/furniture-3d-system/src/lib/supply-chain-product-archive.ts b/furniture-3d-system/src/lib/supply-chain-product-archive.ts index c672f78d..3d06c147 100644 --- a/furniture-3d-system/src/lib/supply-chain-product-archive.ts +++ b/furniture-3d-system/src/lib/supply-chain-product-archive.ts @@ -195,6 +195,8 @@ export const PRODUCT_ARCHIVE_KEYWORD_FIELDS = ['barcode', 'spu', 'sku', 'product export type ProductArchiveKeywordField = typeof PRODUCT_ARCHIVE_KEYWORD_FIELDS[number] export type ProductArchiveFilters = { + /** 仅限价管理页签使用:品牌页额外纳入 GBSFY,大货页排除 GBSFY;不改档案品牌字段。 */ + limitProductKind?: 'bulk' | 'brand' salesGranularity?: 'day' | 'month' | 'year' salesPeriod?: string keyword?: string diff --git a/furniture-3d-system/src/server/repositories/__tests__/supply-chain-product-archive.repository.test.ts b/furniture-3d-system/src/server/repositories/__tests__/supply-chain-product-archive.repository.test.ts index 0e1821a3..17b6da58 100644 --- a/furniture-3d-system/src/server/repositories/__tests__/supply-chain-product-archive.repository.test.ts +++ b/furniture-3d-system/src/server/repositories/__tests__/supply-chain-product-archive.repository.test.ts @@ -26,6 +26,25 @@ describe('supply-chain product archive query filters', () => { expect(sorted.map((row) => row.id)).toEqual(['high-new', 'high-old', 'low', 'none']) }) + it('moves GBSFY into brand tab and excludes it from bulk without changing brand data', () => { + const sofiaCodeClause = { sku: { startsWith: 'GBSFY', mode: 'insensitive' } } + const brandWhere = buildProductArchiveWhere({ + brand: ['代理品牌-索菲亚'], + limitProductKind: 'brand', + }) + const bulkWhere = buildProductArchiveWhere({ + brand: ['其他品牌-普通大货'], + limitProductKind: 'bulk', + }) + + expect(brandWhere.AND).toEqual(expect.arrayContaining([ + { OR: [{ brand: { in: ['代理品牌-索菲亚'] } }, sofiaCodeClause] }, + ])) + expect(bulkWhere.AND).toEqual(expect.arrayContaining([ + { AND: [{ brand: { in: ['其他品牌-普通大货'] } }, { NOT: sofiaCodeClause }] }, + ])) + }) + it('field permission limits keyword matches to visible fields', () => { const where = buildProductArchiveWhere(stripProductArchiveBlockedFilters( { keyword: '机密供应商' }, new Set(['supplier', 'sku']), diff --git a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-filters.ts b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-filters.ts index cbee24b8..9b8a0a0e 100644 --- a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-filters.ts +++ b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-filters.ts @@ -284,6 +284,19 @@ export function buildProductArchiveWhere( const emptyClause = includesEmpty ? buildEmptyValueClause(databaseField) : null + if (filterKey === 'brand' && filters.limitProductKind) { + const brandClause = valueClause && emptyClause + ? { OR: [valueClause, emptyClause] } + : valueClause || emptyClause + if (!brandClause) continue + const sofiaCodeClause = { + sku: { startsWith: 'GBSFY', mode: 'insensitive' as const }, + } + clauses.push(filters.limitProductKind === 'brand' + ? { OR: [brandClause, sofiaCodeClause] } + : { AND: [brandClause, { NOT: sofiaCodeClause }] }) + continue + } if (valueClause && emptyClause) { clauses.push({ OR: [valueClause, emptyClause] } as Prisma.SupplyChainProductArchiveWhereInput) } else if (valueClause || emptyClause) { diff --git a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sync.service.test.ts b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sync.service.test.ts index 48c69283..4b2b3f40 100644 --- a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sync.service.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sync.service.test.ts @@ -4,7 +4,6 @@ import { PRODUCT_DEVELOPMENT_ARCHIVE_SOURCE_CODE, SupplyChainProductArchiveSyncService, mapProductCatalogWideArchiveSource, - normalizeProductArchiveBrand, } from '../supply-chain-product-archive-sync.service' import { recordProductArchiveFieldChanges, @@ -102,20 +101,6 @@ describe('supply-chain product archive wide-catalog sync', () => { ) }) - it('corrects mislabeled Sofia products from the GBSFY SKU prefix', () => { - const mapped = mapProductCatalogWideArchiveSource({ - ...createSourceRecord(), - archive_spu: 'GBSFY-XG0293', - archive_sku: 'GBSFY-XG029301', - barcode_business_key: 'GBSFY-XG029301N1441V01', - brand_name: '其他品牌-普通大货', - }) - - expect(mapped?.archive.brand).toBe('代理品牌-索菲亚') - expect(normalizeProductArchiveBrand('gbsfy-ad036001', '')).toBe('代理品牌-索菲亚') - expect(normalizeProductArchiveBrand('CZ014006', '其他品牌-普通大货')).toBe('其他品牌-普通大货') - }) - it('leaves the image empty when the current SKU has no image', () => { const mapped = mapProductCatalogWideArchiveSource({ ...createSourceRecord(), diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts index 05df2635..a3f55ed0 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts @@ -23,6 +23,7 @@ const archiveExcludeRuleSchema = z.object({ }).strict() const productArchiveFiltersSchema = z.object({ + limitProductKind: z.enum(['bulk', 'brand']).optional(), salesGranularity: archiveSalesGranularitySchema.optional(), salesPeriod: archiveSalesPeriodSchema.optional(), keyword: z.string().trim().min(1).max(160).optional(), diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive-sync.service.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive-sync.service.ts index 4081aea5..ee67b8a5 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive-sync.service.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive-sync.service.ts @@ -87,13 +87,6 @@ export function resolveProductArchivePackageType(value: string) { return PACKAGE_TYPE_BY_SET_COUNT[value.trim()] ?? '' } -/** 索菲亚货号以 GBSFY 开头;源品牌字段偶有误标,以货号前缀纠正。 */ -export function normalizeProductArchiveBrand(sku: string, brand: string) { - return sku.trim().toUpperCase().startsWith('GBSFY') - ? '代理品牌-索菲亚' - : brand.trim() -} - export function calculateProductArchiveVolume( length: string, width: string, @@ -167,7 +160,7 @@ export function mapProductCatalogWideArchiveSource( installDocId: text(record.drawing_id), installMethod: text(record.install_method), spec: text(record.archive_spec), - brand: normalizeProductArchiveBrand(text(record.archive_sku), text(record.brand_name)), + brand: text(record.brand_name), orderType: normalizeProductArchiveOrderType(record.purchase_order_method), inOutWarehouse: text(record.factory_warehouse), isSeasonal: '',