优化
This commit is contained in:
@@ -2,7 +2,7 @@ import { loadOneFragment } from './phase3-order-splitting.mjs'
|
||||
import { freezeOrderCommand } from './orders.mjs'
|
||||
import { verifyPalletLabels } from './phase3-loading-finalize.mjs'
|
||||
import { stableLoadedCartonMarks } from './phase3-loading.mjs'
|
||||
import { awaitProducerConsumption, convertReceivingContainers, runReceiving, resolveSharedWarehouse,
|
||||
import { awaitProducerConsumption, convertReceivingContainers, runReceiving,
|
||||
resolvePickerAccess, createPicker, disablePicker } from './phase4-overseas-pda.mjs'
|
||||
import { pickAll, verifyDepartureReadiness, saveReceiptAndComplete } from './phase5-overseas-fulfillment.mjs'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
@@ -19,6 +19,47 @@ const id = value => value == null ? '' : String(value)
|
||||
const success = response => response?.http === 200 && String(response?.body?.code) === '200'
|
||||
const query = (path, values) => `${path}?${new URLSearchParams(values)}`
|
||||
|
||||
/** Resolve only this run's consumed physical sources, never a fixed warehouse ID or list position. */
|
||||
export async function resolveF40ReceivingWarehouse(context, producers) {
|
||||
check(context, 'F40 选仓仅接受唯一的本轮实装来源', Array.isArray(producers) && producers.length > 0
|
||||
&& producers.every(row => row?.containerNo && row?.inboundCode && id(row.tenantId)
|
||||
&& id(row.containerBookingId) && id(row.sourceInboundId))
|
||||
&& new Set(producers.map(row => id(row.containerBookingId))).size === producers.length
|
||||
&& new Set(producers.map(row => id(row.tenantId))).size === 1, 'exact-producer-identities')
|
||||
const sources = []
|
||||
for (const producer of producers) {
|
||||
const page = await api(context, 'GET', 'F40 读取本柜实际尾程来源与接柜仓',
|
||||
query('/api/platform/v1/last-leg/orders', { page: 1, size: 200, containerNo: producer.containerNo }))
|
||||
const rows = Array.isArray(page?.records) ? page.records : []
|
||||
const matching = rows.filter(row => row.containerNo === producer.containerNo
|
||||
&& id(row.sourceContainerBookingId) === id(producer.containerBookingId)
|
||||
&& id(row.customerId) === id(producer.tenantId)
|
||||
&& id(row.sourceInboundId) === id(producer.sourceInboundId) && row.inboundCode === producer.inboundCode)
|
||||
check(context, 'F40 尾程来源柜客户入仓身份唯一且接柜仓明确', matching.length === 1
|
||||
&& (page?.total == null || Number(page.total) <= rows.length)
|
||||
&& Boolean(id(matching[0]?.id) && id(matching[0]?.overseasWarehouseId)),
|
||||
`container=${producer.containerNo}, exactSources=${matching.length}`)
|
||||
sources.push(matching[0])
|
||||
}
|
||||
const warehouseId = id(sources[0].overseasWarehouseId)
|
||||
check(context, 'F40 全部实装来源属于同一实际接柜仓',
|
||||
sources.every(row => id(row.overseasWarehouseId) === warehouseId), 'unique-source-receiving-warehouse')
|
||||
const page = await api(context, 'GET', 'F40 按来源仓精确核验Warehouse权威收货主档',
|
||||
'/api/platform/v1/warehouse-admin?tenantId=0&page=1&size=200&capabilityCode=RECEIVING&warehouseType=OVERSEAS&status=ACTIVE')
|
||||
const rows = Array.isArray(page?.records) ? page.records : []
|
||||
const matching = rows.filter(row => id(row.id) === warehouseId)
|
||||
const warehouse = matching[0]
|
||||
check(context, 'F40 实际接柜仓为唯一启用海外收货仓且编码一致', matching.length === 1
|
||||
&& warehouse.warehouseType === 'OVERSEAS' && warehouse.status === 'ACTIVE'
|
||||
&& sources.every(row => !row.overseasWarehouseCode || row.overseasWarehouseCode === warehouse.warehouseCode),
|
||||
`warehouseId=${warehouseId}`)
|
||||
return { id: warehouseId, name: warehouse.warehouseName || warehouse.name,
|
||||
code: warehouse.warehouseCode, sourceOrders: sources.map(row => ({ orderId: id(row.id),
|
||||
customerId: id(row.customerId), sourceContainerBookingId: id(row.sourceContainerBookingId),
|
||||
sourceInboundId: id(row.sourceInboundId), inboundCode: row.inboundCode, containerNo: row.containerNo,
|
||||
overseasWarehouseId: id(row.overseasWarehouseId) })) }
|
||||
}
|
||||
|
||||
export function f40LocalDateTime(value) { return value.toISOString().slice(0,19) }
|
||||
export function preserveReceivingEvidence(proof, scope) {
|
||||
proof.receivingCompletionEvidence = scope.createdResources?.phase4OverseasPda?.receivingCompletionEvidence || {}
|
||||
@@ -128,6 +169,19 @@ export function validSignatureGate(snapshot, firstSource, allSigned = false) {
|
||||
&& snapshot?.rootInbound?.processStatus === 'DELIVERED'
|
||||
&& snapshot?.rootInbound?.orderStatusSnapshot !== 'COMPLETED'
|
||||
}
|
||||
// TEST Lastleg runs with TZ=Asia/Shanghai. Its public signedAt is local;
|
||||
// the completion event and Order projection preserve the same instant in UTC.
|
||||
export function rootCompletionTimeMatches(completedAt, signedTimes) {
|
||||
const instant = (value, offset) => {
|
||||
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})?$/.test(value)) return NaN
|
||||
return Date.parse(value + (/(?:Z|[+-]\d{2}:\d{2})$/.test(value) ? '' : offset))
|
||||
}
|
||||
if (!Array.isArray(signedTimes) || signedTimes.length === 0) return false
|
||||
const signatures = signedTimes.map(value => instant(value, '+08:00'))
|
||||
return signatures.every(Number.isFinite) && Number.isFinite(instant(completedAt, 'Z'))
|
||||
&& instant(completedAt, 'Z') === Math.max(...signatures)
|
||||
}
|
||||
|
||||
export function exactCompletionOutboxProof(evidence, customerId, rootOrderId, sourceOrderIds) {
|
||||
const expected = [...new Set(sourceOrderIds.map(id))].sort()
|
||||
const rows = Array.isArray(evidence?.orders) ? evidence.orders : []
|
||||
@@ -613,8 +667,13 @@ async function runRootFulfillment(context) {
|
||||
&& id(before.root.rootOrderId) === rootOrderId && before.root.status !== 'COMPLETED'
|
||||
&& before.child.status !== 'COMPLETED' && completionCounts(before.root).own === 0, 'before-signatures')
|
||||
proof.snapshots.push({ phase:'BEFORE_SIGNATURES', ...before })
|
||||
const producers = cancelRemainder ? [first] : [first,second]
|
||||
proof.currentStage = 'OVERSEAS_SOURCE_WAREHOUSE'
|
||||
check(scope, 'F40 实装来源真实装柜事件已消费', await awaitProducerConsumption(scope,producers), `producers=${producers.length}`)
|
||||
const warehouse = await resolveF40ReceivingWarehouse(scope,producers)
|
||||
proof.receivingWarehouseResolution = warehouse
|
||||
proof.currentStage = 'OVERSEAS_PDA_IDENTITY'
|
||||
const warehouse = await resolveSharedWarehouse(scope), access = await resolvePickerAccess(scope)
|
||||
const access = await resolvePickerAccess(scope)
|
||||
check(scope, 'F40 实际海外接柜仓和PDA权限有效', Boolean(warehouse?.id && access), 'resourcePresent')
|
||||
scope.marker = `${scope.context.f40BaseMarker}-PDA`
|
||||
const picker = await createPicker(scope, 7, warehouse.id, access)
|
||||
@@ -622,7 +681,6 @@ async function runRootFulfillment(context) {
|
||||
check(scope, 'F40 海外PDA操作员会话有效', Boolean(picker?.token), 'tokenPresent')
|
||||
picker.warehouseId = warehouse.id
|
||||
check(scope, 'F40 客户会话属于该根订单客户', id(profile.id) === customerId, 'customerOwned')
|
||||
const producers = cancelRemainder ? [first] : [first,second]
|
||||
scope.createdResources.phase4InboundProducerFixtures = { marker: scope.marker, containers:producers }
|
||||
scope.createdResources.phase4OverseasPda = { marker:scope.marker, generatedPalletsByInbound:{},
|
||||
palletIds:[],locationIds:[],completedContainerFacts:[], completedContainers:[],
|
||||
@@ -630,7 +688,6 @@ async function runRootFulfillment(context) {
|
||||
receivingCompletionEvidence:{} }
|
||||
preserveReceivingEvidence(proof,scope)
|
||||
proof.currentStage = 'OVERSEAS_PLAN_CONVERSION'
|
||||
check(scope, 'F40 实装来源真实装柜事件已消费', await awaitProducerConsumption(scope,producers), `producers=${producers.length}`)
|
||||
check(scope, 'F40 实装来源真实计划及托盘转换成功', await convertReceivingContainers(scope,producers), `producers=${producers.length}`)
|
||||
const signatureSources = signatureOrder === 'CHILD_FIRST' ? [second,first] : producers
|
||||
for (const [index, producer] of signatureSources.entries()) {
|
||||
@@ -663,7 +720,7 @@ async function runRootFulfillment(context) {
|
||||
const stable = await state(scope,customerId,rootOrderId,childOrderId,first.inboundId)
|
||||
proof.finalProjections = await verifyRootProjections(scope,customerId,rootOrderId,login.accessToken,stable)
|
||||
const finalSignedAt = proof.signatures.map(row => row.signedAt).sort().at(-1)
|
||||
check(scope,'F40 根完成时间为实际有效来源签收时间最大值', stable.root.rawOrder?.completedAt === finalSignedAt,
|
||||
check(scope,'F40 根完成时间为实际有效来源签收时间最大值', rootCompletionTimeMatches(stable.root.rawOrder?.completedAt, proof.signatures.map(row => row.signedAt)),
|
||||
`completedAt=${stable.root.rawOrder?.completedAt}, maxSignedAt=${finalSignedAt}`)
|
||||
const reader = context.context?.f40ReadOnlyOutboxEvidence
|
||||
const outboxRequest = {customerId,rootOrderId,sourceOrderIds:[rootOrderId,childOrderId],
|
||||
|
||||
Reference in New Issue
Block a user