140 lines
5.9 KiB
JavaScript
140 lines
5.9 KiB
JavaScript
#!/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 moduleHtml = fs.readFileSync(path.join(root, 'prototype-src', 'modules', '03-overseas-warehouse.html'), 'utf8');
|
|
const overlaysHtml = fs.readFileSync(path.join(root, 'prototype-src', 'overlays.html'), 'utf8');
|
|
const appJs = fs.readFileSync(path.join(root, 'prototype-src', 'app.js'), 'utf8');
|
|
const failures = [];
|
|
|
|
function expectMatch(source, pattern, label) {
|
|
if (!pattern.test(source)) failures.push(`missing ${label}`);
|
|
}
|
|
|
|
function functionSource(name, source = appJs) {
|
|
const match = new RegExp(`function\\s+${name}\\s*\\([^)]*\\)\\s*\\{`).exec(source);
|
|
if (!match) return '';
|
|
const start = source.indexOf('{', match.index);
|
|
let depth = 0;
|
|
let quote = '';
|
|
let escaped = false;
|
|
for (let index = start; index < source.length; index += 1) {
|
|
const char = source[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 source.slice(match.index, index + 1);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
const statusSelect = moduleHtml.match(/<select\b(?=[^>]*\bid="fboo-status")[^>]*>([\s\S]*?)<\/select>/);
|
|
if (!statusSelect) failures.push('missing 出库单状态筛选');
|
|
else expectMatch(statusSelect[1], /<option>已取消<\/option>/, '已取消筛选选项');
|
|
|
|
expectMatch(overlaysHtml, /id="modal-fboo-cancel"/, '取消出库单弹窗');
|
|
expectMatch(overlaysHtml, /id="fboo-cancel-summary"/, '取消出库单摘要');
|
|
expectMatch(overlaysHtml, /id="fboo-cancel-no"/, '取消出库单号');
|
|
expectMatch(overlaysHtml, /id="fboo-cancel-reason"[^>]*maxlength="200"/, '取消原因字段');
|
|
expectMatch(overlaysHtml, /onclick="confirmFbaOutboundCancellation\(\)"/, '确认取消按钮');
|
|
|
|
for (const name of [
|
|
'getFbaOutboundStockRows',
|
|
'prepareFbaOutboundCancellation',
|
|
'openFbaOutboundCancellation',
|
|
'confirmFbaOutboundCancellation',
|
|
'getFilteredFbaOutboundOrders',
|
|
'fbooApplyCurrentSearch',
|
|
]) {
|
|
if (!functionSource(name)) failures.push(`missing ${name} function`);
|
|
}
|
|
|
|
expectMatch(appJs, /data-fboo-cancel-order/, '列表取消数据入口');
|
|
expectMatch(appJs, /openFbaOutboundCancellation\(cancel\.dataset\.fbooCancelOrder\)/, '取消入口事件委托');
|
|
expectMatch(appJs, /['"]已取消['"]\s*:\s*['"]tag-gray['"]/, '已取消灰色标签');
|
|
expectMatch(appJs, /仅待发货出库单可取消/, '仅待发货取消校验');
|
|
expectMatch(appJs, /取消原因/, '详情取消原因');
|
|
expectMatch(appJs, /取消时间/, '详情取消时间');
|
|
|
|
const stockRowsSource = functionSource('getFbaOutboundStockRows');
|
|
const prepareSource = functionSource('prepareFbaOutboundCancellation');
|
|
if (stockRowsSource && prepareSource) {
|
|
try {
|
|
const prepareFbaOutboundCancellation = new Function(
|
|
`${stockRowsSource}\n${prepareSource}\nreturn prepareFbaOutboundCancellation;`,
|
|
)();
|
|
const stocks = [
|
|
{ sku: 'SKU-A', wh: '洛杉矶仓', availQty: 10 },
|
|
{ sku: 'SKU-B', wh: '纽约仓', availQty: 20 },
|
|
];
|
|
const order = {
|
|
outboundNo: 'FBOO-TEST',
|
|
status: '待发货',
|
|
items: [
|
|
{ stockIndex: 0, sku: 'SKU-A', warehouse: '洛杉矶仓', quantity: 3 },
|
|
{ stockIndex: 0, sku: 'SKU-A', warehouse: '洛杉矶仓', quantity: 2 },
|
|
{ stockIndex: 1, sku: 'SKU-B', warehouse: '纽约仓', quantity: 4 },
|
|
],
|
|
};
|
|
const originalStocks = structuredClone(stocks);
|
|
const originalOrder = structuredClone(order);
|
|
const plan = prepareFbaOutboundCancellation(order, stocks, ' 客户取消 ', '2026-08-14 20:00');
|
|
assert.equal(plan.ok, true);
|
|
assert.deepEqual(plan.stockUpdates, { 0: 15, 1: 24 });
|
|
assert.deepEqual(plan.orderChanges, {
|
|
status: '已取消',
|
|
cancelReason: '客户取消',
|
|
cancelledAt: '2026-08-14 20:00',
|
|
updatedAt: '2026-08-14 20:00',
|
|
});
|
|
assert.deepEqual(stocks, originalStocks);
|
|
assert.deepEqual(order, originalOrder);
|
|
|
|
const shipped = prepareFbaOutboundCancellation({ ...order, status: '已发货' }, stocks, '客户取消', '2026-08-14 20:00');
|
|
assert.deepEqual(shipped, { ok: false, message: '仅待发货出库单可取消' });
|
|
|
|
const emptyReason = prepareFbaOutboundCancellation(order, stocks, ' ', '2026-08-14 20:00');
|
|
assert.deepEqual(emptyReason, { ok: false, message: '请填写取消原因' });
|
|
|
|
const missingStockOrder = {
|
|
outboundNo: 'FBOO-MISSING',
|
|
status: '待发货',
|
|
items: [{ stockIndex: 9, sku: 'SKU-MISSING', warehouse: '未知仓', quantity: 2 }],
|
|
};
|
|
const missingOriginal = structuredClone(stocks);
|
|
const missing = prepareFbaOutboundCancellation(missingStockOrder, stocks, '客户取消', '2026-08-14 20:00');
|
|
assert.deepEqual(missing, { ok: false, message: '库存返还校验失败,请刷新后重试' });
|
|
assert.deepEqual(stocks, missingOriginal);
|
|
assert.equal(missingStockOrder.status, '待发货');
|
|
} catch (error) {
|
|
failures.push(`取消计划行为失败:${error.message}`);
|
|
}
|
|
}
|
|
|
|
const confirmSource = functionSource('confirmFbaOutboundCancellation');
|
|
if (confirmSource) {
|
|
expectMatch(confirmSource, /prepareFbaOutboundCancellation/, '提交调用取消计划');
|
|
expectMatch(confirmSource, /warehouseStockData\[index\]\.availQty\s*=\s*plan\.stockUpdates\[index\]/, '提交一次性写回库存');
|
|
expectMatch(confirmSource, /Object\.assign\(order,plan\.orderChanges\)/, '提交写回取消状态');
|
|
expectMatch(confirmSource, /fbooApplyCurrentSearch\(\)/, '取消后保留筛选结果');
|
|
}
|
|
|
|
if (failures.length) {
|
|
console.error('FBA_OUTBOUND_CANCEL_CHECK_FAIL');
|
|
failures.forEach((failure) => console.error(`- ${failure}`));
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log('FBA_OUTBOUND_CANCEL_CHECK_PASS');
|
|
}
|