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