Files

107 lines
8.1 KiB
JavaScript
Raw Permalink Normal View History

2026-10-21 13:22:35 +08:00
#!/usr/bin/env node
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { describeAcceptanceScope } from './admin-test-env-acceptance.mjs'
2026-10-22 14:37:59 +08:00
/** 首页八个业务入口的脚本审计;登记范围不是运行证据。 */
export function describeHomepageBusinessCoverage() {
const scope = describeAcceptanceScope()
return [
{ title: '头程下单', route: '/orders/first-leg/create', modules: ['orders', 'phase5-overseas-fulfillment'],
additionalModules: ['phase5-root-fulfillment', 'dropship-pallet-matrix'],
covered: '默认头程至海外履约主链;拆分根单、一件代发矩阵为可选专项',
remaining: '各订单类型、税制及根/余量分支未由默认链全部执行' },
{ title: '退货换标', route: '/overseas-warehouse/return-relabel/create', modules: ['phase4-overseas-return-receipt', 'phase4-overseas-return-followups'],
covered: '退货预报、审核、签收、合箱、换标、客户确认及换标后出库',
2026-10-22 14:37:59 +08:00
remaining: '换标、客户确认及换标后出库已接入默认后续模块;固定客户跨客户边界仍缺资源,真实HTTP待验' },
{ title: '美国本土卡', route: '/cross-border/domestic-trucking/create', modules: ['phase4-overseas-business'],
covered: '创建、编辑、审核及原单实收、两托部分至全部分配、派车、运输完成,逐步幂等重放', remaining: '独立本土卡车正常及部分分配链已接入;超托审核与真实HTTP待验,不能替代车队签收证据' },
2026-10-22 14:37:59 +08:00
{ title: '美本土散板', route: '/cross-border/pallet-shipping/create', modules: ['phase4-overseas-business', 'phase5-overseas-fulfillment'],
covered: '原单创建、需求池接车、PDA实收、原车加货及共享装车签收,核对原单终态与数量', remaining: '正常链已接入默认编排;异常履约、完整费用边界及真实HTTP待验' },
{ title: '海外采购', route: '/cross-border/procurement/create', modules: ['phase4-overseas-business', 'phase4-overseas-wallet-and-pallet-cost'],
covered: '创建、备注编辑、审核驳回;固定客户正常样品采购至寄客户及资金核对已接入默认链',
2026-10-22 14:37:59 +08:00
remaining: '固定客户已接入正常样品采购、收货、核费及寄客户;异常、退款及其他采购类型未全覆盖,真实HTTP待验' },
{ title: '贴标申请', route: '/overseas-warehouse/labeling/create', modules: ['phase4-overseas-client-services', 'phase4-overseas-dispatch', 'phase5-overseas-fulfillment'],
covered: '客户原申请审核、PDA换标、客户确认、同单新箱唛出库签收、车次释放及原费用结算回查', remaining: '异常边界与真实HTTP仍待验' },
2026-10-22 14:37:59 +08:00
{ title: '仓储转运', route: '/overseas-warehouse/storage-transfer/create', modules: ['phase4-overseas-client-services'],
covered: '客户申请、提交、审核与拒绝、取消边界;已有库存原单分配、关联换标、出库、异仓收货及唯一库存流水与结算终态核验', remaining: '在途转运入库计量、多来源及部分收货异常矩阵仍待补;真实HTTP待验' },
2026-10-22 14:37:59 +08:00
{ title: '货物废弃', route: '/overseas-warehouse/cargo-disposal/create', modules: ['phase4-overseas-client-services'],
covered: '申请、拒绝释放、批准锁箱、PDA照片与实际扣库、客户确认及终态重放', remaining: '已接入同一申请公共库存总账核对;独立箱/SKU流水证据、多来源边界及真实HTTP仍待验' },
].map(business => ({ ...business,
defaultModules: business.modules.filter(name => scope.defaultModules.includes(name)),
additionalModules: (business.additionalModules || []).map(name => ({ name,
defaultSelected: scope.defaultModules.includes(name),
skippedWithFixedResources: scope.fixedResourceSkippedModules.includes(name) })),
endToEndCoverage: 'INCOMPLETE', currentVersionVerified: false,
}))
}
2026-10-21 13:22:35 +08:00
/** 只输出白名单统计和模块名,不转抄报告中的账号、地址、凭据或业务载荷。 */
export function summarizeReport(report) {
if (!report || !Array.isArray(report.steps) || report.steps.length === 0
|| report.steps.some((step) => typeof step.passed !== 'boolean')) {
throw new Error('报告缺少有效 steps,不能认定验收结果')
}
const steps = report.steps
const failed = steps.filter((step) => !step.passed && !step.skipped)
const skipped = steps.filter((step) => step.skipped)
const skippedModules = Array.isArray(report.modules) ? report.modules.filter((module) => module.skipped).length : 0
const gaps = Array.isArray(report.coverageGaps) ? report.coverageGaps : []
const blockers = gaps.filter((gap) => gap.blocking || gap.deploymentBlocker)
const scope = report.executionScope
// 历史报告不包含计划信息,不能从断言数量或模块名字推定完整执行。
const scopeKnown = Array.isArray(scope?.plannedModules) && scope.plannedModules.length > 0
&& Array.isArray(scope.defaultModules) && scope.defaultModules.length > 0
const observedModules = new Set(steps.map((step) => step.module))
const missingModules = scopeKnown ? scope.plannedModules.filter((name) => !observedModules.has(name)) : []
const limited = Boolean(scope?.preparationOnly || scope?.returnMainChainOnly || report.fullAcceptance === false)
const failedResult = Boolean(report.fatal) || report.success === false || failed.length > 0 || blockers.length > 0
return {
result: failedResult ? 'FAILED' : skipped.length || skippedModules || gaps.length || missingModules.length || limited ? 'INCOMPLETE' : 'RECORDED_CHECKS_PASSED',
passed: steps.filter((step) => step.passed && !step.skipped).length,
failed: failed.length,
skipped: skipped.length,
skippedModules,
blockingGaps: blockers.length,
nonBlockingGaps: gaps.length - blockers.length,
failedModules: [...new Set(failed.map((step) => step.module).filter(Boolean))],
// 无请求的失败包含前置不足和模块退出,不能计作独立接口缺陷。
failuresWithRequest: failed.filter((step) => step.request).length,
failuresWithoutRequest: failed.filter((step) => !step.request).length,
scope: !scopeKnown ? 'UNKNOWN_LEGACY_REPORT' : limited ? 'LIMITED_SCOPE'
: scope.defaultModules.every((name) => scope.plannedModules.includes(name)) ? 'DEFAULT_ADMIN_PLAN' : 'SELECTED_MODULES',
missingPlannedModules: missingModules,
currentVersionVerified: false,
pageAcceptance: 'NOT_VERIFIED_BY_HTTP',
}
}
/** 默认仅展示范围;显式报告只读分析,不发现历史目录、不联网、不执行验收。 */
async function main(argv) {
if (argv.length && !(argv.length === 2 && argv[0] === '--report')) {
throw new Error('用法:node scripts/acceptance-overview.mjs [--report <明确JSON路径>]')
}
const scope = describeAcceptanceScope()
const result = {
purpose: '离线验收范围总览,不执行HTTP,不证明当前部署通过',
admin: { entry: 'scripts/admin-test-env-acceptance.mjs', ...scope },
2026-10-22 14:37:59 +08:00
homepageBusinesses: describeHomepageBusinessCoverage(),
2026-10-21 13:22:35 +08:00
otherEntries: [
{ entry: 'scripts/client-test-env-acceptance.mjs', scope: 'Client全量HTTP,独立执行' },
{ entry: 'scripts/client-phase2-test-env-acceptance.mjs', scope: '测量计费专项,不替代Client全量' },
{ entry: 'scripts/fleet-test-env-acceptance.mjs', scope: '车辆专项,不计入Admin默认全量' },
],
boundaries: ['可选模块不等于默认执行;其他专项入口详见scripts/README.md',
'本地合同、HTTP、页面与真机验收分别报告',
'报告通过只代表该次记录;需另核对源码、配置与实际部署版本',
'环境与凭据前置仍由实际入口校验;本工具不读取环境文件'],
}
if (argv.length) result.report = summarizeReport(JSON.parse(await readFile(resolve(argv[1]), 'utf8')))
console.log(JSON.stringify(result, null, 2))
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
main(process.argv.slice(2)).catch((error) => { console.error(error.message); process.exitCode = 1 })
}