优化
This commit is contained in:
@@ -521,10 +521,8 @@ export class AcceptanceRun {
|
||||
|
||||
async preparePhase3ContainerFixture() {
|
||||
const originalMarker = this.marker
|
||||
// Keep the shared orchestration marker stable. Downstream loading,
|
||||
// overview and finance modules select fixtures by context.marker; using a
|
||||
// private -CAB suffix here made the producer invisible to those modules.
|
||||
const fixtureMarker = originalMarker
|
||||
// Isolate order idempotency, then publish the fixture under the run marker.
|
||||
const fixtureMarker = `${originalMarker}-CAB`
|
||||
const createdKeys = ['orders', 'ocrInbound', 'testInbound', 'inboundMeasurement']
|
||||
const contextKeys = ['inboundWeightOcr', 'ocrInbound', 'testInbound', 'createdInbound']
|
||||
const createdSnapshot = new Map(createdKeys.map((key) => [key, {
|
||||
@@ -546,7 +544,8 @@ export class AcceptanceRun {
|
||||
this.createdResources.orchestrationFixtures = {
|
||||
...(this.createdResources.orchestrationFixtures || {}),
|
||||
phase3Container: fixture ? {
|
||||
marker: fixtureMarker,
|
||||
marker: originalMarker,
|
||||
sourceMarker: fixtureMarker,
|
||||
tenantId: fixture.tenantId,
|
||||
orderId: fixture.orderId || this.createdResources.orders?.orderId,
|
||||
inboundId: fixture.inboundId,
|
||||
|
||||
@@ -70,15 +70,22 @@ export async function run(context) {
|
||||
context.assert(MODULE, '订舱存在匹配本轮柜型的箱型需求', changed,
|
||||
Boolean(requirement?.id), `requirementId=${id(requirement?.id) || 'missing'}`, OPTIONS)
|
||||
if (!requirement?.id || id(containerFlow?.tenantId) !== id(changeConfirmed?.oceanBooking?.customerId)) return
|
||||
const allocation = await request(context, 'POST', `${path}/allocations`, {
|
||||
requirementId: id(requirement.id), containerBookingId: id(containerFlow.containerBookingId), expectedVersion: version,
|
||||
}, { 'Idempotency-Key': `${context.marker}-ocean-container-allocation`.slice(0, 128) })
|
||||
const allocated = context.apiSuccess(MODULE, '放舱前分配本轮柜次', allocation, '', OPTIONS)
|
||||
version = Number(allocated?.oceanBooking?.version)
|
||||
context.assert(MODULE, '订舱柜次分配持久化且版本递增', allocation,
|
||||
allocated?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))
|
||||
&& Number.isInteger(version),
|
||||
`containerBookingId=${id(containerFlow.containerBookingId)}, version=${version}`, OPTIONS)
|
||||
const existingAllocation = changeConfirmed?.containerAllocations?.find?.(
|
||||
(item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))
|
||||
if (existingAllocation) {
|
||||
context.assert(MODULE, '确认订舱已自动关联本轮柜次', changed, true,
|
||||
`containerBookingId=${id(containerFlow.containerBookingId)}`, OPTIONS)
|
||||
} else {
|
||||
const allocation = await request(context, 'POST', `${path}/allocations`, {
|
||||
requirementId: id(requirement.id), containerBookingId: id(containerFlow.containerBookingId), expectedVersion: version,
|
||||
}, { 'Idempotency-Key': `${context.marker}-ocean-container-allocation`.slice(0, 128) })
|
||||
const allocated = context.apiSuccess(MODULE, '放舱前分配本轮柜次', allocation, '', OPTIONS)
|
||||
version = Number(allocated?.oceanBooking?.version)
|
||||
context.assert(MODULE, '订舱柜次分配持久化且版本递增', allocation,
|
||||
allocated?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))
|
||||
&& Number.isInteger(version),
|
||||
`containerBookingId=${id(containerFlow.containerBookingId)}, version=${version}`, OPTIONS)
|
||||
}
|
||||
}
|
||||
const released = await request(context, 'POST', `${path}/release`, {
|
||||
expectedVersion: version, billOfLadingNo: `BL-${suffix}`, vesselName: `放舱船-${suffix}`,
|
||||
|
||||
@@ -145,7 +145,7 @@ export async function run(context) {
|
||||
expectedVersion: Number(foreignPlan?.version), operationId: `${context.marker}-other-channel-assign`.slice(0, 128),
|
||||
})
|
||||
context.addStep(MODULE, '异渠道入库单拒绝装入当前柜次', foreignAssignResponse,
|
||||
foreignAssignResponse.http === 200 && ['A0400', '400'].includes(codeOf(foreignAssignResponse)),
|
||||
foreignAssignResponse.http === 200 && ['A0422', '422'].includes(codeOf(foreignAssignResponse)),
|
||||
`HTTP=${foreignAssignResponse.http}, code=${codeOf(foreignAssignResponse)}`, OPTIONS)
|
||||
|
||||
const containerDetailResponse = await context.request('GET', `${CONTAINERS}/${containerId}`)
|
||||
@@ -275,7 +275,7 @@ export async function run(context) {
|
||||
version: Number(replaced?.version), reason: `${context.marker}-clear-container-no`,
|
||||
})
|
||||
context.addStep(MODULE, 'PDA 任务创建后清空真实柜号失败', clearContainerNoResponse,
|
||||
clearContainerNoResponse.http === 200 && ['A0400', '400'].includes(codeOf(clearContainerNoResponse)),
|
||||
clearContainerNoResponse.http === 200 && ['A0409', '409'].includes(codeOf(clearContainerNoResponse)),
|
||||
`HTTP=${clearContainerNoResponse.http}, code=${codeOf(clearContainerNoResponse)}`, OPTIONS)
|
||||
|
||||
const replacedTaskResponse = await context.request('GET',
|
||||
|
||||
@@ -117,7 +117,38 @@ export async function run(context) {
|
||||
const warehouseId = id(initial?.summary?.warehouseId)
|
||||
const smartLoadingBase = '/api/platform/v1/first-leg/smart-loading'
|
||||
const pdaBase = '/api/platform/v1/first-leg/pda-loading'
|
||||
const candidateResponse = await context.request('GET', `${smartLoadingBase}/candidates?warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const bookingDraft = {
|
||||
customerId: tenantId, customerName: initial?.summary?.customerName,
|
||||
warehouseId, warehouseName: initial?.summary?.warehouseName,
|
||||
channelCode: initial?.summary?.channelCode, channelName: initial?.summary?.channelName,
|
||||
shippingCompanyName: `P3-SHIPPING-${suffix}`, bookingAgentName: `P3-AGENT-${suffix}`,
|
||||
departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX',
|
||||
destinationPortName: '洛杉矶', placeOfDelivery: 'LAX', serviceTerm: 'CY/CY', freightTerm: 'PREPAID',
|
||||
remark: context.marker,
|
||||
parties: [{ partyType: 'SHIPPER', partyName: `P3-SHIPPER-${suffix}` },
|
||||
{ partyType: 'CONSIGNEE', partyName: `P3-CONSIGNEE-${suffix}` },
|
||||
{ partyType: 'NOTIFY_PARTY', partyName: `P3-NOTIFY-${suffix}` }],
|
||||
containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', capacityCbm: 100,
|
||||
maxWeightKg: 100000, quantity: 1 }],
|
||||
}
|
||||
const bookingCreateResponse = await request(context, 'POST', '/api/platform/v1/ocean-bookings',
|
||||
{ expectedVersion: 0, data: bookingDraft },
|
||||
{ 'Idempotency-Key': `${context.marker}-p3-booking-create`.slice(0, 128) })
|
||||
const bookingCreated = context.apiSuccess(MODULE, '创建带渠道仓库快照的三期订舱', bookingCreateResponse, '', OPTIONS)
|
||||
const bookingId = id(bookingCreated?.oceanBooking?.id)
|
||||
if (!bookingId) return
|
||||
const bookingSubmitResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/submit`,
|
||||
{ expectedVersion: Number(bookingCreated.oceanBooking.version), reason: context.marker },
|
||||
{ 'Idempotency-Key': `${context.marker}-p3-booking-submit`.slice(0, 128) })
|
||||
const bookingSubmitted = context.apiSuccess(MODULE, '提交三期订舱', bookingSubmitResponse, '', OPTIONS)
|
||||
const bookingConfirmResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/confirm`,
|
||||
{ expectedVersion: Number(bookingSubmitted?.oceanBooking?.version), reason: context.marker },
|
||||
{ 'Idempotency-Key': `${context.marker}-p3-booking-confirm`.slice(0, 128) })
|
||||
const bookingConfirmed = context.apiSuccess(MODULE, '确认订舱并自动生成三期柜次', bookingConfirmResponse, '', OPTIONS)
|
||||
const containerBookingId = id(bookingConfirmed?.containerAllocations?.[0]?.containerBookingId)
|
||||
if (!containerBookingId) return
|
||||
|
||||
const candidateResponse = await context.request('GET', `${smartLoadingBase}/candidates?bookingId=${encodeURIComponent(containerBookingId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const candidates = context.apiSuccess(MODULE, '查询本轮入库单柜次装柜候选', candidateResponse, '', OPTIONS)
|
||||
const candidate = Array.isArray(candidates) ? candidates.find((item) => id(item.inboundId) === inboundId
|
||||
&& Number(item.availablePackageCount) > 0) : null
|
||||
@@ -128,21 +159,6 @@ export async function run(context) {
|
||||
if (!candidate?.inboundDetailId || !warehouseId) return
|
||||
|
||||
const containerNo = `P3B-${suffix}`
|
||||
const containerResponse = await request(context, 'POST', '/api/platform/v1/containers', {
|
||||
containerNo, cabinetNo: `P3BI-${suffix}`, warehouseId,
|
||||
warehouseName: initial?.summary?.warehouseName || null, billOfLadingNo: `P3BL-${suffix}`,
|
||||
vesselName: `P3-BRIDGE-${suffix}`, voyageNo: `P3V-${suffix}`, containerType: '40HQ',
|
||||
shippingCompanyName: `P3-SHIPPING-${suffix}`, departurePortCode: 'CNSZX', departurePortName: '深圳',
|
||||
destinationPortCode: 'USLAX', destinationPortName: '洛杉矶',
|
||||
destinationWarehouseCode: candidate.destinationWarehouseCode || null,
|
||||
destinationWarehouseName: candidate.destinationWarehouseName || null,
|
||||
capacityCbm: 100, maxWeightKg: 100000, loadingAt: '2026-09-02T08:00:00',
|
||||
estimatedSailingAt: '2026-09-03T08:00:00', estimatedPortArrivalAt: '2026-09-20T08:00:00',
|
||||
estimatedWarehouseArrivalAt: '2026-09-21T08:00:00',
|
||||
})
|
||||
const container = context.apiSuccess(MODULE, '创建与入库预装串联的柜次', containerResponse, '', OPTIONS)
|
||||
const containerBookingId = id(container?.id)
|
||||
if (!containerBookingId) return
|
||||
|
||||
const loadingPlanResponse = await request(context, 'POST', `${smartLoadingBase}/plans`, {
|
||||
bookingId: containerBookingId, remark: context.marker,
|
||||
@@ -203,6 +219,20 @@ export async function run(context) {
|
||||
&& Number(pushedLoadingPlan?.version) === Number(link.loadingPlanVersion) + 1,
|
||||
`status=${pushedLoadingPlan?.status || 'missing'}, version=${pushedLoadingPlan?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const containerBeforeTransportResponse = await context.request('GET',
|
||||
`/api/platform/v1/containers/${containerBookingId}`)
|
||||
const containerBeforeTransport = context.apiSuccess(MODULE, '读取补真实柜号前柜次版本',
|
||||
containerBeforeTransportResponse, '', OPTIONS)
|
||||
const transportResponse = await request(context, 'PATCH',
|
||||
`/api/platform/v1/containers/${containerBookingId}/transport`, {
|
||||
containerNo, voyageNo: `P3V-${suffix}`, version: Number(containerBeforeTransport?.version),
|
||||
reason: `${context.marker}-bind-container-no`,
|
||||
})
|
||||
const transported = context.apiSuccess(MODULE, '补真实柜号并开放三期 PDA 任务', transportResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '真实柜号绑定到稳定柜次且计划身份不变', transportResponse,
|
||||
transported?.containerNo === containerNo && id(transported?.id) === containerBookingId,
|
||||
`containerNo=${transported?.containerNo || 'missing'}, containerBookingId=${id(transported?.id) || 'missing'}`, OPTIONS)
|
||||
|
||||
const blockedBeforePreloadConfirm = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
|
||||
{ expectedVersion: initialVersion, remark: 'must confirm preload first' },
|
||||
{ 'Idempotency-Key': `${context.marker}-p3-complete-before-preload-confirm`.slice(0, 128) })
|
||||
@@ -211,7 +241,7 @@ export async function run(context) {
|
||||
`HTTP=${blockedBeforePreloadConfirm.http}, code=${codeOf(blockedBeforePreloadConfirm)}`, OPTIONS)
|
||||
|
||||
const canonicalTaskResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks?containerNo=${encodeURIComponent(containerNo)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const canonicalTask = context.apiSuccess(MODULE, '读取柜次级 PDA 唯一事实任务', canonicalTaskResponse, '', OPTIONS)
|
||||
const canonicalTaskId = id(canonicalTask?.id)
|
||||
const canonicalItem = canonicalTask?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
|
||||
@@ -298,9 +298,10 @@ test('phase3 loading provisions its own marker-isolated WAIT_SPLIT fixture', asy
|
||||
assert.equal(run.marker.includes('-LOAD'), false)
|
||||
})
|
||||
|
||||
test('container fixture keeps the run marker and does not publish incomplete WAIT_PRICING resources', () => {
|
||||
test('container fixture isolates order idempotency while publishing the run marker', () => {
|
||||
const entry = source('../admin-test-env-acceptance.mjs')
|
||||
assert.match(entry, /const fixtureMarker = originalMarker/)
|
||||
assert.match(entry, /const fixtureMarker = `\$\{originalMarker\}-CAB`/)
|
||||
assert.match(entry, /marker: originalMarker,\s*\n\s*sourceMarker: fixtureMarker/)
|
||||
assert.match(entry, /fixture\.finalStatus !== 'APPROVED'/)
|
||||
assert.match(entry, /禁止回退使用 WAIT_PRICING 订单/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user