优化
This commit is contained in:
@@ -133,14 +133,6 @@ async function merchant(context, token, method, name, path, body = undefined) {
|
||||
return { response, data }
|
||||
}
|
||||
|
||||
function transferDeliveries(data) {
|
||||
return Array.isArray(data?.result?.deliveries) ? data.result.deliveries.filter((value) => nonBlank(value?.deliveryNo)) : []
|
||||
}
|
||||
|
||||
function deliveryNumbers(data) {
|
||||
return transferDeliveries(data).map((value) => id(value.deliveryNo)).sort()
|
||||
}
|
||||
|
||||
export function dispatchDestinationConservation(detail, fixture, deliveries = null) {
|
||||
const codes = values => values.map(value => id(value).trim().toUpperCase())
|
||||
const sameSet = (left,right) => left.length > 0 && right.length > 0 && left.every(nonBlank)
|
||||
@@ -858,7 +850,7 @@ async function verifyRelabelDispatchOutbound(context, relabel, token, warehouseI
|
||||
const proof = await prepareFleetTrip(context, MODULE, relabel.dispatchNo, warehouseId)
|
||||
if (!await pickAll(context, token, relabel.dispatchNo)) return
|
||||
await confirmFleetLoading(context, MODULE, proof, token, relabel.loadingMeasurements)
|
||||
const transferred = await arrangeDispatchDelivery(context, relabel.dispatchNo)
|
||||
const transferred = await readAutomaticDispatchDelivery(context, relabel.dispatchNo)
|
||||
if (!transferred) return
|
||||
const customerId = id(relabel.customerId)
|
||||
const outboundNo = transferred.outbounds.find(row => id(row.customerId) === customerId)?.outboundNo
|
||||
@@ -869,7 +861,7 @@ async function verifyRelabelDispatchOutbound(context, relabel, token, warehouseI
|
||||
success(detail.response) && detail.data?.outbound?.outboundNo === outboundNo
|
||||
&& detail.data?.outbound?.dispatchNo === relabel.dispatchNo
|
||||
&& relabelOutboundMarksMatch(detail.data, relabel), trace(detail.response), OPTIONS)
|
||||
Object.assign(relabel, { outboundNo, deliveryNos: deliveryNumbers(transferred.data) })
|
||||
Object.assign(relabel, { outboundNo, deliveryNos: transferred.deliveries.map(row => row.deliveryNo).sort() })
|
||||
}
|
||||
|
||||
async function loginMerchant(context, customerId) {
|
||||
@@ -909,17 +901,18 @@ function fixture(context) {
|
||||
return { dispatch, receiving }
|
||||
}
|
||||
|
||||
export async function verifyDepartureReadiness(context, token, dispatchNo) {
|
||||
const ready = await pda(context, token, 'GET', '发车前回查PDA公共动作门禁',
|
||||
export async function verifyAutomaticDeparture(context, token, dispatchNo) {
|
||||
const ready = await pda(context, token, 'GET', '回查PDA自动发车结果',
|
||||
`${PDA_DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}`)
|
||||
const dispatch = await platform(context, 'GET', '发车前独立回查实际排车阶段',
|
||||
const dispatch = await platform(context, 'GET', '独立回查平台自动发车结果',
|
||||
`${DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}`)
|
||||
const passed = success(ready.response) && success(dispatch.response)
|
||||
&& [ready.data, dispatch.data].every(data => data?.dispatchNo === dispatchNo
|
||||
&& data.status === 'ACTIVE' && data.stage === 'AFTER_LOADING'
|
||||
&& data.pickingReconciled === true && data.actions?.DEPART?.allowed === true)
|
||||
&& data.status === 'ACTIVE' && data.stage === 'DELIVERING'
|
||||
&& data.pickingReconciled === true && Number(data.counts?.packageCount) > 0
|
||||
&& Number(data.counts?.shippedPackageCount) === Number(data.counts?.packageCount))
|
||||
&& String(ready.data.recordVersion) === String(dispatch.data.recordVersion)
|
||||
context.assert(MODULE, '双端同版本装车后且服务端发车门禁通过', ready.response,
|
||||
context.assert(MODULE, '双端自动发车阶段与版本一致', ready.response,
|
||||
passed, `stage=${ready.data?.stage}, dispatchStage=${dispatch.data?.stage}; ${trace(ready.response)}`, OPTIONS)
|
||||
if (passed) version(ready.data.recordVersion)
|
||||
return passed ? ready : null
|
||||
@@ -983,56 +976,30 @@ export async function pickAll(context, token, dispatchNo) {
|
||||
return passed
|
||||
}
|
||||
|
||||
export async function arrangeDispatchDelivery(context, dispatchNo) {
|
||||
const path = `${DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}`
|
||||
const preview = await platform(context, 'GET', '装车后读取全车真实派送分组与费用证据', `${path}/delivery-arrangement`)
|
||||
const value = preview.data
|
||||
if (!success(preview.response) || value?.dispatchNo !== dispatchNo || value.status !== 'ACTIVE'
|
||||
|| value.stage !== 'AFTER_LOADING' || value.arrange?.allowed !== true
|
||||
|| !nonBlank(value.cargoHash) || !nonBlank(value.loadingCompletionHash)
|
||||
|| !value.groups?.length || value.groups.some(group => !nonBlank(group.groupKey)
|
||||
|| !nonBlank(id(group.customerId)) || !nonBlank(group.destination?.key)
|
||||
|| !group.cargo?.length || !Array.isArray(group.blockReasons) || group.blockReasons.length)) {
|
||||
blocker(context, '全车派送安排', '装车后正式预览或完整客户目的地分组门禁未满足', preview.response)
|
||||
return null
|
||||
}
|
||||
const body = { recordVersion: version(value.recordVersion), idempotencyKey: key(context, 'TRANSFER', dispatchNo),
|
||||
cargoHash: value.cargoHash, loadingCompletionHash: value.loadingCompletionHash,
|
||||
groups: value.groups.map((group, index) => ({ groupKey: group.groupKey, groupCargoHash: group.groupCargoHash,
|
||||
plannedDeliveryAt: new Date(Date.now() + 172_800_000).toISOString(),
|
||||
deliveryMethod: 'TRUCK_PALLET', waybillNo: `${context.marker}-${index + 1}`.slice(0, 64),
|
||||
carrierName: '本轮TEST排车', pickupMethod: 'WAREHOUSE_PICKUP', loadingType: 'PALLET',
|
||||
vehicleType: 'TRUCK', vehicleSize: '按本轮真实实载', appointmentAccount: 'SELF', sellerName: null,
|
||||
remark: `${context.marker}-phase5-arrangement`, externalFreightHash: group.externalFreightHash,
|
||||
feeCoverageCargoHash: group.feeCoverageCargoHash, feeCoverage: [],
|
||||
fees: (group.requiredServices || []).map(service => ({ feeType: service, feeName: service,
|
||||
direction: 'RECEIVABLE', currency: 'USD', amount: 10, remark: `${context.marker}-controlled-test-fee` })) })) }
|
||||
const transferred = await platform(context, 'POST', '平台一次安排全车全部客户目的地派送', `${path}/transfer-delivery`, body)
|
||||
if (!success(transferred.response)) return null
|
||||
const result = transferred.data?.result
|
||||
const deliveries = transferDeliveries(transferred.data)
|
||||
const outbounds = result?.outbounds
|
||||
const customerGroups = value.groups.map(group => `${id(group.customerId)}:${group.destination.key}`).sort()
|
||||
const actualGroups = deliveries.map(row => `${id(row.customerId)}:${row.destination?.key}`).sort()
|
||||
const passed = transferred.data?.dispatchNo === dispatchNo && transferred.data?.status === 'ACTIVE'
|
||||
&& transferred.data?.stage === 'AFTER_LOADING' && result?.dispatchNo === dispatchNo
|
||||
&& result.cargoHash === body.cargoHash && deliveries.length === value.groups.length
|
||||
export async function readAutomaticDispatchDelivery(context, dispatchNo) {
|
||||
const current = await platform(context, 'GET', '回查装车后自动派送单据', `${DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}`)
|
||||
const value = current.data, deliveries = value?.documents?.deliveries, outbounds = value?.documents?.outbounds
|
||||
const positive = count => (typeof count === 'number' || typeof count === 'string')
|
||||
&& /^[1-9][0-9]*$/.test(String(count)) && Number.isSafeInteger(Number(count))
|
||||
const passed = success(current.response) && value?.dispatchNo === dispatchNo && value.status === 'ACTIVE'
|
||||
&& value.stage === 'DELIVERING' && value.pickingReconciled === true && positive(value.counts?.packageCount)
|
||||
&& positive(value.counts.pickedPackageCount) && positive(value.counts.shippedPackageCount)
|
||||
&& Number(value.counts.pickedPackageCount) === Number(value.counts.packageCount)
|
||||
&& Number(value.counts.shippedPackageCount) === Number(value.counts.packageCount)
|
||||
&& Array.isArray(deliveries) && deliveries.length > 0 && Array.isArray(outbounds) && outbounds.length > 0
|
||||
&& new Set(deliveries.map(row => row.deliveryNo)).size === deliveries.length
|
||||
&& JSON.stringify(customerGroups) === JSON.stringify(actualGroups)
|
||||
&& Array.isArray(outbounds) && outbounds.length === new Set(value.groups.map(group => id(group.customerId))).size
|
||||
&& outbounds.every(row => nonBlank(row.outboundNo) && row.status === 'WAIT_OUTBOUND'
|
||||
&& deliveries.filter(delivery => delivery.outboundNo === row.outboundNo).every(delivery => id(delivery.customerId) === id(row.customerId))
|
||||
&& new Set(outbounds.map(row => row.outboundNo)).size === outbounds.length
|
||||
&& new Set(outbounds.map(row => id(row.customerId))).size === outbounds.length
|
||||
&& new Set(deliveries.map(row => JSON.stringify([id(row.customerId), row.destination?.key]))).size === deliveries.length
|
||||
&& deliveries.every(row => nonBlank(row.deliveryNo) && nonBlank(id(row.customerId)) && nonBlank(row.destination?.key)
|
||||
&& row.status === 'DELIVERING' && positive(row.packageCount)
|
||||
&& outbounds.some(outbound => outbound.outboundNo === row.outboundNo && id(outbound.customerId) === id(row.customerId)))
|
||||
&& outbounds.every(row => nonBlank(row.outboundNo) && row.status === 'OUTBOUNDED' && positive(row.packageCount)
|
||||
&& deliveries.filter(delivery => delivery.outboundNo === row.outboundNo).reduce((sum, delivery) => sum + Number(delivery.packageCount), 0) === Number(row.packageCount))
|
||||
&& deliveries.every(row => row.status === 'WAIT_DELIVERY')
|
||||
context.assert(MODULE, '派送按客户出库及客户目的地完整分组并数量守恒', transferred.response, passed, trace(transferred.response), OPTIONS)
|
||||
&& deliveries.reduce((sum, row) => sum + Number(row.packageCount), 0) === Number(value.counts.packageCount)
|
||||
context.assert(MODULE, '自动派送客户归属、单据状态和实发数量守恒', current.response, passed, trace(current.response), OPTIONS)
|
||||
if (!passed) return null
|
||||
const replay = await platform(context, 'POST', '全车安排派送原命令重放', `${path}/transfer-delivery`, body)
|
||||
const stable = success(replay.response) && replay.data?.replayed === true
|
||||
&& String(replay.data.recordVersion) === String(transferred.data.recordVersion)
|
||||
&& JSON.stringify(replay.data.result) === JSON.stringify(result)
|
||||
context.assert(MODULE, '安排重放返回原命令结果且不增单据', replay.response, stable, trace(replay.response), OPTIONS)
|
||||
if (!stable) return null
|
||||
return { ...transferred, preview: value, outbounds, deliveries }
|
||||
return { ...current, outbounds, deliveries }
|
||||
}
|
||||
|
||||
/** 只从本次派送的真实主单及其原出库来源创建退回请求,不要求普通入仓关联。 */
|
||||
@@ -1146,40 +1113,26 @@ export async function run(context) {
|
||||
const primaryInventory = proofs.find(proof => proof.customerId === customerId)
|
||||
if (primaryInventory) context.createdResources.phase5OverseasFulfillment.inventoryProof = { ...primaryInventory, ...primaryInventory.conservation }
|
||||
await confirmFleetLoading(context, MODULE, tripProof, picker.token, source.dispatch.loadingMeasurements)
|
||||
transferred = await arrangeDispatchDelivery(context, dispatchNo)
|
||||
transferred = await readAutomaticDispatchDelivery(context, dispatchNo)
|
||||
if (!transferred) return
|
||||
deliveries = transferred.deliveries; outbounds = transferred.outbounds
|
||||
routeProof.deliveries = deliveries
|
||||
context.assert(MODULE, '转派送客户目的地与本轮冻结来源守恒', transferred.response,
|
||||
dispatchDestinationConservation(beforeEdit.data,source.dispatch,deliveries), trace(transferred.response), OPTIONS)
|
||||
if (!dispatchDestinationConservation(beforeEdit.data,source.dispatch,deliveries)) return
|
||||
const ready = await verifyDepartureReadiness(context, picker.token, dispatchNo)
|
||||
const ready = await verifyAutomaticDeparture(context, picker.token, dispatchNo)
|
||||
if (!ready) return
|
||||
const departBody = { recordVersion: version(ready.data.recordVersion),
|
||||
idempotencyKey: key(context, 'DISPATCH-DEPART', dispatchNo).slice(0, 128), remark: `${context.marker}-depart` }
|
||||
const departed = await pda(context, picker.token, 'POST', 'PDA实际发车并完成真实出库',
|
||||
`${PDA_DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}/depart`, departBody)
|
||||
const expectedPackages = proofs.reduce((sum, proof) => sum + proof.expectedPackages, 0)
|
||||
const departedCorrectly = success(departed.response) && departed.data?.dispatchNo === dispatchNo
|
||||
&& departed.data.status === 'ACTIVE' && departed.data.stage === 'DELIVERING'
|
||||
&& Number(departed.data.result?.shippedPackages) === expectedPackages
|
||||
&& JSON.stringify(deliveryNumbers(departed.data)) === JSON.stringify(deliveries.map(row => row.deliveryNo).sort())
|
||||
context.assert(MODULE, '实际发车进入DELIVERING且全部客户实发数量守恒', departed.response, departedCorrectly, trace(departed.response), OPTIONS)
|
||||
if (!departedCorrectly) return
|
||||
const departReplay = await pda(context, picker.token, 'POST', 'PDA发车原命令重放',
|
||||
`${PDA_DISPATCH_BASE}/${encodeURIComponent(dispatchNo)}/depart`, departBody)
|
||||
const replayStable = success(departReplay.response) && departReplay.data?.replayed === true
|
||||
&& String(departReplay.data.recordVersion) === String(departed.data.recordVersion)
|
||||
&& JSON.stringify(departReplay.data.result) === JSON.stringify(departed.data.result)
|
||||
context.assert(MODULE, 'PDA发车重放保持原结果及公共版本', departReplay.response, replayStable, trace(departReplay.response), OPTIONS)
|
||||
if (!replayStable) return
|
||||
if (Number(ready.data.counts?.shippedPackageCount) !== expectedPackages) {
|
||||
blocker(context, '自动发车数量', '实发数量与本轮扫码来源不一致', ready.response); return
|
||||
}
|
||||
for (const proof of proofs) {
|
||||
proof.stockAfterDepart = await inventorySnapshot(context, proof.customerId, warehouseId, proof.inboundCodes,
|
||||
`客户${proof.customerId}发车后库存`, proof.sourceBalanceIds)
|
||||
proof.ledgerAfterDepart = await shipmentLedger(context, proof.customerId, dispatchNo, `客户${proof.customerId}发车后SHIP`, 'SHIP', dispatchNo)
|
||||
const unchanged = inventorySourcesPresent(proof.stockAfterDepart, proof.sourcePositionBalances)
|
||||
&& sameSnapshot(proof.stockAfter, proof.stockAfterDepart) && sameSnapshot(proof.ledgerAfter, proof.ledgerAfterDepart)
|
||||
context.assert(MODULE, '发车及重放库存和SHIP零额外扣减', departReplay.response, unchanged, trace(departReplay.response), OPTIONS)
|
||||
context.assert(MODULE, '发车及重放库存和SHIP零额外扣减', ready.response, unchanged, trace(ready.response), OPTIONS)
|
||||
if (!unchanged) return
|
||||
}
|
||||
// 换标副场景仍核对真实新箱唛快照,执行同一原生装车前置。
|
||||
|
||||
Reference in New Issue
Block a user