fix(fulfillment): 封堵重复载体并完善统一SKU映射

This commit is contained in:
yml2213
2026-11-03 12:28:05 +08:00
parent 3ba808b814
commit e6763882f5
27 changed files with 1506 additions and 45 deletions
@@ -71,3 +71,28 @@ test('工单门禁:无配置、冲突、缺模板、模板不一致都不放
'work_rule_mismatch',
)
})
test('工单门禁:已分配其它渠道或已有其它真实载体时不放行', () => {
assert.equal(
resolveWorkOrderChannelGate(plan(channel()), undefined, {
fulfillmentStatus: 'assigned',
assignedChannel: 'affiliate_dash',
}).reason,
'already_assigned_other_channel',
)
assert.equal(
resolveWorkOrderChannelGate(plan(channel()), undefined, {
fulfillmentStatus: 'pending',
existingCarrierChannels: ['affiliate_dash'],
}).reason,
'existing_carrier_taken',
)
assert.equal(
resolveWorkOrderChannelGate(plan(channel()), undefined, {
fulfillmentStatus: 'assigned',
assignedChannel: 'work_order',
existingCarrierChannels: ['work_order'],
}).allowed,
true,
)
})
@@ -51,11 +51,27 @@ import {
canonicalizeInternalSku,
resolveInternalSkuKey,
} from '../fulfillment/internal-sku-resolver.js'
import { loadActiveCarrierEvidence } from '../fulfillment/carrier-evidence-service.js'
export function resolveWorkOrderChannelGate(
plan: InternalSkuChannelPlan | undefined,
preferredWorkRuleKey?: string,
context: {
fulfillmentStatus?: string
assignedChannel?: string
existingCarrierChannels?: string[]
} = {},
): { allowed: boolean; reason: string; workRuleKey: string } {
if (
context.fulfillmentStatus === 'assigned' &&
context.assignedChannel &&
context.assignedChannel !== 'work_order'
) {
return { allowed: false, reason: 'already_assigned_other_channel', workRuleKey: '' }
}
if (context.existingCarrierChannels?.some((channel) => channel !== 'work_order')) {
return { allowed: false, reason: 'existing_carrier_taken', workRuleKey: '' }
}
if (!plan?.primary) {
return {
allowed: false,
@@ -109,6 +125,7 @@ export async function syncWorkerOrdersForSourceOrder(
)
}
const channelPlans = await loadInternalSkuChannelPlans([...internalSkuByItemId.values()])
const carrierEvidence = await loadActiveCarrierEvidence(orderItems.map((item) => Number(item.id)))
for (const item of orderItems) {
const internalSku = canonicalizeInternalSku(internalSkuByItemId.get(Number(item.id)))
@@ -123,7 +140,12 @@ export async function syncWorkerOrdersForSourceOrder(
)
continue
}
const channelGate = resolveWorkOrderChannelGate(channelPlan, options.preferredWorkRuleKey)
const assignedChannel = String(item.fulfillment_channel || '').trim()
const channelGate = resolveWorkOrderChannelGate(channelPlan, options.preferredWorkRuleKey, {
fulfillmentStatus: item.fulfillment_status || '',
assignedChannel,
existingCarrierChannels: carrierEvidence.get(Number(item.id))?.channels || [],
})
if (!channelGate.allowed) {
skipped.push({ orderItemId: Number(item.id), reason: channelGate.reason })
decisions.push(