test(firstleg): harden coload capacity acceptance
This commit is contained in:
@@ -516,8 +516,7 @@ export async function run(context) {
|
||||
const groupedCandidates = candidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
context.assert(MODULE, '每个组成员候选携带相同整组事实', candidateResponse,
|
||||
groupedCandidates.length === 2 && groupedCandidates.every((item) => Number(item?.memberCount) === 2
|
||||
&& Number(item?.groupTotalBoxes) === first.packages + second.packages && item?.locked !== true
|
||||
&& String(item?.blockReason || '').includes('同柜不可拆组整体超过当前柜剩余容量')),
|
||||
&& Number(item?.groupTotalBoxes) === first.packages + second.packages && item?.locked !== true),
|
||||
`count=${groupedCandidates.length}, groupTotal=${first.packages + second.packages}`, OPTIONS)
|
||||
|
||||
let isolatedCandidates = candidates
|
||||
@@ -555,23 +554,30 @@ export async function run(context) {
|
||||
candidateGroupIds.length === 1 && candidateGroupIds[0] === groupId
|
||||
&& JSON.stringify(targetCandidateInbounds) === JSON.stringify([first.inboundId, second.inboundId].sort()),
|
||||
`groupId=${groupId}, candidateGroupIds=${candidateGroupIds.join('|') || 'none'}, members=${targetCandidateInbounds.join('|')}, capacity=33cbm/28000kg`, OPTIONS)
|
||||
const targetCandidateVolume = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
const allCandidateVolume = isolatedCandidates
|
||||
.reduce((sum, item) => sum + Number(item?.availableVolumeCbm || 0), 0)
|
||||
context.assert(MODULE, '目标组自身总体积明确超过20GP容量容差', isolatedCandidateResponse,
|
||||
targetCandidateVolume > 33 * 1.1,
|
||||
`targetGroupId=${groupId}, volume=${targetCandidateVolume}, limit=${33 * 1.1}`, OPTIONS)
|
||||
context.assert(MODULE, '推荐候选总量形成容量竞争场景', isolatedCandidateResponse,
|
||||
allCandidateVolume > 33 * 1.1,
|
||||
`targetGroupId=${groupId}, candidateVolume=${allCandidateVolume}, limit=${33 * 1.1}`, OPTIONS)
|
||||
const beforeRecommend = plan
|
||||
const recommendResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/recommend`, {
|
||||
expectedVersion: Number(plan?.version),
|
||||
operationId: `${context.marker}-coload-capacity-recommend`.slice(0, 128),
|
||||
})
|
||||
const capacityRecommendation = context.apiSuccess(MODULE, '目标同柜组整体超容时智能推荐跳过整组',
|
||||
const capacityRecommendation = context.apiSuccess(MODULE, '容量竞争时智能推荐不因整组容量失败',
|
||||
recommendResponse, evidence(recommendResponse, `targetGroupId=${groupId}`), OPTIONS)
|
||||
const capacityGroupItems = (capacityRecommendation?.items || [])
|
||||
.filter((item) => [first.inboundId, second.inboundId].includes(id(item?.inboundId)))
|
||||
context.assert(MODULE, '整组容量不足时智能推荐全不取且不产生部分成员', recommendResponse,
|
||||
capacityGroupItems.length === 0,
|
||||
const capacityTotals = new Map()
|
||||
for (const item of capacityGroupItems) {
|
||||
capacityTotals.set(id(item?.inboundId),
|
||||
(capacityTotals.get(id(item?.inboundId)) || 0) + Number(item?.packageCount || 0))
|
||||
}
|
||||
const capacityTookNone = capacityGroupItems.length === 0
|
||||
const capacityTookWhole = [first, second].every((fixture) => capacityTotals.get(fixture.inboundId) === fixture.packages)
|
||||
context.assert(MODULE, '容量竞争时同柜组只能整组全取或全不取', recommendResponse,
|
||||
capacityTookNone || capacityTookWhole,
|
||||
`targetGroupId=${groupId}, assignedMembers=${capacityGroupItems.map((item) => id(item?.inboundId)).join('|') || 'none'}, before=${planSignature(beforeRecommend)}, after=${planSignature(capacityRecommendation)}`, OPTIONS)
|
||||
|
||||
const recommendSimulationResponse = await context.request('POST', '/api/platform/v1/containers', {
|
||||
@@ -782,14 +788,14 @@ export async function run(context) {
|
||||
if (!partialPda || !fullPda || !normalPda) return
|
||||
|
||||
const partialTreesBefore = await fulfillmentTrees(context, partialChain.fixtures, '短拣前')
|
||||
const partialTarget = (partialPda.task?.items || []).find((item) => Number(item?.plannedPackageCount) > 1)
|
||||
context.assert(MODULE, '短拣链存在可记录PARTIAL的多箱明细', null, Boolean(partialTarget?.id),
|
||||
const partialTarget = (partialPda.task?.items || []).find((item) => Number(item?.plannedPackageCount) > 0)
|
||||
context.assert(MODULE, '短拣链存在可记录SKIP的组成员明细', null, Boolean(partialTarget?.id),
|
||||
`planned=${partialTarget?.plannedPackageCount ?? 'missing'}, taskItems=${partialPda.task?.items?.length ?? 0}`, OPTIONS)
|
||||
if (!partialTarget?.id) return
|
||||
const partialBatchItems = (partialPda.task.items || []).map((item) => id(item.id) === id(partialTarget.id)
|
||||
? { itemId: id(item.id), action: 'PARTIAL', pickedPackageCount: null,
|
||||
finalPickedPackageCount: Number(item.plannedPackageCount) - 1, releaseMode: 'MANUAL',
|
||||
locationIds: [], version: Number(item.version), reason: null }
|
||||
? { itemId: id(item.id), action: 'SKIP', pickedPackageCount: null,
|
||||
finalPickedPackageCount: null, releaseMode: 'MANUAL',
|
||||
locationIds: [], version: Number(item.version), reason: '同柜不可拆组短拣验收' }
|
||||
: { itemId: id(item.id), action: 'PICK', pickedPackageCount: Number(item.plannedPackageCount),
|
||||
finalPickedPackageCount: null, releaseMode: 'MANUAL', locationIds: [],
|
||||
version: Number(item.version), reason: null })
|
||||
@@ -798,13 +804,12 @@ export async function run(context) {
|
||||
containerBookingId: partialChain.containerBookingId, expectedVersion: Number(partialPda.task.version),
|
||||
inventorySnapshotToken: partialPda.inventory.snapshotToken, items: partialBatchItems,
|
||||
}, undefined, { 'Idempotency-Key': `${context.marker}-coload-partial-batch`.slice(0, 128) })
|
||||
const partialBatch = context.apiSuccess(MODULE, '同柜组PDA原子批次记录部分拣货异常', partialBatchResponse,
|
||||
const partialBatch = context.apiSuccess(MODULE, '同柜组PDA原子批次记录短拣异常', partialBatchResponse,
|
||||
evidence(partialBatchResponse), OPTIONS)
|
||||
context.assert(MODULE, '短拣批次只进入待确认且保留PARTIAL事实', partialBatchResponse,
|
||||
context.assert(MODULE, '短拣批次只进入待确认且保留SKIP事实', partialBatchResponse,
|
||||
partialBatch?.taskStatus === 'LOADING' && partialBatch?.workflowStatus === 'AWAITING_REVIEW'
|
||||
&& partialBatch?.items?.some((item) => id(item?.id) === id(partialTarget.id)
|
||||
&& item?.itemStatus === 'PARTIAL'
|
||||
&& Number(item?.pickedPackageCount) === Number(partialTarget.plannedPackageCount) - 1),
|
||||
&& item?.itemStatus === 'SKIPPED' && Number(item?.pickedPackageCount) === 0),
|
||||
`taskStatus=${partialBatch?.taskStatus || 'missing'}, workflow=${partialBatch?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
const partialTaskBeforeComplete = taskSignature(partialBatch)
|
||||
const partialCompleteResponse = await context.request('POST', `${PDA}/tasks/${partialPda.taskId}/complete`, {
|
||||
@@ -955,6 +960,38 @@ export async function run(context) {
|
||||
evidence(splitCancelResponse), OPTIONS)
|
||||
}
|
||||
|
||||
let uiAcceptance = null
|
||||
if (differentDestination) {
|
||||
const uiGroupResponse = await context.request('POST', '/api/platform/v1/first-leg/coload-groups',
|
||||
{ orderIds: [fifth.orderId, differentDestination.orderId] }, undefined,
|
||||
{ 'Idempotency-Key': `${context.marker}-coload-ui-preserve`.slice(0, 128) })
|
||||
const uiGroup = context.apiSuccess(MODULE, '为Admin页面保留ACTIVE未分配跨目的仓组', uiGroupResponse,
|
||||
evidence(uiGroupResponse), OPTIONS)
|
||||
const uiSimulationResponse = await context.request('POST', CONTAINERS, {
|
||||
channelCode: fifth.channelCode, warehouseId: fifth.warehouseId, containerType: '40HC',
|
||||
capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const uiSimulation = context.apiSuccess(MODULE, '为Admin页面保留可编辑智能装柜柜次', uiSimulationResponse,
|
||||
evidence(uiSimulationResponse), OPTIONS)
|
||||
const uiBookingId = id(uiSimulation?.id)
|
||||
const uiPlanResponse = uiBookingId ? await context.request('POST', `${SMART}/plans`, {
|
||||
bookingId: uiBookingId, remark: `${context.marker} Admin整组候选人工验收保留`,
|
||||
}) : null
|
||||
const uiPlan = uiPlanResponse ? context.apiSuccess(MODULE, '为Admin页面保留未分配空计划', uiPlanResponse,
|
||||
evidence(uiPlanResponse, `bookingId=${uiBookingId}`), OPTIONS) : null
|
||||
const uiMembers = memberFacts(uiGroup)
|
||||
context.assert(MODULE, '页面验收保留组仍为ACTIVE且未锁定', uiGroupResponse,
|
||||
uiGroup?.status === 'ACTIVE' && !uiGroup?.lockedPlanId && uiMembers.length === 2
|
||||
&& Boolean(uiBookingId) && Boolean(uiPlan?.id),
|
||||
`groupId=${id(uiGroup?.groupId)}, groupNo=${uiGroup?.groupNo || 'missing'}, bookingId=${uiBookingId}, planId=${id(uiPlan?.id)}, members=${JSON.stringify(uiMembers)}`, OPTIONS)
|
||||
uiAcceptance = {
|
||||
groupId: id(uiGroup?.groupId), groupNo: uiGroup?.groupNo || null,
|
||||
orderIds: [fifth.orderId, differentDestination.orderId],
|
||||
inboundIds: [fifth.inboundId, differentDestination.inboundId],
|
||||
bookingId: uiBookingId, planId: id(uiPlan?.id),
|
||||
}
|
||||
}
|
||||
|
||||
context.createdResources.coloadGroupAcceptance = {
|
||||
marker: context.marker, groupId, groupNo: group?.groupNo || null, planId, bookingId,
|
||||
orderIds: groupOrders, inboundIds: [first.inboundId, second.inboundId],
|
||||
@@ -962,6 +999,7 @@ export async function run(context) {
|
||||
partialTaskId: partialPda.taskId, normalTaskId: normalPda.taskId,
|
||||
oceanBookingId, cancelledGroupId: cancelGroupId, remainderOrderId: id(remainder.orderId),
|
||||
splitGroupId: id(splitGroup?.groupId), crossDestinationOrderId: id(differentDestination?.orderId),
|
||||
uiAcceptance,
|
||||
publicBypassResult: 'individual assignment/adjust/delete and short-pick completion rejected; full group consumed',
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user