fix(consignment): 待匹配看板同分时璀璨不同优先排序
- 同分排序键改为 璀璨 → 系列 → 钻石;璀璨不同(冲突/高配)优先,璀璨相同靠后 - 新增「璀璨不同必须排在璀璨相同之前」单元测试,分数口径保持不变
This commit is contained in:
+53
-2
@@ -262,7 +262,7 @@ const BUILT_IN_SPECS = [
|
||||
sortOrder: Number(sortOrder),
|
||||
}))
|
||||
|
||||
test('看板新口径:同分时按 系列 → 钻石 → 璀璨 优先,枪型相同的必须排前面', () => {
|
||||
test('看板新口径:同分且璀璨相同时,按 系列 → 钻石 收敛', () => {
|
||||
const profile = buildProfile({
|
||||
config: { ...DEFAULT_CONSIGNMENT_PROFILE_CONFIG, specs: BUILT_IN_SPECS },
|
||||
})
|
||||
@@ -313,7 +313,7 @@ test('看板新口径:同分时按 系列 → 钻石 → 璀璨 优先,枪
|
||||
)
|
||||
})
|
||||
|
||||
test('看板新口径:系列都相同时,钻石相同优先于仅璀璨冲突', () => {
|
||||
test('看板新口径:同分时璀璨不同优先于璀璨相同(即使钻石冲突)', () => {
|
||||
const profile = buildProfile({
|
||||
config: { ...DEFAULT_CONSIGNMENT_PROFILE_CONFIG, specs: BUILT_IN_SPECS },
|
||||
})
|
||||
@@ -361,6 +361,57 @@ test('看板新口径:系列都相同时,钻石相同优先于仅璀璨冲
|
||||
)
|
||||
})
|
||||
|
||||
test('看板新口径:同分时璀璨不同的组合必须排在璀璨相同的组合之前', () => {
|
||||
const profile = buildProfile({
|
||||
config: { ...DEFAULT_CONSIGNMENT_PROFILE_CONFIG, specs: BUILT_IN_SPECS },
|
||||
})
|
||||
const order = buildOrder({
|
||||
id: 303,
|
||||
product_name: '安卓血宴3钻带1璀璨',
|
||||
material_json: JSON.stringify({ collect: { fields: { serverZone: '安卓' } } }),
|
||||
})
|
||||
// 璀璨不同(订单 1 / 库存 2),但枪型、钻级都相同。
|
||||
const brillianceDiff = buildInventory({
|
||||
id: 405,
|
||||
category_id: profile.categoryId,
|
||||
item_name: '安卓血宴3钻2璀璨 苏尔南',
|
||||
system: '安卓',
|
||||
server_zone: '互通',
|
||||
series: '血宴',
|
||||
diamond_level: 3,
|
||||
brilliance_count: 2,
|
||||
inbound_at: '2026-11-05T09:00:00.000Z',
|
||||
})
|
||||
// 璀璨相同,但枪型不同:与上一条同为 80 分。
|
||||
const brillianceSame = buildInventory({
|
||||
id: 406,
|
||||
category_id: profile.categoryId,
|
||||
item_name: '安卓怒嚎3钻1璀璨 苏尔南',
|
||||
system: '安卓',
|
||||
server_zone: '互通',
|
||||
series: '怒嚎',
|
||||
diamond_level: 3,
|
||||
brilliance_count: 1,
|
||||
inbound_at: '2026-11-01T09:00:00.000Z',
|
||||
})
|
||||
|
||||
const result = buildPendingConsignmentMatchPairs({
|
||||
orders: [order],
|
||||
// 故意把「璀璨相同」的库存放前面,验证排序而不是输入顺序。
|
||||
inventories: [brillianceSame, brillianceDiff],
|
||||
profileByOrderId: new Map([[303, profile]]),
|
||||
includeConflicts: true,
|
||||
})
|
||||
|
||||
assert.deepEqual(
|
||||
result.pairs.map((pair) => [pair.inventoryId, pair.score]),
|
||||
[
|
||||
[405, 80],
|
||||
[406, 80],
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
test('匹配看板:关键字同时搜索工单号与寄售单号', () => {
|
||||
const orders = [buildOrder({ id: 1, work_order_no: 'WK001' })]
|
||||
const inventories = [
|
||||
|
||||
@@ -251,14 +251,14 @@ function scoreFromDetails(details: PendingConsignmentMatchDetail[]): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* 同分排序的规格优先级:系列(枪型)> 钻石等级 > 璀璨数。
|
||||
* 同分排序的规格优先级:璀璨数 > 系列(枪型)> 钻石等级。
|
||||
*
|
||||
* 等权评分下「系列冲突」和「璀璨冲突」都是 80 分,若不在这里分层,
|
||||
* 同分就只能按工单 ID 排序,枪型不匹配的组合会压过枪型相同的高配组合。
|
||||
* 硬性口径:同分时「璀璨不同」的组合必须排在「璀璨相同」之前,
|
||||
* 让运营优先看到需要核对/补璀璨的配对;璀璨一致时再按枪型、钻级收敛。
|
||||
*/
|
||||
const SPEC_PRIORITY_KEYS = ['series', 'diamondLevel', 'brillianceCount'] as const
|
||||
const SPEC_PRIORITY_KEYS = ['brillianceCount', 'series', 'diamondLevel'] as const
|
||||
|
||||
/** 字段状态的优先级:命中 > 高配(partial)> 订单未写 > 冲突。 */
|
||||
/** 普通字段:命中优先(相同 > 高配 > 未写 > 冲突)。 */
|
||||
const SPEC_STATUS_RANK: Record<string, number> = {
|
||||
match: 4,
|
||||
partial: 3,
|
||||
@@ -267,11 +267,22 @@ const SPEC_STATUS_RANK: Record<string, number> = {
|
||||
ignored: 0,
|
||||
}
|
||||
|
||||
/** 璀璨数:不同优先(差异 > 高配 > 未写 > 相同)。 */
|
||||
const BRILLIANCE_STATUS_RANK: Record<string, number> = {
|
||||
conflict: 4,
|
||||
partial: 3,
|
||||
unknown: 2,
|
||||
match: 1,
|
||||
ignored: 0,
|
||||
}
|
||||
|
||||
/** 生成可逐项比较的优先级键;缺少明细(旧评分兜底)时全为 0。 */
|
||||
function buildSpecPriorityKey(details: PendingConsignmentMatchDetail[]): number[] {
|
||||
return SPEC_PRIORITY_KEYS.map((key) => {
|
||||
const detail = details.find((item) => item.key === key)
|
||||
return detail ? (SPEC_STATUS_RANK[detail.status] ?? 0) : 0
|
||||
if (!detail) return 0
|
||||
const rank = key === 'brillianceCount' ? BRILLIANCE_STATUS_RANK : SPEC_STATUS_RANK
|
||||
return rank[detail.status] ?? 0
|
||||
})
|
||||
}
|
||||
|
||||
@@ -565,9 +576,8 @@ export function buildPendingConsignmentMatchPairs(input: {
|
||||
|
||||
pairs.sort((left, right) => {
|
||||
if (right.score !== left.score) return right.score - left.score
|
||||
// 同分时先比关键规格:系列(枪型)> 钻石等级 > 璀璨数。
|
||||
// 等权评分下「系列冲突」与「璀璨冲突」都是 80 分,不分层就会按工单 ID 排序,
|
||||
// 导致枪型不匹配的组合压过「枪型相同、仅璀璨/钻级有差」的高配组合。
|
||||
// 同分时先比璀璨数:璀璨不同(需人工核对)优先,再按 系列 → 钻石 收敛。
|
||||
// 等权评分下「系列冲突」与「璀璨冲突」都是 80 分,不分层就会按工单 ID 排序。
|
||||
const leftPriority = buildSpecPriorityKey(left.matchDetails)
|
||||
const rightPriority = buildSpecPriorityKey(right.matchDetails)
|
||||
for (let index = 0; index < leftPriority.length; index += 1) {
|
||||
|
||||
Reference in New Issue
Block a user