This commit is contained in:
2026-09-02 14:39:28 +08:00
parent b6800a48c6
commit 9ea4ccb9b1
8 changed files with 34 additions and 10 deletions
@@ -17,8 +17,8 @@ async function post(context, path, body, operation) {
return context.request('POST', path, body, context.token, { 'Idempotency-Key': key(context, operation) })
}
async function uploadBillOfLading(context, appointmentId, index) {
const path = `${TRUCK_BASE}/${appointmentId}/attachments/upload?attachmentType=BILL_OF_LADING`
async function uploadDispatchOrder(context, appointmentId, index) {
const path = `${TRUCK_BASE}/${appointmentId}/attachments/upload?attachmentType=DISPATCH_ORDER`
const form = new FormData()
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
form.append('file', new Blob([png], { type: 'image/png' }), `${context.marker}-truck-${index}.png`)
@@ -127,7 +127,7 @@ async function createTruck(context, oceanBooking, index) {
const response = await context.request('POST', TRUCK_BASE, truckPayload(context, oceanBooking, carrier, index))
const value = context.apiSuccess(MODULE, `创建拖车状态样本 ${index}`, response, '', OPTIONS)
if (!value?.id) throw new Error(`创建拖车样本 ${index} 失败`)
const billUpload = await uploadBillOfLading(context, id(value.id), index)
const billUpload = await uploadDispatchOrder(context, id(value.id), index)
const billAttachment = context.apiSuccess(MODULE, `拖车状态样本 ${index} 上传提单附件`, billUpload, '', OPTIONS)
if (!billAttachment?.id) throw new Error(`拖车样本 ${index} 未上传有效提单附件`)
return value
@@ -133,9 +133,18 @@ function routes(context, merchantToken) {
}
export async function prepare(context) {
const outerMarker = context.marker
const restore = await context.preparePhase3LoadingFixture('-CUSTOMS-MIXED', false, {
primary: 'BUY_DECLARATION', incremental: 'TAX_REFUND',
})
const executionMarker = `${outerMarker}-CUSTOMS-MIXED`
context.marker = executionMarker
if (context.createdResources?.orchestrationFixtures?.phase3Loading) {
context.createdResources.orchestrationFixtures.phase3Loading.marker = executionMarker
}
if (context.createdResources?.orchestrationFixtures?.phase3LoadingIncremental) {
context.createdResources.orchestrationFixtures.phase3LoadingIncremental.marker = executionMarker
}
const loading = await import('./phase3-loading.mjs')
await context.executeModule(`${MODULE}-loading`, () => loading.run(context))
const finalize = await import('./phase3-loading-finalize.mjs')
@@ -143,6 +152,7 @@ export async function prepare(context) {
const source = context.createdResources?.phase3Loading
if (!source?.loadingPlanId || !source?.containerBookingId || source?.finalStatus !== 'LOADED') {
context.marker = outerMarker
restore()
throw new Error(`混合柜未进入LOADED: planId=${id(source?.loadingPlanId) || 'missing'}, status=${source?.finalStatus || 'missing'}`)
}
@@ -157,6 +167,7 @@ export async function prepare(context) {
buy.length > 0 && refund.length > 0 && inbounds.every((item) => /^\d{16,}$/.test(id(item.inboundId))),
`buy=${buy.length}, tax=${refund.length}, inbounds=${inbounds.length}`, OPTIONS)
if (!buy.length || !refund.length) {
context.marker = outerMarker
restore()
throw new Error(`正式装柜API未形成混合柜: buy=${buy.length}, tax=${refund.length}`)
}
@@ -178,10 +189,11 @@ export async function prepare(context) {
const buyInboundIds = new Set(buy.map((item) => id(item.inboundId)))
const planBuyRows = planItems.filter((item) => buyInboundIds.has(id(item.inboundId))).length
const expectedBuyRows = planBuyRows > 0 ? planBuyRows : buy.length
context.marker = outerMarker
context.createdResources.phase3ContainerCustomsFixtures = {
marker: context.marker, requestedKinds: ['MIXED'],
marker: outerMarker, requestedKinds: ['MIXED'],
containers: [{
kind: 'MIXED', marker: context.marker, status: 'LOADED', autoDispatchMode: 'IMMEDIATE',
kind: 'MIXED', marker: outerMarker, status: 'LOADED', autoDispatchMode: 'IMMEDIATE',
loadingPlanId: id(source.loadingPlanId), containerBookingId: id(source.containerBookingId),
oceanBookingId: id(source.oceanBookingId), containerNo: source.containerNo,
inbounds: inbounds.map((item) => ({
@@ -194,6 +206,7 @@ export async function prepare(context) {
context.context.phase3ContainerCustomsRoutes = routes(context, merchantToken)
return () => {
delete context.context.phase3ContainerCustomsRoutes
context.marker = outerMarker
restore()
}
}
@@ -18,6 +18,7 @@ function nullableText(value) { return value == null || typeof value === 'string'
function nullableIso(value) { return value == null || (typeof value === 'string' && !Number.isNaN(Date.parse(value))) }
function validLongId(value) { return typeof value === 'string' && /^\d{16,}$/.test(value) }
function validNumericId(value) { return typeof value === 'string' && /^\d+$/.test(value) && value !== '0' }
function validOperatorId(value) { return typeof value === 'string' && /^\d+$/.test(value) }
function validContainerNo(value) {
if (typeof value !== 'string' || !ISO_CONTAINER_PATTERN.test(value)) return false
const letterValues = [10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24,
@@ -88,7 +89,7 @@ function validHistory(item) {
return validLongId(item?.id) && Boolean(item?.action)
&& (item?.fromVersion == null || number(item.fromVersion))
&& (item?.toVersion == null || number(item.toVersion))
&& nullableText(item?.reason) && (item?.operatorId == null || validNumericId(item.operatorId))
&& nullableText(item?.reason) && (item?.operatorId == null || validOperatorId(item.operatorId))
&& nullableText(item?.operatorName) && nullableIso(item?.createdAt)
}
@@ -244,7 +244,7 @@ async function awaitProducerConsumption(context, producers) {
if (!expected.length) return true
const consumed = new Set()
const lastResponses = new Map()
for (let attempt = 1; attempt <= 30; attempt += 1) {
for (let attempt = 1; attempt <= 90; attempt += 1) {
const missing = expected.filter((containerNo) => !consumed.has(containerNo))
const results = await Promise.all(missing.map(async (containerNo) => {
const response = await context.requestOverseas('GET',