fix(acceptance): select booking agents by carrier

This commit is contained in:
2026-09-02 16:38:44 +08:00
parent 1b1f2718b3
commit a8c71fef12
5 changed files with 35 additions and 17 deletions
@@ -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', () => {
@@ -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)
@@ -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'
@@ -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, {
@@ -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