|
|
|
@@ -229,9 +229,11 @@ export async function run(context) {
|
|
|
|
|
&& Number(item.availablePackageCount) > 0) : []
|
|
|
|
|
let candidate = inboundCandidates[0]
|
|
|
|
|
let dropshipCandidates = []
|
|
|
|
|
let dropshipCandidatePath = null
|
|
|
|
|
if (source?.fulfillmentType === 'ONE_CLICK_DROPSHIP') {
|
|
|
|
|
const group = channelGroup(initial?.summary?.channelCode)
|
|
|
|
|
const candidatePath = `${smartLoadingBase}/inbound-candidates?channelGroupCodes=${encodeURIComponent(group)}&warehouseId=${encodeURIComponent(warehouseId)}&customerId=${encodeURIComponent(tenantId)}&inboundCode=${encodeURIComponent(initial?.summary?.inboundCode || source?.inboundCode || '')}&orderStatuses=WAIT_SPLIT&page=1&size=20`
|
|
|
|
|
dropshipCandidatePath = candidatePath
|
|
|
|
|
const palletCandidatesResponse = await context.request('GET', candidatePath)
|
|
|
|
|
const palletCandidatePage = context.apiSuccess(MODULE, '查询一件代发整托装柜候选', palletCandidatesResponse, '', OPTIONS)
|
|
|
|
|
dropshipCandidates = Array.isArray(palletCandidatePage?.records)
|
|
|
|
@@ -255,24 +257,112 @@ export async function run(context) {
|
|
|
|
|
if (!loadingPlanId) return
|
|
|
|
|
const dropshipAssignment = source?.fulfillmentType === 'ONE_CLICK_DROPSHIP'
|
|
|
|
|
const initialAssignmentCount = dropshipAssignment
|
|
|
|
|
? dropshipCandidates.filter((item) => id(item.palletId) === id(candidate.palletId))
|
|
|
|
|
.reduce((total, item) => total + Number(item.availablePackageCount || 0), 0) : 1
|
|
|
|
|
const assignmentPath = dropshipAssignment
|
|
|
|
|
? `${smartLoadingBase}/plans/${loadingPlanId}/pallet-assignments`
|
|
|
|
|
: `${smartLoadingBase}/plans/${loadingPlanId}/assignments`
|
|
|
|
|
const assignmentBody = dropshipAssignment
|
|
|
|
|
? { palletId: id(candidate.palletId), expectedVersion: Number(loadingPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-bridge-assign-pallet`.slice(0, 128) }
|
|
|
|
|
: { inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: initialAssignmentCount,
|
|
|
|
|
expectedVersion: Number(loadingPlan.version), operationId: `${context.marker}-${loadingPlanId}-bridge-assign`.slice(0, 128) }
|
|
|
|
|
const assignmentResponse = await request(context, 'POST', assignmentPath, assignmentBody)
|
|
|
|
|
const assignedLoadingPlan = context.apiSuccess(MODULE, '将本轮入库货物分配到柜次计划', assignmentResponse, '', OPTIONS)
|
|
|
|
|
? dropshipCandidates.reduce((total, item) => total + Number(item.availablePackageCount || 0), 0) : 1
|
|
|
|
|
let assignmentResponse
|
|
|
|
|
let assignedLoadingPlan
|
|
|
|
|
if (dropshipAssignment) {
|
|
|
|
|
const assignmentPath = `${smartLoadingBase}/plans/${loadingPlanId}/pallet-assignments`
|
|
|
|
|
let workingPlan = loadingPlan
|
|
|
|
|
for (const [index, pallet] of dropshipCandidates.entries()) {
|
|
|
|
|
const body = { palletId: id(pallet.palletId), expectedVersion: Number(workingPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-bridge-assign-pallet-${id(pallet.palletId)}`.slice(0, 128) }
|
|
|
|
|
assignmentResponse = await request(context, 'POST', assignmentPath, body)
|
|
|
|
|
workingPlan = context.apiSuccess(MODULE, `逐托分配一件代发托盘${index + 1}/${dropshipCandidates.length}`,
|
|
|
|
|
assignmentResponse, '', OPTIONS)
|
|
|
|
|
if (!workingPlan) return
|
|
|
|
|
if (index === 0 && dropshipCandidates.length > 1 && dropshipCandidatePath) {
|
|
|
|
|
const remainingResponse = await context.request('GET', dropshipCandidatePath)
|
|
|
|
|
const remainingPage = context.apiSuccess(MODULE, '分配首托后刷新同票剩余整托候选',
|
|
|
|
|
remainingResponse, '', OPTIONS)
|
|
|
|
|
const remainingIds = new Set((remainingPage?.records || []).map((item) => id(item?.palletId)))
|
|
|
|
|
const expectedRemaining = dropshipCandidates.slice(1).map((item) => id(item.palletId))
|
|
|
|
|
context.assert(MODULE, '一件代发分配首托后其余托盘仍可独立分配', remainingResponse,
|
|
|
|
|
expectedRemaining.every((palletId) => remainingIds.has(palletId)),
|
|
|
|
|
`remaining=${[...remainingIds].join(',') || 'missing'}, expected=${expectedRemaining.join(',')}`, OPTIONS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assignedLoadingPlan = workingPlan
|
|
|
|
|
} else {
|
|
|
|
|
const assignmentPath = `${smartLoadingBase}/plans/${loadingPlanId}/assignments`
|
|
|
|
|
const assignmentBody = { inboundId, inboundDetailId: id(candidate.inboundDetailId),
|
|
|
|
|
packageCount: initialAssignmentCount, expectedVersion: Number(loadingPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-bridge-assign`.slice(0, 128) }
|
|
|
|
|
assignmentResponse = await request(context, 'POST', assignmentPath, assignmentBody)
|
|
|
|
|
assignedLoadingPlan = context.apiSuccess(MODULE, '将本轮入库货物分配到柜次计划', assignmentResponse, '', OPTIONS)
|
|
|
|
|
}
|
|
|
|
|
context.assert(MODULE, '柜次计划明细和版本真实持久化', assignmentResponse,
|
|
|
|
|
assignedLoadingPlan?.items?.filter?.((item) => id(item.inboundId) === inboundId)
|
|
|
|
|
.reduce((total, item) => total + Number(item.packageCount || 0), 0) === initialAssignmentCount
|
|
|
|
|
&& Number(assignedLoadingPlan?.version) >= 1,
|
|
|
|
|
`loadingPlanId=${loadingPlanId}, assignedPackages=${initialAssignmentCount}, version=${assignedLoadingPlan?.version ?? 'missing'}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
if (dropshipAssignment && dropshipCandidates.length > 1) {
|
|
|
|
|
const firstPalletItem = assignedLoadingPlan?.items?.find?.((item) =>
|
|
|
|
|
id(item.inboundId) === inboundId && id(item.palletId) === id(dropshipCandidates[0].palletId))
|
|
|
|
|
const singleDelete = await request(context, 'DELETE',
|
|
|
|
|
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(firstPalletItem?.id)}`, {
|
|
|
|
|
expectedPlanVersion: Number(assignedLoadingPlan?.version),
|
|
|
|
|
expectedItemVersion: Number(firstPalletItem?.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-reject-single-pallet-line-delete`.slice(0, 128),
|
|
|
|
|
})
|
|
|
|
|
context.assert(MODULE, '已封托托盘拒绝按单条装柜明细删除', singleDelete,
|
|
|
|
|
singleDelete.http === 200 && codeOf(singleDelete) === 'A0409',
|
|
|
|
|
`HTTP=${singleDelete.http}, code=${codeOf(singleDelete)}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
let emptiedPlan = assignedLoadingPlan
|
|
|
|
|
for (const [index, pallet] of dropshipCandidates.entries()) {
|
|
|
|
|
const removedResponse = await request(context, 'DELETE',
|
|
|
|
|
`${smartLoadingBase}/plans/${loadingPlanId}/pallet-assignments/${id(pallet.palletId)}`, {
|
|
|
|
|
expectedPlanVersion: Number(emptiedPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-remove-whole-pallet-${id(pallet.palletId)}`.slice(0, 128),
|
|
|
|
|
})
|
|
|
|
|
emptiedPlan = context.apiSuccess(MODULE, `整托移除一件代发托盘${index + 1}/${dropshipCandidates.length}`,
|
|
|
|
|
removedResponse, '', OPTIONS)
|
|
|
|
|
if (!emptiedPlan) return
|
|
|
|
|
}
|
|
|
|
|
context.assert(MODULE, '逐托移除后本票装柜明细归零', assignmentResponse,
|
|
|
|
|
!emptiedPlan.items?.some?.((item) => id(item.inboundId) === inboundId),
|
|
|
|
|
`remaining=${emptiedPlan.items?.filter?.((item) => id(item.inboundId) === inboundId).length ?? 'missing'}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const recommendResponse = await request(context, 'POST',
|
|
|
|
|
`${smartLoadingBase}/plans/${loadingPlanId}/recommend`, {
|
|
|
|
|
expectedVersion: Number(emptiedPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-recommend-multi-pallet`.slice(0, 128),
|
|
|
|
|
customerId: tenantId,
|
|
|
|
|
inboundCode: initial?.summary?.inboundCode || source?.inboundCode,
|
|
|
|
|
})
|
|
|
|
|
const recommendedPlan = context.apiSuccess(MODULE, '智能推荐按整托重新加入一件代发多托',
|
|
|
|
|
recommendResponse, '', OPTIONS)
|
|
|
|
|
const expectedPalletIds = new Set(dropshipCandidates.map((item) => id(item.palletId)))
|
|
|
|
|
const recommendedPalletIds = new Set((recommendedPlan?.items || [])
|
|
|
|
|
.filter((item) => id(item.inboundId) === inboundId).map((item) => id(item.palletId)))
|
|
|
|
|
context.assert(MODULE, '智能推荐保留同票全部封托托盘且不按箱拆分', recommendResponse,
|
|
|
|
|
expectedPalletIds.size === dropshipCandidates.length
|
|
|
|
|
&& [...expectedPalletIds].every((palletId) => recommendedPalletIds.has(palletId)),
|
|
|
|
|
`expected=${[...expectedPalletIds].join(',')}, actual=${[...recommendedPalletIds].join(',') || 'missing'}`, OPTIONS)
|
|
|
|
|
if (!recommendedPlan || ![...expectedPalletIds].every((palletId) => recommendedPalletIds.has(palletId))) return
|
|
|
|
|
|
|
|
|
|
const clearedResponse = await request(context, 'DELETE',
|
|
|
|
|
`${smartLoadingBase}/plans/${loadingPlanId}/unpicked-items`, {
|
|
|
|
|
expectedPlanVersion: Number(recommendedPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-clear-recommendation`.slice(0, 128),
|
|
|
|
|
})
|
|
|
|
|
let exactPlan = context.apiSuccess(MODULE, '移除未拣并释放智能推荐临时分配',
|
|
|
|
|
clearedResponse, '', OPTIONS)
|
|
|
|
|
if (!exactPlan) return
|
|
|
|
|
for (const [index, pallet] of dropshipCandidates.entries()) {
|
|
|
|
|
assignmentResponse = await request(context, 'POST',
|
|
|
|
|
`${smartLoadingBase}/plans/${loadingPlanId}/pallet-assignments`, {
|
|
|
|
|
palletId: id(pallet.palletId), expectedVersion: Number(exactPlan.version),
|
|
|
|
|
operationId: `${context.marker}-${loadingPlanId}-final-pallet-${id(pallet.palletId)}`.slice(0, 128),
|
|
|
|
|
})
|
|
|
|
|
exactPlan = context.apiSuccess(MODULE, `智能推荐回归后恢复本票整托${index + 1}/${dropshipCandidates.length}`,
|
|
|
|
|
assignmentResponse, '', OPTIONS)
|
|
|
|
|
if (!exactPlan) return
|
|
|
|
|
}
|
|
|
|
|
assignedLoadingPlan = exactPlan
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const assignedItem = assignedLoadingPlan?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
|
|
|
|
const orderRemarks = source || {}
|
|
|
|
|
context.assert(MODULE, '装柜明细客户备注与最新内部备注来源精确且不冒用货物备注', assignmentResponse,
|
|
|
|
@@ -690,16 +780,18 @@ export async function run(context) {
|
|
|
|
|
|
|
|
|
|
if (dropshipAssignment) {
|
|
|
|
|
const palletUnits = Array.isArray(inventory?.units) ? inventory.units : []
|
|
|
|
|
const expectedPallets = new Map(dropshipCandidates.map((item) =>
|
|
|
|
|
[id(item.palletId), item.palletNo]))
|
|
|
|
|
const taskUnitsValid = canonicalPickItems.every((item) => item.unitType === 'SEALED_PALLET'
|
|
|
|
|
&& id(item.palletId) === id(candidate.palletId) && item.palletNo === candidate.palletNo
|
|
|
|
|
&& expectedPallets.get(id(item.palletId)) === item.palletNo
|
|
|
|
|
&& /^DP-[0-9]{6}-[0-9]{4}$/.test(item.palletNo) && item.indivisible === true)
|
|
|
|
|
const inventoryUnitsValid = canonicalPickItems.every((item) => palletUnits.some((unit) =>
|
|
|
|
|
id(unit.pickItemId) === id(item.id) && id(unit.loadingPlanItemId) === id(item.loadingPlanItemId)
|
|
|
|
|
&& id(unit.inboundId) === id(item.inboundId) && unit.unitType === 'SEALED_PALLET'
|
|
|
|
|
&& id(unit.palletId) === id(item.palletId) && unit.palletNo === item.palletNo && unit.indivisible === true))
|
|
|
|
|
context.assert(MODULE, 'PDA task与inventory明确返回SEALED_PALLET及DP整托四字段', inventoryResponse,
|
|
|
|
|
taskUnitsValid && inventoryUnitsValid,
|
|
|
|
|
`taskUnits=${canonicalPickItems.length}, inventoryUnits=${palletUnits.length}`, OPTIONS)
|
|
|
|
|
taskUnitsValid && inventoryUnitsValid && canonicalPickItems.length === expectedPallets.size,
|
|
|
|
|
`taskUnits=${canonicalPickItems.length}, inventoryUnits=${palletUnits.length}, expectedPallets=${expectedPallets.size}`, OPTIONS)
|
|
|
|
|
if (!taskUnitsValid || !inventoryUnitsValid) return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|