From 92564cd7318a49e7d2262ef6f3279ac8f95b21fd Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Sun, 15 Nov 2026 14:01:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(consignment):=20=E5=BE=85=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E7=9C=8B=E6=9D=BF=E5=90=8C=E5=88=86=E6=97=B6=E7=92=80=E7=92=A8?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E4=BC=98=E5=85=88=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 同分排序键改为 璀璨 → 系列 → 钻石;璀璨不同(冲突/高配)优先,璀璨相同靠后 - 新增「璀璨不同必须排在璀璨相同之前」单元测试,分数口径保持不变 --- .../consignment-pending-match-service.test.ts | 55 ++++++++++++++++++- .../consignment-pending-match-service.ts | 28 +++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/apps/backend/src/services/worker-platform/consignment-pending-match-service.test.ts b/apps/backend/src/services/worker-platform/consignment-pending-match-service.test.ts index bed12a59..0cee92b5 100644 --- a/apps/backend/src/services/worker-platform/consignment-pending-match-service.test.ts +++ b/apps/backend/src/services/worker-platform/consignment-pending-match-service.test.ts @@ -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 = [ diff --git a/apps/backend/src/services/worker-platform/consignment-pending-match-service.ts b/apps/backend/src/services/worker-platform/consignment-pending-match-service.ts index 9a2b905b..6bd6c087 100644 --- a/apps/backend/src/services/worker-platform/consignment-pending-match-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-pending-match-service.ts @@ -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 = { match: 4, partial: 3, @@ -267,11 +267,22 @@ const SPEC_STATUS_RANK: Record = { ignored: 0, } +/** 璀璨数:不同优先(差异 > 高配 > 未写 > 相同)。 */ +const BRILLIANCE_STATUS_RANK: Record = { + 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) {