fix(supply-chain): 限价明细「负责人限价」筛选补上候选值与筛选逻辑
- 负责人限价是 SupplyChainStyleLimitPrice.ownerLimitPrice 的派生列(按货号),此前 option 接口没有对应分支, 下拉框显示「没有可选值」、选了也不生效 - 候选值:无其他筛选时取限价表的全部去重价格(两位小数,与界面一致);有筛选时只取当前结果集里货号的限价; 无流程限价的商品提供「空值」选项 - 筛选:选中的价格先经 ROUND(price,2) 换回货号再按 sku 过滤;选中的价格无对应货号返回空结果; 「空值」= 货号不在限价表里 - 补 3 条单测(价格→货号、无匹配、空值)
This commit is contained in:
parent
ee983673d5
commit
c104a029c9
@ -11,17 +11,24 @@ import {
|
||||
} from '../supply-chain-product-archive-filters'
|
||||
|
||||
jest.mock('@/lib/prisma', () => ({
|
||||
prisma: { $queryRaw: jest.fn() },
|
||||
prisma: {
|
||||
$queryRaw: jest.fn(),
|
||||
supplyChainStyleLimitPrice: { findMany: jest.fn() },
|
||||
},
|
||||
}))
|
||||
|
||||
const mockedQueryRaw = (jest.requireMock('@/lib/prisma') as {
|
||||
prisma: { $queryRaw: jest.Mock }
|
||||
}).prisma.$queryRaw
|
||||
const mockedStyleLimitFindMany = (jest.requireMock('@/lib/prisma') as {
|
||||
prisma: { supplyChainStyleLimitPrice: { findMany: jest.Mock } }
|
||||
}).prisma.supplyChainStyleLimitPrice.findMany
|
||||
|
||||
beforeEach(() => {
|
||||
clearNormalCodeValueCache()
|
||||
mockedQueryRaw.mockReset()
|
||||
mockedQueryRaw.mockResolvedValue([{ value: 'DNZ0411' }, { value: 'XG0022' }])
|
||||
mockedStyleLimitFindMany.mockReset()
|
||||
})
|
||||
|
||||
describe('normal archive code pattern', () => {
|
||||
@ -241,4 +248,30 @@ describe('buildProductArchiveWhereWithTextRules normal code', () => {
|
||||
expect(serialized).toContain('整装')
|
||||
expect(serialized).toContain('组装')
|
||||
})
|
||||
|
||||
// 「负责人限价」是 SupplyChainStyleLimitPrice 的派生列:按价格换回货号再过滤。
|
||||
it('filters 负责人限价 by mapping the selected price back to 货号', async () => {
|
||||
mockedQueryRaw.mockResolvedValueOnce([{ styleCode: 'SJ004833' }, { styleCode: 'SJ004834' }])
|
||||
const where = await buildProductArchiveWhereWithTextRules({ opsPriceLimit: ['213.80'] }) as { AND?: unknown[] }
|
||||
const serialized = JSON.stringify(where.AND)
|
||||
|
||||
expect(serialized).toContain('{"sku":{"in":["SJ004833","SJ004834"]}}')
|
||||
})
|
||||
|
||||
it('returns no rows when the selected 负责人限价 has no matching 货号', async () => {
|
||||
mockedQueryRaw.mockResolvedValueOnce([])
|
||||
const where = await buildProductArchiveWhereWithTextRules({ opsPriceLimit: ['123.45'] }) as { AND?: unknown[] }
|
||||
|
||||
expect(JSON.stringify(where.AND)).toContain('__NO_MATCH__')
|
||||
})
|
||||
|
||||
it('treats 负责人限价空值 as 货号不在限价表里', async () => {
|
||||
mockedStyleLimitFindMany.mockResolvedValueOnce([{ styleCode: 'SJ004833' }, { styleCode: 'SJ004834' }])
|
||||
const where = await buildProductArchiveWhereWithTextRules({
|
||||
opsPriceLimit: [PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE],
|
||||
}) as { AND?: unknown[] }
|
||||
const serialized = JSON.stringify(where.AND)
|
||||
|
||||
expect(serialized).toContain('"NOT":{"sku":{"in":["SJ004833","SJ004834"]}}')
|
||||
})
|
||||
})
|
||||
|
||||
@ -605,6 +605,38 @@ export async function buildProductArchiveWhereWithTextRules(
|
||||
if (clause) extra.push(clause)
|
||||
}
|
||||
}
|
||||
// 「负责人限价」:派生自 SupplyChainStyleLimitPrice.ownerLimitPrice(按货号),
|
||||
// 先把选中的价格换回货号,再按 sku 过滤;「空值」= 不在限价表里的货号。
|
||||
if (omitFilter !== 'opsPriceLimit') {
|
||||
const rawValues = (filters.opsPriceLimit ?? []).filter(Boolean)
|
||||
if (rawValues.length > 0) {
|
||||
const includesEmpty = rawValues.includes(PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE)
|
||||
const prices = rawValues
|
||||
.filter((value) => value !== PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE)
|
||||
.map((value) => Number(String(value).replace(/[^\d.-]/g, '')))
|
||||
.filter((value) => Number.isFinite(value))
|
||||
const priceClauses: Prisma.SupplyChainProductArchiveWhereInput[] = []
|
||||
if (prices.length > 0) {
|
||||
const rows = await prisma.$queryRaw<Array<{ styleCode: string }>>(Prisma.sql`
|
||||
SELECT DISTINCT "styleCode"
|
||||
FROM "SupplyChainStyleLimitPrice"
|
||||
WHERE "ownerLimitPrice" IS NOT NULL
|
||||
AND ROUND("ownerLimitPrice"::numeric, 2) IN (${Prisma.join(prices)})
|
||||
`)
|
||||
const styleCodes = rows.map((row) => row.styleCode).filter(Boolean)
|
||||
// 选中的价格在限价表里没有对应货号时返回空结果(而不是忽略条件)
|
||||
priceClauses.push({ sku: { in: styleCodes.length > 0 ? styleCodes : ['__NO_MATCH__'] } })
|
||||
}
|
||||
if (includesEmpty) {
|
||||
const limitedStyles = await prisma.supplyChainStyleLimitPrice.findMany({ select: { styleCode: true } })
|
||||
priceClauses.push({
|
||||
NOT: { sku: { in: limitedStyles.map((row) => row.styleCode).filter(Boolean) } },
|
||||
})
|
||||
}
|
||||
if (priceClauses.length === 1) extra.push(priceClauses[0])
|
||||
else if (priceClauses.length > 1) extra.push({ OR: priceClauses })
|
||||
}
|
||||
}
|
||||
const asyncClauses = [...extra, ...ownerClauses]
|
||||
return asyncClauses.length > 0 ? { AND: [base, ...asyncClauses] } : base
|
||||
}
|
||||
|
||||
@ -140,6 +140,34 @@ class SupplyChainProductArchiveRepositoryClass implements SupplyChainProductArch
|
||||
const values = index.spuSet.size > 0 ? ['有', '无'] : ['无']
|
||||
return pageOptions(values, params)
|
||||
}
|
||||
// 「负责人限价」是派生列(SupplyChainStyleLimitPrice.ownerLimitPrice,按货号),
|
||||
// 候选值 = 当前筛选范围内实际出现的限价(两位小数,与界面展示一致)。
|
||||
if (params.field === 'opsPriceLimit') {
|
||||
const query = await preparePeriodArchiveQuery(params.filters, params.field)
|
||||
const unfiltered = query.ids === null && Object.keys(query.where).length === 0
|
||||
if (unfiltered) {
|
||||
const rows = await prisma.$queryRaw<Array<{ price: string }>>(Prisma.sql`
|
||||
SELECT DISTINCT ROUND("ownerLimitPrice"::numeric, 2)::text AS price
|
||||
FROM "SupplyChainStyleLimitPrice"
|
||||
WHERE "ownerLimitPrice" IS NOT NULL
|
||||
ORDER BY price
|
||||
`)
|
||||
// 没有流程限价的商品占多数,「空值」始终是一个有效候选
|
||||
return pageOptions([...rows.map((row) => row.price), PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE], params)
|
||||
}
|
||||
const records = await readSelectedArchives(query, { sku: true })
|
||||
const styleCodes = [...new Set(records.map((record) => String(record.sku ?? '').trim()).filter(Boolean))]
|
||||
const limitRows = styleCodes.length
|
||||
? await prisma.supplyChainStyleLimitPrice.findMany({
|
||||
where: { styleCode: { in: styleCodes }, ownerLimitPrice: { not: null } },
|
||||
select: { styleCode: true, ownerLimitPrice: true },
|
||||
})
|
||||
: []
|
||||
const values = [...new Set(limitRows.map((row) => Number(row.ownerLimitPrice).toFixed(2)))]
|
||||
const coveredStyles = new Set(limitRows.map((row) => row.styleCode))
|
||||
const hasMissingLimit = styleCodes.some((styleCode) => !coveredStyles.has(styleCode))
|
||||
return pageOptions(hasMissingLimit ? [...values, PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE] : values, params)
|
||||
}
|
||||
const field = FILTER_OPTION_DATABASE_FIELDS[params.field as keyof typeof FILTER_OPTION_DATABASE_FIELDS]
|
||||
if (!field) return { values: [], hasMore: false, total: 0 }
|
||||
const query = await preparePeriodArchiveQuery(params.filters, params.field)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user