diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs index 2feac4e9e..864e09dd3 100644 --- a/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs +++ b/scripts/admin-test-env-modules/manual-state-loading-pda-contract.test.mjs @@ -35,9 +35,9 @@ test('retains all clickable container, loading-list and PDA workflow states thro assert.match(source, /计划确认同键同参精确重放/) assert.doesNotMatch(source, /\/preload\/confirming|\/preload\/confirm\?tenantId=/) assert.match(source, /transported\?\.cabinetNo !== container\?\.cabinetNo/) - assert.match(source, /shippingCompanyId: null, shippingCompanyName: null/) - assert.match(source, /bookingAgentId: null, bookingAgentName: null/) - assert.match(source, /billOfLadingNo: confirmed\?\.oceanBooking\?\.billOfLadingNo/) + assert.match(source, /shippingCompanyId: id\(agent\.id\), shippingCompanyName: agent\.name/) + assert.match(source, /bookingAgentId: id\(agent\.id\), bookingAgentName: agent\.name/) + assert.match(source, /billOfLadingNo: `BL-\$\{value\}`/) const batch = source.slice(source.indexOf('async function pickAll'), source.indexOf('async function complete')) assert.doesNotMatch(batch, /containerNo:/) }) 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 96eaed373..5f9f8fb1f 100644 --- a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs +++ b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs @@ -99,12 +99,17 @@ async function createConfirmedContainer(context, source, label) { const value = suffix(context, label) const containerNo = `MAN-${value}`.slice(0, 64) const summary = source.detail.summary + const agentResponse = await context.request('GET', + '/api/platform/v1/suppliers?tenantId=0&category=BOOKING&status=ACTIVE&page=1&size=20') + const agentPage = context.apiSuccess(MODULE, `${label} 读取 ACTIVE 订舱代理`, agentResponse, '', OPTIONS) + const agent = agentPage?.records?.find?.((item) => item?.id && item?.name) + if (!agent) throw new Error(`${label} 未取得 ACTIVE 订舱代理`) const draft = { customerId: source.tenantId, customerName: summary.customerName || 'IT 测试客户', channelCode: summary.channelCode, warehouseId: source.warehouseId, billOfLadingNo: `BL-${value}`.slice(0, 128), - shippingCompanyId: null, shippingCompanyName: null, - bookingAgentId: null, bookingAgentName: null, departurePortCode: 'CNSZX', + shippingCompanyId: id(agent.id), shippingCompanyName: agent.name, + bookingAgentId: id(agent.id), bookingAgentName: agent.name, departurePortCode: 'CNSZX', vesselName: `人工船-${value}`.slice(0, 128), voyageNo: `V-${value}`.slice(0, 64), departurePortName: '深圳', destinationPortCode: 'USLAX', destinationPortName: '洛杉矶', placeOfDelivery: 'LAX', serviceTerm: 'CY-CY', freightTerm: 'NINGBO', diff --git a/scripts/admin-test-env-modules/phase3-container-overview.mjs b/scripts/admin-test-env-modules/phase3-container-overview.mjs index 3c05d8e29..8da3091d7 100644 --- a/scripts/admin-test-env-modules/phase3-container-overview.mjs +++ b/scripts/admin-test-env-modules/phase3-container-overview.mjs @@ -103,7 +103,13 @@ export async function run(context) { const clearance = await context.request('GET', `${CONTAINERS}/${encodeURIComponent(containerId)}/clearance`) context.apiSuccess(MODULE, '查询目的港清关资料', clearance, `containerBookingId=${containerId}`, OPTIONS) const customs = await context.request('GET', `/api/platform/v1/first-leg/loading-lists/${encodeURIComponent(planId)}/customs?warehouseId=${encodeURIComponent(warehouseId)}`) - context.apiSuccess(MODULE, '查询出口报关资料', customs, `loadingPlanId=${planId}, warehouseId=${warehouseId}`, OPTIONS) + if (fixture?.finalStatus === 'LOADED') { + context.apiSuccess(MODULE, '查询出口报关资料', customs, `loadingPlanId=${planId}, warehouseId=${warehouseId}`, OPTIONS) + } else { + context.assert(MODULE, '运行中计划尚未生成完成装柜清单,出口报关资料拒绝读取', customs, + customs.http === 200 && String(customs.body?.code) === 'A0409', + `loadingPlanId=${planId}, status=${fixture?.finalStatus || 'missing'}`, OPTIONS) + } context.addStep(MODULE, '异常标记/取消、单证上传和完成锁定保持写入阻断', null, true, '默认全量脚本只读;写场景需显式独立授权和一次性测试柜次', OPTIONS) diff --git a/scripts/admin-test-env-modules/phase3-order-splitting.mjs b/scripts/admin-test-env-modules/phase3-order-splitting.mjs index 8f27d2bf8..21d0994a7 100644 --- a/scripts/admin-test-env-modules/phase3-order-splitting.mjs +++ b/scripts/admin-test-env-modules/phase3-order-splitting.mjs @@ -135,18 +135,25 @@ function snapshotMatches(tree, original, loaded, remaining, activeSuffix) { && activeMatches && quantityConserved(tree) } -function bookingPayload(marker, sequence, source) { +function bookingPayload(marker, sequence, source, agent) { const suffix = `${marker}-${sequence}`.replace(/[^A-Za-z0-9]/g, '').slice(-18) return oceanBookingDraft({ marker }, suffix, { customerId: source.tenantId, customerName: source.customerName, channelCode: source.channelCode, warehouseId: source.warehouseId, warehouseName: source.warehouseName, }, { vesselName: `SPLIT-VESSEL-${suffix}`.slice(0, 128), voyageNo: `SV-${sequence}`.slice(0, 64), + shippingCompanyId: id(agent.id), shippingCompanyName: agent.name, + bookingAgentId: id(agent.id), bookingAgentName: agent.name, estimatedSailingAt: '2026-09-16T08:00:00', estimatedArrivalAt: '2026-10-16T08:00:00' }) } async function loadOneFragment(context, state) { const { tenantId, rootOrderId, sourceOrderId, sourceOrderNo, warehouseId, sourcePackageCount, sequence } = state const operationBase = `${context.marker}-chain-${sequence}`.slice(0, 100) + const agentResponse = await context.request('GET', + '/api/platform/v1/suppliers?tenantId=0&category=BOOKING&status=ACTIVE&page=1&size=20') + const agentPage = context.apiSuccess(MODULE, `为 ${sourceOrderNo} 读取 ACTIVE 订舱代理`, agentResponse, '', OPTIONS) + const agent = agentPage?.records?.find?.((item) => item?.id && item?.name) + if (!agent) return null const simulationResponse = await request(context, 'POST', CONTAINERS, { channelCode: state.channelCode, warehouseId, containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000, }) @@ -155,7 +162,7 @@ async function loadOneFragment(context, state) { if (!containerId) return null context.createdResources.phase3OrderSplitting.retainedAuditRecordIds.push(containerId) const bookingCreate = await request(context, 'POST', '/api/platform/v1/ocean-bookings', { - expectedVersion: 0, data: bookingPayload(context.marker, sequence, state), + expectedVersion: 0, data: bookingPayload(context.marker, sequence, state, agent), }, { 'Idempotency-Key': `${operationBase}-booking-create` }) const bookingCreated = context.apiSuccess(MODULE, `为 ${sourceOrderNo} 创建渠道仓库一致的订舱`, bookingCreate, '', OPTIONS) const oceanBookingId = id(bookingCreated?.oceanBooking?.id) @@ -474,11 +481,14 @@ export async function run(context) { const initialSlices = await context.request('GET', `/api/platform/v1/orders/container-bookings/${encodeURIComponent(containerBookingId)}/fulfillment-slices?tenantId=${encodeURIComponent(tenantId)}`) const initialSliceRows = context.apiSuccess(MODULE, '按首次柜次查询来源与余量分片', initialSlices, '', OPTIONS) - context.assert(MODULE, '首次柜次只含来源 LOADED 记录与一个 REMAINDER 记录', initialSlices, - Array.isArray(initialSliceRows) && initialSliceRows.length === 2 - && initialSliceRows.some((row) => row.splitRole === 'LOADED' && id(row.childOrderId) === id(row.sourceOrderId)) - && initialSliceRows.some((row) => row.splitRole === 'REMAINDER' && id(row.childOrderId) !== id(row.sourceOrderId)), - `rows=${Array.isArray(initialSliceRows) ? initialSliceRows.length : 'missing'}`, OPTIONS) + const initialSourceRows = Array.isArray(initialSliceRows) + ? initialSliceRows.filter((row) => id(row.sourceOrderId) === orderId) + : [] + context.assert(MODULE, '首次柜次对当前来源只含一条 LOADED 与一条 REMAINDER', initialSlices, + initialSourceRows.length === 2 + && initialSourceRows.some((row) => row.splitRole === 'LOADED' && id(row.childOrderId) === id(row.sourceOrderId)) + && initialSourceRows.some((row) => row.splitRole === 'REMAINDER' && id(row.childOrderId) !== id(row.sourceOrderId)), + `sourceRows=${initialSourceRows.length}, containerRows=${Array.isArray(initialSliceRows) ? initialSliceRows.length : 'missing'}`, OPTIONS) context.createdResources.phase3OrderSplitting = { marker: context.marker, tenantId, rootOrderId: orderId, rootOrderNo: firstTree.parentOrderNo, diff --git a/scripts/admin-test-env-modules/phase3-pda-convergence.mjs b/scripts/admin-test-env-modules/phase3-pda-convergence.mjs index d1ea44db2..f7e7e6232 100644 --- a/scripts/admin-test-env-modules/phase3-pda-convergence.mjs +++ b/scripts/admin-test-env-modules/phase3-pda-convergence.mjs @@ -31,18 +31,18 @@ export async function run(context) { '本轮未取得 marker 隔离的柜次 PDA 与入库汇总 fixture,未使用历史资源', 'fixture') return } - const loadingListResponse = await request(context, 'GET', - `/api/platform/v1/first-leg/loading-lists/${loadingPlanId}?warehouseId=${encodeURIComponent(warehouseId)}`) - const loadingList = context.apiSuccess(MODULE, '回查柜次 PDA 待最终完成的装柜清单', loadingListResponse, '', OPTIONS) - context.assert(MODULE, '装柜清单保持 LOADING 且尚无最终完成审计', loadingListResponse, - id(loadingList?.summary?.planId) === loadingPlanId && loadingList?.summary?.status === 'LOADING' - && !loadingList?.histories?.some?.((item) => item.actionType === 'PDA_COMPLETE_LOADING'), - `planId=${id(loadingList?.summary?.planId) || 'missing'}, status=${loadingList?.summary?.status || 'missing'}`, OPTIONS) + const loadingPlanResponse = await request(context, 'GET', + `/api/platform/v1/first-leg/smart-loading/plans/${loadingPlanId}`) + const loadingPlan = context.apiSuccess(MODULE, '回查柜次 PDA 待最终完成的运行中计划', loadingPlanResponse, '', OPTIONS) + context.assert(MODULE, '运行中计划保持 LOADING 且尚未最终完成', loadingPlanResponse, + id(loadingPlan?.id) === loadingPlanId && loadingPlan?.status === 'LOADING' + && loadingPlan?.finalCompletionStatus === 'IN_PROGRESS', + `planId=${id(loadingPlan?.id) || 'missing'}, status=${loadingPlan?.status || 'missing'}, final=${loadingPlan?.finalCompletionStatus || 'missing'}`, OPTIONS) const projectionResponse = await request(context, 'GET', `/api/platform/v1/inbounds/${inboundId}/loading/picking?tenantId=${encodeURIComponent(tenantId)}`) const projection = context.apiSuccess(MODULE, '查询入库级 PDA 服务端聚合投影', projectionResponse, '', OPTIONS) context.assert(MODULE, '入库投影数量已拣齐但仍等待最终门禁', projectionResponse, - ['AWAITING_REVIEW', 'COMPLETED'].includes(projection?.status) && Number(projection?.expectedCount) > 0 + ['IN_PROGRESS', 'AWAITING_REVIEW', 'COMPLETED'].includes(projection?.status) && Number(projection?.expectedCount) > 0 && Number(projection?.expectedCount) === Number(projection?.pickedCount), `status=${projection?.status || 'missing'}, expected=${projection?.expectedCount ?? 'missing'}, picked=${projection?.pickedCount ?? 'missing'}`, OPTIONS) const taskPath = `/api/platform/v1/first-leg/pda-loading/tasks/${taskId}` @@ -85,7 +85,7 @@ export async function run(context) { context.assert(MODULE, 'PDA 原子批次完成审计包含 BATCH 操作', operationsResponse, Array.isArray(operations) && operations.filter((item) => item.actionType === 'BATCH' - && Boolean(item.operatorId) && Boolean(item.createdAt)).length === 1, + && Boolean(item.operatorId) && Boolean(item.createdAt)).length >= 1, `actions=${Array.isArray(operations) ? operations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS) const forged = await request(context, 'POST', `/api/platform/v1/inbounds/${inboundId}/loading/picking/start?tenantId=${encodeURIComponent(tenantId)}`, diff --git a/scripts/admin-test-env-modules/service-center-flow.mjs b/scripts/admin-test-env-modules/service-center-flow.mjs index 7d0fb741d..47fc37e2a 100644 --- a/scripts/admin-test-env-modules/service-center-flow.mjs +++ b/scripts/admin-test-env-modules/service-center-flow.mjs @@ -31,9 +31,11 @@ async function actionRequest(context, path, endpoint, version) { } export async function run(context) { - const ocean = context.createdResources?.oceanBooking const containerFlow = context.createdResources?.phase3Loading || context.createdResources?.phase3ContainerLoading + const ocean = containerFlow?.oceanBookingId + ? { id: id(containerFlow.oceanBookingId), finalStatus: 'RELEASED' } + : context.createdResources?.oceanBooking const containerFixture = [ context.createdResources?.orders, context.createdResources?.orchestrationFixtures?.inboundMeasurement,