This commit is contained in:
2026-08-21 09:20:26 +08:00
parent c9c6d5a0f9
commit 49465315e2
89 changed files with 16413 additions and 1776 deletions
@@ -0,0 +1,179 @@
#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const root = path.resolve(__dirname, '..');
const tail = fs.readFileSync(path.join(root, 'prototype-src', 'tail.html'), 'utf8');
const moduleHtml = fs.readFileSync(
path.join(root, 'prototype-src', 'modules', '04-overseas-delivery.html'),
'utf8',
);
const app = fs.readFileSync(path.join(root, 'prototype-src', 'app.js'), 'utf8');
function extractFunction(name, source) {
const declaration = new RegExp(`function\\s+${name}\\s*\\(`).exec(source);
if (!declaration) return '';
const bodyStart = source.indexOf('{', declaration.index + declaration[0].length);
let depth = 0;
let state = 'code';
let escaped = false;
for (let index = bodyStart; index < source.length; index += 1) {
const char = source[index];
const next = source[index + 1];
if (state === 'line-comment') {
if (char === '\n') state = 'code';
continue;
}
if (state === 'block-comment') {
if (char === '*' && next === '/') {
state = 'code';
index += 1;
}
continue;
}
if (state !== 'code') {
if (escaped) escaped = false;
else if (char === '\\') escaped = true;
else if (
(state === 'single' && char === "'")
|| (state === 'double' && char === '"')
|| (state === 'template' && char === '`')
) state = 'code';
continue;
}
if (char === '/' && next === '/') {
state = 'line-comment';
index += 1;
} else if (char === '/' && next === '*') {
state = 'block-comment';
index += 1;
} else if (char === "'") state = 'single';
else if (char === '"') state = 'double';
else if (char === '`') state = 'template';
else if (char === '{') depth += 1;
else if (char === '}') {
depth -= 1;
if (depth === 0) return source.slice(declaration.index, index + 1);
}
}
return '';
}
function loadPureFunction(name) {
const source = extractFunction(name, app);
assert.ok(source, `缺少 ${name} 函数`);
return new Function(`${source}\nreturn ${name};`)();
}
assert.match(
tail,
/id="bol-deliveryMethod"[^>]*onchange="handleOutboundVehicleDeliveryMethodChange\(\)"/,
'派送方式变化时应更新快递字段',
);
assert.match(tail, /id="bol-courier-company-field"[^>]*hidden/, '缺少默认隐藏的快递公司字段');
assert.match(tail, /id="bol-courierCompanyId"/, '缺少快递公司选择框');
assert.match(
tail,
/<label>派送费用(USD) \*<\/label><input id="bol-dispatchFee"[^>]*type="number"[^>]*min="0"[^>]*step="0\.01"[^>]*required/,
'转派送弹框应只保留必填的派送费用',
);
assert.doesNotMatch(tail, /id="bol-estimatedDispatchFee"/, '不应保留预估派送费用输入');
assert.doesNotMatch(tail, /id="bol-courierFee"/, '不应保留独立快递费用输入');
assert.doesNotMatch(tail, /id="bol-courier-fee-field"/, '不应保留独立快递费用字段容器');
assert.doesNotMatch(
tail,
/<\/template>\s*<\/div>\s*<\/div>\s*<div class="modal-footer"/,
'转派送按钮栏必须位于弹框主体内,避免移动端 flex 挤压',
);
const feePanel = /<div id="dispatch-fee-panel"[\s\S]*?<\/table>[\s\S]*?<\/div>/.exec(moduleHtml)?.[0] || '';
assert.match(feePanel, /<tbody id="tbody-dispatch-fee">/, '费用明细表缺少稳定表体 ID');
assert.match(moduleHtml, /id="dispatch-fee-total"/, '费用明细缺少动态总数 ID');
const validate = loadPureFunction('validateOutboundVehicleDeliveryDraft');
const companies = [
{id: 'E001', name: 'DHL', status: '合作中'},
{id: 'E002', name: '停用快递', status: '已终止'},
];
const base = {
deliveryDate: '2026-08-14',
pickupMethod: '一提一卸',
loadType: '托盘',
dispatchFee: 200,
loadingFee: 0,
};
assert.equal(validate({...base, deliveryMethod: '客户自提'}, companies), '');
assert.match(
validate({...base, deliveryMethod: '客户自提', dispatchFee: ''}, companies),
/派送费用/,
);
assert.match(
validate({...base, deliveryMethod: '客户自提', dispatchFee: '-1'}, companies),
/派送费用/,
);
assert.match(
validate({...base, deliveryMethod: '客户自提', dispatchFee: 'not-a-number'}, companies),
/派送费用/,
);
assert.match(
validate({...base, deliveryMethod: '快递', courierCompanyId: ''}, companies),
/快递公司/,
);
assert.match(
validate({...base, deliveryMethod: '快递', courierCompanyId: 'E002'}, companies),
/快递公司/,
);
assert.equal(
validate({...base, deliveryMethod: '快递', courierCompanyId: 'E001'}, companies),
'',
);
const getDetails = loadPureFunction('getOutboundVehicleCourierFeeDetails');
assert.deepEqual(
getDetails([
{
dispatchNo: 'PSD001',
source: 'outboundVehicle',
deliveryMethod: '快递',
courierCompanyName: 'DHL',
dispatchFee: 12.5,
appointmentDate: '2026-08-14',
},
{dispatchNo: 'PSD002', source: 'outboundVehicle', deliveryMethod: '客户自提', dispatchFee: 99},
{dispatchNo: 'PSD003', source: 'outboundVehicle', deliveryMethod: '快递', dispatchFee: -1},
]),
[{dispatchNo: 'PSD001', company: 'DHL', businessDate: '2026-08-14', currency: 'USD', amount: 12.5}],
);
const deliveryMethodChange = extractFunction('handleOutboundVehicleDeliveryMethodChange', app);
assert.match(deliveryMethodChange, /status\s*===\s*'合作中'/, '快递公司下拉应只显示合作中公司');
assert.doesNotMatch(deliveryMethodChange, /bol-courierFee|bol-courier-fee-field/, '派送方式联动不应再控制独立快递费用');
const saveBol = extractFunction('saveBol', app);
assert.match(saveBol, /validateOutboundVehicleDeliveryDraft\(draft\s*,\s*genExpressData\)/, '保存时应使用快递公司主数据校验');
assert.match(saveBol, /courierCompanyId/, '派送单应保存快递公司 ID 快照');
assert.match(saveBol, /courierCompanyName/, '派送单应保存快递公司名称快照');
assert.doesNotMatch(saveBol, /value\('bol-estimatedDispatchFee'\)/, '保存时不应读取预估派送费用输入');
assert.doesNotMatch(saveBol, /value\('bol-courierFee'\)/, '保存时不应读取独立快递费用输入');
assert.match(saveBol, /estimatedDispatchFee\s*:\s*deliveryFee/, '现有预估费用展示应复用唯一派送费用');
const renderFeeRows = extractFunction('renderOutboundVehicleCourierFeeRows', app);
assert.match(renderFeeRows, /data-generated-courier-fee/, '动态费用行应支持幂等清理');
assert.match(renderFeeRows, /待审核/, '自动生成快递费用应为待审核');
assert.match(renderFeeRows, /应付/, '自动生成快递费用应为应付');
assert.match(renderFeeRows, /快递费/, '自动生成费用类型应为快递费');
assert.match(renderFeeRows, /排车转派送自动生成/, '自动生成快递费用应记录来源备注');
assert.match(
extractFunction('renderLastMileDispatchRows', app),
/renderOutboundVehicleCourierFeeRows\(\)/,
'派送单列表刷新时应同步刷新快递费用明细',
);
assert.match(
extractFunction('switchDispatchTab', app),
/tab\s*===\s*'fee'[^;]*renderOutboundVehicleCourierFeeRows\(\)/,
'切换到费用明细标签时应刷新快递费用明细',
);
console.log('OUTBOUND_VEHICLE_COURIER_FEE_CHECK_PASS');