From e46d541780032f51fc7026a28628dc49f0946784 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 26 Sep 2026 13:57:53 +0800 Subject: [PATCH] fix(firstleg): map broker submission fields in customs container --- .../mapper/ContainerCustomsMapper.java | 3 ++- ...ntainer-customs-acceptance-contract.test.mjs | 8 ++++---- .../phase3-container-customs.mjs | 17 ++++++++++------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java index 5839894b1..7f13b0b06 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java @@ -441,7 +441,8 @@ public interface ContainerCustomsMapper { String cabinetNo,String containerNo) {} record ContainerRow(Long id,Long loadingPlanId,Long containerBookingId,String status,Long startedBy, String startedByName,LocalDateTime startedAt,Long completedBy,String completedByName, - LocalDateTime completedAt,LocalDateTime createdAt,LocalDateTime updatedAt,Integer version) {} + LocalDateTime completedAt,Long brokerSubmittedBy,String brokerSubmittedByName, + LocalDateTime brokerSubmittedAt,LocalDateTime createdAt,LocalDateTime updatedAt,Integer version) {} record DocumentRow(Long id,Long customsContainerId,Long tenantId,Long fileId,String fileName,String fileHash, String contentType,Long sizeBytes,String status,Long uploadedBy,LocalDateTime uploadedAt, Integer frozenVersion,LocalDateTime frozenAt,Integer version) {} 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 7a7dc1541..d5d3f09ed 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 @@ -49,8 +49,8 @@ test('mixed fixture producer is registered and uses only public HTTP composition test('coverage matrix freezes every requested product boundary', () => { const text = CONTAINER_CUSTOMS_MATRIX.map((item) => `${item.scenario}:${item.expected}`).join('\n') - for (const token of ['买单XLSX', '多对多', '覆盖', '改关联', '删除', '冻结', '逐份完成文件', - '最后一份', '混合', '缺资料', '开始后锁定', '客户隔离', '买单隐藏', '50MiB', '任意格式', + for (const token of ['买单XLSX', '多对多', '覆盖', '改关联', '删除', '冻结', '回执确认', '报关行执行', '完税文件', + '最后上传', '混合', '缺资料', '开始后锁定', '客户隔离', '买单隐藏', '50MiB', '任意格式', '幂等', '并发', 'A0409', '零漂移', '自动离仓', 'Outbox', '历史', 'Admin与Client权限']) { assert.match(text, new RegExp(token)) } @@ -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任务', @@ -87,7 +87,7 @@ test('HTTP scenario contains file, state, idempotency, concurrency and zero-drif test('database evidence list covers atomicity, history, dispatch and outbox without embedding SQL writes', () => { for (const table of ['fl_customs_container', 'fl_customs_customer_document_inbound', - 'fl_customs_completion_document', 'fl_customs_command', 'fl_customs_container_history', + 'fl_customs_completion_document', 'fl_customs_final_document', 'fl_customs_command', 'fl_customs_container_history', 'fl_outbound_dispatch', 'fl_event_outbox']) assert.ok(READ_ONLY_DB_EVIDENCE.includes(table), table) assert.doesNotMatch(source, /\b(?:INSERT|UPDATE|DELETE)\s+(?:INTO|FROM|[a-z_])/i) }) diff --git a/scripts/admin-test-env-modules/phase3-container-customs.mjs b/scripts/admin-test-env-modules/phase3-container-customs.mjs index 1b8f1339b..b467a8b65 100644 --- a/scripts/admin-test-env-modules/phase3-container-customs.mjs +++ b/scripts/admin-test-env-modules/phase3-container-customs.mjs @@ -58,12 +58,12 @@ async function runAllBuy(context, routes, fixture) { xlsx.http === 200 && xlsxMagic(xlsx.bytes), `HTTP=${xlsx.http}, bytes=${xlsx.bytes?.length ?? 0}, contentType=${xlsx.contentType || 'missing'}`, OPTIONS) - const startBody = { expectedVersion: Number(detail.body?.data?.version), remark: context.marker } + const startBody = { expectedVersion: Number(detail.body?.data?.version), confirm: true, remark: context.marker } const started = await routes.platformStart(context, fixture, startBody, key(context, 'all-buy-start')) context.assert(MODULE, '全买单柜开始报关', started, success(started) && started.body?.data?.status === 'DECLARING', evidence(started), OPTIONS) - const completeBody = { expectedVersion: Number(started.body?.data?.version), remark: context.marker } + const completeBody = { expectedVersion: Number(started.body?.data?.version), confirm: true, remark: context.marker } const completed = await routes.platformComplete(context, fixture, completeBody, key(context, 'all-buy-complete')) context.assert(MODULE, '全买单柜完成并触发自动离仓意图', completed, success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS) @@ -153,7 +153,7 @@ async function runRefund(context, routes, fixture) { const ready = await platformDetail(context, routes, fixture) const started = await routes.platformStart(context, fixture, { - expectedVersion: Number(ready.body?.data?.version), remark: context.marker, + expectedVersion: Number(ready.body?.data?.version), confirm: true, remark: context.marker, }, key(context, 'refund-start')) context.assert(MODULE, '纯退税柜资料齐全后开始并冻结', started, success(started) && started.body?.data?.status === 'DECLARING' @@ -184,7 +184,7 @@ async function runRefund(context, routes, fixture) { const documents = frozenSnapshot?.documents || [] for (const [index, document] of documents.entries()) { const receipt = await routes.platformUploadReceipt(context, fixture, { - documentId: id(document.documentId), expectedVersion: Number(document.source?.version), + documentId: id(document.documentId), expectedVersion: 0, fileName: `${context.marker}-broker-receipt-${index + 1}.pdf`, contentType: 'application/pdf', content: Uint8Array.from([0x52, index + 1]), }, key(context, `receipt-${index + 1}`)) @@ -216,11 +216,11 @@ async function runRefund(context, routes, fixture) { success(afterConfirm) && afterConfirm.body?.data?.status === 'DECLARING' && confirmed.every(success), evidence(afterConfirm), OPTIONS) const submitted = await routes.platformSubmitToBroker(context, fixture, - { expectedVersion: Number(afterConfirm.body?.data?.version), remark: context.marker }, key(context, 'submit-to-broker')) + { expectedVersion: Number(afterConfirm.body?.data?.version), confirm: true, remark: context.marker }, key(context, 'submit-to-broker')) context.assert(MODULE, '平台发送客户确认回执给报关行执行', submitted, success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS) const submitReplay = await routes.platformSubmitToBroker(context, fixture, - { expectedVersion: Number(submitted.body?.data?.version), remark: context.marker }, key(context, 'submit-to-broker')) + { expectedVersion: Number(afterConfirm.body?.data?.version), confirm: true, remark: context.marker }, key(context, 'submit-to-broker')) context.assert(MODULE, '发送报关行执行同键重放', submitReplay, success(submitReplay), evidence(submitReplay), OPTIONS) for (const [index, document] of documents.entries()) { const finalDocument = await routes.platformUploadFinal(context, fixture, { @@ -365,7 +365,7 @@ async function runMixedConcurrency(context, routes, fixture) { && receiptConfirmReplay.body?.data?.status === 'DECLARING', [receiptConfirm, receiptConfirmReplay].map((response) => evidence(response)).join('; '), OPTIONS) const submitted = await routes.platformSubmitToBroker(context, fixture, - { expectedVersion: Number(receiptConfirm.body?.data?.version), remark: context.marker }, key(context, 'mixed-submit-to-broker')) + { expectedVersion: Number(receiptConfirm.body?.data?.version), confirm: true, remark: context.marker }, key(context, 'mixed-submit-to-broker')) context.assert(MODULE, '混合柜发送确认回执给报关行执行', submitted, success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS) const finalRequest = { @@ -381,6 +381,9 @@ async function runMixedConcurrency(context, routes, fixture) { && id(finalDocument.body?.data?.finalDocument?.fileId) === id(finalReplay.body?.data?.finalDocument?.fileId) context.assert(MODULE, '真实报关完税文件同键重放收敛', finalReplay || finalDocument, finalConverged, [finalDocument, finalReplay].filter(Boolean).map((response) => evidence(response)).join('; '), OPTIONS) + if (!finalConverged) { + blocker(context, '完税文件幂等收敛', '完成文件未成功或同键重放未收敛,已停止后续自动离仓写入') + } const finalDifferent = await routes.platformUploadFinal(context, fixture, { ...finalRequest, content: Uint8Array.from([0x44, 0x49, 0x46]) }, finalKey) context.assert(MODULE, '完税文件同键异载荷A0409', finalDifferent, a0409(finalDifferent), evidence(finalDifferent), OPTIONS)