import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import test from 'node:test' import { computeConsignmentMatchScore, describeConsignmentMatch, resolveMatchCoverage, } from '../src/utils/consignment-match-coverage.ts' test('匹配打分:与仓库列表同口径(精确 100 / 高配 85 / 未知 50 / 冲突 0)', () => { assert.equal(computeConsignmentMatchScore({ matchType: 'exact', matchDetails: [] }), 100) assert.equal(computeConsignmentMatchScore({ matchType: 'partial', matchDetails: [] }), 85) assert.equal(computeConsignmentMatchScore({ matchType: 'unknown', matchDetails: [] }), 50) assert.equal(computeConsignmentMatchScore({ matchType: 'conflict', matchDetails: [] }), 0) assert.equal( computeConsignmentMatchScore({ matchType: 'partial', matchDetails: [ { key: 'grade', label: '等级', orderValue: 'A', inventoryValue: 'S', status: 'match', }, { key: 'series', label: '系列', orderValue: 'A', inventoryValue: 'S', status: 'partial', }, ], }), 93, ) }) test('匹配说明:精确/非精确/冲突都必须有可读文案', () => { assert.equal(describeConsignmentMatch({ matchType: 'exact' }), '规格完全一致') assert.equal( describeConsignmentMatch({ matchType: 'partial', matchReasons: [] }), '规格高于要求(非精确)', ) assert.equal( describeConsignmentMatch({ matchType: 'conflict', matchReasons: ['系统不一致', '区服不一致'], }), '不匹配:系统不一致;区服不一致', ) }) test('拼单覆盖:1+1 不够 3 份,2+1 才允许提交', () => { const short = resolveMatchCoverage({ sharingEnabled: true, totalQuantity: 3, activeQuantity: 0, selected: [ { inventoryId: 1, remainingQuantity: 1 }, { inventoryId: 2, remainingQuantity: 1 }, ], }) assert.equal(short.requiredQuantity, 3) assert.equal(short.selectedQuantity, 2) assert.equal(short.sufficient, false) assert.equal(short.gap, 1) const enough = resolveMatchCoverage({ sharingEnabled: true, totalQuantity: 3, activeQuantity: 0, selected: [ { inventoryId: 1, remainingQuantity: 2 }, { inventoryId: 2, remainingQuantity: 1 }, ], }) assert.equal(enough.selectedQuantity, 3) assert.equal(enough.sufficient, true) assert.equal(enough.gap, 0) }) test('拼单覆盖:已匹配 1 份时只需再选 2 份', () => { const result = resolveMatchCoverage({ sharingEnabled: true, totalQuantity: 3, activeQuantity: 1, selected: [{ inventoryId: 1, remainingQuantity: 2 }], }) assert.equal(result.requiredQuantity, 2) assert.equal(result.sufficient, true) }) test('整单覆盖:必须一条库存满足全部数量', () => { const short = resolveMatchCoverage({ sharingEnabled: false, totalQuantity: 3, activeQuantity: 0, selected: [{ inventoryId: 1, remainingQuantity: 2 }], }) assert.equal(short.requiredQuantity, 3) assert.equal(short.selectedQuantity, 2) assert.equal(short.sufficient, false) assert.equal(short.gap, 1) const enough = resolveMatchCoverage({ sharingEnabled: false, totalQuantity: 3, activeQuantity: 0, selected: [{ inventoryId: 1, remainingQuantity: 3 }], }) assert.equal(enough.sufficient, true) }) test('工单页:内部库存匹配只对待分配/待抢单显示,不再包含内部匹配中', () => { const source = readFileSync( fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)), 'utf8', ) assert.ok( source.includes("['unassigned', 'open'].includes(String(row.status || ''))"), '内部库存匹配入口必须只覆盖待分配/待抢单', ) assert.ok(!source.includes("'internal_matching'"), '状态筛选与入口都不应再包含内部匹配中') }) test('匹配弹窗:接口错误必须展示,不能伪装成没有库存', () => { const source = readFileSync( fileURLToPath( new URL('../src/components/admin/ConsignmentWorkOrderMatchModal.tsx', import.meta.url), ), 'utf8', ) assert.ok(source.includes('candidatesQuery.isError'), '必须处理候选查询错误') assert.ok(source.includes('读取可匹配寄卖库存失败'), '必须展示错误标题') assert.ok(source.includes('重试'), '必须提供重试入口') assert.ok( source.includes( '!candidatesQuery.isLoading && !candidatesQuery.isError && options.length === 0', ), '空状态必须排除错误场景', ) }) test('工单页:手动匹配寄卖成功后必须按真实查询键刷新列表与库存', () => { const source = readFileSync( fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)), 'utf8', ) const modalIndex = source.indexOf(' 0, '工单页必须挂载手动匹配寄卖弹窗') const matchedIndex = source.indexOf('onMatched={() => {', modalIndex) const matchedBlock = source.slice(matchedIndex, source.indexOf('}}', matchedIndex) + 2) assert.ok(matchedBlock.includes('refreshAll()'), '匹配成功必须调用 refreshAll() 重取列表') assert.ok( !matchedBlock.includes("'admin-work-orders'") && !matchedBlock.includes("'admin-work-orders-stats'"), '不得 invalidate 不存在的 admin-work-orders / admin-work-orders-stats 查询键', ) const refreshIndex = source.indexOf('async function refreshAll()') const refreshBlock = source.slice( refreshIndex, source.indexOf('async function runAction', refreshIndex), ) for (const key of [ 'admin-worker-platform-orders', 'admin-worker-platform-summary', 'admin-consignment-inventories', 'admin-consignment-stats', ]) { assert.ok(refreshBlock.includes(key), `refreshAll() 必须刷新 ${key}`) } })