97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
process.env.CANGKU_SKIP_BOOTSTRAP = '1';
|
||
const http = require('http');
|
||
const XLSX = require('xlsx');
|
||
|
||
const FIELD_MAP = [
|
||
['期初数量', 'openingQty'],
|
||
['加工进仓', 'processIn'],
|
||
['采购退货', 'purchaseReturn'],
|
||
['采购进仓', 'purchaseIn'],
|
||
['其它进仓', 'otherIn'],
|
||
['其它退货', 'otherReturn'],
|
||
['销售出仓', 'salesOut'],
|
||
['盘点', 'stocktaking'],
|
||
['加工出仓', 'processOut'],
|
||
['调拨出', 'transferOut'],
|
||
['销售退货', 'salesReturn'],
|
||
['调拨入', 'transferIn'],
|
||
['期初', 'openingQtyExtra'],
|
||
['其它出库', 'otherOut'],
|
||
['期末数量', 'closingQty'],
|
||
];
|
||
|
||
function req(method, path, token, body) {
|
||
return new Promise((resolve, reject) => {
|
||
const data = body ? JSON.stringify(body) : null;
|
||
const r = http.request({
|
||
host: '127.0.0.1', port: 4001, path, method,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
|
||
...(token ? { Authorization: 'Bearer ' + token } : {}),
|
||
},
|
||
}, (resp) => { let s = ''; resp.on('data', (d) => { s += d; }); resp.on('end', () => resolve({ code: resp.statusCode, body: s })); });
|
||
r.on('error', reject);
|
||
if (data) r.write(data);
|
||
r.end();
|
||
});
|
||
}
|
||
|
||
(async () => {
|
||
const login = JSON.parse((await req('POST', '/api/auth/local-preview', null, {})).body);
|
||
const token = login.access_token || login.token;
|
||
const res = await req('GET', '/api/daily-inventory', token);
|
||
if (res.code !== 200) { console.log('接口失败', res.code, res.body.slice(0, 300)); process.exit(1); }
|
||
const d = JSON.parse(res.body).data;
|
||
|
||
const wb = XLSX.readFile('C:/Users/15018737611/Desktop/库存监控.xlsx');
|
||
const rows = XLSX.utils.sheet_to_json(wb.Sheets['Sheet1'], { header: 1, defval: '' });
|
||
const h = rows[0];
|
||
const idx = Object.fromEntries(h.map((c, i) => [String(c).trim(), i]));
|
||
const byName = new Map(d.items.map((it) => [it.warehouse, it]));
|
||
|
||
console.log('接口: 基准日 %s | 快照日 %s | asOf %s | 仓数 %d', d.baselineDate, d.latestBizDate, d.asOf, d.items.length);
|
||
console.log();
|
||
|
||
const diffCounts = {};
|
||
let totalCells = 0;
|
||
let diffCells = 0;
|
||
const samples = [];
|
||
|
||
for (const r of rows.slice(1)) {
|
||
const name = String(r[idx['仓库']] || '').trim();
|
||
if (!name) continue;
|
||
const item = byName.get(name);
|
||
if (!item) { console.log('接口缺少仓库:', name); continue; }
|
||
for (const [cn, field] of FIELD_MAP) {
|
||
const raw = r[idx[cn]];
|
||
const expected = typeof raw === 'number' ? raw : Number(String(raw || '').replace(/,/g, '')) || 0;
|
||
const actual = Number(item[field] ?? 0);
|
||
totalCells += 1;
|
||
// 期末允许 1 件内的副本同步误差
|
||
const tol = field === 'closingQty' ? 10 : 0;
|
||
if (Math.abs(expected - actual) > tol) {
|
||
diffCells += 1;
|
||
diffCounts[cn] = (diffCounts[cn] || 0) + 1;
|
||
if (samples.length < 12) samples.push(`${name} | ${cn} | 表格 ${expected} vs 接口 ${actual}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('比对单元格总数: %d,不一致: %d', totalCells, diffCells);
|
||
if (diffCells) {
|
||
console.log('\n不一致列统计:', JSON.stringify(diffCounts));
|
||
console.log('样例:');
|
||
samples.forEach((s) => console.log(' ' + s));
|
||
} else {
|
||
console.log('✓ 全部单元格与表格一致');
|
||
}
|
||
|
||
const withGrade = d.items.filter((x) => x.hasGrade).length;
|
||
console.log('\n有分级数据仓: %d/%d', withGrade, d.items.length);
|
||
console.log('无分级的仓:', d.items.filter((x) => !x.hasGrade).map((x) => x.warehouse).join('、'));
|
||
console.log('\n合计: 期初 %s,期末 %s', d.totals.openingQty, d.totals.closingQty);
|
||
console.log('崔桥样例:', JSON.stringify(d.items[0], null, 1).replace(/\n\s*/g, ' '));
|
||
process.exit(0);
|
||
})().catch((e) => { console.log('ERR', e.message); process.exit(1); });
|