fix(firstleg): map broker submission fields in customs container

This commit is contained in:
2026-09-26 13:57:53 +08:00
parent ff2386e5bf
commit e46d541780
3 changed files with 16 additions and 12 deletions
@@ -441,7 +441,8 @@ public interface ContainerCustomsMapper {
String cabinetNo,String containerNo) {} String cabinetNo,String containerNo) {}
record ContainerRow(Long id,Long loadingPlanId,Long containerBookingId,String status,Long startedBy, record ContainerRow(Long id,Long loadingPlanId,Long containerBookingId,String status,Long startedBy,
String startedByName,LocalDateTime startedAt,Long completedBy,String completedByName, 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, record DocumentRow(Long id,Long customsContainerId,Long tenantId,Long fileId,String fileName,String fileHash,
String contentType,Long sizeBytes,String status,Long uploadedBy,LocalDateTime uploadedAt, String contentType,Long sizeBytes,String status,Long uploadedBy,LocalDateTime uploadedAt,
Integer frozenVersion,LocalDateTime frozenAt,Integer version) {} Integer frozenVersion,LocalDateTime frozenAt,Integer version) {}
@@ -49,8 +49,8 @@ test('mixed fixture producer is registered and uses only public HTTP composition
test('coverage matrix freezes every requested product boundary', () => { test('coverage matrix freezes every requested product boundary', () => {
const text = CONTAINER_CUSTOMS_MATRIX.map((item) => `${item.scenario}:${item.expected}`).join('\n') const text = CONTAINER_CUSTOMS_MATRIX.map((item) => `${item.scenario}:${item.expected}`).join('\n')
for (const token of ['买单XLSX', '多对多', '覆盖', '改关联', '删除', '冻结', '逐份完成文件', for (const token of ['买单XLSX', '多对多', '覆盖', '改关联', '删除', '冻结', '回执确认', '报关行执行', '完税文件',
'最后一份', '混合', '缺资料', '开始后锁定', '客户隔离', '买单隐藏', '50MiB', '任意格式', '最后上传', '混合', '缺资料', '开始后锁定', '客户隔离', '买单隐藏', '50MiB', '任意格式',
'幂等', '并发', 'A0409', '零漂移', '自动离仓', 'Outbox', '历史', 'Admin与Client权限']) { '幂等', '并发', 'A0409', '零漂移', '自动离仓', 'Outbox', '历史', 'Admin与Client权限']) {
assert.match(text, new RegExp(token)) 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', for (const token of ['EXACT_50_MB + 1', '.customblob', 'application/octet-stream',
'缺退税资料禁止开始且零漂移', '开始后资料范围锁定A0409零漂移', '缺退税资料禁止开始且零漂移', '开始后资料范围锁定A0409零漂移',
"['上传'", "['覆盖'", "['改关联'", "['删除'", '开始后${label}锁定A0409零漂移', "['上传'", "['覆盖'", "['改关联'", "['删除'", '开始后${label}锁定A0409零漂移',
'最后一份完成文件前禁止整柜完成', '客户确认整份回执后纯退税柜完成', '客户确认整份回执并同键重放', '回执未确认且未发送执行时禁止上传完税文件', '客户确认整份回执后仍处于报关中', '真实报关完税文件齐全后纯退税柜完成', '客户确认回执后仍未完成整柜',
'混合柜平台详情精确包含BUY与TAX长ID票', 'Client只见本客户TAX票且完全隐藏BUY票', '混合柜平台详情精确包含BUY与TAX长ID票', 'Client只见本客户TAX票且完全隐藏BUY票',
'买单导出只处理实际装柜BUY行', '混合柜最后一份完成文件后整柜COMPLETED', '买单导出只处理实际装柜BUY行', '混合柜最后一份完成文件后整柜COMPLETED',
'自动离仓只写一次SYSTEM历史', '自动离仓只创建一个OUTBOUND_CONFIRM长ID任务', '自动离仓只写一次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', () => { 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', 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) '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) assert.doesNotMatch(source, /\b(?:INSERT|UPDATE|DELETE)\s+(?:INTO|FROM|[a-z_])/i)
}) })
@@ -58,12 +58,12 @@ async function runAllBuy(context, routes, fixture) {
xlsx.http === 200 && xlsxMagic(xlsx.bytes), xlsx.http === 200 && xlsxMagic(xlsx.bytes),
`HTTP=${xlsx.http}, bytes=${xlsx.bytes?.length ?? 0}, contentType=${xlsx.contentType || 'missing'}`, OPTIONS) `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')) const started = await routes.platformStart(context, fixture, startBody, key(context, 'all-buy-start'))
context.assert(MODULE, '全买单柜开始报关', started, context.assert(MODULE, '全买单柜开始报关', started,
success(started) && started.body?.data?.status === 'DECLARING', evidence(started), OPTIONS) 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')) const completed = await routes.platformComplete(context, fixture, completeBody, key(context, 'all-buy-complete'))
context.assert(MODULE, '全买单柜完成并触发自动离仓意图', completed, context.assert(MODULE, '全买单柜完成并触发自动离仓意图', completed,
success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS) 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 ready = await platformDetail(context, routes, fixture)
const started = await routes.platformStart(context, 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')) }, key(context, 'refund-start'))
context.assert(MODULE, '纯退税柜资料齐全后开始并冻结', started, context.assert(MODULE, '纯退税柜资料齐全后开始并冻结', started,
success(started) && started.body?.data?.status === 'DECLARING' success(started) && started.body?.data?.status === 'DECLARING'
@@ -184,7 +184,7 @@ async function runRefund(context, routes, fixture) {
const documents = frozenSnapshot?.documents || [] const documents = frozenSnapshot?.documents || []
for (const [index, document] of documents.entries()) { for (const [index, document] of documents.entries()) {
const receipt = await routes.platformUploadReceipt(context, fixture, { 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`, fileName: `${context.marker}-broker-receipt-${index + 1}.pdf`,
contentType: 'application/pdf', content: Uint8Array.from([0x52, index + 1]), contentType: 'application/pdf', content: Uint8Array.from([0x52, index + 1]),
}, key(context, `receipt-${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), success(afterConfirm) && afterConfirm.body?.data?.status === 'DECLARING' && confirmed.every(success),
evidence(afterConfirm), OPTIONS) evidence(afterConfirm), OPTIONS)
const submitted = await routes.platformSubmitToBroker(context, fixture, 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, context.assert(MODULE, '平台发送客户确认回执给报关行执行', submitted,
success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS) success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS)
const submitReplay = await routes.platformSubmitToBroker(context, fixture, 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) context.assert(MODULE, '发送报关行执行同键重放', submitReplay, success(submitReplay), evidence(submitReplay), OPTIONS)
for (const [index, document] of documents.entries()) { for (const [index, document] of documents.entries()) {
const finalDocument = await routes.platformUploadFinal(context, fixture, { const finalDocument = await routes.platformUploadFinal(context, fixture, {
@@ -365,7 +365,7 @@ async function runMixedConcurrency(context, routes, fixture) {
&& receiptConfirmReplay.body?.data?.status === 'DECLARING', && receiptConfirmReplay.body?.data?.status === 'DECLARING',
[receiptConfirm, receiptConfirmReplay].map((response) => evidence(response)).join('; '), OPTIONS) [receiptConfirm, receiptConfirmReplay].map((response) => evidence(response)).join('; '), OPTIONS)
const submitted = await routes.platformSubmitToBroker(context, fixture, 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, context.assert(MODULE, '混合柜发送确认回执给报关行执行', submitted,
success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS) success(submitted) && submitted.body?.data?.status === 'DECLARING', evidence(submitted), OPTIONS)
const finalRequest = { const finalRequest = {
@@ -381,6 +381,9 @@ async function runMixedConcurrency(context, routes, fixture) {
&& id(finalDocument.body?.data?.finalDocument?.fileId) === id(finalReplay.body?.data?.finalDocument?.fileId) && id(finalDocument.body?.data?.finalDocument?.fileId) === id(finalReplay.body?.data?.finalDocument?.fileId)
context.assert(MODULE, '真实报关完税文件同键重放收敛', finalReplay || finalDocument, finalConverged, context.assert(MODULE, '真实报关完税文件同键重放收敛', finalReplay || finalDocument, finalConverged,
[finalDocument, finalReplay].filter(Boolean).map((response) => evidence(response)).join('; '), OPTIONS) [finalDocument, finalReplay].filter(Boolean).map((response) => evidence(response)).join('; '), OPTIONS)
if (!finalConverged) {
blocker(context, '完税文件幂等收敛', '完成文件未成功或同键重放未收敛,已停止后续自动离仓写入')
}
const finalDifferent = await routes.platformUploadFinal(context, fixture, const finalDifferent = await routes.platformUploadFinal(context, fixture,
{ ...finalRequest, content: Uint8Array.from([0x44, 0x49, 0x46]) }, finalKey) { ...finalRequest, content: Uint8Array.from([0x44, 0x49, 0x46]) }, finalKey)
context.assert(MODULE, '完税文件同键异载荷A0409', finalDifferent, a0409(finalDifferent), evidence(finalDifferent), OPTIONS) context.assert(MODULE, '完税文件同键异载荷A0409', finalDifferent, a0409(finalDifferent), evidence(finalDifferent), OPTIONS)