Files
baiyue-firstleg/scripts/admin-test-env-modules/container-customs-acceptance-matrix.mjs
T

106 lines
5.3 KiB
JavaScript

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',
])