const MODULE = 'manual-order-splitting-fixture' const OPTIONS = { scenario: 'manual-order-splitting-fixture', deploymentBlocker: true } function id(value) { return value == null ? '' : String(value) } function positiveInteger(value) { return Number.isInteger(Number(value)) && Number(value) > 0 } function normalizedFixture(value) { return { sequence: Number(value.sequence), tenantId: id(value.tenantId), orderId: id(value.orderId), orderNo: value.orderNo || null, inboundId: id(value.inboundId), inboundCode: value.inboundCode || null, warehouseId: id(value.receivingWarehouseId), warehouseName: value.receivingWarehouseName || null, channelCode: value.channelCode || null, channelName: value.channelName || null, totalPackageCount: Number(value.totalPackageCount), destinationType: value.destinationType || null, fulfillmentType: value.fulfillmentType || null, containerMode: value.containerMode || null, } } export async function run(context) { const fixtures = (context.createdResources?.waitSplitFixtures || []).map(normalizedFixture) const valid = fixtures.length >= 4 && fixtures.every((item) => item.tenantId && item.orderId && item.inboundId && item.warehouseId && item.channelCode && positiveInteger(item.totalPackageCount)) context.assert(MODULE, '至少取得四票本轮 WAIT_SPLIT 人工分柜数据', null, valid, `count=${fixtures.length}, invalid=${fixtures.filter((item) => !item.tenantId || !item.orderId || !item.inboundId || !item.warehouseId || !item.channelCode || !positiveInteger(item.totalPackageCount)).length}`, OPTIONS) if (!valid) return const tenantIds = new Set(fixtures.map((item) => item.tenantId)) const warehouseIds = new Set(fixtures.map((item) => item.warehouseId)) const channelCodes = new Set(fixtures.map((item) => item.channelCode)) const sameScope = tenantIds.size === 1 && warehouseIds.size === 1 && channelCodes.size === 1 context.assert(MODULE, '人工分柜正向样本固定同一客户、起运仓和渠道', null, sameScope, `tenantIds=${[...tenantIds].join(',')}, warehouseIds=${[...warehouseIds].join(',')}, channelCodes=${[...channelCodes].join(',')}`, OPTIONS) if (!sameScope) return const byPackages = [...fixtures].sort((left, right) => right.totalPackageCount - left.totalPackageCount || left.sequence - right.sequence) const partial = byPackages.find((item) => item.totalPackageCount >= 2) const remaining = fixtures.filter((item) => item.inboundId !== partial?.inboundId) const full = remaining.slice(0, 2) const control = remaining[2] const scenarioReady = Boolean(partial && full.length === 2 && control) context.assert(MODULE, '人工分柜场景包含部分分配、两票整票拼柜和未分配对照', null, scenarioReady, `partial=${partial?.inboundCode || 'missing'}, full=${full.map((item) => item.inboundCode).join(',') || 'missing'}, control=${control?.inboundCode || 'missing'}`, OPTIONS) if (!scenarioReady) return const assignments = [ { role: 'PARTIAL', ...partial, expectedAllocatedPackageCount: partial.totalPackageCount - 1, expectedRemainingPackageCount: 1 }, ...full.map((item, index) => ({ role: `FULL_${index + 1}`, ...item, expectedAllocatedPackageCount: item.totalPackageCount, expectedRemainingPackageCount: 0 })), ] const manifest = { schemaVersion: 1, marker: context.marker, stage: 'WAIT_SPLIT_READY_FOR_MANUAL_ASSIGNMENT', tenantId: fixtures[0].tenantId, warehouseId: fixtures[0].warehouseId, warehouseName: fixtures[0].warehouseName, channelCode: fixtures[0].channelCode, channelName: fixtures[0].channelName, adminEntry: '/container-loading', instructions: [ '在智能装柜页面创建一个同仓同渠道的内部模拟柜。', '创建智能装柜计划,并严格按 assignments 的 expectedAllocatedPackageCount 分配。', '保留 unassignedControl 不加入计划,用于核对候选剩余与误选保护。', '人工操作后记录 loadingPlanId,并运行 manual-order-splitting-verify 只读核验。', ], assignments, unassignedControl: { role: 'UNASSIGNED_CONTROL', ...control, expectedAllocatedPackageCount: 0, expectedRemainingPackageCount: control.totalPackageCount }, allFixtures: fixtures, } context.createdResources.manualOrderSplittingFixture = manifest context.assert(MODULE, '人工分柜清单数量守恒', null, assignments.every((item) => item.totalPackageCount === item.expectedAllocatedPackageCount + item.expectedRemainingPackageCount), `assignments=${assignments.length}, plannedPackages=${assignments.reduce((sum, item) => sum + item.expectedAllocatedPackageCount, 0)}`, OPTIONS) }