feat(consignment): 拼单一次拼满,内部匹配到期统一回大厅
- 拼单(自动/手动)先规划再一次落库,凑不齐整单失败并回滚,不再进入 internal_matching - 删除「超时后回大厅」开关与 fallbackToHall 配置,到期统一回大厅(迁移 152 清理历史字段)
This commit is contained in:
@@ -90,7 +90,7 @@ type ConfigFormValues = {
|
||||
exactOnly: boolean
|
||||
preferLowReward: boolean
|
||||
}
|
||||
internalMatch: { delayMinutes: number; fallbackToHall: boolean }
|
||||
internalMatch: { delayMinutes: number }
|
||||
timeout: {
|
||||
hours: number
|
||||
policy: 'reopen' | 'cancel_release' | 'cancel_deduct' | 'rematch_release' | 'rematch_deduct'
|
||||
@@ -201,7 +201,15 @@ function toFormValues(profile: ConsignmentProfile): ConfigFormValues {
|
||||
exactOnly: config.matching.exactOnly,
|
||||
preferLowReward: config.matching.preferLowReward,
|
||||
},
|
||||
internalMatch: { ...config.internalMatch },
|
||||
internalMatch: {
|
||||
// 内部等待只属于手动匹配;非手动模式切回手动时给默认 30 分钟。
|
||||
delayMinutes:
|
||||
config.matching.mode === 'manual'
|
||||
? Number(config.internalMatch.delayMinutes || 0)
|
||||
: Number(config.internalMatch.delayMinutes) > 0
|
||||
? Number(config.internalMatch.delayMinutes)
|
||||
: 30,
|
||||
},
|
||||
timeout: {
|
||||
hours: Math.max(0, Number(config.timeout.minutes ?? 30) / 60),
|
||||
policy: config.timeout.policy,
|
||||
@@ -234,7 +242,7 @@ function emptyFormValues(): ConfigFormValues {
|
||||
exactOnly: true,
|
||||
preferLowReward: true,
|
||||
},
|
||||
internalMatch: { delayMinutes: 30, fallbackToHall: true },
|
||||
internalMatch: { delayMinutes: 30 },
|
||||
timeout: { hours: 0.5, policy: 'reopen' },
|
||||
priceDrop: { enabled: true, hours: 24, mode: 'percent', value: 10 },
|
||||
}
|
||||
@@ -303,11 +311,9 @@ function buildPayload(
|
||||
preferLowReward: values.matching.preferLowReward,
|
||||
},
|
||||
internalMatch: {
|
||||
// 关闭匹配时不再等待内部库存,直接进大厅。
|
||||
// 内部等待只属于手动匹配:关闭匹配不再等待;自动匹配不使用等待窗口。
|
||||
delayMinutes:
|
||||
values.matching.mode === 'off' ? 0 : Number(values.internalMatch.delayMinutes || 0),
|
||||
fallbackToHall:
|
||||
values.matching.mode === 'off' ? false : values.internalMatch.fallbackToHall,
|
||||
},
|
||||
timeout: {
|
||||
minutes: Math.max(0, Math.round(Number(values.timeout.hours || 0) * 60)),
|
||||
@@ -1720,7 +1726,7 @@ export default function ConsignmentConfigPanel() {
|
||||
<ConsignmentSectionTitle
|
||||
index="08"
|
||||
title="匹配设置"
|
||||
description="匹配模式决定新单是否进入内部库存匹配队列"
|
||||
description="匹配模式决定新单如何匹配库存;内部等待仅手动匹配使用"
|
||||
/>
|
||||
</div>
|
||||
<div style={gridStyle}>
|
||||
@@ -1755,41 +1761,21 @@ export default function ConsignmentConfigPanel() {
|
||||
>
|
||||
<Switch checkedChildren="优先" unCheckedChildren="不优先" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="手动匹配等待(分钟)"
|
||||
name={['internalMatch', 'delayMinutes']}
|
||||
extra={
|
||||
matchingMode === 'auto'
|
||||
? '自动匹配在发布时立即匹配,未命中直接进大厅,不使用等待窗口'
|
||||
: '仅手动匹配生效:工单在内部池等待运营挑库存'
|
||||
}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
precision={0}
|
||||
style={{ width: '100%' }}
|
||||
addonAfter="分钟"
|
||||
disabled={matchingMode === 'auto'}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="超时后回大厅"
|
||||
name={['internalMatch', 'fallbackToHall']}
|
||||
valuePropName="checked"
|
||||
extra={
|
||||
matchingMode === 'auto'
|
||||
? '自动匹配未命中直接进大厅,不涉及此策略'
|
||||
: '关闭后超时重新匹配(最多 3 次),仍未命中回大厅'
|
||||
}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Switch
|
||||
checkedChildren="回大厅"
|
||||
unCheckedChildren="重新匹配"
|
||||
disabled={matchingMode === 'auto'}
|
||||
/>
|
||||
</Form.Item>
|
||||
{matchingMode === 'manual' ? (
|
||||
<Form.Item
|
||||
label="手动匹配等待(分钟)"
|
||||
name={['internalMatch', 'delayMinutes']}
|
||||
extra="仅手动匹配生效:工单在内部池等待运营挑库存"
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
precision={0}
|
||||
style={{ width: '100%' }}
|
||||
addonAfter="分钟"
|
||||
/>
|
||||
</Form.Item>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1801,6 +1787,14 @@ export default function ConsignmentConfigPanel() {
|
||||
message="关闭匹配后:新单直接进入接单大厅,不等待内部库存,也不会自动匹配。"
|
||||
/>
|
||||
) : null}
|
||||
{matchingMode === 'auto' ? (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginTop: 12 }}
|
||||
message="自动匹配:发布/补资料时立即匹配,未命中直接进大厅,不使用内部等待窗口。"
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="order-service-section">
|
||||
|
||||
@@ -418,7 +418,7 @@ export type ConsignmentProfileConfig = {
|
||||
exactOnly: boolean
|
||||
preferLowReward: boolean
|
||||
}
|
||||
internalMatch: { delayMinutes: number; fallbackToHall: boolean }
|
||||
internalMatch: { delayMinutes: number }
|
||||
timeout: {
|
||||
minutes: number
|
||||
policy: 'reopen' | 'cancel_release' | 'cancel_deduct' | 'rematch_release' | 'rematch_deduct'
|
||||
|
||||
@@ -10,21 +10,15 @@ function readPanelSource() {
|
||||
).replace(/\r\n/g, '\n')
|
||||
}
|
||||
|
||||
test('自动匹配:手动等待与超时策略在 auto 下禁用并给出说明', () => {
|
||||
test('手动匹配:只保留等待时长,不再提供「超时后回大厅」开关', () => {
|
||||
const source = readPanelSource()
|
||||
assert.ok(source.includes('label="手动匹配等待(分钟)"'), '等待时长文案必须明确只作用于手动匹配')
|
||||
assert.ok(source.includes('label="超时后回大厅"'), '必须提供超时回大厅开关')
|
||||
assert.ok(source.includes("matchingMode === 'manual' ? ("), '等待时长必须只在手动匹配下展示')
|
||||
assert.ok(!source.includes('超时后回大厅'), '回大厅是固定行为,不应再有开关')
|
||||
assert.ok(!source.includes('fallbackToHall'), '配置与表单必须移除 fallbackToHall')
|
||||
assert.ok(
|
||||
source.includes('自动匹配在发布时立即匹配,未命中直接进大厅,不使用等待窗口'),
|
||||
source.includes('自动匹配:发布/补资料时立即匹配,未命中直接进大厅,不使用内部等待窗口。'),
|
||||
'auto 模式必须说明不使用等待窗口',
|
||||
)
|
||||
assert.ok(
|
||||
source.includes('关闭后超时重新匹配(最多 3 次),仍未命中回大厅'),
|
||||
'关闭回大厅时必须说明重试上限',
|
||||
)
|
||||
assert.ok(
|
||||
(source.match(/disabled=\{matchingMode === 'auto'\}/g) || []).length >= 2,
|
||||
'等待时长与超时策略在 auto 下都必须禁用',
|
||||
)
|
||||
assert.ok(!source.includes('label="内部匹配等待(分钟)"'), '旧文案必须移除')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user