fix(consignment): 手动匹配寄卖后刷新工单列表并展示寄卖价格

This commit is contained in:
2026-11-07 15:57:39 +08:00
parent 8920afa0e5
commit 0e9087c8bb
11 changed files with 712 additions and 75 deletions
@@ -25,6 +25,13 @@ test('对接客服 tab 必须被 isWorkerPlatformTab 接受,并登记切页刷
])
})
test('订单 tab 切页刷新必须指向真实列表与统计查询键', () => {
assert.deepEqual(WORKER_PLATFORM_TAB_QUERY_KEYS.orders, [
['admin-worker-platform-orders'],
['admin-worker-platform-summary'],
])
})
test('每个登记了切页缓存的 tab 都必须在合法 tab 名单里', () => {
const nonTabs = new Set(WORKER_PLATFORM_NON_TAB_QUERY_KEYS)
for (const key of Object.keys(WORKER_PLATFORM_TAB_QUERY_KEYS)) {
@@ -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}`)
}
})
@@ -0,0 +1,32 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import test from 'node:test'
function readWorkOrdersPanel() {
return readFileSync(
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
'utf8',
)
}
test('工单表:发单金额后必须有独立「寄卖价格」列', () => {
const source = readWorkOrdersPanel()
assert.ok(source.includes("title: '发单金额'"), '找不到发单金额列')
assert.ok(source.includes("title: '寄卖价格'"), '缺少寄卖价格列')
assert.ok(source.includes('consignmentSaleTotalFen'), '主列必须读取整单寄卖总价')
assert.ok(source.includes('consignmentSaleQuantity'), '数量 > 1 时必须展示单价 × 数量')
const amountIndex = source.indexOf("title: '发单金额'")
const priceIndex = source.indexOf("title: '寄卖价格'", amountIndex)
assert.ok(priceIndex > amountIndex, '寄卖价格列必须在发单金额之后')
})
test('拼单明细:必须有「寄卖价格」列并展示单价 + 份额合计', () => {
const source = readWorkOrdersPanel()
assert.ok(source.includes("title: '份额报酬'"), '找不到份额报酬列')
const rewardIndex = source.indexOf("title: '份额报酬'")
const priceIndex = source.indexOf("title: '寄卖价格'", rewardIndex)
assert.ok(priceIndex > rewardIndex, '寄卖价格列必须在份额报酬之后')
assert.ok(source.includes('/份'), '寄卖价格必须展示每份单价')
assert.ok(source.includes('合计 {formatMoney(unitPriceFen * quantity)}'), '必须展示该份额合计')
})