fix(lastleg): complete generated pallet receiving flow
This commit is contained in:
@@ -163,29 +163,42 @@ export async function disablePicker(context, picker, index) {
|
||||
context.addStep(MODULE, `finally停用海外PDA仓库操作员${index}`, response, disabled, trace(response), OPTIONS)
|
||||
}
|
||||
|
||||
async function createPallet(context, inbound, index) {
|
||||
const warehouseId = id(inbound?.overseasWarehouseId)
|
||||
if (!longId(warehouseId)) return null
|
||||
const suffix = markerSuffix(context)
|
||||
const locationCode = `PDA-${suffix}-${index + 1}`.slice(0, 64)
|
||||
const location = await context.requestOverseas('POST', `/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/locations`, {
|
||||
zoneCode: `PDA-${index + 1}`, locationCode,
|
||||
locationType: 'STORAGE', maxLoadKg: 5000, status: 'IDLE', remark: `${context.marker}-receiving`,
|
||||
})
|
||||
context.addStep(MODULE, `创建当前柜实收库位${index + 1}`, location, success(location), trace(location), OPTIONS)
|
||||
const locationId = id(location?.body?.data)
|
||||
if (!longId(locationId)) return null
|
||||
context.createdResources.phase4OverseasPda.locationIds.push(locationId)
|
||||
const palletCode = `PDA-${suffix}-${index + 1}`.slice(0, 128)
|
||||
const unitPrice = Number((1.01 + index).toFixed(2))
|
||||
const pallet = await context.requestOverseas('POST', `/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`, {
|
||||
palletCode, palletType: '木托盘', capacityCbm: 10, unitPrice, status: 'IDLE', locationId,
|
||||
remark: `${context.marker}-receiving-retained`,
|
||||
fbaWarehouseCode: inbound.destinationType === 'FBA' ? inbound.fbaWarehouseCode : null,
|
||||
})
|
||||
context.addStep(MODULE, `创建当前柜实收托盘${index + 1}`, pallet, success(pallet), trace(pallet), OPTIONS)
|
||||
const palletId = id(pallet?.body?.data)
|
||||
return longId(palletId) ? { warehouseId, locationId, locationCode, palletId, palletCode, unitPrice, retained: true } : null
|
||||
function rememberGeneratedPlans(context, plans) {
|
||||
if (!Array.isArray(plans)) return
|
||||
for (const plan of plans) {
|
||||
const inboundCode = id(plan?.lines?.[0]?.inboundCode)
|
||||
if (!nonBlank(inboundCode)) continue
|
||||
const generated = Array.isArray(plan?.pallets) ? plan.pallets : []
|
||||
context.createdResources.phase4OverseasPda.generatedPalletsByInbound[inboundCode] = generated.map((pallet) => ({
|
||||
warehouseId: id(plan?.overseasWarehouseId),
|
||||
locationId: id(pallet?.warehouseLocationId),
|
||||
palletId: id(pallet?.warehousePalletId),
|
||||
palletCode: id(pallet?.palletNo),
|
||||
unitPrice: Number(pallet?.unitPrice || 0),
|
||||
retained: true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveGeneratedPallet(context, inbound) {
|
||||
const candidates = context.createdResources.phase4OverseasPda.generatedPalletsByInbound[inbound.inboundCode] || []
|
||||
const pallet = candidates[0]
|
||||
if (!pallet || !longId(pallet.palletId) || !nonBlank(pallet.palletCode)) return null
|
||||
const warehouseId = id(inbound?.overseasWarehouseId || pallet.warehouseId)
|
||||
const listed = await context.requestOverseas('GET',
|
||||
`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets?page=1&size=10&keyword=${encodeURIComponent(pallet.palletCode)}`)
|
||||
const stored = records(listed?.body?.data).find((row) => id(row?.id) === pallet.palletId && row?.palletCode === pallet.palletCode)
|
||||
const locations = stored && longId(stored?.locationId)
|
||||
? await context.requestOverseas('GET',
|
||||
`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/locations?page=1&size=200`)
|
||||
: null
|
||||
const location = records(locations?.body?.data).find((row) => id(row?.id) === id(stored?.locationId))
|
||||
const resolved = success(listed) && success(locations) && stored && longId(stored?.locationId) && nonBlank(location?.locationCode)
|
||||
context.addStep(MODULE, `复用入仓码${inbound.inboundCode}自动计划托盘`, listed, resolved,
|
||||
`palletCode=${pallet.palletCode}, palletId=${pallet.palletId}, locationId=${id(stored?.locationId) || 'missing'}, locationCode=${location?.locationCode || 'missing'}; ${trace(listed)}`, OPTIONS)
|
||||
if (!resolved) return null
|
||||
return { ...pallet, warehouseId, locationId: id(stored.locationId), locationCode: location.locationCode,
|
||||
unitPrice: Number(stored.unitPrice || pallet.unitPrice || 0) }
|
||||
}
|
||||
|
||||
async function verifyWarehouseQueries(context, token, inboundCode, pallet, destinationType) {
|
||||
@@ -306,34 +319,59 @@ async function convertReceivingContainers(context, producers) {
|
||||
convertible,
|
||||
`sourceContainerBookingId=${id(summary?.sourceContainerBookingId) || 'missing'}, allPlansReady=${summary?.allPlansReady === true}, orders=${totalOrders}, inboundCodes=${inboundCodes}, plans=${existingPlans}, pending=${pendingPlans}; ${trace(summaryResponse)}`, OPTIONS)
|
||||
if (!convertible) return false
|
||||
const body = { sourceContainerBookingId: id(summary.sourceContainerBookingId) }
|
||||
const key = `${context.marker}-pda-convert-${containerNo}`.slice(0, 128)
|
||||
const convertedResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/inbound-plans/convert-container', body,
|
||||
{ 'Idempotency-Key': key }, context.overseasToken)
|
||||
const plans = success(convertedResponse) && Array.isArray(convertedResponse.body?.data)
|
||||
? convertedResponse.body.data : []
|
||||
const convertedInboundCodes = new Set(plans
|
||||
.flatMap((item) => Array.isArray(item?.lines) ? item.lines : [])
|
||||
.map((line) => id(line?.inboundCode)).filter(nonBlank))
|
||||
context.addStep(MODULE, `整柜转换为逐入仓码独立计划 ${containerNo}`, convertedResponse,
|
||||
plans.length === inboundCodes
|
||||
&& plans.every((item) => Array.isArray(item?.lines) && item.lines.length > 0)
|
||||
&& convertedInboundCodes.size === inboundCodes,
|
||||
`plans=${plans.length}, inboundCodes=${convertedInboundCodes.size}, expected=${inboundCodes}; ${trace(convertedResponse)}`, OPTIONS)
|
||||
if (plans.length !== inboundCodes) return false
|
||||
const replayResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/inbound-plans/convert-container', body,
|
||||
{ 'Idempotency-Key': key }, context.overseasToken)
|
||||
context.addStep(MODULE, `整柜转换幂等重放零新增 ${containerNo}`, replayResponse,
|
||||
success(replayResponse) && JSON.stringify(replayResponse.body?.data) === JSON.stringify(plans),
|
||||
trace(replayResponse), OPTIONS)
|
||||
if (!success(replayResponse)) return false
|
||||
const bookingId = id(summary.sourceContainerBookingId)
|
||||
const previewPath = `/api/platform/v1/last-leg/inbound-plans/container-preview?sourceContainerBookingId=${encodeURIComponent(bookingId)}`
|
||||
const previewResponse = await context.requestOverseas('GET', previewPath,
|
||||
undefined, {}, context.overseasToken)
|
||||
const preview = success(previewResponse) ? previewResponse.body?.data : null
|
||||
context.addStep(MODULE, `读取PDA前柜级计划与自动托盘预览 ${containerNo}`, previewResponse,
|
||||
preview?.ready === true && Number(preview?.totalOrderCount) === totalOrders
|
||||
&& Number(preview?.pendingOrderCount) === pendingPlans,
|
||||
`orders=${preview?.totalOrderCount ?? 'missing'}, pendingOrders=${preview?.pendingOrderCount ?? 'missing'}, pendingPallets=${preview?.pendingPalletCount ?? 'missing'}; ${trace(previewResponse)}`,
|
||||
OPTIONS)
|
||||
if (preview?.ready !== true) return false
|
||||
if (pendingPlans > 0) {
|
||||
const body = { sourceContainerBookingId: bookingId,
|
||||
snapshotVersion: Number(preview.snapshotVersion), snapshotHash: preview.snapshotHash }
|
||||
const key = `${context.marker}-pda-convert-${containerNo}`.slice(0, 128)
|
||||
const convertedResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/inbound-plans/convert-container', body,
|
||||
{ 'Idempotency-Key': key }, context.overseasToken)
|
||||
const plans = success(convertedResponse) && Array.isArray(convertedResponse.body?.data)
|
||||
? convertedResponse.body.data : []
|
||||
const convertedInboundCodes = new Set(plans
|
||||
.flatMap((item) => Array.isArray(item?.lines) ? item.lines : [])
|
||||
.map((line) => id(line?.inboundCode)).filter(nonBlank))
|
||||
context.addStep(MODULE, `整柜转换为逐入仓码独立计划及实体托盘 ${containerNo}`, convertedResponse,
|
||||
plans.length === inboundCodes
|
||||
&& plans.every((item) => Array.isArray(item?.lines) && item.lines.length > 0
|
||||
&& Array.isArray(item?.pallets) && item.pallets.length > 0
|
||||
&& item.pallets.every((pallet) => nonBlank(pallet?.palletNo) && longId(id(pallet?.warehousePalletId))))
|
||||
&& convertedInboundCodes.size === inboundCodes,
|
||||
`plans=${plans.length}, inboundCodes=${convertedInboundCodes.size}, expected=${inboundCodes}; ${trace(convertedResponse)}`, OPTIONS)
|
||||
if (plans.length !== inboundCodes) return false
|
||||
rememberGeneratedPlans(context, plans)
|
||||
const replayResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/inbound-plans/convert-container', body,
|
||||
{ 'Idempotency-Key': key }, context.overseasToken)
|
||||
context.addStep(MODULE, `整柜转换幂等重放零新增 ${containerNo}`, replayResponse,
|
||||
success(replayResponse) && JSON.stringify(replayResponse.body?.data) === JSON.stringify(plans),
|
||||
trace(replayResponse), OPTIONS)
|
||||
if (!success(replayResponse)) return false
|
||||
} else {
|
||||
const retainedPlans = context.createdResources?.phase4TailOrderControls?.generatedPlans
|
||||
rememberGeneratedPlans(context, retainedPlans)
|
||||
context.addStep(MODULE, `复用尾程控制已生成的整柜计划与实体托盘 ${containerNo}`, previewResponse,
|
||||
summary?.allPlansReady === true && existingPlans === totalOrders
|
||||
&& Number(preview?.existingPlannedPalletCount) > 0,
|
||||
`plans=${existingPlans}, plannedPallets=${preview?.existingPlannedPalletCount ?? 'missing'}; ${trace(previewResponse)}`,
|
||||
OPTIONS)
|
||||
}
|
||||
const progressResponse = await context.requestOverseas('GET',
|
||||
`/api/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(summary.sourceContainerBookingId)}`,
|
||||
undefined, {}, context.overseasToken)
|
||||
const progress = success(progressResponse) ? progressResponse.body?.data?.summary : null
|
||||
context.addStep(MODULE, `整柜转换后计划完整度闭环 ${containerNo}`, progressResponse,
|
||||
context.addStep(MODULE, `PDA前整柜计划完整度闭环 ${containerNo}`, progressResponse,
|
||||
progress?.allPlansReady === true && Number(progress?.planCount) === totalOrders
|
||||
&& Number(progress?.pendingPlanCount) === 0,
|
||||
`allPlansReady=${progress?.allPlansReady === true}, plans=${progress?.planCount ?? 'missing'}, pending=${progress?.pendingPlanCount ?? 'missing'}, plannedArrivalAt=${progress?.plannedArrivalAt || 'missing'}; ${trace(progressResponse)}`, OPTIONS)
|
||||
@@ -598,10 +636,10 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
|
||||
if (!success(afterResolve.response) || receivingSnapshot(afterResolve.data) !== beforeResolve) return
|
||||
const pallets = new Map()
|
||||
for (const [index, inbound] of current.data.inbounds.entries()) {
|
||||
const pallet = await createPallet(context, inbound, index)
|
||||
const pallet = await resolveGeneratedPallet(context, inbound)
|
||||
if (!pallet) {
|
||||
blockerGap(context, `${inbound.destinationType}入仓码${inbound.inboundCode}实物托盘`,
|
||||
`无法在实际海外收货仓 ${inbound.overseasWarehouseId || 'missing'} 创建可解析真实托盘;目的仓 ${inbound.destinationWarehouseId || 'missing'} 不得代替物理仓`, 'warehouse-resource')
|
||||
blockerGap(context, `${inbound.destinationType}入仓码${inbound.inboundCode}自动计划托盘`,
|
||||
`按柜确认没有为该入仓码留下可解析的实体托盘;禁止在PDA收货时临时新建脱离计划的托盘`, 'warehouse-resource')
|
||||
continue
|
||||
}
|
||||
pallets.set(inbound.inboundCode, pallet)
|
||||
@@ -684,6 +722,7 @@ export async function run(context) {
|
||||
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], completedContainers: [],
|
||||
completedContainerFacts: [],
|
||||
overseasWarehouseId: null, overseasWarehouseName: null,
|
||||
generatedPalletsByInbound: {},
|
||||
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',
|
||||
}
|
||||
const merchantToken = await loginMerchantForNegativeProbe(context)
|
||||
@@ -727,7 +766,7 @@ export async function run(context) {
|
||||
if (allProducers.length) await awaitProducerConsumption(context, allProducers)
|
||||
if (producers.length && !(await convertReceivingContainers(context, producers))) {
|
||||
blockerGap(context, '实际处理柜整柜转换门禁',
|
||||
'目标柜未能通过正式平台convert-container逐入仓码生成独立计划')
|
||||
'目标柜未能通过正式柜级预览与确认形成逐入仓码计划及实体托盘')
|
||||
return
|
||||
}
|
||||
if (producers.length && !(await awaitPickupReadyCandidates(context, primary.token, producers, allProducers))) {
|
||||
|
||||
Reference in New Issue
Block a user