diff --git a/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs b/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs index 4b965fac4..a887f7587 100644 --- a/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs +++ b/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs @@ -59,11 +59,11 @@ test('Firstleg booking supplier snapshot requires ACTIVE BOOKING and has no dome }) test('HTTP ocean booking acceptance selects an ACTIVE BOOKING supplier without a geographic scope', () => { - assert.match(oceanBookingFixture, /category=BOOKING&status=ACTIVE/) + assert.match(oceanBookingFixture, /resolveOceanBookingAgent\(context, MODULE, 'MATSON', OPTIONS\)/) assert.match(oceanBookingFixture, /bookingAgentId: id\(agent\.id\), bookingAgentName: agent\.name/) assert.match(oceanBookingFixture, /shippingCompanyId: id\(carrier\.id\), shippingCompanyName: carrier\.name, carrierCode: 'MATSON'/) assert.match(oceanBookingFixture, /使用 ACTIVE BOOKING 订舱代理事实源/) - assert.doesNotMatch(oceanBookingFixture, /category=BOOKING[^'\n]*scope=/) + assert.doesNotMatch(oceanBookingFixture, /scope=/) }) test('frozen MATSON and COSCO matrix is many-to-many and changing B to COSCO clears the selection', () => { diff --git a/scripts/admin-test-env-modules/ocean-booking-fixture-contract.test.mjs b/scripts/admin-test-env-modules/ocean-booking-fixture-contract.test.mjs index 6252d8407..709073d92 100644 --- a/scripts/admin-test-env-modules/ocean-booking-fixture-contract.test.mjs +++ b/scripts/admin-test-env-modules/ocean-booking-fixture-contract.test.mjs @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' -import { oceanBookingDraft, oceanBookingDraftWithoutCustomer, publishPlatformLogisticsChannels, resolveOceanBookingMaster } from './ocean-booking-fixture.mjs' +import { oceanBookingDraft, oceanBookingDraftWithoutCustomer, publishPlatformLogisticsChannels, resolveOceanBookingAgent, resolveOceanBookingMaster } from './ocean-booking-fixture.mjs' const read = (name) => readFileSync(new URL(name, import.meta.url), 'utf8') const master = { customerId: '21', customerName: 'IT 测试客户', channelCode: 'SEA-US', channelName: '美西海运', warehouseId: '41', warehouseName: '深圳仓' } @@ -68,6 +68,16 @@ test('platform channel producer uses the selected origin warehouse and publishes assert.deepEqual(context.createdResources.platformLogisticsChannels, catalog) }) +test('booking agent fixture uses the carrier-filtered server candidates', async () => { + const calls = [] + const context = { request: async (method, path) => { + calls.push({ method, path }); return { body: { code: '200', data: [{ supplierId: 81, name: 'MATSON 代理' }] } } + }, apiSuccess: (_module, _name, response) => response.body.data, + assert: (_module, _name, _response, condition) => assert.equal(condition, true) } + assert.deepEqual(await resolveOceanBookingAgent(context, 'test', 'matson'), { id: '81', name: 'MATSON 代理' }) + assert.deepEqual(calls, [{ method: 'GET', path: '/api/platform/v1/ocean-bookings/booking-agents?carrierCode=MATSON' }]) +}) + test('all production booking fixtures use DRAFT release and the shared requirements contract', () => { for (const name of ['ocean-bookings.mjs', 'phase3-loading.mjs', 'phase3-container-loading.mjs', 'phase3-order-splitting.mjs', 'manual-state-loading-pda.mjs', 'manual-state-ocean-trucking.mjs']) { const source = read(name) diff --git a/scripts/admin-test-env-modules/ocean-booking-fixture.mjs b/scripts/admin-test-env-modules/ocean-booking-fixture.mjs index b3e3a01cf..e2a26d8f1 100644 --- a/scripts/admin-test-env-modules/ocean-booking-fixture.mjs +++ b/scripts/admin-test-env-modules/ocean-booking-fixture.mjs @@ -52,6 +52,19 @@ export async function resolveOceanBookingMaster(context, module) { return master } +export async function resolveOceanBookingAgent(context, module, carrierCode, options = {}) { + const normalizedCarrierCode = text(carrierCode).toUpperCase() + const response = await context.request('GET', + `/api/platform/v1/ocean-bookings/booking-agents?carrierCode=${encodeURIComponent(normalizedCarrierCode)}`) + const candidates = context.apiSuccess(module, '按船司读取可用订舱代理', response, + `carrierCode=${normalizedCarrierCode}`, options) + const selected = (Array.isArray(candidates) ? candidates : []).find((item) => + text(item?.supplierId) && text(item?.name)) + context.assert(module, '订舱代理已由服务端按船司关联过滤', response, + Boolean(selected), `carrierCode=${normalizedCarrierCode}`, options) + return selected ? { id: text(selected.supplierId), name: text(selected.name) } : null +} + export function oceanBookingDraft(context, suffix, master, labels = {}) { const { channelName: _readOnlyChannelName, warehouseName: _readOnlyWarehouseName, ...writableMaster } = master const normalizedSuffix = text(suffix).replace(/[^A-Za-z0-9]/g, '').slice(-32) || 'BOOKING' diff --git a/scripts/admin-test-env-modules/ocean-bookings.mjs b/scripts/admin-test-env-modules/ocean-bookings.mjs index de5e35e5a..a4797d0d7 100644 --- a/scripts/admin-test-env-modules/ocean-bookings.mjs +++ b/scripts/admin-test-env-modules/ocean-bookings.mjs @@ -1,7 +1,7 @@ const MODULE = 'ocean-bookings' const OPTIONS = { scenario: 'ocean-booking-lifecycle', deploymentBlocker: true } const BASE = '/api/platform/v1/ocean-bookings' -import { oceanBookingDraft, resolveOceanBookingMaster } from './ocean-booking-fixture.mjs' +import { oceanBookingDraft, resolveOceanBookingAgent, resolveOceanBookingMaster } from './ocean-booking-fixture.mjs' import { oceanBookingFieldGaps } from './fulfillment-list-field-contract.mjs' import { ensureMatsonCarrierPartner } from './carrier-partner-fixture.mjs' @@ -46,14 +46,12 @@ export async function run(context) { const master = await resolveOceanBookingMaster(context, MODULE) if (!master) return const suffix = context.marker.replace(/\D/g, '').slice(-10) || 'BOOKING' - const agentResponse = await request(context, 'GET', '/api/platform/v1/suppliers?tenantId=0&category=BOOKING&status=ACTIVE&page=1&size=20') - const agentPage = payload(agentResponse) - const agent = agentPage?.records?.find?.((item) => item?.category === 'BOOKING' && item?.status === 'ACTIVE') - step(context, '使用 ACTIVE BOOKING 订舱代理事实源', agentResponse, Boolean(agent?.id && agent?.name), - `supplierId=${id(agent?.id) || 'missing'}, scope=${agent?.scope || 'not-exposed'}`) + const agent = await resolveOceanBookingAgent(context, MODULE, 'MATSON', OPTIONS) + step(context, '使用 ACTIVE BOOKING 订舱代理事实源', null, Boolean(agent?.id && agent?.name), + `supplierId=${id(agent?.id) || 'missing'}`) if (!agent?.id || !agent?.name) return const carrier = await ensureMatsonCarrierPartner(context, MODULE, OPTIONS) - step(context, '使用 ACTIVE CARRIER 船公司事实源', agentResponse, + step(context, '使用 ACTIVE CARRIER 船公司事实源', null, Boolean(carrier?.id && carrier?.carrierCode === 'MATSON'), `partnerId=${id(carrier?.id) || 'missing'}`) if (!carrier?.id) return const data = fullDraft(context, suffix, master, { diff --git a/scripts/admin-test-env-modules/phase3-container-loading.mjs b/scripts/admin-test-env-modules/phase3-container-loading.mjs index 367b78a19..6a4482b95 100644 --- a/scripts/admin-test-env-modules/phase3-container-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-container-loading.mjs @@ -1,4 +1,4 @@ -import { publishPlatformLogisticsChannels } from './ocean-booking-fixture.mjs' +import { publishPlatformLogisticsChannels, resolveOceanBookingAgent } from './ocean-booking-fixture.mjs' import { diffPushPreloadStability, projectPushPreloadStability } from './push-preload-stability.mjs' import { containerFieldGaps, loadingListFieldGaps, preloadFieldGaps } from './fulfillment-list-field-contract.mjs' import { ensureMatsonCarrierPartner } from './carrier-partner-fixture.mjs' @@ -162,16 +162,13 @@ export async function run(context) { const primaryRequirement = { containerType: '40HC', containerKind: 'FCL', capacityCbm: 76, maxWeightKg: 28000 } const realContainerNo = iso6346ContainerNumber(`${context.marker}-initial`) const wrongContainerNo = `TST-${suffix}-02`.slice(0, 64) - 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, '查询放舱所需 ACTIVE 订舱代理', agentResponse, '', OPTIONS) - const bookingAgent = records(agentPage).find((item) => item?.category === 'BOOKING' && item?.status === 'ACTIVE') - context.assert(MODULE, '放舱订舱使用真实启用供应商', agentResponse, + const bookingAgent = await resolveOceanBookingAgent(context, MODULE, 'MATSON', OPTIONS) + context.assert(MODULE, '放舱订舱使用真实启用供应商', null, Boolean(bookingAgent?.id && bookingAgent?.name), `supplierId=${id(bookingAgent?.id) || 'missing'}`, OPTIONS) if (!bookingAgent?.id || !bookingAgent?.name) return const shippingCarrier = await ensureMatsonCarrierPartner(context, MODULE, OPTIONS) - context.assert(MODULE, '放舱船公司使用真实启用CARRIER往来单位', agentResponse, + context.assert(MODULE, '放舱船公司使用真实启用CARRIER往来单位', null, Boolean(shippingCarrier?.id && shippingCarrier?.carrierCode === 'MATSON'), `partnerId=${id(shippingCarrier?.id) || 'missing'}`, OPTIONS) if (!shippingCarrier?.id) return