feat(consignment): 统一寄卖匹配看板口径并收口占用与重匹配
- 看板与引擎统一数量、模板规格、正向规格信号判定,纳入 internal_matching/unassigned - 库存侧改走新引擎匹配,移除旧 /fulfill 手动入口与旧弹窗 - 验收时将 reserved allocation 收口为 consumed,补份额级收口 - 拼单重新匹配通过 material 排除全部原库存/原卖家 - 开发/测试环境放宽迁移顺序校验,生产保持严格;修正集成测试份额报酬口径
This commit is contained in:
@@ -4,9 +4,8 @@ import { fileURLToPath } from 'node:url'
|
||||
import test from 'node:test'
|
||||
|
||||
/**
|
||||
* 手动匹配弹窗曾经被插到「编辑寄售物品」弹窗**内部**,导致外层弹窗没打开时它根本没挂载:
|
||||
* 用户点「手动匹配订单」什么都不会发生。这类"点了没反应"的问题在浏览器里才看得出来,
|
||||
* 这里用静态检查把它钉住:组件必须挂在顶层(前面不能有未闭合的 Modal)。
|
||||
* 手动匹配弹窗必须挂在页面顶层,不能嵌在别的 Modal 里:
|
||||
* 外层弹窗没打开时它根本不会挂载,用户点按钮会「没反应」。
|
||||
*/
|
||||
function assertTopLevelMount(filePath: string, componentName: string) {
|
||||
const source = readFileSync(filePath, 'utf8')
|
||||
@@ -23,19 +22,19 @@ function assertTopLevelMount(filePath: string, componentName: string) {
|
||||
)
|
||||
}
|
||||
|
||||
test('库存面板:手动匹配订单弹窗挂在顶层(按钮点了必须能弹出来)', () => {
|
||||
test('库存面板:匹配订单弹窗挂在顶层(按钮点了必须能弹出来)', () => {
|
||||
assertTopLevelMount(
|
||||
fileURLToPath(
|
||||
new URL('../src/pages/admin/panels/ConsignmentInventoryPanel.tsx', import.meta.url),
|
||||
),
|
||||
'ConsignmentManualMatchModal',
|
||||
'ConsignmentInventoryMatchModal',
|
||||
)
|
||||
})
|
||||
|
||||
test('订单面板:手动指定现货弹窗同样挂在顶层', () => {
|
||||
test('订单面板:内部库存匹配弹窗同样挂在顶层', () => {
|
||||
assertTopLevelMount(
|
||||
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
||||
'ConsignmentManualMatchModal',
|
||||
'ConsignmentWorkOrderMatchModal',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -57,8 +56,6 @@ test('手动匹配入口必须按 consignment.fulfill 门控(提交的是履
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
// 库存面板:手动匹配订单按钮要与编辑/作废拆开 —— 前者 fulfill(提交打到
|
||||
// /fulfill、/assign-worker,后端要求 consignment.fulfill),后者 manage
|
||||
assert.ok(
|
||||
inventoryPanel.includes("resolveAdminPermission(sessionPermissions, 'consignment.fulfill')"),
|
||||
'库存面板必须解析 consignment.fulfill 权限',
|
||||
@@ -74,56 +71,50 @@ test('手动匹配入口必须按 consignment.fulfill 门控(提交的是履
|
||||
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
)
|
||||
// 拦截弹窗 footer 的入口同样要门控,无 fulfill 权限的人不该看到能直达 403 的按钮
|
||||
const footerButtonIndex = ordersPanel.indexOf('手动指定现货匹配')
|
||||
assert.ok(footerButtonIndex > 0, '找不到「手动指定现货匹配」按钮')
|
||||
const footerButtonIndex = ordersPanel.lastIndexOf('内部库存匹配')
|
||||
assert.ok(footerButtonIndex > 0, '找不到「内部库存匹配」按钮')
|
||||
const beforeFooterButton = ordersPanel.slice(0, footerButtonIndex)
|
||||
assert.ok(
|
||||
beforeFooterButton.lastIndexOf('canFulfillConsignment ?') >
|
||||
beforeFooterButton.lastIndexOf('footer={['),
|
||||
'拦截弹窗 footer 的「手动指定现货匹配」必须按 canFulfillConsignment 门控',
|
||||
'拦截弹窗 footer 的「内部库存匹配」必须按 canFulfillConsignment 门控',
|
||||
)
|
||||
})
|
||||
|
||||
test('手动匹配弹窗:数据过期的 409 要清选择并刷新,走内部现货直接履约', () => {
|
||||
test('库存侧匹配弹窗:必须走新引擎,不再调用旧 /fulfill', () => {
|
||||
const source = readFileSync(
|
||||
fileURLToPath(
|
||||
new URL('../src/components/admin/ConsignmentManualMatchModal.tsx', import.meta.url),
|
||||
new URL('../src/components/admin/ConsignmentInventoryMatchModal.tsx', import.meta.url),
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
// 并发占用/规格在打开弹窗后被改 → 清空选择并重新拉列表,不能让运营循环撞同一个错
|
||||
assert.ok(source.includes('consignment_inventory_race_condition'), '要处理并发占用错误码')
|
||||
assert.ok(source.includes('consignment_spec_mismatch'), '要处理规格变化错误码')
|
||||
assert.ok(source.includes('setSelected(null)'), '数据过期时要清空已选')
|
||||
// 统一内部现货直接履约:调用 fulfillWorkOrderWithConsignment,免外派佣金与二次押金
|
||||
assert.ok(source.includes('fetchPendingPistolMatches'), '库存侧必须复用待匹配看板的统一判定')
|
||||
assert.ok(source.includes('inventoryId: Number(inventoryId)'), '必须按当前库存过滤候选工单')
|
||||
assert.ok(
|
||||
source.includes('fulfillWorkOrderWithConsignment'),
|
||||
'手动匹配必须调用内部现货直接履约接口',
|
||||
source.includes('matchAdminConsignmentWorkOrder'),
|
||||
'库存侧落库必须调用新引擎 /consignment/match',
|
||||
)
|
||||
assert.ok(
|
||||
!source.includes('assignWorkOrderWithConsignmentWorker'),
|
||||
'手动匹配不得再调用旧的指派打手接口',
|
||||
)
|
||||
// 凭证核销反馈与关键字搜索:用共享出口 / 透传后端,不内联复制
|
||||
assert.ok(source.includes("from '@/utils/voucher-consume'"), '核销反馈必须用共享出口')
|
||||
assert.ok(source.includes('keyword: keyword.trim() || undefined'), '库存侧关键字要透传后端')
|
||||
assert.ok(!source.includes('fulfillWorkOrderWithConsignment'), '库存侧不得再调用旧 /fulfill 链路')
|
||||
assert.ok(source.includes('quantitySufficient'), '数量不足的工单必须禁用选择')
|
||||
assert.ok(source.includes('允许非精确/冲突强制匹配'), '非精确必须显式勾选')
|
||||
})
|
||||
|
||||
test('发布拦截弹窗:只走内部现货履约,文案停在待加好友而不是冷静期', () => {
|
||||
test('订单面板:拦截弹窗与行内入口都只走新引擎', () => {
|
||||
const source = readFileSync(
|
||||
fileURLToPath(new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url)),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(source.includes('fulfillWorkOrderWithConsignment'), '拦截弹窗必须调用内部现货履约')
|
||||
assert.ok(source.includes('matchAdminConsignmentWorkOrder'), '拦截弹窗必须调用新引擎匹配')
|
||||
assert.ok(
|
||||
!source.includes('fulfillWorkOrderWithConsignment'),
|
||||
'订单面板不得再调用旧 /fulfill 链路',
|
||||
)
|
||||
assert.ok(
|
||||
!source.includes('assignWorkOrderWithConsignmentWorker'),
|
||||
'拦截弹窗不得再调用旧的指派打手接口',
|
||||
'订单面板不得调用旧的指派打手接口',
|
||||
)
|
||||
assert.ok(!source.includes('手动匹配寄售'), '旧「手动匹配寄售」入口必须移除')
|
||||
assert.ok(source.includes('进入待加好友'), '拦截弹窗必须写明工单进入待加好友')
|
||||
assert.ok(!source.includes('自动进冷静期'), '拦截弹窗不得再写自动进冷静期')
|
||||
assert.ok(!source.includes('进入赠送冷静期'), '成功提示不得再写成进入赠送冷静期')
|
||||
assert.ok(source.includes('入库时间:'), '拦截弹窗现货展示必须包含入库时间')
|
||||
})
|
||||
|
||||
test('寄售仓库库存面板:必须包含「通过审核入库时间」列,且详情弹窗正确展示时间', () => {
|
||||
@@ -139,33 +130,4 @@ test('寄售仓库库存面板:必须包含「通过审核入库时间」列
|
||||
'表格必须格式化入库时间',
|
||||
)
|
||||
assert.ok(source.includes('label="通过审核入库时间"'), '详情弹窗必须包含「通过审核入库时间」项')
|
||||
assert.ok(
|
||||
source.includes('inboundAt: manualMatchItem.inboundAt || manualMatchItem.createdAt'),
|
||||
'打开手动匹配弹窗时必须透传现货的入库时间',
|
||||
)
|
||||
})
|
||||
|
||||
test('手动匹配弹窗:候选订单必须展示创建时间,现货展示审核入库时间', () => {
|
||||
const source = readFileSync(
|
||||
fileURLToPath(
|
||||
new URL('../src/components/admin/ConsignmentManualMatchModal.tsx', import.meta.url),
|
||||
),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
source.includes('创建时间 {formatDateTime(order.createdAt)}'),
|
||||
'候选订单卡片必须展示工单创建时间',
|
||||
)
|
||||
assert.ok(
|
||||
source.includes('审核入库:${formatDateTime(inventoryInboundAt)}'),
|
||||
'弹窗现货提示栏必须展示审核入库时间',
|
||||
)
|
||||
assert.ok(
|
||||
source.includes('审核入库于 {formatDateTime(selected.inventoryInboundAt)}'),
|
||||
'已选现货概要必须展示审核入库时间',
|
||||
)
|
||||
assert.ok(
|
||||
source.includes('创建于 {formatDateTime(selected.workOrderCreatedAt)}'),
|
||||
'已选订单概要必须展示创建时间',
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user