fix(lastleg): complete generated pallet receiving flow
This commit is contained in:
@@ -207,11 +207,95 @@ function completeManualNodes(marker) {
|
||||
})).map((node, index) => index === 3 ? { ...node, nodeAt: null } : node)
|
||||
}
|
||||
|
||||
function previewGroupRank(group) {
|
||||
return ({ FBA: 0, ONE_CLICK_DROPSHIP: 1, PRIVATE_ADDRESS: 2 })[group?.destinationType] ?? 99
|
||||
}
|
||||
|
||||
function previewGroupsSorted(groups) {
|
||||
const rows = Array.isArray(groups) ? groups : []
|
||||
return rows.every((group, index) => {
|
||||
if (index === 0) return true
|
||||
const previous = rows[index - 1]
|
||||
const rankDifference = previewGroupRank(previous) - previewGroupRank(group)
|
||||
return rankDifference < 0 || (rankDifference === 0
|
||||
&& String(previous?.destinationWarehouseCode || '').localeCompare(
|
||||
String(group?.destinationWarehouseCode || '')) <= 0)
|
||||
})
|
||||
}
|
||||
|
||||
async function convertContainerPlans(context, producer, details) {
|
||||
const bookingId = id(producer.containerBookingId)
|
||||
const previewPath = query(`${PLANS}/container-preview`, { sourceContainerBookingId: bookingId })
|
||||
const preview = await api(context, 'GET', '预览整柜待生成入仓计划清单', previewPath)
|
||||
const groups = Array.isArray(preview.data?.groups) ? preview.data.groups : []
|
||||
const previewOrders = groups.flatMap((group) => Array.isArray(group?.orders) ? group.orders : [])
|
||||
const pendingPalletCount = Number(preview.data?.pendingPalletCount)
|
||||
context.assert(MODULE, '柜级预览列清尾程单、入仓码、目的仓、业务类型与建议托数', preview.response,
|
||||
preview.data?.ready === true && id(preview.data?.sourceContainerBookingId) === bookingId
|
||||
&& previewOrders.length === details.length && pendingPalletCount > 0
|
||||
&& previewOrders.every((order) => nonBlank(order?.businessOrderNo) && nonBlank(order?.inboundCode)
|
||||
&& nonBlank(order?.destinationType) && nonBlank(order?.destinationWarehouseCode)
|
||||
&& Number(order?.suggestedPalletCount) > 0 && order?.readiness === 'READY'
|
||||
&& Array.isArray(order?.blockers) && order.blockers.length === 0),
|
||||
`orders=${previewOrders.length}, pendingPallets=${pendingPalletCount}, groups=${groups.length}; ${trace(preview.response)}`,
|
||||
OPTIONS)
|
||||
context.assert(MODULE, '柜级预览按FBA、一件代发、私人地址及目的仓稳定排序', preview.response,
|
||||
previewGroupsSorted(groups), groups.map((group) => `${group.destinationType}:${group.destinationWarehouseCode}`).join(','), OPTIONS)
|
||||
context.assert(MODULE, '一件代发的FBA目的仓快照保留给自动托盘', preview.response,
|
||||
previewOrders.filter((order) => order?.destinationType === 'ONE_CLICK_DROPSHIP'
|
||||
&& nonBlank(order?.destinationWarehouseCode)).every((order) => nonBlank(order?.fbaWarehouseCode)),
|
||||
`dropshipFba=${previewOrders.filter((order) => nonBlank(order?.fbaWarehouseCode)).length}`, OPTIONS)
|
||||
if (!success(preview.response) || preview.data?.ready !== true || pendingPalletCount <= 0) return null
|
||||
|
||||
const body = { sourceContainerBookingId: bookingId, snapshotVersion: Number(preview.data.snapshotVersion),
|
||||
snapshotHash: preview.data.snapshotHash }
|
||||
const key = `${context.marker}-container-plan-confirm`.slice(0, 128)
|
||||
const converted = await api(context, 'POST', '按柜确认并自动生成入仓计划与实体托盘',
|
||||
`${PLANS}/convert-container`, body, { 'Idempotency-Key': key })
|
||||
const plans = Array.isArray(converted.data) ? converted.data : []
|
||||
const pallets = plans.flatMap((plan) => Array.isArray(plan?.pallets) ? plan.pallets.map((pallet) => ({ plan, pallet })) : [])
|
||||
context.assert(MODULE, '按柜确认逐票生成独立计划且托盘总数与预览一致', converted.response,
|
||||
plans.length === details.length && pallets.length === pendingPalletCount
|
||||
&& plans.every((plan) => plan?.creationMode === 'AUTO' && nonBlank(plan?.planNo)
|
||||
&& Array.isArray(plan?.lines) && plan.lines.length === 1)
|
||||
&& pallets.every(({ pallet }) => nonBlank(pallet?.palletNo) && id(pallet?.warehousePalletId)
|
||||
&& id(pallet?.warehouseLocationId) && pallet?.assignmentStatus === 'RESERVED'),
|
||||
`plans=${plans.length}, pallets=${pallets.length}, expectedPallets=${pendingPalletCount}; ${trace(converted.response)}`,
|
||||
OPTIONS)
|
||||
if (!success(converted.response)) return null
|
||||
|
||||
for (const { plan, pallet } of pallets) {
|
||||
const line = plan.lines[0]
|
||||
const palletPath = query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(id(preview.data.overseasWarehouseId))}/pallets`, {
|
||||
page: 1, size: 10, keyword: pallet.palletNo,
|
||||
})
|
||||
const listed = await api(context, 'GET', `回查自动托盘${pallet.palletNo}`, palletPath)
|
||||
const stored = records(listed.data).find((row) => row?.palletCode === pallet.palletNo)
|
||||
context.assert(MODULE, '自动托盘进入托盘列表并保留FBA目的仓', listed.response,
|
||||
id(stored?.id) === id(pallet.warehousePalletId) && stored?.status === 'OCCUPIED'
|
||||
&& (!nonBlank(line?.fbaWarehouseCode) || stored?.fbaWarehouseCode === line.fbaWarehouseCode),
|
||||
`pallet=${pallet.palletNo}, expectedFba=${line?.fbaWarehouseCode || 'none'}, actualFba=${stored?.fbaWarehouseCode || 'none'}; ${trace(listed.response)}`,
|
||||
OPTIONS)
|
||||
}
|
||||
|
||||
const replay = await api(context, 'POST', '按柜确认同键同快照幂等重放',
|
||||
`${PLANS}/convert-container`, body, { 'Idempotency-Key': key })
|
||||
context.assert(MODULE, '按柜确认重放不新增计划或托盘', replay.response,
|
||||
JSON.stringify(replay.data) === JSON.stringify(converted.data), trace(replay.response), OPTIONS)
|
||||
const after = await api(context, 'GET', '回查整柜计划与托盘均已生成', previewPath)
|
||||
context.assert(MODULE, '确认后预览待生成票数和托数归零', after.response,
|
||||
Number(after.data?.pendingOrderCount) === 0 && Number(after.data?.pendingPalletCount) === 0
|
||||
&& Number(after.data?.totalPlannedPalletCount) >= pendingPalletCount,
|
||||
`pendingOrders=${after.data?.pendingOrderCount}, pendingPallets=${after.data?.pendingPalletCount}, totalPlannedPallets=${after.data?.totalPlannedPalletCount}; ${trace(after.response)}`,
|
||||
OPTIONS)
|
||||
return plans
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
context.createdResources.phase4TailOrderControls = {
|
||||
marker: context.marker, containerNo: null, orderIds: [], planNos: [], representativeOrderId: null,
|
||||
finalStatus: null,
|
||||
cleanup: 'Manual inbound plans remain as the marker-owned prerequisites consumed by overseas PDA receiving',
|
||||
cleanup: 'Automatically generated container inbound plans and pallets remain as marker-owned prerequisites consumed by overseas PDA receiving',
|
||||
}
|
||||
const producer = targetProducer(context)
|
||||
if (!producer?.containerNo || !producer?.tenantId) {
|
||||
@@ -299,38 +383,13 @@ export async function run(context) {
|
||||
nodesRead.data?.manualOverrideEnabled === true && nodesRead.data?.nodes?.length === 8
|
||||
&& nodesRead.data.nodes.every((node) => node?.source === 'MANUAL'), trace(nodesRead.response), OPTIONS)
|
||||
|
||||
const plans = []
|
||||
for (const original of refreshedDetails) {
|
||||
const refreshed = await detail(context, original, `创建人工计划前刷新订单${id(original.id)}版本`)
|
||||
const order = refreshed.data?.order
|
||||
if (!success(refreshed.response) || !order) return
|
||||
const body = manualPlanBody(order, order.version, context.marker)
|
||||
const key = `${context.marker}-manual-plan-${id(order.id)}`.slice(0, 128)
|
||||
const created = await api(context, 'POST', `为本轮尾程订单${id(order.id)}创建人工计划`, `${PLANS}/manual`, body,
|
||||
{ 'Idempotency-Key': key })
|
||||
context.assert(MODULE, '人工计划按订单稳定接柜仓回读当前主档并冻结入仓码、托数和操作标志', created.response,
|
||||
Boolean(created.data?.planNo) && created.data?.creationMode === 'MANUAL'
|
||||
&& Boolean(created.data?.overseasWarehouseCode) && Boolean(created.data?.overseasWarehouseName)
|
||||
&& created.data?.plannedArrivalDate === body.plannedArrivalDate
|
||||
&& created.data?.palletType === body.palletType && created.data?.urgent === true
|
||||
&& Number(created.data?.palletCount) === body.palletCount
|
||||
&& created.data?.lines?.some((line) => id(line?.orderId) === id(order.id) && line?.inboundCode === order.inboundCode),
|
||||
`orderId=${id(order.id)}, planNo=${created.data?.planNo || 'missing'}; ${trace(created.response)}`, OPTIONS)
|
||||
if (!success(created.response) || !created.data?.planNo) return
|
||||
const replay = await api(context, 'POST', `人工计划${created.data.planNo}幂等重放`, `${PLANS}/manual`, body,
|
||||
{ 'Idempotency-Key': key })
|
||||
context.assert(MODULE, '人工计划重放复用同一计划且零新增', replay.response,
|
||||
replay.data?.planNo === created.data.planNo, trace(replay.response), OPTIONS)
|
||||
if (plans.length === 0) {
|
||||
const hashConflict = await context.requestOverseas('POST', `${PLANS}/manual`,
|
||||
{ ...body, remark: `${context.marker}-DIFFERENT` }, { 'Idempotency-Key': key }, context.overseasToken)
|
||||
context.addStep(MODULE, '人工计划同键异载荷返回A0409', hashConflict, conflict(hashConflict), trace(hashConflict), OPTIONS)
|
||||
}
|
||||
plans.push(created.data.planNo)
|
||||
}
|
||||
const generated = await convertContainerPlans(context, producer, refreshedDetails)
|
||||
if (!generated) return
|
||||
context.createdResources.phase4TailOrderControls = {
|
||||
...context.createdResources.phase4TailOrderControls,
|
||||
containerNo: producer.containerNo, orderIds: refreshedDetails.map((order) => id(order.id)),
|
||||
planNos: plans, representativeOrderId: id(representative.id), finalStatus: 'MANUAL_PLANS_READY',
|
||||
planNos: generated.map((plan) => plan.planNo), representativeOrderId: id(representative.id),
|
||||
generatedPlans: generated,
|
||||
finalStatus: 'AUTO_CONTAINER_PLANS_READY',
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user