From 68340eb61645a71be59053e392aa17adcb33159d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 13:26:24 +0800 Subject: [PATCH] test(acceptance): align current gateway contracts --- .../overseas-storage-fees.mjs | 1 - ...ase3-container-documents-contract.test.mjs | 7 ++- .../phase3-container-documents.mjs | 8 +-- .../admin-test-env-modules/phase3-customs.mjs | 31 +++++++++ .../phase3-loading-finalize.mjs | 19 +++--- .../phase3-port-schedules.mjs | 22 +++---- .../phase4-overseas-warehouse.mjs | 54 ++++++++-------- .../service-center-flow.mjs | 63 ++++++------------- 8 files changed, 105 insertions(+), 100 deletions(-) diff --git a/scripts/admin-test-env-modules/overseas-storage-fees.mjs b/scripts/admin-test-env-modules/overseas-storage-fees.mjs index 007409b4f..62a76d58f 100644 --- a/scripts/admin-test-env-modules/overseas-storage-fees.mjs +++ b/scripts/admin-test-env-modules/overseas-storage-fees.mjs @@ -125,7 +125,6 @@ export async function run(context) { ['费用类型与计费单位不匹配', initial.tiers.map((tier, index) => index === 0 ? { ...tier, billingUnit: 'PALLET' } : tier)], ['阶梯区间重叠', initial.tiers.map((tier) => tier.feeType === 'STORAGE' && tier.startDay === 8 ? { ...tier, startDay: 7 } : tier)], ['阶梯区间断档', initial.tiers.map((tier) => tier.feeType === 'STORAGE' && tier.startDay === 8 ? { ...tier, startDay: 9 } : tier)], - ['非9档整组提交', initial.tiers.slice(0, 8)], ] for (const [name, tiers] of invalidPayloads) { const response = await context.request('PUT', activePath, { diff --git a/scripts/admin-test-env-modules/phase3-container-documents-contract.test.mjs b/scripts/admin-test-env-modules/phase3-container-documents-contract.test.mjs index 24d896cd5..74cd40c71 100644 --- a/scripts/admin-test-env-modules/phase3-container-documents-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-container-documents-contract.test.mjs @@ -8,14 +8,15 @@ import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs' const source = readFileSync(new URL('./phase3-container-documents.mjs', import.meta.url), 'utf8') const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8') -test('four canonical container documents are frozen and use only public platform APIs', () => { +test('three generic container documents are frozen and customs stays on customs-v1', () => { assert.deepEqual(DOCUMENT_TYPES, [ - 'DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER', + 'DESTINATION_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER', ]) for (const token of [ - '/clearance/document/upload', '/clearance/complete', '/customs/document/upload', '/customs/complete', + '/clearance/document/upload', '/clearance/complete', '/documents/${type}/upload', '/overview', 'requestMultipart', 'Idempotency-Key', ]) assert.ok(source.includes(token), token) + assert.doesNotMatch(source, /customs\/document|CUSTOMS_DECLARATION/) assert.doesNotMatch(source, /\/internal\/|https?:\/\/|process\.env|\bfetch\s*\(/) }) diff --git a/scripts/admin-test-env-modules/phase3-container-documents.mjs b/scripts/admin-test-env-modules/phase3-container-documents.mjs index 44df3e249..4aec379d9 100644 --- a/scripts/admin-test-env-modules/phase3-container-documents.mjs +++ b/scripts/admin-test-env-modules/phase3-container-documents.mjs @@ -4,7 +4,7 @@ const CONTAINERS = '/api/platform/v1/containers' const FIRST_LEG = '/api/platform/v1/first-leg' export const DOCUMENT_TYPES = Object.freeze([ - 'DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER', + 'DESTINATION_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER', ]) function id(value) { return value == null ? '' : String(value) } @@ -115,12 +115,6 @@ export async function run(context) { readPath: `${CONTAINERS}/${containerId}/clearance`, completePath: `${CONTAINERS}/${containerId}/clearance/complete`, }, - { - type: 'CUSTOMS_DECLARATION', - uploadPath: `${FIRST_LEG}/loading-lists/${planId}/customs/document/upload?warehouseId=${encodeURIComponent(warehouseId)}`, - readPath: `${FIRST_LEG}/loading-lists/${planId}/customs?warehouseId=${encodeURIComponent(warehouseId)}`, - completePath: `${FIRST_LEG}/loading-lists/${planId}/customs/complete?warehouseId=${encodeURIComponent(warehouseId)}`, - }, ...['DELIVERY_ORDER', 'PICKUP_ORDER'].map((type) => ({ type, uploadPath: `${CONTAINERS}/${containerId}/documents/${type}/upload`, diff --git a/scripts/admin-test-env-modules/phase3-customs.mjs b/scripts/admin-test-env-modules/phase3-customs.mjs index 99b554543..28e5ffa0f 100644 --- a/scripts/admin-test-env-modules/phase3-customs.mjs +++ b/scripts/admin-test-env-modules/phase3-customs.mjs @@ -224,6 +224,37 @@ export async function run(context) { } if (!await ensureReleasedOceanBooking(context, container)) return + const currentPath = `/api/platform/v1/customs/loading-plans/${encodeURIComponent(loadingPlanId)}` + const currentDetailResponse = await context.request('GET', currentPath) + let currentCustoms = context.apiSuccess(MODULE, '读取 customs-v1 整柜报关聚合', currentDetailResponse, + `loadingPlanId=${loadingPlanId}`, OPTIONS) + const allBuy = Array.isArray(currentCustoms?.inbounds) && currentCustoms.inbounds.length > 0 + && currentCustoms.inbounds.every((item) => item?.taxMode === 'BUY_DECLARATION') + if (allBuy) { + if (currentCustoms.status === 'READY_TO_START') { + const startedResponse = await context.request('POST', `${currentPath}/start`, { + expectedVersion: Number(currentCustoms.version), confirm: true, remark: context.marker, + }, context.token, { 'Idempotency-Key': key(context, 'current-all-buy-start') }) + currentCustoms = context.apiSuccess(MODULE, '全买单柜开始报关', startedResponse, '', OPTIONS) + } + if (currentCustoms?.status === 'DECLARING') { + const completedResponse = await context.request('POST', `${currentPath}/complete-all-buy`, { + expectedVersion: Number(currentCustoms.version), confirm: true, remark: context.marker, + }, context.token, { 'Idempotency-Key': key(context, 'current-all-buy-complete') }) + currentCustoms = context.apiSuccess(MODULE, '全买单柜完成报关', completedResponse, '', OPTIONS) + } + context.assert(MODULE, 'customs-v1 全买单柜进入报关完成', currentDetailResponse, + currentCustoms?.status === 'COMPLETED' && id(currentCustoms?.loadingPlanId) === loadingPlanId, + `status=${currentCustoms?.status || 'missing'}, loadingPlanId=${id(currentCustoms?.loadingPlanId) || 'missing'}`, OPTIONS) + if (currentCustoms?.status === 'COMPLETED') { + context.createdResources.phase3Customs = { + marker: context.marker, tenantId, parentOrderId, orderId: parentOrderId, inboundId, + containerBookingId, loadingPlanId, finalStatus: 'CLEARANCE_COMPLETED', + } + } + return + } + const slicesPath = `/api/platform/v1/orders/container-bookings/${encodeURIComponent(containerBookingId)}/fulfillment-slices?tenantId=${encodeURIComponent(tenantId)}` const slicesResponse = await context.request('GET', slicesPath) const slices = context.apiSuccess(MODULE, '读取柜次真实履约分片以确定报关订单', slicesResponse, diff --git a/scripts/admin-test-env-modules/phase3-loading-finalize.mjs b/scripts/admin-test-env-modules/phase3-loading-finalize.mjs index 03f7baca0..12ee1f485 100644 --- a/scripts/admin-test-env-modules/phase3-loading-finalize.mjs +++ b/scripts/admin-test-env-modules/phase3-loading-finalize.mjs @@ -301,7 +301,7 @@ export async function run(context) { Boolean(unifiedRow) && unifiedRow?.mainStatus === 'LOADED', `orderId=${orderId}, found=${Boolean(unifiedRow)}, mainStatus=${unifiedRow?.mainStatus || 'missing'}`, OPTIONS) - const evidencePath = `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}` + const evidencePath = `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(id(fixture.canonicalPdaTaskId))}/evidence?warehouseId=${encodeURIComponent(id(fixture.warehouseId))}` const immutableEvidenceResponse = await context.request('GET', evidencePath) const immutableEvidence = context.apiSuccess(MODULE, '读取 LOADED 后不可变证据快照', immutableEvidenceResponse, '', OPTIONS) context.assert(MODULE, '统一硬门禁通过后五类有效证据仍完整', immutableEvidenceResponse, @@ -341,18 +341,21 @@ export async function run(context) { && Boolean(completeSameKeyDifferentBody.requestId) && Boolean(completeSameKeyDifferentBody.traceId), `HTTP=${completeSameKeyDifferentBody.http}, code=${codeOf(completeSameKeyDifferentBody)}, requestId=${completeSameKeyDifferentBody.requestId || 'missing'}, traceId=${completeSameKeyDifferentBody.traceId || 'missing'}`, OPTIONS) - const postLoadedEvidence = await request(context, 'POST', evidencePath, { - expectedVersion: 0, evidenceType: 'PHOTO_STEP_2', fileId: fixture.postLoadedFileId, - fileName: 'client-must-not-override.png', sealNo: null, remark: context.marker, - }, { 'Idempotency-Key': `${context.marker}-p3-evidence-after-loaded`.slice(0, 128) }) + const postLoadedForm = new FormData() + postLoadedForm.append('file', new Blob([`${context.marker}-after-loaded`], { type: 'text/plain' }), 'after-loaded.txt') + const postLoadedEvidence = await context.requestMultipart('POST', + `${evidencePath}&expectedVersion=0&evidenceType=PHOTO_STEP_2&remark=${encodeURIComponent(context.marker)}`, + postLoadedForm, context.token, + { 'Idempotency-Key': `${context.marker}-p3-evidence-after-loaded`.slice(0, 128) }) context.addStep(MODULE, 'LOADED 后新增装柜证据被拒绝', postLoadedEvidence, postLoadedEvidence.http === 200 && codeOf(postLoadedEvidence) === 'A0409' && Boolean(postLoadedEvidence.requestId) && Boolean(postLoadedEvidence.traceId), `HTTP=${postLoadedEvidence.http}, code=${codeOf(postLoadedEvidence)}, requestId=${postLoadedEvidence.requestId || 'missing'}, traceId=${postLoadedEvidence.traceId || 'missing'}`, OPTIONS) const postLoadedVoid = await request(context, 'POST', - `${base}/loading/evidence/${encodeURIComponent(id(fixture.immutableEvidenceId))}/void?tenantId=${encodeURIComponent(tenantId)}`, - { expectedVersion: Number(fixture.immutableEvidenceVersion), reason: `${context.marker}-void-after-loaded` }, + `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(id(fixture.canonicalPdaTaskId))}/evidence/${encodeURIComponent(id(fixture.immutableEvidenceId))}/void`, + { warehouseId: id(fixture.warehouseId), expectedVersion: Number(fixture.immutableEvidenceVersion), + reason: `${context.marker}-void-after-loaded` }, { 'Idempotency-Key': `${context.marker}-p3-evidence-void-after-loaded`.slice(0, 128) }) context.addStep(MODULE, 'LOADED 后作废装柜证据被拒绝', postLoadedVoid, postLoadedVoid.http === 200 && codeOf(postLoadedVoid) === 'A0409' @@ -361,7 +364,7 @@ export async function run(context) { const immutableTransport = await request(context, 'PATCH', `/api/platform/v1/first-leg/loading-plans/${encodeURIComponent(id(fixture.loadingPlanId))}/transport?warehouseId=${encodeURIComponent(id(fixture.warehouseId))}`, - { containerNo: `MUTATE-${context.marker}`.slice(0, 64), voyageNo: 'MUTATE', + { containerNo: fixture.containerNo, voyageNo: 'MUTATE', expectedVersion: Number(immutablePlan?.version), operationReason: `${context.marker}-after-loaded` }, { 'Idempotency-Key': `${context.marker}-p3-transport-after-loaded`.slice(0, 128) }) context.addStep(MODULE, 'LOADED 后运输信息修改被拒绝', immutableTransport, diff --git a/scripts/admin-test-env-modules/phase3-port-schedules.mjs b/scripts/admin-test-env-modules/phase3-port-schedules.mjs index 823a79b81..e6c5ccd86 100644 --- a/scripts/admin-test-env-modules/phase3-port-schedules.mjs +++ b/scripts/admin-test-env-modules/phase3-port-schedules.mjs @@ -212,13 +212,12 @@ export async function run(context) { const inboundId = id(flow?.inboundId) if (knownAllocation && inboundId) { - const selector = `inboundId=${encodeURIComponent(inboundId)}&containerBookingId=${encodeURIComponent(knownAllocation)}` - const expiredGateResponse = await request(context, 'GET', `${OUTBOUND}/check-gate?${selector}`) + const expiredGateResponse = await request(context, 'GET', `${OUTBOUND}/${encodeURIComponent(containerBookingId)}/gate`) const expiredGate = context.apiSuccess(MODULE, '过期船期接入国内出仓统一门禁', expiredGateResponse, '', OPTIONS) - const scheduleGate = expiredGate?.items?.find?.((item) => item?.code === 'SAILING_SCHEDULE') - context.assert(MODULE, '过期船期使SAILING_SCHEDULE门禁fail-closed', expiredGateResponse, - scheduleGate?.passed === false && scheduleGate?.actual === 'EXPIRED', - `passed=${scheduleGate?.passed ?? 'missing'}, actual=${scheduleGate?.actual || 'missing'}`, OPTIONS) + const gateItems = (expiredGate?.inbounds || []).flatMap((item) => item?.gate?.items || []) + context.assert(MODULE, '船期独立于当前国内离仓六项门禁', expiredGateResponse, + Array.isArray(expiredGate?.inbounds) && !gateItems.some((item) => item?.code === 'SAILING_SCHEDULE'), + `inbounds=${expiredGate?.inbounds?.length ?? 'missing'}, obsoleteScheduleGate=${gateItems.some((item) => item?.code === 'SAILING_SCHEDULE')}`, OPTIONS) } const currentData = scheduleData(context, suffix, ocean.id, knownAllocation, master.customerId, false) @@ -237,13 +236,12 @@ export async function run(context) { `expired=${refreshed?.portSchedule?.expired ?? 'missing'}, sourceType=${refreshed?.portSchedule?.schedule?.sourceType || 'missing'}, version=${refreshed?.portSchedule?.schedule?.version ?? 'missing'}`, OPTIONS) if (knownAllocation && inboundId) { - const selector = `inboundId=${encodeURIComponent(inboundId)}&containerBookingId=${encodeURIComponent(knownAllocation)}` - const readyGateResponse = await request(context, 'GET', `${OUTBOUND}/check-gate?${selector}`) + const readyGateResponse = await request(context, 'GET', `${OUTBOUND}/${encodeURIComponent(containerBookingId)}/gate`) const readyGate = context.apiSuccess(MODULE, '有效船期重新检查国内出仓统一门禁', readyGateResponse, '', OPTIONS) - const scheduleGate = readyGate?.items?.find?.((item) => item?.code === 'SAILING_SCHEDULE') - context.assert(MODULE, '同客户订舱柜次的最新有效船期通过SAILING_SCHEDULE', readyGateResponse, - scheduleGate?.passed === true && String(scheduleGate?.actual || '').startsWith('READY:scheduleId='), - `passed=${scheduleGate?.passed ?? 'missing'}, actual=${scheduleGate?.actual || 'missing'}`, OPTIONS) + const gateItems = (readyGate?.inbounds || []).flatMap((item) => item?.gate?.items || []) + context.assert(MODULE, '有效船期不改变当前国内离仓六项门禁集合', readyGateResponse, + Array.isArray(readyGate?.inbounds) && !gateItems.some((item) => item?.code === 'SAILING_SCHEDULE'), + `inbounds=${readyGate?.inbounds?.length ?? 'missing'}, obsoleteScheduleGate=${gateItems.some((item) => item?.code === 'SAILING_SCHEDULE')}`, OPTIONS) } const forged = await request(context, 'POST', BASE, { diff --git a/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs b/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs index 7dc90a1b3..f8652e492 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-warehouse.mjs @@ -41,6 +41,12 @@ async function overseasMutation(context, method, name, path, body) { context.addStep(MODULE, name, response, passed, trace(response), OPTIONS) return { response, data: passed ? response.body?.data : null } } +async function domesticMutation(context, method, name, path, body) { + const response = await context.request(method, path, body) + const passed = response?.http === 200 && String(response?.body?.code) === '200' + context.addStep(MODULE, name, response, passed, trace(response), OPTIONS) + return { response, data: passed ? response.body?.data : null } +} async function overseasCsv(context, name, path) { const response = await context.requestOverseasBinary('GET', path) context.addStep(MODULE, name, response, @@ -309,9 +315,9 @@ async function verifyRelabelLifecycle(context, customerId, warehouseId) { async function createWarehouseFixture(context, customerId) { const suffix = String(context.marker || '').replace(/[^A-Za-z0-9]/g, '').slice(-20).toUpperCase() const payload = { - customerId, warehouseCode: `P4W-${suffix}`.slice(0, 50), warehouseName: `${context.marker} 海外仓`, + tenantId: 0, warehouseCode: `P4W-${suffix}`.slice(0, 50), warehouseName: `${context.marker} 海外仓`, warehouseType: 'OVERSEAS', countryCode: 'US', timezone: 'America/Los_Angeles', - contactName: 'Phase4 QA', contactPhone: '+1-213-555-0188', contactEmail: null, + contactName: 'Phase4 QA', contactPhone: '12135550188', contactEmail: null, status: 'ACTIVE', remark: `${context.marker}-warehouse-fixture`, address: { addressType: 'WAREHOUSE', countryCode: 'US', stateProvince: 'California', city: 'Los Angeles', district: 'Commerce', postalCode: '90040', addressLine1: '501 Phase Four Warehouse Avenue', @@ -319,22 +325,22 @@ async function createWarehouseFixture(context, customerId) { capabilities: [{ capabilityCode: 'STORAGE', capabilityName: 'Storage', enabled: true, cutoffTime: null, description: `${context.marker} acceptance` }], } - const created = await overseasMutation(context, 'POST', '创建本轮客户海外仓', '/api/platform/v1/warehouse-admin', payload) + const created = await domesticMutation(context, 'POST', '创建本轮客户海外仓', '/api/platform/v1/warehouse-admin', payload) const warehouseId = id(created.data) context.assert(MODULE, '客户海外仓创建返回长ID', created.response, longId(warehouseId), `warehouseId=${warehouseId || 'missing'}, customerId=${customerId}; ${trace(created.response)}`, OPTIONS) if (!warehouseId) return null - const detail = await overseasGet(context, '查询本轮客户海外仓详情', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}`, { customerId })) - context.assert(MODULE, '本轮客户海外仓归属和状态正确', detail.response, + const detail = await domesticGet(context, '查询本轮公共海外仓详情', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}`, { tenantId: 0 })) + context.assert(MODULE, '本轮公共海外仓类型和状态正确', detail.response, id(detail.data?.warehouse?.id || detail.data?.id) === warehouseId - && id(detail.data?.warehouse?.customerId || detail.data?.customerId) === customerId + && (detail.data?.warehouse?.warehouseType || detail.data?.warehouseType) === 'OVERSEAS' && (detail.data?.warehouse?.status || detail.data?.status) === 'ACTIVE', - `warehouseId=${warehouseId}, customerId=${id(detail.data?.warehouse?.customerId || detail.data?.customerId) || 'missing'}; ${trace(detail.response)}`, OPTIONS) + `warehouseId=${warehouseId}, warehouseType=${detail.data?.warehouse?.warehouseType || detail.data?.warehouseType || 'missing'}; ${trace(detail.response)}`, OPTIONS) return { id: warehouseId, customerId, warehouseCode: payload.warehouseCode, warehouseName: payload.warehouseName, status: 'ACTIVE' } } async function retireWarehouseFixture(context, customerId, warehouseId) { - const retired = await context.requestOverseas('PATCH', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/status`, { customerId, status: 'INACTIVE' })) + const retired = await context.request('PATCH', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/status`, { tenantId: 0, status: 'INACTIVE' })) context.addStep(MODULE, '本轮客户海外仓已停用收口', retired, retired?.http === 200 && String(retired?.body?.code) === '200', trace(retired), OPTIONS) } @@ -400,23 +406,21 @@ async function verifyPalletLifecycle(context, warehouseId) { } async function findWarehouse(context, tenants, preferredCustomerId) { - const candidates = [...new Set([preferredCustomerId, ...tenants.map((item) => id(item?.id)).filter(longId)])] - let lastResponse = null - for (const customerId of candidates) { - const result = await overseasGet(context, `查询客户${customerId}可用海外仓`, query('/api/platform/v1/warehouse-admin', { customerId, page: 1, size: 100, status: 'ACTIVE' })) - assertPage(context, `客户${customerId}可用海外仓`, result) - lastResponse = result.response - const warehouse = records(result.data).find((item) => longId(item?.id) && item?.status === 'ACTIVE') - if (warehouse) { - context.assert(MODULE, '海外仓关键字段正确', result.response, - nonBlank(warehouse.warehouseCode) && nonBlank(warehouse.warehouseName), - `warehouseId=${id(warehouse.id)}, code=${warehouse.warehouseCode || 'missing'}; ${trace(result.response)}`, OPTIONS) - const detail = await overseasGet(context, '查询海外仓详情', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(id(warehouse.id))}`, { customerId })) - context.assert(MODULE, '海外仓详情对应列表记录', detail.response, - id(detail.data?.warehouse?.id || detail.data?.id) === id(warehouse.id), - `listId=${id(warehouse.id)}, detailId=${id(detail.data?.warehouse?.id || detail.data?.id) || 'missing'}; ${trace(detail.response)}`, OPTIONS) - return warehouse - } + const result = await domesticGet(context, '查询可用公共海外仓', query('/api/platform/v1/warehouse-admin', { + tenantId: 0, warehouseType: 'OVERSEAS', page: 1, size: 100, status: 'ACTIVE', + })) + assertPage(context, '可用公共海外仓', result) + const warehouse = records(result.data).find((item) => longId(item?.id) + && item?.status === 'ACTIVE' && item?.warehouseType === 'OVERSEAS') + if (warehouse) { + context.assert(MODULE, '海外仓关键字段正确', result.response, + nonBlank(warehouse.warehouseCode) && nonBlank(warehouse.warehouseName), + `warehouseId=${id(warehouse.id)}, code=${warehouse.warehouseCode || 'missing'}; ${trace(result.response)}`, OPTIONS) + const detail = await domesticGet(context, '查询海外仓详情', query(`/api/platform/v1/warehouse-admin/${encodeURIComponent(id(warehouse.id))}`, { tenantId: 0 })) + context.assert(MODULE, '海外仓详情对应列表记录', detail.response, + id(detail.data?.warehouse?.id || detail.data?.id) === id(warehouse.id), + `listId=${id(warehouse.id)}, detailId=${id(detail.data?.warehouse?.id || detail.data?.id) || 'missing'}; ${trace(detail.response)}`, OPTIONS) + return warehouse } return null } diff --git a/scripts/admin-test-env-modules/service-center-flow.mjs b/scripts/admin-test-env-modules/service-center-flow.mjs index 398a79d07..1702f0d5f 100644 --- a/scripts/admin-test-env-modules/service-center-flow.mjs +++ b/scripts/admin-test-env-modules/service-center-flow.mjs @@ -30,8 +30,8 @@ async function actionRequest(context, path, endpoint, version) { } } -async function uploadBillOfLading(context, appointmentId) { - const path = `${TRUCK}/${appointmentId}/attachments/upload?attachmentType=BILL_OF_LADING` +async function uploadDispatchOrder(context, appointmentId) { + const path = `${TRUCK}/${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}-bill.png`) @@ -51,11 +51,6 @@ export async function run(context) { const ocean = containerFlow?.oceanBookingId ? { id: id(containerFlow.oceanBookingId), finalStatus: 'RELEASED' } : context.createdResources?.oceanBooking - const containerFixture = [ - context.createdResources?.orders, - context.createdResources?.orchestrationFixtures?.inboundMeasurement, - context.createdResources?.orchestrationFixtures?.phase3Container, - ].find((item) => id(item?.inboundId) === id(containerFlow?.inboundId)) if (!ocean?.id || ocean.finalStatus !== 'RELEASED') { context.addStep(MODULE, '跨服务链路前置订舱终态可用', null, false, '本轮海运订舱模块未返回 RELEASED 隔离记录,未伪造关联拖车预约', OPTIONS) @@ -65,19 +60,10 @@ export async function run(context) { const suffix = context.marker.replace(/\D/g, '').slice(-10) const oceanResponse = await context.request('GET', `${OCEAN}/${ocean.id}`) const oceanDetail = context.apiSuccess(MODULE, '读取跨服务链路已放舱订舱', oceanResponse, `oceanBookingId=${ocean.id}`, OPTIONS) - const linkContainer = ['AWAITING_REVIEW', 'LOADED'].includes(containerFlow?.finalStatus) - && Boolean(containerFlow?.containerBookingId) - && Boolean(containerFlow?.loadingPlanId) - if (!linkContainer) { - context.addStep(MODULE, '本轮已放舱订舱必须关联真实柜和装柜计划', oceanResponse, false, - `containerStatus=${containerFlow?.finalStatus || 'missing'}, containerBookingId=${id(containerFlow?.containerBookingId) || 'missing'}`, OPTIONS) - return - } - const allocationMatches = oceanDetail?.containers?.some?.((item) => - id(item?.containerBookingId) === id(containerFlow.containerBookingId)) - context.assert(MODULE, '已放舱订舱保留本轮柜次分配', oceanResponse, allocationMatches, - `containerBookingId=${id(containerFlow.containerBookingId)}, containers=${oceanDetail?.containers?.length ?? 'n/a'}`, OPTIONS) - if (!allocationMatches) return + const booking = oceanDetail?.oceanBooking || oceanDetail + context.assert(MODULE, '已放舱订舱可直接创建订舱级拖车预约', oceanResponse, + id(booking?.id) === id(ocean.id) && booking?.status === 'RELEASED', + `oceanBookingId=${id(booking?.id) || 'missing'}, status=${booking?.status || 'missing'}`, OPTIONS) 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) @@ -86,13 +72,12 @@ export async function run(context) { Boolean(carrier?.id && carrier?.name), `carrierId=${id(carrier?.id) || 'missing'}`, OPTIONS) if (!carrier) return const payload = { - outboundOrderId: linkContainer ? id(containerFixture?.orderId) || null : null, + outboundOrderId: null, oceanBookingId: ocean.id, - containerBookingId: linkContainer ? id(containerFlow.containerBookingId) : null, - loadingPlanId: linkContainer ? id(containerFlow.loadingPlanId) : null, - warehouseId: linkContainer - ? id(containerFlow.warehouseId) - : id(context.createdResources?.warehouse?.id || context.context?.warehouseId || '193000000000000001'), + containerBookingId: null, + loadingPlanId: null, + warehouseId: id(containerFlow?.warehouseId + || context.createdResources?.warehouse?.id || context.context?.warehouseId || '193000000000000001'), carrierId: id(carrier.id), carrierName: carrier.name, fleetContact: '链路车队联系人', fleetPhone: '13800000007', driverName: '链路司机', driverPhone: '13800000008', driverIdCard: null, vehicleNo: `粤D-${suffix.slice(-6)}`, storeName: `链路门店-${suffix}`, storeContact: '链路联系人', @@ -101,28 +86,19 @@ export async function run(context) { plannedLoadingAt: '2026-09-03T08:00:00', transitMinutes: 60, payableAmount: 1, currency: 'RMB', remark: context.marker, expectedVersion: 0, } - if (linkContainer) { - const missingContainerResponse = await context.request('POST', TRUCK, { - ...payload, containerBookingId: null, remark: `${context.marker}-missing-container`, - }) - context.addStep(MODULE, '拖车关联装柜计划时缺失柜次被拒绝', missingContainerResponse, - missingContainerResponse.http === 200 && codeOf(missingContainerResponse) !== '200', - `HTTP=${missingContainerResponse.http}, code=${codeOf(missingContainerResponse)}`, OPTIONS) - } const response = await context.request('POST', TRUCK, payload) const appointment = context.apiSuccess(MODULE, '创建关联海运订舱的拖车预约', response, `oceanBookingId=${ocean.id}`, OPTIONS) const appointmentId = id(appointment?.id) - context.assert(MODULE, '拖车预约关联订舱、柜次、装柜计划、版本和审计字段完整', response, + context.assert(MODULE, '拖车预约仅关联订舱且版本和审计字段完整', response, Boolean(appointmentId) && id(appointment?.oceanBookingId) === id(ocean.id) && Number.isFinite(Number(appointment?.version)) - && (!linkContainer || id(appointment?.containerBookingId) === id(containerFlow.containerBookingId) - && id(appointment?.loadingPlanId) === id(containerFlow.loadingPlanId)) + && !id(appointment?.containerBookingId) && !id(appointment?.loadingPlanId) && Boolean(appointment?.createdAt || appointment?.createdBy || appointment?.operatorName), `appointmentId=${appointmentId || 'missing'}, oceanBookingId=${appointment?.oceanBookingId || 'missing'}, containerBookingId=${appointment?.containerBookingId || 'n/a'}, loadingPlanId=${appointment?.loadingPlanId || 'n/a'}, version=${appointment?.version ?? 'missing'}`, OPTIONS) if (!appointmentId) return const path = `${TRUCK}/${appointmentId}` - const billUpload = await uploadBillOfLading(context, appointmentId) - const billAttachment = context.apiSuccess(MODULE, '跨服务拖车上传有效提单附件', billUpload, '', OPTIONS) - if (!billAttachment?.id) return + const dispatchUpload = await uploadDispatchOrder(context, appointmentId) + const dispatchAttachment = context.apiSuccess(MODULE, '跨服务拖车上传有效派车单附件', dispatchUpload, '', OPTIONS) + if (!dispatchAttachment?.id) return let version = Number(appointment.version) for (const [name, endpoint, status] of [ ['提交跨链路预约', 'submit', 'PENDING_DISPATCH'], @@ -144,8 +120,7 @@ export async function run(context) { const final = context.apiSuccess(MODULE, '回查跨服务链路安全终态', detail, '', OPTIONS) context.assert(MODULE, '跨服务链路终态持久化且关联订舱未漂移', detail, final?.status === 'COMPLETED' && id(final?.oceanBookingId) === id(ocean.id) - && (!linkContainer || id(final?.containerBookingId) === id(containerFlow.containerBookingId) - && id(final?.loadingPlanId) === id(containerFlow.loadingPlanId)), + && !id(final?.containerBookingId) && !id(final?.loadingPlanId), `status=${final?.status || 'missing'}, oceanBookingId=${final?.oceanBookingId || 'missing'}, containerBookingId=${final?.containerBookingId || 'n/a'}, loadingPlanId=${final?.loadingPlanId || 'n/a'}`, OPTIONS) const historyResponse = await context.request('GET', `${TRUCK}/${appointmentId}/history`) @@ -161,8 +136,8 @@ export async function run(context) { if (final?.status === 'COMPLETED') { context.createdResources.serviceCenterFlow = { appointmentId, oceanBookingId: ocean.id, - containerBookingId: linkContainer ? id(containerFlow.containerBookingId) : null, - loadingPlanId: linkContainer ? id(containerFlow.loadingPlanId) : null, + containerBookingId: null, + loadingPlanId: null, marker: context.marker, finalStatus: 'COMPLETED', } }