业务库只能还原 4/12 个流水列且口径与聚水潭不一致(采购按单据日期、销售按订单发货日期, jst_salesOutboundInfo.qty 全为 0),调拨/盘点/加工/其它出入库无单据表,故以官方导出为准: - 新增本地表 daily_inventory_official(biz_date + warehouse 存期初/12 流水列/期末) - 新增解析与导入服务:xlsx 解析、仓库 ID 映射、「期初+流水=期末」恒等式校验 - GET /api/daily-inventory 支持 ?date= 指定业务日,官方值优先、缺失回退推算并返回来源标记 - 新增 POST /api/daily-inventory/official-import(xlsx 上传/JSON)与 GET /official 查询 - 导入后回写本地库存快照(官方期初→上一日期末、官方期末→当日期末),使推算也对齐 - 前端:业务日切换、官方/推算来源标记、导入弹窗、口径说明同步更新 - 仓库清单补入「简尚-横林三仓主仓」(聚水潭导出中存在) - 命令行导入:npm run inventory:import-official -- <日期> <xlsx>
98 lines
3.9 KiB
JavaScript
98 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 导入聚水潭「进销存按仓导出」到每日进销存校正表
|
||
*
|
||
* 用法:
|
||
* node scripts/importDailyInventoryOfficial.js <业务日期> <xlsx 路径>
|
||
* node scripts/importDailyInventoryOfficial.js 2026-09-26 ./进销存_按仓_20260926.xlsx
|
||
*
|
||
* 作用:
|
||
* 1) 解析导出文件,校验「期初 + Σ流水 = 期末」逐仓闭合
|
||
* 2) 写入本地表 daily_inventory_official(同一业务日整体覆盖)
|
||
* 3) 回写本地快照:官方期初 → 上一业务日期末、官方期末 → 该业务日期末
|
||
*
|
||
* 页面右上角「导入聚水潭进销存」走的是同一个后端接口(POST /api/daily-inventory/official-import)。
|
||
*/
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { parseWorkbook, importOfficial, checkBalance } = require('../src/services/dailyInventoryOfficial');
|
||
const snapshotDb = require('../src/config/dailyInventorySnapshotDb');
|
||
const { DAILY_INVENTORY_BASELINE } = require('../src/config/dailyInventoryBaseline');
|
||
|
||
function prevDay(bizDate) {
|
||
const d = new Date(`${bizDate}T00:00:00Z`);
|
||
d.setUTCDate(d.getUTCDate() - 1);
|
||
return d.toISOString().slice(0, 10);
|
||
}
|
||
|
||
async function main() {
|
||
const [bizDate, file] = process.argv.slice(2);
|
||
if (!bizDate || !/^\d{4}-\d{2}-\d{2}$/.test(bizDate)) {
|
||
console.error('用法: node scripts/importDailyInventoryOfficial.js <业务日期 YYYY-MM-DD> <xlsx 路径>');
|
||
process.exit(1);
|
||
}
|
||
if (!file || !fs.existsSync(file)) {
|
||
console.error(`文件不存在: ${file}`);
|
||
process.exit(1);
|
||
}
|
||
|
||
const { rows, warnings, sheetName } = parseWorkbook(fs.readFileSync(file));
|
||
console.log(`解析 ${path.basename(file)} [${sheetName}]:${rows.length} 行,提示:${warnings.join(';') || '无'}`);
|
||
if (!rows.length) {
|
||
console.error('未解析到仓库行,已中止');
|
||
process.exit(1);
|
||
}
|
||
|
||
const mismatches = checkBalance(rows);
|
||
if (mismatches.length) {
|
||
console.warn(`⚠ 以下 ${mismatches.length} 个仓「期初 + 流水 ≠ 期末」:`);
|
||
mismatches.forEach((m) => console.warn(` ${m.warehouse}: 差 ${m.diff}`));
|
||
} else {
|
||
console.log('恒等式校验:期初 + 流水 = 期末,逐仓闭合 ✓');
|
||
}
|
||
|
||
const result = await importOfficial(bizDate, rows);
|
||
console.log(`写入 official:${result.imported} 行(${bizDate})`);
|
||
if (result.unknownWarehouses.length) {
|
||
console.warn(`⚠ 未匹配到仓库 ID(仅按仓库名存档):${result.unknownWarehouses.join('、')}`);
|
||
}
|
||
if (result.missingWarehouses.length) {
|
||
console.warn(`⚠ 清单中未出现在导出里的仓库:${result.missingWarehouses.join('、')}`);
|
||
}
|
||
|
||
const byId = new Map(DAILY_INVENTORY_BASELINE.map((b) => [String(b.wmsCoId), b]));
|
||
const known = rows.filter((r) => r.wmsCoId != null);
|
||
const prevDate = prevDay(bizDate);
|
||
const prev = await snapshotDb.fetchSnapshot(prevDate);
|
||
const prevCount = await snapshotDb.upsertSnapshot(prevDate, known
|
||
.filter((r) => r.openingQty != null)
|
||
.map((r) => ({
|
||
wmsCoId: r.wmsCoId,
|
||
warehouse: r.warehouse,
|
||
dbWarehouse: byId.get(String(r.wmsCoId))?.dbWarehouse || null,
|
||
closingQty: Number(r.openingQty),
|
||
skuCount: Number(prev.get(String(r.wmsCoId))?.skuCount || 0),
|
||
takenAt: null,
|
||
})));
|
||
const current = await snapshotDb.fetchSnapshot(bizDate);
|
||
const currentCount = await snapshotDb.upsertSnapshot(bizDate, known
|
||
.filter((r) => r.closingQty != null)
|
||
.map((r) => ({
|
||
wmsCoId: r.wmsCoId,
|
||
warehouse: r.warehouse,
|
||
dbWarehouse: byId.get(String(r.wmsCoId))?.dbWarehouse || null,
|
||
closingQty: Number(r.closingQty),
|
||
skuCount: Number(current.get(String(r.wmsCoId))?.skuCount || 0),
|
||
takenAt: null,
|
||
})));
|
||
console.log(`快照回写:${prevDate}(官方期初 → 期末)${prevCount} 行,${bizDate}(官方期末)${currentCount} 行`);
|
||
process.exit(0);
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error('导入失败:', e.message);
|
||
process.exit(1);
|
||
});
|