优化
This commit is contained in:
@@ -61,6 +61,165 @@ function receivingSnapshot(data) {
|
||||
})
|
||||
}
|
||||
|
||||
const RECEIVING_BALANCE_BUCKETS = ['availablePackages', 'holdPackages', 'allocatedPackages', 'pickedPackages', 'inTransitPackages', 'receivingPackages', 'procurementPackages']
|
||||
const RECEIVING_POSITION_BUCKETS = ['availablePackages', 'holdPackages', 'allocatedPackages', 'pickedPackages', 'receivingPackages']
|
||||
const RECEIVING_FIELDS = {
|
||||
balances: ['id', 'customerId', 'overseasWarehouseId', 'destinationWarehouseId', 'destinationWarehouseCode', 'inboundOrderId', 'inboundOrderNo', 'inboundCode', 'skuCode', ...RECEIVING_BALANCE_BUCKETS],
|
||||
positions: ['id', 'balanceId', 'customerId', 'overseasWarehouseId', 'destinationWarehouseCode', 'palletId', 'palletNo', 'batchNo', 'warehouseLocationId', 'warehouseLocationCode', 'status', ...RECEIVING_POSITION_BUCKETS],
|
||||
stockReceipts: ['id', 'customerId', 'planId', 'taskId', 'allocationId', 'lineId', 'orderId', 'inboundCode', 'skuCode', 'warehousePalletId', 'palletNo', 'warehouseLocationId', 'warehouseLocationCode', 'batchNo', 'receivedPackages', 'inventoryBalanceId', 'inventoryPositionId', 'inventoryOperationKey'],
|
||||
inventoryOperations: ['id', 'customerId', 'idempotencyKey', 'operationType', 'requestHash', 'status'],
|
||||
stockInLedger: ['id', 'customerId', 'balanceId', 'positionId', 'overseasWarehouseId', 'inboundOrderId', 'inboundCode', 'palletId', 'palletNo', 'batchNo', 'skuCode', 'businessType', 'businessOrderNo', 'sourceEventId', 'idempotencyKey', 'statusFrom', 'statusTo', 'beforePackages', 'deltaPackages', 'afterPackages'],
|
||||
receivingOperations: ['id', 'sessionId', 'action', 'idempotencyKey', 'requestHash'],
|
||||
httpGroups: ['id', 'customerId', 'overseasWarehouseId', 'destinationWarehouseId', 'destinationWarehouseCode', 'skuCode', ...RECEIVING_BALANCE_BUCKETS],
|
||||
httpLedger: ['id', 'customerId', 'overseasWarehouseId', 'inboundOrderId', 'inboundOrderNo', 'inboundCode', 'palletNo', 'batchNo', 'skuCode', 'businessType', 'businessOrderNo', 'statusFrom', 'statusTo', 'beforePackages', 'deltaPackages', 'afterPackages'],
|
||||
}
|
||||
RECEIVING_FIELDS.palletComposition = RECEIVING_FIELDS.positions
|
||||
const receivingNumberFields = new Set([...RECEIVING_BALANCE_BUCKETS, 'receivedPackages', 'beforePackages', 'deltaPackages', 'afterPackages'])
|
||||
const receivingArrays = Object.keys(RECEIVING_FIELDS)
|
||||
const sameEvidence = (left, right) => JSON.stringify(left) === JSON.stringify(right)
|
||||
const receivingTotal = (rows, field) => rows.reduce((total, row) => total + row[field], 0)
|
||||
const balanceGroupKey = row => JSON.stringify([row.customerId, row.overseasWarehouseId, row.destinationWarehouseId, row.destinationWarehouseCode, row.skuCode])
|
||||
|
||||
// Only safe business identities and bucket facts enter reports. Never save reader connection,
|
||||
// tokens, operator identifiers, request/result JSON, binary files or arbitrary provider fields.
|
||||
export function normalizeReceivingEvidence(value) {
|
||||
if (!value || receivingArrays.some(name => !Array.isArray(value[name]))) return null
|
||||
return Object.fromEntries(receivingArrays.map(name => [name, value[name].map(row =>
|
||||
Object.fromEntries(RECEIVING_FIELDS[name].map(field => [field, receivingNumberFields.has(field)
|
||||
? (row[field] !== null && row[field] !== undefined && row[field] !== '' && Number.isSafeInteger(Number(row[field])) ? Number(row[field]) : null)
|
||||
: row[field] == null ? null : id(row[field])]))).sort((a, b) => id(a.id).localeCompare(id(b.id)))]))
|
||||
}
|
||||
|
||||
export function receivingEvidenceValid(snapshot, scope) {
|
||||
if (!snapshot || receivingArrays.some(name => !Array.isArray(snapshot[name]) || snapshot[name].some(row => !longId(row.id))
|
||||
|| new Set(snapshot[name].map(row => row.id)).size !== snapshot[name].length)) return false
|
||||
const bucketsValid = (row, fields) => fields.every(field => Number.isSafeInteger(row[field]) && row[field] >= 0)
|
||||
const owned = row => row.customerId === scope.customerId && row.overseasWarehouseId === scope.warehouseId
|
||||
if (!snapshot.balances.every(row => owned(row) && bucketsValid(row, RECEIVING_BALANCE_BUCKETS))
|
||||
|| !snapshot.httpGroups.every(row => owned(row) && bucketsValid(row, RECEIVING_BALANCE_BUCKETS))
|
||||
|| !snapshot.httpLedger.every(owned)
|
||||
|| !snapshot.positions.every(row => owned(row) && bucketsValid(row, RECEIVING_POSITION_BUCKETS)
|
||||
&& snapshot.balances.some(balance => balance.id === row.balanceId) && longId(row.palletId) && nonBlank(row.palletNo)
|
||||
&& longId(row.warehouseLocationId) && nonBlank(row.warehouseLocationCode))
|
||||
|| !sameEvidence(snapshot.positions, snapshot.palletComposition)) return false
|
||||
if (!snapshot.balances.every(balance => RECEIVING_POSITION_BUCKETS.every(field =>
|
||||
receivingTotal(snapshot.positions.filter(row => row.balanceId === balance.id), field) === balance[field]))) return false
|
||||
const groups = new Map()
|
||||
for (const balance of snapshot.balances) {
|
||||
const key = balanceGroupKey(balance)
|
||||
const current = groups.get(key) || Object.fromEntries(RECEIVING_BALANCE_BUCKETS.map(field => [field, 0]))
|
||||
for (const field of RECEIVING_BALANCE_BUCKETS) current[field] += balance[field]
|
||||
groups.set(key, current)
|
||||
}
|
||||
for (const [key, totals] of groups) if (!RECEIVING_BALANCE_BUCKETS.some(field => totals[field] > 0)) groups.delete(key)
|
||||
if (groups.size !== snapshot.httpGroups.length || new Set(snapshot.httpGroups.map(balanceGroupKey)).size !== snapshot.httpGroups.length
|
||||
|| !snapshot.httpGroups.every(row => groups.has(balanceGroupKey(row)) && RECEIVING_BALANCE_BUCKETS.every(field => row[field] === groups.get(balanceGroupKey(row))[field]))) return false
|
||||
if (!snapshot.stockReceipts.every(row => row.customerId === scope.customerId && scope.inboundCodes.includes(row.inboundCode)
|
||||
&& longId(row.planId) && longId(row.taskId) && longId(row.allocationId) && longId(row.lineId) && longId(row.orderId)
|
||||
&& nonBlank(row.skuCode) && Number.isSafeInteger(row.receivedPackages) && row.receivedPackages > 0)) return false
|
||||
if (snapshot.inventoryOperations.length !== snapshot.stockReceipts.length || snapshot.stockInLedger.length !== snapshot.stockReceipts.length) return false
|
||||
for (const receipt of snapshot.stockReceipts) {
|
||||
const baseKey = `PDA_STOCK_IN:${receipt.taskId}:${receipt.allocationId}:${receipt.skuCode}`
|
||||
const operations = snapshot.inventoryOperations.filter(row => row.idempotencyKey === baseKey)
|
||||
if (receipt.inventoryOperationKey !== baseKey || operations.length !== 1) return false
|
||||
const operation = operations[0]
|
||||
const ledgers = snapshot.stockInLedger.filter(row => row.idempotencyKey === `${baseKey}:${operation.id}`)
|
||||
if (operation.customerId !== scope.customerId || operation.operationType !== 'STOCK_IN' || operation.status !== 'COMPLETED'
|
||||
|| !/^[a-f0-9]{64}$/.test(operation.requestHash || '') || ledgers.length !== 1) return false
|
||||
const ledger = ledgers[0]
|
||||
const balance = snapshot.balances.find(row => row.id === receipt.inventoryBalanceId)
|
||||
const position = snapshot.positions.find(row => row.id === receipt.inventoryPositionId)
|
||||
if (!balance || !position || position.balanceId !== balance.id || !owned(ledger) || ledger.businessType !== 'STOCK_IN'
|
||||
|| ledger.balanceId !== balance.id || ledger.positionId !== position.id || ledger.inboundOrderId !== receipt.orderId
|
||||
|| ledger.inboundCode !== receipt.inboundCode || ledger.skuCode !== receipt.skuCode || !nonBlank(ledger.sourceEventId)
|
||||
|| ledger.palletId !== receipt.warehousePalletId || ledger.palletNo !== receipt.palletNo || ledger.batchNo !== receipt.batchNo
|
||||
|| position.palletId !== receipt.warehousePalletId || position.palletNo !== receipt.palletNo
|
||||
|| position.warehouseLocationId !== receipt.warehouseLocationId || position.warehouseLocationCode !== receipt.warehouseLocationCode
|
||||
|| ledger.deltaPackages !== receipt.receivedPackages || ledger.afterPackages - ledger.beforePackages !== receipt.receivedPackages
|
||||
|| ledger.statusTo !== 'AVAILABLE') return false
|
||||
}
|
||||
return snapshot.receivingOperations.every(row => row.action === 'COMPLETE' && longId(row.sessionId)
|
||||
&& nonBlank(row.idempotencyKey) && /^[a-f0-9]{64}$/.test(row.requestHash || ''))
|
||||
}
|
||||
|
||||
export function receivingCompletionProof(scope, stages) {
|
||||
const names = ['BEFORE_COMPLETE', 'AFTER_COMPLETE', 'AFTER_SAME_KEY_REPLAY', 'AFTER_FRESH_KEY_REJECTION']
|
||||
const snapshotsValid = names.every(name => receivingEvidenceValid(stages[name], scope))
|
||||
const checks = { completeScope: scope.inboundCodes.length > 0 && new Set(scope.inboundCodes).size === scope.inboundCodes.length
|
||||
&& scope.inboundCodes.every(code => Number.isSafeInteger(scope.expectedPackagesByInbound[code]) && scope.expectedPackagesByInbound[code] > 0)
|
||||
&& scope.sourceInboundIds.length === scope.inboundCodes.length && new Set(scope.sourceInboundIds).size === scope.sourceInboundIds.length,
|
||||
fourCompleteSnapshots: snapshotsValid }
|
||||
if (!snapshotsValid) return { passed: false, checks }
|
||||
const before = stages.BEFORE_COMPLETE, after = stages.AFTER_COMPLETE
|
||||
const expected = Object.values(scope.expectedPackagesByInbound).reduce((sum, count) => sum + count, 0)
|
||||
checks.freshSourceHasNoPriorStock = ['stockReceipts', 'inventoryOperations', 'stockInLedger', 'receivingOperations'].every(name => before[name].length === 0)
|
||||
checks.exactSourceReceiptAndLedgerQuantities = scope.inboundCodes.every(code => receivingTotal(after.stockReceipts.filter(row => row.inboundCode === code), 'receivedPackages') === scope.expectedPackagesByInbound[code]
|
||||
&& receivingTotal(after.stockInLedger.filter(row => row.inboundCode === code), 'deltaPackages') === scope.expectedPackagesByInbound[code])
|
||||
checks.completeCommandExactlyOnce = after.receivingOperations.length === 1 && after.receivingOperations[0].idempotencyKey === scope.completeKey
|
||||
checks.firstCompleteAllBalanceBuckets = RECEIVING_BALANCE_BUCKETS.every(field => receivingTotal(after.balances, field) - receivingTotal(before.balances, field)
|
||||
=== (field === 'availablePackages' ? expected : field === 'inTransitPackages' ? -scope.expectedTransitPackages : 0))
|
||||
checks.firstCompleteAllPositionBuckets = RECEIVING_POSITION_BUCKETS.every(field => receivingTotal(after.positions, field) - receivingTotal(before.positions, field)
|
||||
=== (field === 'availablePackages' ? expected : 0))
|
||||
const touchedBalances = new Set(after.stockReceipts.map(row => row.inventoryBalanceId))
|
||||
const touchedPositions = new Set(after.stockReceipts.map(row => row.inventoryPositionId))
|
||||
checks.untouchedBalancesAndPositionsUnchanged = sameEvidence(before.balances.filter(row => !touchedBalances.has(row.id)), after.balances.filter(row => !touchedBalances.has(row.id)))
|
||||
&& sameEvidence(before.positions.filter(row => !touchedPositions.has(row.id)), after.positions.filter(row => !touchedPositions.has(row.id)))
|
||||
const oldLedgerIds = new Set(before.httpLedger.map(row => row.id))
|
||||
const delta = after.httpLedger.filter(row => !oldLedgerIds.has(row.id))
|
||||
checks.publicImmutableLedgerPreservedAndNewStockExact = sameEvidence(before.httpLedger, after.httpLedger.filter(row => oldLedgerIds.has(row.id)))
|
||||
&& delta.filter(row => row.businessType === 'STOCK_IN').length === after.stockInLedger.length
|
||||
&& delta.filter(row => row.businessType === 'STOCK_IN').every(row => after.stockInLedger.some(source => source.id === row.id && source.deltaPackages === row.deltaPackages))
|
||||
&& delta.every(row => scope.inboundCodes.includes(row.inboundCode) && ['STOCK_IN', 'STOCK_IN_TRANSIT_RELEASE'].includes(row.businessType))
|
||||
checks.sameKeyReplayAllRawBucketsPositionsPalletsAndImmutableKeysUnchanged = sameEvidence(after, stages.AFTER_SAME_KEY_REPLAY)
|
||||
checks.freshKeyRejectedAllRawBucketsPositionsPalletsAndImmutableKeysUnchanged = sameEvidence(after, stages.AFTER_FRESH_KEY_REJECTION)
|
||||
return { passed: Object.values(checks).every(Boolean), checks, expectedPackages: expected, expectedTransitPackages: scope.expectedTransitPackages }
|
||||
}
|
||||
|
||||
async function receivingEvidencePages(context, scope, resource, name) {
|
||||
const collected = []
|
||||
for (let page = 1; page <= 100; page++) {
|
||||
const response = await context.request('GET', `/api/platform/v1/last-leg/inventory/${resource}?customerId=${encodeURIComponent(scope.customerId)}&overseasWarehouseId=${encodeURIComponent(scope.warehouseId)}&page=${page}&size=200`)
|
||||
const data = response.body?.data
|
||||
const valid = success(response) && Array.isArray(data?.records) && Number.isSafeInteger(Number(data?.total)) && Number(data.total) >= 0
|
||||
context.addStep(MODULE, `${name}完整客户仓${resource}第${page}页`, response, valid, trace(response), OPTIONS)
|
||||
if (!valid) return null
|
||||
collected.push(...data.records)
|
||||
if (collected.length >= Number(data.total)) {
|
||||
const complete = collected.length === Number(data.total) && new Set(collected.map(row => id(row.id))).size === collected.length
|
||||
context.assert(MODULE, `${name}${resource}分页无截断重复`, response, complete, `rows=${collected.length}, total=${data.total}`, OPTIONS)
|
||||
return complete ? collected : null
|
||||
}
|
||||
if (!data.records.length) break
|
||||
}
|
||||
blockerGap(context, `${name}库存分页`, '完整客户仓分页未读完,不能冻结截断库存', 'receiving-replay-evidence')
|
||||
return null
|
||||
}
|
||||
|
||||
export async function captureReceivingEvidence(context, scope, stage, target) {
|
||||
const slot = target.stages[stage] = { captured: false, evidence: null }
|
||||
const reader = context.context?.phase4ReceivingEvidenceReader
|
||||
if (typeof reader !== 'function') {
|
||||
blockerGap(context, `${stage}原始库存与不可变完整键取证`, '未注入phase4ReceivingEvidenceReader;公共ledger不返回原始balance/position/幂等键且无immutable receipt接口,不能以终态补历史快照', 'receiving-replay-evidence')
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const httpGroups = await receivingEvidencePages(context, scope, 'balances', stage)
|
||||
const httpLedger = await receivingEvidencePages(context, scope, 'ledger', stage)
|
||||
if (!httpGroups || !httpLedger) return null
|
||||
const raw = await reader({ ...scope, stage })
|
||||
const evidence = normalizeReceivingEvidence({ ...raw, httpGroups, httpLedger })
|
||||
slot.evidence = evidence
|
||||
slot.captured = receivingEvidenceValid(evidence, scope)
|
||||
context.assert(MODULE, `${stage}完整raw库存位置托盘组成及收货完整键核平`, null, slot.captured,
|
||||
`bookingId=${scope.bookingId}, balances=${evidence?.balances.length ?? 'missing'}, positions=${evidence?.positions.length ?? 'missing'}, receipts=${evidence?.stockReceipts.length ?? 'missing'}`, OPTIONS)
|
||||
return evidence
|
||||
} catch {
|
||||
slot.error = 'Read-only receiving evidence reader failed; sensitive connection/provider details omitted'
|
||||
blockerGap(context, `${stage}只读取证`, slot.error, 'receiving-replay-evidence')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function expandOverseasCartonCandidates(inbounds, producerCartonMarks = []) {
|
||||
const marks = (Array.isArray(producerCartonMarks) ? producerCartonMarks : [producerCartonMarks])
|
||||
.map(id).filter(nonBlank)
|
||||
@@ -533,7 +692,7 @@ export async function resolveCartonFixtures(context, token, inbounds, producerCa
|
||||
return cartonFixtures
|
||||
}
|
||||
|
||||
async function probeCartonResolveNegatives(context, token, current) {
|
||||
export async function probeCartonResolveNegatives(context, token, current, runCrossContainerProbe = true) {
|
||||
const fba = current.inbounds.find((item) => item.destinationType === 'FBA')
|
||||
await expected(context, token, 'POST', '无#箱码被服务端拒绝',
|
||||
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
|
||||
@@ -545,6 +704,7 @@ async function probeCartonResolveNegatives(context, token, current) {
|
||||
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
|
||||
{ cartonMark: `UNMATCHED-${markerSuffix(context)}#ANY-SUFFIX` }, cartonResolveRejected)
|
||||
|
||||
if (runCrossContainerProbe) {
|
||||
const allProducers = context.createdResources?.phase4InboundProducerFixtures?.containers || []
|
||||
const crossContainerProbe = crossContainerCartonProbe(allProducers,current)
|
||||
if (crossContainerProbe) {
|
||||
@@ -556,6 +716,7 @@ async function probeCartonResolveNegatives(context, token, current) {
|
||||
} else {
|
||||
blockerGap(context, '箱码跨柜负向', '缺少另一真实柜的冻结箱码,不能伪造跨柜归属反证')
|
||||
}
|
||||
}
|
||||
|
||||
const overseas = current.inbounds.find((item) => item.destinationType === 'OVERSEAS')
|
||||
if (overseas) {
|
||||
@@ -747,7 +908,9 @@ export async function runReceiving(context, token, secondaryToken, merchantToken
|
||||
const current = await pda(context, token, 'GET', '签收后按账号恢复当前柜', '/api/pda/v1/last-leg/receiving-containers/current')
|
||||
if (!success(current.response)) return
|
||||
const beforeResolve = receivingSnapshot(current.data)
|
||||
await probeCartonResolveNegatives(context, token, current.data)
|
||||
// Single-container completion/cancellation continuation does not provision a second
|
||||
// loaded source. The default full receiving chain still requires its cross-container proof.
|
||||
await probeCartonResolveNegatives(context, token, current.data, runAuthorizationProbes)
|
||||
const producerCartonMarks = preferredProducer?.cartonMarks?.length
|
||||
? preferredProducer.cartonMarks : [preferredProducer?.cartonMark]
|
||||
const cartonFixtures = await resolveCartonFixtures(context, token, current.data.inbounds, producerCartonMarks, preferredProducer)
|
||||
@@ -841,11 +1004,35 @@ export async function runReceiving(context, token, secondaryToken, merchantToken
|
||||
return
|
||||
}
|
||||
const completeBody = { idempotencyKey: key(context, 'complete') }
|
||||
const sourceInboundIds = [...new Set([id(preferredProducer?.sourceInboundId),
|
||||
...(preferredProducer?.palletLabelManifest?.groups || []).flatMap(group => (group.labels || []).map(label => id(label.inboundId)))].filter(longId))].sort()
|
||||
const scope = { customerId: id(preferredProducer?.tenantId), warehouseId: id(refreshed.data?.overseasWarehouseId),
|
||||
bookingId: sourceContainerBookingId, containerNo,
|
||||
inboundCodes: refreshed.data.inbounds.map(inbound => inbound.inboundCode).sort(), sourceInboundIds,
|
||||
expectedPackagesByInbound: Object.fromEntries(refreshed.data.inbounds.map(inbound => [inbound.inboundCode, Number(inbound.totalPackages)])),
|
||||
expectedTransitPackages: refreshed.data.inbounds.filter(inbound => inbound.orderType === 'FIRST_LEG').reduce((sum, inbound) => sum + Number(inbound.totalPackages), 0),
|
||||
completeKey: completeBody.idempotencyKey, freshKey: key(context, 'complete-fresh-repeat') }
|
||||
const receivingProof = { scope, stages: {}, proof: null, status: 'UNPROVEN' }
|
||||
context.createdResources.phase4OverseasPda.receivingCompletionEvidence ||= {}
|
||||
context.createdResources.phase4OverseasPda.receivingCompletionEvidence[sourceContainerBookingId] = receivingProof
|
||||
await captureReceivingEvidence(context, scope, 'BEFORE_COMPLETE', receivingProof)
|
||||
const completed = await pda(context, token, 'POST', '完成当前柜并写入库存与卸柜事件', '/api/pda/v1/last-leg/receiving-containers/current/complete', completeBody)
|
||||
await captureReceivingEvidence(context, scope, 'AFTER_COMPLETE', receivingProof)
|
||||
if (!success(completed.response)) return
|
||||
const completeReplay = await pda(context, token, 'POST', '完成当前柜同键同载荷重放', '/api/pda/v1/last-leg/receiving-containers/current/complete', completeBody)
|
||||
context.assert(MODULE, '完成幂等重放仍返回COMPLETED', completeReplay.response,
|
||||
success(completeReplay.response) && completeReplay.data?.status === 'COMPLETED', trace(completeReplay.response), OPTIONS)
|
||||
success(completeReplay.response) && completeReplay.data?.status === 'COMPLETED' && sameEvidence(completeReplay.data, completed.data), trace(completeReplay.response), OPTIONS)
|
||||
await captureReceivingEvidence(context, scope, 'AFTER_SAME_KEY_REPLAY', receivingProof)
|
||||
const freshComplete = await expected(context, token, 'POST', '已完成柜新键重复完成被拒绝且不能再次写库存',
|
||||
'/api/pda/v1/last-leg/receiving-containers/current/complete', { idempotencyKey: scope.freshKey },
|
||||
response => a0404Message(response, /^Current receiving container session was not found$/))
|
||||
await captureReceivingEvidence(context, scope, 'AFTER_FRESH_KEY_REJECTION', receivingProof)
|
||||
receivingProof.proof = receivingCompletionProof(scope, Object.fromEntries(Object.entries(receivingProof.stages).map(([stage, row]) => [stage, row.evidence])))
|
||||
receivingProof.commandResults = { firstCompleteSucceeded: success(completed.response), sameKeyReturnedExactOriginal: success(completeReplay.response) && sameEvidence(completeReplay.data, completed.data),
|
||||
freshKeyRejected: a0404Message(freshComplete, /^Current receiving container session was not found$/) }
|
||||
receivingProof.status = receivingProof.proof.passed && Object.values(receivingProof.commandResults).every(Boolean) ? 'VERIFIED' : 'UNPROVEN'
|
||||
for (const [check, passed] of Object.entries(receivingProof.proof.checks)) context.assert(MODULE, `收货完成库存守恒 ${check}`, null, passed,
|
||||
`bookingId=${sourceContainerBookingId}, proofStatus=${receivingProof.status}`, OPTIONS)
|
||||
const after = await context.request('GET', '/api/pda/v1/last-leg/receiving-containers/current', undefined, token, {})
|
||||
context.addStep(MODULE, '完成后current返回404正常空态', after, notFound(after), trace(after), OPTIONS)
|
||||
context.createdResources.phase4OverseasPda.finalStatus = completed.data?.status || null
|
||||
|
||||
Reference in New Issue
Block a user