export const CONTAINER_CUSTOMS_MODULE = 'phase3-container-customs' export const EXACT_50_MB = 50 * 1024 * 1024 export const CONTAINER_CUSTOMS_FIXTURE_KINDS = Object.freeze([ 'ALL_BUY_DECLARATION', 'ALL_TAX_REFUND', 'MIXED', ]) export const REQUIRED_ROUTE_OPERATIONS = Object.freeze([ 'platformDetail', 'merchantDetail', 'buyDeclarationXlsx', 'merchantUploadDocument', 'merchantReplaceDocument', 'merchantUpdateScope', 'merchantDeleteDocument', 'merchantConfirmReceipt', 'platformStart', 'platformSendInitialToBroker', 'platformUploadReceipt', 'platformSubmitToBroker', 'platformUploadFinal', 'platformComplete', 'platformDownloadReceipt', 'platformDownloadFinal', 'merchantDownloadReceipt', 'merchantDownloadFinal', 'outboundState', 'satisfyOutboundPrerequisites', ]) 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: 'MIXED', scenario: '买单与退税混合', expected: '买单行进入XLSX,退税票逐票被资料覆盖,整柜统一开始/完成' }, { priority: 'P0', fixture: 'ALL_TAX_REFUND', scenario: '缺资料禁止开始', expected: 'A0409且聚合、文件、关联、历史零漂移' }, { priority: 'P0', fixture: 'MIXED', scenario: '自动离仓失败恢复', expected: '报关COMPLETED不回滚;待条件满足后后台重试并单次离仓' }, { priority: 'P1', fixture: 'ALL_TAX_REFUND', scenario: '开始后锁定', expected: '上传、覆盖、改关联、删除全部A0409零漂移' }, { priority: 'P1', fixture: 'ALL_TAX_REFUND', scenario: '完成文件继承范围', expected: '完成文件只继承冻结关联,不接收客户端范围' }, { priority: 'P1', fixture: 'MIXED', scenario: '客户隔离与买单隐藏', expected: '客户只见本企业退税资料;买单XLSX及其他客户文件不可见' }, { priority: 'P1', fixture: 'ALL_TAX_REFUND', scenario: '文件边界', expected: '任意格式允许;50MiB成功;50MiB+1拒绝且零写入' }, { priority: 'P1', fixture: 'MIXED', scenario: '幂等与并发', expected: '同键同载荷同结果;异载荷/并发冲突A0409且零漂移' }, { priority: 'P1', fixture: 'MIXED', scenario: '历史与Outbox', expected: '每个真实动作单条历史;自动离仓意图/结果可追踪且无重复' }, { priority: 'P1', fixture: 'MIXED', scenario: 'Admin与Client权限', expected: '列表/详情/写权限分离,客户下载仅限自身可见文件' }, ]) export function validateContainerCustomsFixtures(value, marker, requestedKinds = CONTAINER_CUSTOMS_FIXTURE_KINDS) { if (value?.marker !== marker || !Array.isArray(value?.containers)) return false const byKind = new Map(value.containers.map((item) => [item?.kind, item])) return requestedKinds.every((kind) => { const fixture = byKind.get(kind) return fixture?.marker === marker && fixture?.status === 'LOADED' && /^\d+$/.test(String(fixture?.loadingPlanId ?? '')) && /^\d+$/.test(String(fixture?.containerBookingId ?? '')) && Array.isArray(fixture?.inbounds) && fixture.inbounds.length > 0 && fixture.inbounds.every((inbound) => /^\d+$/.test(String(inbound?.inboundId ?? '')) && /^\d+$/.test(String(inbound?.customerId ?? '')) && ['BUY_DECLARATION', 'TAX_REFUND'].includes(inbound?.taxMode)) }) } export function validateRouteContract(routes) { return routes?.version != null && REQUIRED_ROUTE_OPERATIONS.every((name) => typeof routes?.[name] === 'function') } export function stableSnapshot(value) { const normalize = (current) => { if (Array.isArray(current)) return current.map(normalize) if (current != null && typeof current === 'object') { return Object.fromEntries(Object.keys(current).sort().map((key) => [key, normalize(current[key])])) } return current } return JSON.stringify(normalize(value)) } export function zeroDrift(before, after) { return stableSnapshot(before) === stableSnapshot(after) } export function xlsxMagic(bytes) { return bytes instanceof Uint8Array && bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b && bytes[2] === 0x03 && bytes[3] === 0x04 } export const READ_ONLY_DB_EVIDENCE = Object.freeze([ 'fl_customs_container', 'fl_customs_customer_document', 'fl_customs_customer_document_inbound', 'fl_customs_customer_document_version', 'fl_customs_frozen_document_inbound', 'fl_customs_completion_document', 'fl_customs_completion_document_version', 'fl_customs_final_document', 'fl_customs_command', 'fl_customs_container_history', 'fl_outbound_dispatch', 'fl_outbound_dispatch_history', 'fl_event_outbox', ])