diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs index 5f9f8fb1f..44a0d7fed 100644 --- a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs +++ b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs @@ -48,6 +48,35 @@ async function uploadDocument(context, path, expectedVersion, operationReason, l } } +async function ensureCompletionEvidence(context, chain, label) { + const tenantId = chain.source.tenantId + const inboundId = chain.source.inboundId + 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]) + for (const evidenceType of ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']) { + const uploadPath = `/api/platform/v1/files/loading-evidence/${tenantId}/${inboundId}?evidenceType=${encodeURIComponent(evidenceType)}` + const form = new FormData() + form.append('file', new Blob([png], { type: 'image/png' }), `${label}-${evidenceType}.png`) + const rawUpload = await fetch(`${context.options.gateway}${uploadPath}`, { + method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` }, + body: form, signal: AbortSignal.timeout(60_000), + }) + const uploadBody = await rawUpload.json() + const upload = { method: 'POST', path: uploadPath, http: rawUpload.status, body: uploadBody, + requestId: uploadBody?.requestId || rawUpload.headers.get('x-request-id'), + traceId: uploadBody?.traceId || rawUpload.headers.get('x-trace-id') } + const file = context.apiSuccess(MODULE, `${label} 上传 ${evidenceType} 装柜证据`, upload, '', OPTIONS) + const fileId = id(file?.id || file?.fileId) + if (!fileId) throw new Error(`${label} ${evidenceType} 未返回文件ID`) + const bind = await post(context, + `/api/platform/v1/inbounds/${inboundId}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`, { + expectedVersion: 0, evidenceType, fileId, + sealNo: evidenceType === 'SEAL' ? `SEAL-${suffix(context, label)}`.slice(0, 64) : null, + remark: context.marker, + }, `${label}-evidence-${evidenceType}`) + context.apiSuccess(MODULE, `${label} 绑定 ${evidenceType} 装柜证据`, bind, '', OPTIONS) + } +} + async function prepareInbound(context, label) { const originalMarker = context.marker const fixtureMarker = `${originalMarker}-MAN-${label}` @@ -267,6 +296,7 @@ async function pickAll(context, chain, label) { } async function complete(context, chain, label) { + await ensureCompletionEvidence(context, chain, label) const response = await context.request('POST', `${PDA}/tasks/${chain.task.id}/complete`, { expectedTaskVersion: Number(chain.task.version), operationId: operationId(context, `${label}-complete`), remark: `${context.marker} 人工完成状态样本`, diff --git a/scripts/admin-test-env-modules/phase3-order-splitting.mjs b/scripts/admin-test-env-modules/phase3-order-splitting.mjs index 21d0994a7..17ddc2da3 100644 --- a/scripts/admin-test-env-modules/phase3-order-splitting.mjs +++ b/scripts/admin-test-env-modules/phase3-order-splitting.mjs @@ -68,6 +68,35 @@ async function request(context, method, path, body, headers = {}, token = contex } } +async function ensureCompletionEvidence(context, tenantId, inboundId, keyBase) { + 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]) + for (const evidenceType of ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']) { + const uploadPath = `/api/platform/v1/files/loading-evidence/${tenantId}/${inboundId}?evidenceType=${encodeURIComponent(evidenceType)}` + const form = new FormData() + form.append('file', new Blob([png], { type: 'image/png' }), `${keyBase}-${evidenceType}.png`) + const rawUpload = await fetch(`${context.options.gateway}${uploadPath}`, { + method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` }, + body: form, signal: AbortSignal.timeout(60_000), + }) + const uploadBody = await rawUpload.json() + const upload = { method: 'POST', path: uploadPath, http: rawUpload.status, body: uploadBody, + requestId: uploadBody?.requestId || rawUpload.headers.get('x-request-id'), + traceId: uploadBody?.traceId || rawUpload.headers.get('x-trace-id') } + const file = context.apiSuccess(MODULE, `${keyBase} 上传 ${evidenceType} 装柜证据`, upload, '', OPTIONS) + const fileId = id(file?.id || file?.fileId) + if (!fileId) return false + const bind = await request(context, 'POST', + `/api/platform/v1/inbounds/${inboundId}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`, { + expectedVersion: 0, evidenceType, fileId, + sealNo: evidenceType === 'SEAL' ? `SEAL-${keyBase}`.slice(0, 64) : null, + remark: context.marker, + }, { 'Idempotency-Key': `${keyBase}-evidence-${evidenceType}`.slice(0, 128) }) + const bound = context.apiSuccess(MODULE, `${keyBase} 绑定 ${evidenceType} 装柜证据`, bind, '', OPTIONS) + if (!bound) return false + } + return true +} + function treePath(orderId, tenantId) { return `/api/platform/v1/orders/${encodeURIComponent(orderId)}/fulfillment-tree?tenantId=${encodeURIComponent(tenantId)}` } @@ -378,6 +407,8 @@ async function loadOneFragment(context, state) { `HTTP=${differentReplay.http}, code=${codeOf(differentReplay)}, requestId=${differentReplay.requestId || 'missing'}, traceId=${differentReplay.traceId || 'missing'}`, OPTIONS) } + if (!await ensureCompletionEvidence(context, tenantId, id(candidate.inboundId), operationBase)) return null + const completePath = `${PDA}/tasks/${taskId}/complete` const completePayload = { expectedTaskVersion: Number(picked?.version), operationId: `${operationBase}-complete`.slice(0, 128), diff --git a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs index cb213aac1..89534db99 100644 --- a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs @@ -498,8 +498,8 @@ test('downstream container consumers prefer the phase3Loading fixture', () => { 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, /customerId, customerName, carrierId: null/) - assert.match(serviceCenter, /carrierId: null, carrierName: null/) + assert.match(serviceCenter, /category=TRUCKING&status=ACTIVE/) + assert.match(serviceCenter, /customerId, customerName, carrierId: id\(carrier\.id\), carrierName: carrier\.name/) assert.doesNotMatch(serviceCenter, /链路承运商-/) assert.doesNotMatch(serviceCenter, /customerName: `服务中心链路客户-/) const schedules = source('./phase3-port-schedules.mjs') diff --git a/scripts/admin-test-env-modules/service-center-flow.mjs b/scripts/admin-test-env-modules/service-center-flow.mjs index 47fc37e2a..14bc94830 100644 --- a/scripts/admin-test-env-modules/service-center-flow.mjs +++ b/scripts/admin-test-env-modules/service-center-flow.mjs @@ -72,6 +72,13 @@ export async function run(context) { `containerBookingId=${id(containerFlow.containerBookingId)}, containers=${oceanDetail?.containers?.length ?? 'n/a'}`, OPTIONS) if (!allocationMatches) return } + const carrierResponse = await context.request('GET', + '/api/platform/v1/suppliers?tenantId=0&category=TRUCKING&status=ACTIVE&page=1&size=20') + const carrierPage = context.apiSuccess(MODULE, '读取 ACTIVE TRUCKING 承运商事实源', carrierResponse, '', OPTIONS) + const carrier = carrierPage?.records?.find?.((item) => item?.id && item?.name) + context.assert(MODULE, '跨服务拖车使用正式 ACTIVE TRUCKING 承运商', carrierResponse, + Boolean(carrier?.id && carrier?.name), `carrierId=${id(carrier?.id) || 'missing'}`, OPTIONS) + if (!carrier) return const payload = { outboundOrderId: linkContainer ? id(containerFixture?.orderId) || null : null, oceanBookingId: ocean.id, @@ -80,7 +87,7 @@ export async function run(context) { warehouseId: linkContainer ? id(containerFlow.warehouseId) : id(context.createdResources?.warehouse?.id || context.context?.warehouseId || '193000000000000001'), - customerId, customerName, carrierId: null, carrierName: null, + customerId, customerName, carrierId: id(carrier.id), carrierName: carrier.name, fleetContact: '链路车队联系人', fleetPhone: '13800000007', driverName: '链路司机', driverPhone: '13800000008', driverIdCard: null, vehicleNo: `粤D-${suffix.slice(-6)}`, storeName: `链路门店-${suffix}`, storeContact: '链路联系人', storePhone: '13800000009', storeAddress: '深圳市南山区链路地址', storeCity: '深圳市', storeZipcode: '518000',