fix(supply-chain): 仅在品牌页按GBSFY归类

This commit is contained in:
居居 2026-10-19 15:50:59 +08:00
parent 9915d7764b
commit f00d686c76
8 changed files with 41 additions and 23 deletions

View File

@ -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')
})
})

View File

@ -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

View File

@ -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

View File

@ -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']),

View File

@ -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) {

View File

@ -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(),

View File

@ -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(),

View File

@ -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: '',