diff --git a/scripts/admin-test-env-modules/orders-lifecycle-idempotency-contract.test.mjs b/scripts/admin-test-env-modules/orders-lifecycle-idempotency-contract.test.mjs index 8c71f0bff..75f43d7ca 100644 --- a/scripts/admin-test-env-modules/orders-lifecycle-idempotency-contract.test.mjs +++ b/scripts/admin-test-env-modules/orders-lifecycle-idempotency-contract.test.mjs @@ -46,3 +46,10 @@ test('approval convergence is a hard WAIT_MEASURE prerequisite without coverage assert.match(source, /throw new Error\('订单审核后的入库单与履约任务未在30秒内收敛/) assert.doesNotMatch(source, /审核后入库单与履约任务异步收敛[\s\S]{0,300}addCoverageGap/) }) + +test('post-approval declaration update precedes asynchronous fulfilment convergence', () => { + const approval = source.indexOf("Admin 审核通过本轮订单") + const declaration = source.indexOf("Admin 修改审核后客户申报资料") + const convergence = source.indexOf('const inboundResult = await waitForInbound') + assert.ok(approval >= 0 && declaration > approval && convergence > declaration) +}) diff --git a/scripts/admin-test-env-modules/orders.mjs b/scripts/admin-test-env-modules/orders.mjs index 599ccc45f..3ef047798 100644 --- a/scripts/admin-test-env-modules/orders.mjs +++ b/scripts/admin-test-env-modules/orders.mjs @@ -684,6 +684,41 @@ export async function run(context) { `/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/approve?tenantId=${encodeURIComponent(tenantId)}`) context.apiSuccess(MODULE, 'Admin 审核通过本轮订单', approveResponse, `orderId=${orderId}`) + // The declaration API deliberately accepts APPROVED only. Do this before waiting for + // asynchronous inbound/task convergence, which may move the order into fulfilment. + const editableDetailResponse = await context.request('GET', firstLegDetailPath) + const editableDetail = context.apiSuccess(MODULE, 'Admin 读取审核后客户申报资料', editableDetailResponse) + const declarationDetail = editableDetail?.order?.order ? editableDetail.order : editableDetail + const declarationOrder = declarationDetail?.order || {} + const declarationCargoes = Array.isArray(declarationDetail?.cargoes) ? declarationDetail.cargoes : [] + const declarationVersion = Number(declarationOrder?.version) + const declarationRemark = `${context.marker} Admin 修改客户申报资料` + const declarationPayload = { + expectedVersion: declarationVersion, + customerRemark: declarationRemark, + expectedArrivalAt: declarationOrder?.expectedArrivalAt || editableDetail?.summary?.expectedArrivalAt || null, + channelCode: declarationOrder?.channelCode || editableDetail?.summary?.channelCode, + channelName: declarationOrder?.channelName || editableDetail?.summary?.channelName, + cargoes: declarationCargoes.map(declarationCargo), + } + const declarationUpdateResponse = await context.request('PUT', + `/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`, + declarationPayload) + const declarationUpdate = context.apiSuccess(MODULE, 'Admin 修改审核后客户申报资料', declarationUpdateResponse) + context.assert(MODULE, '申报修改保持订单号入仓码和渠道且版本递增', declarationUpdateResponse, + Number.isFinite(declarationVersion) && Number(declarationUpdate?.version) === declarationVersion + 1 + && id(declarationUpdate?.inboundCode) === id(firstLegDetail?.order?.inboundCode) + && declarationUpdate?.channelChanged === false, + `version=${declarationVersion}->${declarationUpdate?.version ?? 'missing'}, inboundCode=${declarationUpdate?.inboundCode ?? 'missing'}`, + CONTRACT_OPTIONS) + + const staleDeclarationResponse = await context.request('PUT', + `/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`, + { ...declarationPayload, customerRemark: `${declarationRemark} stale` }) + context.assert(MODULE, '申报资料旧版本并发修改返回A0409', staleDeclarationResponse, + staleDeclarationResponse.http === 200 && String(staleDeclarationResponse.body?.code) === 'A0409', + `code=${staleDeclarationResponse.body?.code ?? 'missing'}`, CONTRACT_OPTIONS) + const inboundResult = await waitForInbound(context, tenantId, orderId) const inboundId = id(inboundResult.detail?.summary?.inboundId) if (!inboundId || !inboundResult.task) { @@ -716,39 +751,6 @@ export async function run(context) { pageOk(inboundPage) && records(inboundPage).some((row) => id(row.id) === inboundId), `inboundId=${inboundId}, total=${inboundPage?.total ?? 'n/a'}`) - const editableDetailResponse = await context.request('GET', firstLegDetailPath) - const editableDetail = context.apiSuccess(MODULE, 'Admin 读取审核后客户申报资料', editableDetailResponse) - const declarationDetail = editableDetail?.order?.order ? editableDetail.order : editableDetail - const declarationOrder = declarationDetail?.order || {} - const declarationCargoes = Array.isArray(declarationDetail?.cargoes) ? declarationDetail.cargoes : [] - const declarationVersion = Number(declarationOrder?.version) - const declarationRemark = `${context.marker} Admin 修改客户申报资料` - const declarationPayload = { - expectedVersion: declarationVersion, - customerRemark: declarationRemark, - expectedArrivalAt: declarationOrder?.expectedArrivalAt || editableDetail?.summary?.expectedArrivalAt || null, - channelCode: declarationOrder?.channelCode || editableDetail?.summary?.channelCode, - channelName: declarationOrder?.channelName || editableDetail?.summary?.channelName, - cargoes: declarationCargoes.map(declarationCargo), - } - const declarationUpdateResponse = await context.request('PUT', - `/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`, - declarationPayload) - const declarationUpdate = context.apiSuccess(MODULE, 'Admin 修改审核后客户申报资料', declarationUpdateResponse) - context.assert(MODULE, '申报修改保持订单号入仓码和渠道且版本递增', declarationUpdateResponse, - Number.isFinite(declarationVersion) && Number(declarationUpdate?.version) === declarationVersion + 1 - && id(declarationUpdate?.inboundCode) === id(inbound.summary.inboundCode) - && declarationUpdate?.channelChanged === false, - `version=${declarationVersion}->${declarationUpdate?.version ?? 'missing'}, inboundCode=${declarationUpdate?.inboundCode ?? 'missing'}`, - CONTRACT_OPTIONS) - - const staleDeclarationResponse = await context.request('PUT', - `/api/platform/v1/orders/${encodeURIComponent(orderId)}/declaration?tenantId=${encodeURIComponent(tenantId)}`, - { ...declarationPayload, customerRemark: `${declarationRemark} stale` }) - context.assert(MODULE, '申报资料旧版本并发修改返回A0409', staleDeclarationResponse, - staleDeclarationResponse.http === 200 && String(staleDeclarationResponse.body?.code) === 'A0409', - `code=${staleDeclarationResponse.body?.code ?? 'missing'}`, CONTRACT_OPTIONS) - let declarationConverged = false let convergedInboundResponse = inboundResponse for (let attempt = 0; attempt < 30; attempt += 1) {