diff --git a/scripts/admin-test-env-modules/container-customs-acceptance-contract.test.mjs b/scripts/admin-test-env-modules/container-customs-acceptance-contract.test.mjs index ae7b4af76..7a7dc1541 100644 --- a/scripts/admin-test-env-modules/container-customs-acceptance-contract.test.mjs +++ b/scripts/admin-test-env-modules/container-customs-acceptance-contract.test.mjs @@ -69,7 +69,7 @@ test('HTTP scenario contains file, state, idempotency, concurrency and zero-drif for (const token of ['EXACT_50_MB + 1', '.customblob', 'application/octet-stream', '缺退税资料禁止开始且零漂移', '开始后资料范围锁定A0409零漂移', "['上传'", "['覆盖'", "['改关联'", "['删除'", '开始后${label}锁定A0409零漂移', - '最后一份完成文件前禁止整柜完成', '最后一份完成文件后纯退税柜完成', + '最后一份完成文件前禁止整柜完成', '客户确认整份回执后纯退税柜完成', '客户确认整份回执并同键重放', '混合柜平台详情精确包含BUY与TAX长ID票', 'Client只见本客户TAX票且完全隐藏BUY票', '买单导出只处理实际装柜BUY行', '混合柜最后一份完成文件后整柜COMPLETED', '自动离仓只写一次SYSTEM历史', '自动离仓只创建一个OUTBOUND_CONFIRM长ID任务', diff --git a/scripts/admin-test-env-modules/container-customs-acceptance-matrix.mjs b/scripts/admin-test-env-modules/container-customs-acceptance-matrix.mjs index 1c3f1c363..7d49e220d 100644 --- a/scripts/admin-test-env-modules/container-customs-acceptance-matrix.mjs +++ b/scripts/admin-test-env-modules/container-customs-acceptance-matrix.mjs @@ -15,6 +15,7 @@ export const REQUIRED_ROUTE_OPERATIONS = Object.freeze([ 'merchantReplaceDocument', 'merchantUpdateScope', 'merchantDeleteDocument', + 'merchantConfirmReceipt', 'platformStart', 'platformUploadCompletion', 'platformComplete', @@ -28,7 +29,7 @@ export const CONTAINER_CUSTOMS_MATRIX = Object.freeze([ { priority: 'P0', fixture: 'ALL_BUY_DECLARATION', scenario: 'LOADED详情与买单XLSX', expected: 'XLSX只含柜内买单实装行,下载窗口可用' }, { priority: 'P0', fixture: 'ALL_BUY_DECLARATION', scenario: '开始与完成', expected: 'READY_TO_START→DECLARING→COMPLETED,重放返回同一结果' }, { priority: 'P0', fixture: 'ALL_TAX_REFUND', scenario: '客户多对多资料', expected: '上传、覆盖、改关联、删除均保留版本与客户范围' }, - { priority: 'P0', fixture: 'ALL_TAX_REFUND', scenario: '冻结与逐份完成文件', expected: '开始冻结来源文件和关联;最后一份完成文件后才允许整柜完成' }, + { priority: 'P0', fixture: 'ALL_TAX_REFUND', scenario: '冻结、回执确认与逐份完成文件', expected: '开始冻结来源文件和关联;最后一份平台回执上传后由客户确认整份回执,才允许整柜完成' }, { priority: 'P0', fixture: 'MIXED', scenario: '买单与退税混合', expected: '买单行进入XLSX,退税票逐票被资料覆盖,整柜统一开始/完成' }, { priority: 'P0', fixture: 'ALL_TAX_REFUND', scenario: '缺资料禁止开始', expected: 'A0409且聚合、文件、关联、历史零漂移' }, { priority: 'P0', fixture: 'MIXED', scenario: '自动离仓失败恢复', expected: '报关COMPLETED不回滚;待条件满足后后台重试并单次离仓' }, diff --git a/scripts/admin-test-env-modules/phase3-container-customs-fixture.mjs b/scripts/admin-test-env-modules/phase3-container-customs-fixture.mjs index c61286914..3df5cc060 100644 --- a/scripts/admin-test-env-modules/phase3-container-customs-fixture.mjs +++ b/scripts/admin-test-env-modules/phase3-container-customs-fixture.mjs @@ -81,6 +81,10 @@ function routes(context, merchantToken) { merchantDeleteDocument: (ctx, fixture, customer, request, idempotencyKey) => ctx.request('DELETE', `${MERCHANT}/documents/${encodeURIComponent(id(request.documentId))}?expectedVersion=${Number(request.expectedVersion)}`, undefined, customer.customerToken || merchantToken, { 'Idempotency-Key': idempotencyKey }), + merchantConfirmReceipt: (ctx, fixture, customer, request, idempotencyKey) => ctx.request('POST', + `${MERCHANT}/documents/${encodeURIComponent(id(request.documentId))}/receipt/${request.confirm === false ? 'reject' : 'confirm'}`, + { expectedVersion: Number(request.expectedVersion), remark: request.remark || '' }, + customer.customerToken || merchantToken, { 'Idempotency-Key': idempotencyKey }), platformStart: (ctx, fixture, request, idempotencyKey) => ctx.request('POST', `${PLATFORM}/${encodeURIComponent(id(fixture.loadingPlanId))}/start`, request, ctx.token, { 'Idempotency-Key': idempotencyKey }), diff --git a/scripts/admin-test-env-modules/phase3-container-customs.mjs b/scripts/admin-test-env-modules/phase3-container-customs.mjs index dfe66a3f5..ecfed8107 100644 --- a/scripts/admin-test-env-modules/phase3-container-customs.mjs +++ b/scripts/admin-test-env-modules/phase3-container-customs.mjs @@ -201,11 +201,22 @@ async function runRefund(context, routes, fixture) { } } const completedReady = await platformDetail(context, routes, fixture) - const completed = await routes.platformComplete(context, fixture, { - expectedVersion: Number(completedReady.body?.data?.version), remark: context.marker, - }, key(context, 'refund-complete')) - context.assert(MODULE, '最后一份完成文件后纯退税柜完成', completed, - success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS) + const confirmed = [] + for (const customer of customers) { + const document = documents.find((item) => (item.inboundIds || []).some((inboundId) => + customer.inboundIds.map(id).includes(id(inboundId)))) + if (!document) continue + const version = Number(document.source?.version || completedReady.body?.data?.documents + ?.find((item) => id(item.documentId) === id(document.documentId))?.source?.version || 0) + const response = await routes.merchantConfirmReceipt(context, fixture, customer, { + documentId: id(document.documentId), expectedVersion: version, confirm: true, remark: context.marker, + }, key(context, `refund-receipt-confirm-${customer.customerId}`)) + confirmed.push(response) + } + const completed = await platformDetail(context, routes, fixture) + context.assert(MODULE, '客户确认整份回执后纯退税柜完成', completed, + success(completed) && completed.body?.data?.status === 'COMPLETED' + && confirmed.every(success), evidence(completed), OPTIONS) const firstDocument = documents[0] if (firstDocument) { @@ -335,6 +346,19 @@ async function runMixedConcurrency(context, routes, fixture) { context.assert(MODULE, '完成文件同键异载荷A0409', completionDifferent, a0409(completionDifferent), evidence(completionDifferent), OPTIONS) + const receiptVersion = Number(completion.body?.data?.source?.version || completion.body?.data?.version || 0) + const receiptConfirm = await routes.merchantConfirmReceipt(context, fixture, customer, { + documentId: customer.documentId, expectedVersion: receiptVersion, confirm: true, remark: context.marker, + }, key(context, 'mixed-receipt-confirm')) + const receiptReplay = await routes.merchantConfirmReceipt(context, fixture, customer, { + documentId: customer.documentId, expectedVersion: receiptVersion, confirm: true, remark: context.marker, + }, key(context, 'mixed-receipt-confirm')) + context.assert(MODULE, '客户确认整份回执并同键重放', receiptReplay || receiptConfirm, + success(receiptConfirm) && success(receiptReplay) + && receiptConfirm.body?.data?.status === 'COMPLETED' + && receiptReplay.body?.data?.status === 'COMPLETED', + [receiptConfirm, receiptReplay].map((response) => evidence(response)).join('; '), OPTIONS) + let completed = await platformDetail(context, routes, fixture) context.assert(MODULE, '混合柜最后一份完成文件后整柜COMPLETED', completed, success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS)