405 lines
25 KiB
JavaScript
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');
|
|
}
|