Files
docs/悦/货代SAAS平台后台/tests/verify-last-mile-orders.cjs
T
2026-08-12 19:11:56 +08:00

405 lines
25 KiB
JavaScript

#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const html = fs.readFileSync(path.resolve(__dirname, '..', '货代SaaS平台原型.html'), 'utf8');
const failures = [];
function requireMatch(pattern, label, source = html) {
if (!pattern.test(source)) failures.push(`missing ${label}`);
}
function extractArrayLiteral(constName) {
const declarationStart = html.indexOf(`const ${constName}`);
const arrayStart = declarationStart >= 0 ? html.indexOf('[', declarationStart) : -1;
if (arrayStart < 0) return '';
let depth = 0;
let quote = '';
let escaped = false;
for (let index = arrayStart; index < html.length; index += 1) {
const char = html[index];
if (quote) {
if (escaped) escaped = false;
else if (char === '\\') escaped = true;
else if (char === quote) quote = '';
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '[') depth += 1;
if (char === ']' && --depth === 0) return html.slice(arrayStart, index + 1);
}
return '';
}
function functionSource(name) {
const start = html.indexOf(`function ${name}(`);
if (start < 0) return '';
const nextFunction = html.slice(start + 1).match(/\nfunction\s+[A-Za-z_$][\w$]*\s*\(/);
const end = nextFunction ? start + 1 + nextFunction.index : html.length;
return html.slice(start, end);
}
const overseasNavStart = html.indexOf('<div class="sidebar-item" data-menu="fba-return">');
const overseasNavEnd = html.indexOf('<div class="sidebar-item" data-menu="outbound">', overseasNavStart);
const overseasNav = overseasNavStart >= 0 && overseasNavEnd > overseasNavStart
? html.slice(overseasNavStart, overseasNavEnd)
: '';
requireMatch(/switchPage\('last-mile-orders',this\)[^>]*>尾程订单<\/div>/, '海外仓尾程订单导航', overseasNav);
const lastMileNavIndex = overseasNav.indexOf("switchPage('last-mile-orders',this)");
const inboundNavIndex = overseasNav.indexOf("switchPage('fba-inbound-order',this)");
if (!(lastMileNavIndex >= 0 && lastMileNavIndex < inboundNavIndex)) {
failures.push('尾程订单导航未位于入库单之前');
}
const vehicleNavIndex = overseasNav.indexOf("switchPage('outbound-vehicle',this)");
const outboundOrderNavIndex = overseasNav.indexOf("switchPage('fba-outbound-order',this)");
const relabelOrderNavIndex = overseasNav.indexOf("switchPage('fba-relabel-order',this)");
const palletNavIndex = overseasNav.indexOf("switchPage('fba-pallet',this)");
if (!(lastMileNavIndex >= 0 && lastMileNavIndex < inboundNavIndex && inboundNavIndex < vehicleNavIndex && vehicleNavIndex < outboundOrderNavIndex && outboundOrderNavIndex < relabelOrderNavIndex && relabelOrderNavIndex < palletNavIndex)) {
failures.push('海外仓导航未按尾程订单、入库单、排车列表、出库单、换标单排序');
}
const outboundNavStart = html.indexOf('<div class="sidebar-item" data-menu="outbound">');
const outboundNavEnd = html.indexOf('<div class="sidebar-item" data-menu="booking">', outboundNavStart);
const outboundNav = outboundNavStart >= 0 && outboundNavEnd > outboundNavStart
? html.slice(outboundNavStart, outboundNavEnd)
: '';
if (/switchPage\('outbound-vehicle',this\)[^>]*>排车列表<\/div>/.test(outboundNav)) {
failures.push('海外派送仍显示排车列表导航');
}
requireMatch(/id="page-last-mile-orders"/, '尾程订单列表页面');
requireMatch(/id="page-last-mile-order-detail"/, '尾程订单详情页面');
requireMatch(/id="lmo-detail-order-details"/, '尾程订单明细模块');
requireMatch(/id="tbody-last-mile-orders"/, '尾程订单列表表体');
requireMatch(/openBolModal\([^)]*\)[^>]*>转派送<\/button>/, '排车列表转派送按钮');
if (/openBolModal\([^)]*\)[^>]*>转BOL<\/button>/.test(html)) failures.push('排车列表仍显示转BOL按钮');
const lastMilePageStart = html.indexOf('<div class="page" id="page-last-mile-orders">');
const lastMilePageEnd = html.indexOf('<!-- ==================== OVERSEAS WAREHOUSE - 尾程订单详情', lastMilePageStart);
const lastMilePage = lastMilePageStart >= 0 && lastMilePageEnd > lastMilePageStart
? html.slice(lastMilePageStart, lastMilePageEnd)
: '';
const lastMileDetailStart = html.indexOf('<div class="page" id="page-last-mile-order-detail">');
const lastMileDetailEnd = html.indexOf('<!-- 尾程订单 - 入库计划 -->', lastMileDetailStart);
const lastMileDetailPage = lastMileDetailStart >= 0 && lastMileDetailEnd > lastMileDetailStart
? html.slice(lastMileDetailStart, lastMileDetailEnd)
: '';
requireMatch(/onclick="openLastMileImport\(\)"[^>]*>导入<\/button>/, '尾程订单导入按钮', lastMilePage);
const lastMileTableStart = lastMilePage.indexOf('<table');
const lastMileTableEnd = lastMilePage.indexOf('</table>', lastMileTableStart);
const lastMileTable = lastMileTableStart >= 0 && lastMileTableEnd > lastMileTableStart
? lastMilePage.slice(lastMileTableStart, lastMileTableEnd)
: '';
requireMatch(/>箱数<\/th>\s*<th[^>]*>体积\(m³\)<\/th>/, '箱数后的体积列表列', lastMileTable);
requireMatch(/>入仓码<\/th>/, '入仓码列表列', lastMileTable);
if (/>头程单号<\/th>/.test(lastMileTable)) failures.push('尾程订单列表仍显示头程单号列');
requireMatch(/>海外仓<\/th>\s*<th[^>]*>转仓<\/th>\s*<th[^>]*>FBA仓库<\/th>/, '海外仓后的转仓列表列', lastMileTable);
for (const label of ['数量', '体积重量', '预计到港时间', '实际到港时间']) {
if (!lastMileTable.includes(`>${label}</th>`)) failures.push(`missing 尾程订单列表列 ${label}`);
}
requireMatch(/>当前状态<\/th>[\s\S]*>备注\(仅自己\)<\/th>[\s\S]*>备注\(客户\)<\/th>[\s\S]*>备注\(仓库\)<\/th>/, '尾程订单三列备注表头', lastMileTable);
requireMatch(/id="lmo-first-leg-no"/, '头程单号查询条件');
requireMatch(/id="lmo-status"/, '尾程状态查询条件');
const consolidatedFilterMatch = lastMilePage.match(/<select(?=[^>]*\bid=["']lmo-consolidated-filter["'])[^>]*>[\s\S]*?<\/select>/);
const consolidatedFilterSource = consolidatedFilterMatch ? consolidatedFilterMatch[0] : '';
requireMatch(/<select(?=[^>]*\bid=["']lmo-consolidated-filter["'])[^>]*>/, '是否合托查询条件', lastMilePage);
requireMatch(/<option[^>]*value=["']["'][^>]*>\s*全部\s*<\/option>/, '是否合托查询全部选项', consolidatedFilterSource);
requireMatch(/<option[^>]*value=["']yes["'][^>]*>\s*是\s*<\/option>/, '是否合托查询是选项', consolidatedFilterSource);
requireMatch(/<option[^>]*value=["']no["'][^>]*>\s*否\s*<\/option>/, '是否合托查询否选项', consolidatedFilterSource);
requireMatch(/<label>预计到港时间<\/label>[\s\S]{0,240}id="lmo-port-eta-start"[^>]*type="date"[\s\S]{0,160}id="lmo-port-eta-end"[^>]*type="date"/, '预计到港时间范围查询条件', lastMilePage);
requireMatch(/class="lmo-search-field lmo-search-date-field"/, '预计到港时间窄屏跨列标记', lastMilePage);
requireMatch(/@media\(max-width:900px\)[^{]*\{[\s\S]{0,500}\.lmo-search-date-field\s*\{\s*grid-column:1\/-1/, '预计到港时间窄屏跨列样式');
if (/流程进度|id="lmo-detail-progress"/.test(lastMileDetailPage)) failures.push('尾程订单详情仍显示流程进度');
for (const label of ['全部', '待分配']) {
if (!lastMilePage.includes(`data-lmo-tab="${label}"`)) failures.push(`missing 尾程订单页签 ${label}`);
}
for (const label of ['待预约', '待入库', '待排车', '待派送']) {
if (lastMilePage.includes(`data-lmo-tab="${label}"`)) failures.push(`尾程订单页签仍显示 ${label}`);
}
requireMatch(/function switchLastMileStatusTab\(/, '尾程订单状态页签切换函数');
requireMatch(/function getLastMileTabMatch\(/, '尾程订单状态页签筛选规则');
requireMatch(/currentLastMileTab/, '尾程订单当前页签状态');
requireMatch(/lmo-hide-operation/, '全部页签隐藏操作列样式');
requireMatch(/const transfer=[^;]*转仓[\s\S]{0,500}currentLastMileTab==='待分配'[^\n]*\$\{transfer\}[^\n]*入库计划/, '待分配页签保留转仓和入库计划');
const operationItemsStart = html.indexOf('function renderLastMileOperationItems(');
const operationItemsEnd = html.indexOf('function renderLastMileOrders(', operationItemsStart);
const operationItemsSource = operationItemsStart >= 0 && operationItemsEnd > operationItemsStart
? html.slice(operationItemsStart, operationItemsEnd)
: '';
if (/>预约<\/a>|>排车<\/a>/.test(operationItemsSource)) failures.push('尾程订单操作菜单仍显示预约或排车');
requireMatch(/function scheduleLastMileVehicle\(/, '尾程订单排车函数');
requireMatch(/入库计划[\s\S]*合托[\s\S]*FBA预约[\s\S]*出库计划/, '尾程四项操作');
requireMatch(/openLastMileTransfer\('\$\{order\.no\}'\)[^>]*>转仓<\/a>/, '尾程转仓操作项');
requireMatch(/#page-last-mile-orders \.more-dropdown-menu\s*\{[^}]*display:none/, '尾程操作菜单默认隐藏');
requireMatch(/#page-last-mile-orders \.more-dropdown\.open \.more-dropdown-menu\s*\{[^}]*display:block/, '尾程操作菜单展开样式');
requireMatch(/id="modal-lmo-inbound-plan"/, '入库计划弹窗');
requireMatch(/id="lmo-inbound-pallets"[^>]*type="number"[^>]*min="1"/, '入库计划托数编辑字段');
const inboundPlanModalStart = html.indexOf('<div class="modal-overlay" id="modal-lmo-inbound-plan">');
const inboundPlanModalEnd = html.indexOf('<!-- 尾程订单 - 合托 -->', inboundPlanModalStart);
const inboundPlanModalSource = inboundPlanModalStart >= 0 && inboundPlanModalEnd > inboundPlanModalStart
? html.slice(inboundPlanModalStart, inboundPlanModalEnd)
: '';
const inboundConsolidatedCheckboxMatch = inboundPlanModalSource.match(/<input(?=[^>]*\bid=["']lmo-inbound-consolidated["'])(?=[^>]*\btype=["']checkbox["'])[^>]*>/);
if (!inboundConsolidatedCheckboxMatch) {
failures.push('missing 入库计划是否合托复选框');
} else if (/\srequired(?:\s|=|\/?>)/.test(inboundConsolidatedCheckboxMatch[0])) {
failures.push('入库计划是否合托复选框不应必填');
}
requireMatch(/<label(?=[^>]*\bfor=["']lmo-inbound-consolidated["'])[^>]*>[\s\S]{0,120}是否合托[\s\S]{0,120}<\/label>/, '关联入库计划是否合托复选框的标签', inboundPlanModalSource);
requireMatch(/id="modal-lmo-consolidate"/, '合托弹窗');
requireMatch(/id="modal-lmo-fba-appointment"/, 'FBA预约弹窗');
requireMatch(/id="modal-lmo-outbound-plan"/, '出库计划弹窗');
requireMatch(/id="modal-lmo-transfer"/, '转仓弹窗');
requireMatch(/id="lmo-transfer-current"/, '转仓当前仓字段');
requireMatch(/id="lmo-transfer-destination"/, '转仓目的仓字段');
requireMatch(/id="lmo-transfer-remark"/, '转仓备注字段');
requireMatch(/id="modal-lmo-import"/, '尾程导入弹窗');
requireMatch(/id="lmo-import-file"[^>]*accept="\.csv"/, '尾程 CSV 文件选择');
requireMatch(/id="tbody-lmo-import-preview"/, '尾程导入预览表体');
requireMatch(/id="lmo-import-summary"/, '尾程导入校验统计');
requireMatch(/const lastMileOrderData\s*=\s*\[/, '尾程订单数据源');
const lastMileSamplesLiteral = extractArrayLiteral('lastMileFirstLegSamples');
let lastMileFirstLegSamples = [];
try {
lastMileFirstLegSamples = vm.runInNewContext(`(${lastMileSamplesLiteral})`, Object.create(null), {timeout: 1000});
} catch {
failures.push('missing 可解析的尾程订单样例数据');
}
const pendingAllocationCandidates = Array.isArray(lastMileFirstLegSamples)
? lastMileFirstLegSamples.filter(order => order
&& order.status === '出库完成'
&& !order.inboundPlan
&& !order.consolidation
&& !order.isConsolidated)
: [];
const completePendingAllocationCandidates = pendingAllocationCandidates.filter(order =>
typeof order.firstLegNo === 'string' && order.firstLegNo.trim()
&& typeof order.containerNo === 'string' && order.containerNo.trim());
const firstLegNoCounts = new Map();
const containerNoCounts = new Map();
completePendingAllocationCandidates.forEach(order => {
const firstLegNo = order.firstLegNo.trim();
const containerNo = order.containerNo.trim();
firstLegNoCounts.set(firstLegNo, (firstLegNoCounts.get(firstLegNo) || 0) + 1);
containerNoCounts.set(containerNo, (containerNoCounts.get(containerNo) || 0) + 1);
});
const uniquePendingAllocationSamples = completePendingAllocationCandidates.filter(order =>
firstLegNoCounts.get(order.firstLegNo.trim()) === 1
&& containerNoCounts.get(order.containerNo.trim()) === 1);
const pendingAllocationSampleCount = uniquePendingAllocationSamples.length;
if (pendingAllocationSampleCount < 5) {
failures.push(`完整且 firstLegNo/containerNo 唯一的尾程待分配样例不足 5 条(当前 ${pendingAllocationSampleCount} 条)`);
}
requireMatch(/volumeWeight\s*:/, '尾程订单体积重量数据字段');
requireMatch(/actualArrivalTime\s*:/, '尾程订单实际到港时间数据字段');
requireMatch(/serviceType\s*:/, '尾程订单业务类型数据字段');
requireMatch(/serviceType:\s*['"]一件代发['"]/, '一件代发尾程样例');
requireMatch(/function createLastMileOrderFromFirstLeg\(/, '头程出库完成生成尾程订单函数');
requireMatch(/firstLegOrder\.status\s*!==\s*['"]出库完成['"]/, '非出库完成头程订单拦截');
requireMatch(/lastMileOrderData\.some\([^)]*firstLegNo/, '尾程订单幂等生成校验');
requireMatch(/function renderLastMileOrders\(/, '尾程订单列表渲染函数');
const renderLastMileStart = html.indexOf('function renderLastMileOrders(');
const renderLastMileEnd = html.indexOf('function lastMileSearch(', renderLastMileStart);
const renderLastMileSource = renderLastMileStart >= 0 && renderLastMileEnd > renderLastMileStart
? html.slice(renderLastMileStart, renderLastMileEnd)
: '';
requireMatch(/order\.boxes[\s\S]{0,160}order\.volume/, '尾程订单列表体积渲染', renderLastMileSource);
requireMatch(/order\.pieces[\s\S]{0,220}order\.volumeWeight[\s\S]{0,220}order\.portEta[\s\S]{0,220}order\.actualArrivalTime/, '尾程订单新增字段渲染', renderLastMileSource);
requireMatch(/order\.transferWarehouse[\s\S]{0,160}order\.fbaWarehouse/, '尾程订单转仓字段渲染', renderLastMileSource);
requireMatch(/<td><span class="tag \$\{order\.serviceType===['"]一件代发['"]\?['"]tag-cyan['"]:['"]tag-blue['"]\}[^>]*>\$\{lmoEsc\(order\.serviceType\|\|['"]头程['"]\)\}/, '订单类型列渲染', renderLastMileSource);
if (/\$\{serviceTag\}|const serviceTag=/.test(renderLastMileSource)) failures.push('尾程单号仍显示重复订单类型标记');
requireMatch(/order\.inboundPlan&&order\.inboundPlan\.inboundCode/, '尾程订单入仓码动态展示', renderLastMileSource);
const consolidatedMarkerPattern = /isLastMileConsolidated\(\s*order\s*\)\s*\?[\s\S]{0,320}已合托[\s\S]{0,160}:\s*["'`]—["'`]/;
const unconsolidatedPlaceholderPattern = /!\s*isLastMileConsolidated\(\s*order\s*\)\s*\?[\s\S]{0,160}["'`]—["'`][\s\S]{0,160}:\s*["'`][\s\S]{0,240}已合托/;
if (!consolidatedMarkerPattern.test(renderLastMileSource) && !unconsolidatedPlaceholderPattern.test(renderLastMileSource)) {
failures.push('missing 尾程订单列表由统一合托判断驱动已合托标记和占位符');
}
requireMatch(/order\.noteSelf[\s\S]{0,260}order\.noteCustomer[\s\S]{0,260}order\.noteWarehouse/, '尾程订单备注输入框渲染', renderLastMileSource);
requireMatch(/function updateLastMileNote\(/, '尾程订单备注保存函数');
requireMatch(/function refreshLastMileViews\([^)]*\)\{[^}]*renderLastMileOrders\(getLastMileFilteredOrders\(\)\)/, '操作保存后保持当前页签与查询筛选');
requireMatch(/function lastMileSearch\(/, '尾程订单查询函数');
requireMatch(/function lastMileReset\(/, '尾程订单重置函数');
const lastMileFilterSource = functionSource('getLastMileFilteredOrders');
const consolidatedFilterReadMatch = lastMileFilterSource.match(/([A-Za-z_$][\w$]*)\s*=\s*val\(\s*["']lmo-consolidated-filter["']\s*\)/);
if (!consolidatedFilterReadMatch) {
failures.push('missing 是否合托查询条件读取');
failures.push('missing 是否合托为是时未合托订单 return false');
failures.push('missing 是否合托为否时已合托订单 return false');
} else {
const filterVariable = consolidatedFilterReadMatch[1].replace(/[$]/g, '\\$&');
const helperCall = 'isLastMileConsolidated\\(\\s*order\\s*\\)';
const yesComparison = `${filterVariable}\\s*===\\s*["']yes["']`;
const noComparison = `${filterVariable}\\s*===\\s*["']no["']`;
const falseBranch = '[\\s\\S]{0,60}\\)\\s*\\{?\\s*return\\s+false\\s*;?';
const yesFilterPattern = new RegExp(`if\\s*\\((?:${yesComparison}[\\s\\S]{0,120}!\\s*${helperCall}|!\\s*${helperCall}[\\s\\S]{0,120}${yesComparison})${falseBranch}`);
const noFilterPattern = new RegExp(`if\\s*\\((?:${noComparison}[\\s\\S]{0,120}(?<!\\!)${helperCall}|(?<!\\!)${helperCall}[\\s\\S]{0,120}${noComparison})${falseBranch}`);
requireMatch(yesFilterPattern, '是否合托为是时未合托订单 return false', lastMileFilterSource);
requireMatch(noFilterPattern, '是否合托为否时已合托订单 return false', lastMileFilterSource);
}
const lastMileResetSource = functionSource('lastMileReset');
let resetsConsolidatedFilter = false;
try {
const consolidatedFilterElement = {value: 'yes'};
const resetContext = {
currentLastMileTab: '待分配',
document: {
getElementById: id => id === 'lmo-consolidated-filter' ? consolidatedFilterElement : {value: 'unchanged'},
querySelectorAll: () => []
},
selectedLastMileOrderNos: {clear() {}},
renderLastMileOrders() {},
showToast() {}
};
vm.runInNewContext(`${lastMileResetSource}\nlastMileReset();`, resetContext, {timeout: 1000});
resetsConsolidatedFilter = consolidatedFilterElement.value === '';
} catch {}
if (!resetsConsolidatedFilter) failures.push('missing 是否合托查询条件重置为 value 空字符串');
requireMatch(/val\('lmo-port-eta-start'\)/, '预计到港开始时间读取');
requireMatch(/val\('lmo-port-eta-end'\)/, '预计到港结束时间读取');
requireMatch(/portEtaStart\s*&&\s*order\.portEta\s*<\s*portEtaStart/, '预计到港开始时间筛选');
requireMatch(/portEtaEnd\s*&&\s*order\.portEta\s*>\s*portEtaEnd/, '预计到港结束时间筛选');
requireMatch(/['"]lmo-port-eta-start['"][\s\S]{0,80}['"]lmo-port-eta-end['"]/, '预计到港时间重置');
requireMatch(/function openLastMileOrderDetail\(/, '尾程订单详情入口');
requireMatch(/function renderLastMileOrderDetail\(/, '尾程订单详情渲染函数');
const detailRenderStart = html.indexOf('function renderLastMileOrderDetail(');
const detailRenderEnd = html.indexOf('function lmoActionOrder(', detailRenderStart);
const detailRenderSource = detailRenderStart >= 0 && detailRenderEnd > detailRenderStart
? html.slice(detailRenderStart, detailRenderEnd)
: '';
if (/lmo-detail-progress|const steps=/.test(detailRenderSource)) failures.push('尾程订单详情渲染仍包含流程进度');
requireMatch(/function backToLastMileOrders\(/, '尾程订单详情返回函数');
requireMatch(/function openLastMileTransfer\(/, '打开尾程转仓函数');
requireMatch(/function saveLastMileTransfer\(/, '保存尾程转仓函数');
requireMatch(/transferWarehouse[\s\S]{0,240}transferRemark[\s\S]{0,300}node:\s*['"]转仓['"]/, '转仓数据与日志保存');
requireMatch(/function openLastMileImport\(/, '打开尾程导入函数');
requireMatch(/function downloadLastMileImportTemplate\(/, '下载尾程导入模板函数');
requireMatch(/function parseLastMileCsv\(/, '尾程 CSV 解析函数');
requireMatch(/function handleLastMileImportFile\(/, '尾程导入文件处理函数');
requireMatch(/function confirmLastMileImport\(/, '确认尾程导入函数');
requireMatch(/lastMileOrderData\.some\([^)]*firstLegNo/, '尾程导入重复单号校验');
requireMatch(/function isLastMileInboundLocked\([^)]*\)[\s\S]*customsStatus\s*===\s*['"]清关完成['"]/, '清关完成入库计划锁定规则');
requireMatch(/function isLastMileConsolidated\(/, '统一合托状态判断函数');
const lastMileConsolidatedSource = functionSource('isLastMileConsolidated');
if (lastMileConsolidatedSource) {
let consolidatedBehavior = [];
try {
consolidatedBehavior = vm.runInNewContext(`${lastMileConsolidatedSource}\n[
isLastMileConsolidated({isConsolidated:true}),
isLastMileConsolidated({consolidation:{batchNo:'CT-TEST'}}),
isLastMileConsolidated({})
]`, Object.create(null), {timeout: 1000});
} catch {}
if (consolidatedBehavior.length !== 3
|| consolidatedBehavior[0] !== true
|| consolidatedBehavior[1] !== true
|| consolidatedBehavior[2] !== false) {
failures.push('统一合托状态判断未返回 true / true / false 的布尔结果');
}
}
requireMatch(/function openLastMileInboundPlan\(/, '入库计划打开函数');
requireMatch(/function saveLastMileInboundPlan\(/, '入库计划保存函数');
const openInboundPlanSource = functionSource('openLastMileInboundPlan');
function verifyInboundPlanOpenChecked(helperResult) {
const order = {warehouse: '测试仓', eta: '2026-08-20', pallets: 2, inboundPlan: null};
const consolidatedCheckbox = {checked: !helperResult};
const elements = {
'lmo-inbound-warehouse': {value: ''},
'lmo-inbound-eta': {value: ''},
'lmo-inbound-code': {value: ''},
'lmo-inbound-pallets': {value: ''},
'lmo-inbound-remark': {value: ''},
'lmo-inbound-consolidated': consolidatedCheckbox
};
let helperOrder = null;
try {
const openContext = {
lmoActionOrder: () => order,
isLastMileInboundLocked: () => false,
isLastMileConsolidated: value => { helperOrder = value; return helperResult; },
document: {getElementById: id => elements[id]},
showModal() {},
showToast() {}
};
vm.runInNewContext(`${openInboundPlanSource}\nopenLastMileInboundPlan('LMO-TEST');`, openContext, {timeout: 1000});
} catch {
return false;
}
return helperOrder === order && consolidatedCheckbox.checked === helperResult;
}
if (!verifyInboundPlanOpenChecked(true) || !verifyInboundPlanOpenChecked(false)) {
failures.push('missing 入库计划打开时由 helper 回填同一是否合托复选框');
}
const saveInboundPlanSource = functionSource('saveLastMileInboundPlan');
function verifyInboundPlanSaveChecked(checked) {
const order = {
no: 'LMO-TEST', inboundNo: 'FBI-TEST', inboundPlan: null, isConsolidated: !checked,
pallets: 1, boxes: 2, volume: 3, firstLegNo: 'HC-TEST', owner: '测试货主',
containerNo: 'CONT-TEST', logs: []
};
const consolidatedCheckbox = {checked};
const elements = {
'lmo-inbound-warehouse': {value: '测试仓'},
'lmo-inbound-eta': {value: '2026-08-20'},
'lmo-inbound-code': {value: 'IN-TEST'},
'lmo-inbound-pallets': {value: '2'},
'lmo-inbound-remark': {value: '测试'},
'lmo-inbound-consolidated': consolidatedCheckbox
};
try {
const saveContext = {
currentLastMileActionNo: order.no,
lastMileOrderData: [order],
fbaInboundOrderData: [{inboundNo: order.inboundNo}],
isLastMileInboundLocked: () => false,
document: {getElementById: id => elements[id]},
lmoNow: () => '2026-08-11 15:30',
hideModal() {},
showToast() {},
refreshLastMileViews() {}
};
vm.runInNewContext(`${saveInboundPlanSource}\nsaveLastMileInboundPlan();`, saveContext, {timeout: 1000});
} catch {
return false;
}
return order.isConsolidated === consolidatedCheckbox.checked;
}
if (!verifyInboundPlanSaveChecked(true) || !verifyInboundPlanSaveChecked(false)) {
failures.push('missing 入库计划保存读取同一是否合托复选框并持久化');
}
requireMatch(/saveLastMileInboundPlan\([^)]*\)[\s\S]*isLastMileInboundLocked\(/, '入库计划保存二次锁定校验');
requireMatch(/lmo-inbound-pallets[\s\S]{0,140}plan\.pallets\|\|order\.pallets/, '入库计划托数回填');
requireMatch(/Number\(document\.getElementById\('lmo-inbound-pallets'\)\.value\)[\s\S]{0,500}order\.pallets=pallets/, '入库计划托数校验并更新订单');
requireMatch(/palletTotal:order\.pallets[\s\S]{0,380}palletTotal:order\.pallets/, '关联入库单托数新增与更新');
requireMatch(/fbaInboundOrderData\.(?:unshift|push)\(/, '入库计划生成入库单');
requireMatch(/function openLastMileConsolidate\(/, '合托打开函数');
requireMatch(/function saveLastMileConsolidate\(/, '合托保存函数');
requireMatch(/function openLastMileOutboundPlan\(/, '出库计划打开函数');
requireMatch(/function saveLastMileOutboundPlan\(/, '出库计划保存函数');
requireMatch(/fbaOutboundOrderData\.(?:unshift|push)\(/, '出库计划生成出库单');
requireMatch(/function openLastMileAppointment\(/, 'FBA预约打开函数');
requireMatch(/function saveLastMileAppointment\(/, 'FBA预约保存函数');
requireMatch(/预约成功[\s\S]*dispatchNo/, '预约成功生成派送单规则');
requireMatch(/lastMileDispatchData\.(?:unshift|push)\(/, '派送单数据生成');
requireMatch(/!order\.dispatchNo/, '派送单幂等生成校验');
requireMatch(/id="tbody-outbound-dispatch"/, '现有派送单列表可接收尾程派送单');
requireMatch(/function renderLastMileDispatchRows\(/, '尾程派送单列表渲染函数');
requireMatch(/page\s*===\s*['"]outbound-dispatch['"][^\n]*renderLastMileDispatchRows\(/, '派送单页面刷新尾程数据');
requireMatch(/page\s*===\s*['"]last-mile-orders['"][^\n]*renderLastMileOrders\(getLastMileFilteredOrders\(\)\)/, '尾程订单页面初始化保持当前筛选');
if (failures.length) {
console.error('LAST_MILE_ORDERS_CHECK_FAIL');
failures.forEach((failure) => console.error(`- ${failure}`));
process.exitCode = 1;
} else {
console.log('LAST_MILE_ORDERS_CHECK_PASS');
}