- 看板与引擎统一数量、模板规格、正向规格信号判定,纳入 internal_matching/unassigned - 库存侧改走新引擎匹配,移除旧 /fulfill 手动入口与旧弹窗 - 验收时将 reserved allocation 收口为 consumed,补份额级收口 - 拼单重新匹配通过 material 排除全部原库存/原卖家 - 开发/测试环境放宽迁移顺序校验,生产保持严格;修正集成测试份额报酬口径
134 lines
5.5 KiB
TypeScript
134 lines
5.5 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
import test from 'node:test'
|
|
|
|
/**
|
|
* 手动匹配弹窗必须挂在页面顶层,不能嵌在别的 Modal 里:
|
|
* 外层弹窗没打开时它根本不会挂载,用户点按钮会「没反应」。
|
|
*/
|
|
function assertTopLevelMount(filePath: string, componentName: string) {
|
|
const source = readFileSync(filePath, 'utf8')
|
|
const index = source.indexOf(`<${componentName}`)
|
|
assert.ok(index > 0, `${filePath} 里找不到 ${componentName} 的挂载点`)
|
|
|
|
const before = source.slice(0, index)
|
|
const opened = (before.match(/<Modal[\s>]/g) || []).length
|
|
const closed = (before.match(/<\/Modal>/g) || []).length
|
|
assert.equal(
|
|
opened - closed,
|
|
0,
|
|
`${componentName} 必须挂在顶层:不能嵌在别的 Modal 里,否则外层弹窗未打开时它不会渲染`,
|
|
)
|
|
}
|
|
|
|
test('库存面板:匹配订单弹窗挂在顶层(按钮点了必须能弹出来)', () => {
|
|
assertTopLevelMount(
|
|
fileURLToPath(
|
|
new URL('../src/pages/admin/panels/ConsignmentInventoryPanel.tsx', import.meta.url),
|
|
),
|
|
'ConsignmentInventoryMatchModal',
|
|
)
|
|
})
|
|
|
|
test('订单面板:内部库存匹配弹窗同样挂在顶层', () => {
|
|
assertTopLevelMount(
|
|
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
|
'ConsignmentWorkOrderMatchModal',
|
|
)
|
|
})
|
|
|
|
test('按钮点击要真的把状态设上,弹窗的 open 由该状态驱动', () => {
|
|
const source = readFileSync(
|
|
fileURLToPath(
|
|
new URL('../src/pages/admin/panels/ConsignmentInventoryPanel.tsx', import.meta.url),
|
|
),
|
|
'utf8',
|
|
)
|
|
assert.ok(source.includes('setManualMatchItem(record)'), '行内按钮必须设置待匹配的现货')
|
|
assert.ok(source.includes('open={Boolean(manualMatchItem)}'), '弹窗 open 必须由该状态驱动')
|
|
})
|
|
|
|
test('手动匹配入口必须按 consignment.fulfill 门控(提交的是履约动作,不能只挂 manage)', () => {
|
|
const inventoryPanel = readFileSync(
|
|
fileURLToPath(
|
|
new URL('../src/pages/admin/panels/ConsignmentInventoryPanel.tsx', import.meta.url),
|
|
),
|
|
'utf8',
|
|
)
|
|
assert.ok(
|
|
inventoryPanel.includes("resolveAdminPermission(sessionPermissions, 'consignment.fulfill')"),
|
|
'库存面板必须解析 consignment.fulfill 权限',
|
|
)
|
|
const buttonIndex = inventoryPanel.indexOf('手动匹配订单')
|
|
assert.ok(buttonIndex > 0, '找不到「手动匹配订单」按钮')
|
|
assert.ok(
|
|
inventoryPanel.slice(0, buttonIndex).lastIndexOf('canFulfillConsignment ?') > 0,
|
|
'「手动匹配订单」必须挂在 canFulfillConsignment 门下',
|
|
)
|
|
|
|
const ordersPanel = readFileSync(
|
|
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
|
'utf8',
|
|
)
|
|
const footerButtonIndex = ordersPanel.lastIndexOf('内部库存匹配')
|
|
assert.ok(footerButtonIndex > 0, '找不到「内部库存匹配」按钮')
|
|
const beforeFooterButton = ordersPanel.slice(0, footerButtonIndex)
|
|
assert.ok(
|
|
beforeFooterButton.lastIndexOf('canFulfillConsignment ?') >
|
|
beforeFooterButton.lastIndexOf('footer={['),
|
|
'拦截弹窗 footer 的「内部库存匹配」必须按 canFulfillConsignment 门控',
|
|
)
|
|
})
|
|
|
|
test('库存侧匹配弹窗:必须走新引擎,不再调用旧 /fulfill', () => {
|
|
const source = readFileSync(
|
|
fileURLToPath(
|
|
new URL('../src/components/admin/ConsignmentInventoryMatchModal.tsx', import.meta.url),
|
|
),
|
|
'utf8',
|
|
)
|
|
assert.ok(source.includes('fetchPendingPistolMatches'), '库存侧必须复用待匹配看板的统一判定')
|
|
assert.ok(source.includes('inventoryId: Number(inventoryId)'), '必须按当前库存过滤候选工单')
|
|
assert.ok(
|
|
source.includes('matchAdminConsignmentWorkOrder'),
|
|
'库存侧落库必须调用新引擎 /consignment/match',
|
|
)
|
|
assert.ok(!source.includes('fulfillWorkOrderWithConsignment'), '库存侧不得再调用旧 /fulfill 链路')
|
|
assert.ok(source.includes('quantitySufficient'), '数量不足的工单必须禁用选择')
|
|
assert.ok(source.includes('允许非精确/冲突强制匹配'), '非精确必须显式勾选')
|
|
})
|
|
|
|
test('订单面板:拦截弹窗与行内入口都只走新引擎', () => {
|
|
const source = readFileSync(
|
|
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
|
'utf8',
|
|
)
|
|
assert.ok(source.includes('matchAdminConsignmentWorkOrder'), '拦截弹窗必须调用新引擎匹配')
|
|
assert.ok(
|
|
!source.includes('fulfillWorkOrderWithConsignment'),
|
|
'订单面板不得再调用旧 /fulfill 链路',
|
|
)
|
|
assert.ok(
|
|
!source.includes('assignWorkOrderWithConsignmentWorker'),
|
|
'订单面板不得调用旧的指派打手接口',
|
|
)
|
|
assert.ok(!source.includes('手动匹配寄售'), '旧「手动匹配寄售」入口必须移除')
|
|
assert.ok(source.includes('进入待加好友'), '拦截弹窗必须写明工单进入待加好友')
|
|
})
|
|
|
|
test('寄售仓库库存面板:必须包含「通过审核入库时间」列,且详情弹窗正确展示时间', () => {
|
|
const source = readFileSync(
|
|
fileURLToPath(
|
|
new URL('../src/pages/admin/panels/ConsignmentInventoryPanel.tsx', import.meta.url),
|
|
),
|
|
'utf8',
|
|
)
|
|
assert.ok(source.includes("title: '通过审核入库时间'"), '库存表格必须有「通过审核入库时间」列')
|
|
assert.ok(
|
|
source.includes('formatDateTime(record.inboundAt || record.createdAt)'),
|
|
'表格必须格式化入库时间',
|
|
)
|
|
assert.ok(source.includes('label="通过审核入库时间"'), '详情弹窗必须包含「通过审核入库时间」项')
|
|
})
|