79 lines
4.6 KiB
JavaScript
79 lines
4.6 KiB
JavaScript
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)
|
||
|
|
}
|