fix(consignment): 手动匹配寄卖后刷新工单列表并展示寄卖价格
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
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('工单页:internal_matching 显示「手动匹配寄卖」入口', () => {
|
||||
const source = readFileSync(
|
||||
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(source.includes('手动匹配寄卖'), '内部匹配中的按钮文案必须是手动匹配寄卖')
|
||||
assert.ok(
|
||||
source.includes("String(row.status || '') === 'internal_matching'"),
|
||||
'按钮文案必须只在 internal_matching 下切换',
|
||||
)
|
||||
})
|
||||
|
||||
test('工单页:手动匹配寄卖成功后必须按真实查询键刷新列表与库存', () => {
|
||||
const source = readFileSync(
|
||||
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
)
|
||||
const modalIndex = source.indexOf('<ConsignmentWorkOrderMatchModal')
|
||||
assert.ok(modalIndex > 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}`)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user