fix(firstleg): atomically finalize PDA completion

This commit is contained in:
2026-09-01 11:11:43 +08:00
parent de0eddeee8
commit b5bc77b153
10 changed files with 181 additions and 46 deletions
@@ -377,7 +377,10 @@ test('container loading enforces channel isolation and stable PDA identity acros
assert.match(value, /bookingAgentId: id\(bookingAgent\.id\), bookingAgentName: bookingAgent\.name/)
assert.match(value, /const frozenTransport = Object\.fromEntries\(transportFields\.map/)
assert.match(value, /if \(!frozenTransportMatches\) return/)
assert.match(value, /expectedVersion: Number\(bookingCreatedDraft\?\.oceanBooking\?\.version\), reason: context\.marker/)
assert.match(value, /const releaseVersion = Number\(bookingDocument\?\.bookingVersion\)/)
assert.match(value, /expectedVersion: releaseVersion, reason: context\.marker/)
assertOrder(value, ['上传有效订舱确认文件', '订舱确认文件已关联且版本递增',
'订舱放舱为 RELEASED 且不预生成真实货柜'])
assert.doesNotMatch(value, /const sailingAt = new Date\(Date\.now\(\)/)
const batch = value.slice(value.indexOf('const staleBatchResponse'), value.indexOf('const completePath'))
assert.doesNotMatch(batch, /containerNo:/)
@@ -412,16 +415,18 @@ test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING au
const convergence = source('./phase3-pda-convergence.mjs')
assertOrder(container, [
'最后一箱拣货后 PDA 任务等待人工确认装柜完成',
'上传柜次 ${evidenceType} 真实证据文件',
'绑定柜次 ${evidenceType} 装柜证据',
'上传并绑定柜次 ${evidenceType} 真实证据文件',
'${evidenceType} 有效证据归属稳定且锁码门禁完整',
'PDA 完成前回查五类有效装柜证据与锁码',
'PDA 人工确认装柜完成',
'PDA 提交人工确认装柜完成',
'等待 PDA 装柜完成异步收敛',
])
assert.match(container, /const evidenceTypes = \['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL'\]/)
assert.match(container, /businessType === `LOADING_\$\{evidenceType\}`/)
assert.match(container, /form\.append\('evidenceType', evidenceType\)/)
assert.match(container, /new Blob\(\[png\], \{ type: 'image\/png' \}\)/)
assert.match(container, /item\.evidenceType === 'SEAL' && item\.sealNo === sealNo/)
assert.match(container, /if \(!evidenceTypes\.every[\s\S]*\) return/)
assert.match(container, /requestBinary\('GET',[\s\S]*files\/loading-evidence/)
assert.match(container, /requestBinary\('GET',[\s\S]*\/evidence\/\$\{encodeURIComponent\(evidenceId\)\}\/content/)
assert.match(container, /回查完成后的 PDA 操作审计/)
assert.match(container, /item\.actionType === 'BATCH'/)
assert.match(container, /item\.actionType === 'COMPLETE_LOADING'/)
@@ -502,7 +507,7 @@ test('phase3 loading resolves legal SKIP and PARTIAL exceptions before downstrea
assert.match(loading, /Number\(exceptionOverview\?\.pda\?\.unresolvedItemCount\) === 0/)
assert.doesNotMatch(loading, /addCoverageGap\([^)]*(SKIP|PARTIAL|未解决异常)/)
assert.match(entry, /this\.context\.phase3LoadingIncrementalDetailCount = 4/)
assert.match(measurement, /增量 fixture 形成四条真实独立货物测量明细/)
assert.match(measurement, /增量 fixture 形成动态真实独立货物测量明细/)
assert.match(measurement, /detailBoxCode = `\$\{inboundCode\}#\$\{originalPackageCount\}-\$\{cartonIndex\}`/)
assert.match(measurement, /items: draftItems/)
assert.match(measurement, /expectedPackages === originalPackageCount/)
@@ -560,9 +565,11 @@ test('downstream container consumers prefer the phase3Loading fixture', () => {
assert.match(ocean, /finalStatus: 'RELEASED', containerBookingId: null, loadingPlanId: null/)
assert.match(source('./phase3-loading.mjs'), /oceanBookingId: bookingId, loadingPlanId, containerBookingId/)
assert.match(serviceCenter, /if \(!allocationMatches\) return/)
assert.match(serviceCenter, /const customerName = String\(oceanDetail\?\.oceanBooking\?\.customerName \|\| ''\)\.trim\(\)/)
assert.match(serviceCenter, /category=TRUCKING&status=ACTIVE/)
assert.match(serviceCenter, /customerId, customerName: null, carrierId: id\(carrier\.id\), carrierName: carrier\.name/)
assert.match(serviceCenter, /oceanBookingId: ocean\.id/)
assert.match(serviceCenter, /containerBookingId: linkContainer \? id\(containerFlow\.containerBookingId\) : null/)
assert.match(serviceCenter, /carrierId: id\(carrier\.id\), carrierName: carrier\.name/)
assert.doesNotMatch(serviceCenter, /customerId:|customerName:/)
assert.doesNotMatch(serviceCenter, /链路承运商-/)
assert.doesNotMatch(serviceCenter, /customerName: `服务中心链路客户-/)
const schedules = source('./phase3-port-schedules.mjs')
@@ -596,7 +603,8 @@ test('container overview always follows phase3 loading in default and custom mod
const entry = source('../admin-test-env-acceptance.mjs')
assert.match(entry, /const DEFAULT_MODULES = normalizeModuleOrder\(CONFIGURED_MODULES\)/)
assert.match(entry, /modules: normalizeModuleOrder\(/)
assert.match(entry, /const modules = normalizeModuleOrder\(/)
assert.match(entry, /return \{[\s\S]{0,500}modules,/)
})
test('overview does not fall back to a container fixture when loading evidence was requested', () => {