cangku/backend/scripts/verify-daily-inventory.js

97 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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); });