口径校准(对照聚水潭进销存导出 09-23 期初基准): 崔桥工厂仓 基准 92388 / pm 路径 114286 / 回推 ≈89650 横林二仓 基准 63780 / pm 路径 1197 / 回推 ≈64029 山东青岗集 基准 88377 / pm 路径 67339 / 回推 ≈88210 河南洛宁 基准 83305 / pm 路径 76992 / 回推 ≈82473 pm_stockMonitor.jsStock 只覆盖纳入监控范围的 SKU 子集(横林二仓实测 1197 vs 全仓 63780),系统性偏低,不能充当全仓结存;「实时库存扣 流水回推」与基准偏差 0.3%~2.7%(来自缺失的 8 类单据),显著更准。
111 lines
4.2 KiB
JavaScript
111 lines
4.2 KiB
JavaScript
'use strict';
|
||
|
||
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
|
||
const {
|
||
AVAILABLE_FLOW_FIELDS,
|
||
buildInClause,
|
||
fetchFlows,
|
||
} = require('../src/services/dailyInventoryFlows');
|
||
const { deriveAndStoreOpeningSnapshot } = require('../src/services/dailyInventoryOpening');
|
||
|
||
const BASELINE = [
|
||
{ warehouse: '仓A主仓', dbWarehouse: '仓A', wmsCoId: 101 },
|
||
{ warehouse: '仓B主仓', dbWarehouse: '仓B', wmsCoId: 102 },
|
||
{ warehouse: '仓C主仓', dbWarehouse: null, wmsCoId: 103 },
|
||
];
|
||
|
||
/** 构造一个按 SQL 关键字路由的假业务库 */
|
||
function fakeCloudDb(handler) {
|
||
return { query: async (sql, params) => handler(String(sql), params || {}) };
|
||
}
|
||
|
||
test('buildInClause 展开参数且不为 async', () => {
|
||
const { clause, params } = buildInClause([1, 2], 'w');
|
||
assert.equal(clause, '@w0,@w1');
|
||
assert.deepEqual(params, { w0: 1, w1: 2 });
|
||
});
|
||
|
||
test('fetchFlows 查询四类单据并按符号约定返回(流入正、流出负)', async () => {
|
||
const seen = [];
|
||
const db = fakeCloudDb((sql, params) => {
|
||
seen.push(sql);
|
||
if (sql.includes('jst_purchaseItem')) return [{ wmsCoId: 101, qty: '100' }];
|
||
if (sql.includes('jst_purchaseoutItem')) return [{ wmsCoId: 101, qty: '30' }];
|
||
if (sql.includes('jst_OrderItem')) return [{ wmsCoId: 101, qty: '50' }];
|
||
if (sql.includes('jst_returnAndRefundItem')) return [{ wmsCoId: 101, qty: '8' }];
|
||
return [];
|
||
});
|
||
const map = await fetchFlows(db, [101, 102], '2026-09-26 01:00:00');
|
||
const f = map.get('101');
|
||
assert.equal(f.purchaseIn, 100, '采购进仓为流入正数');
|
||
assert.equal(f.purchaseReturn, -30, '采购退货为流出负数');
|
||
assert.equal(f.salesOut, -50, '销售出仓为流出负数');
|
||
assert.equal(f.salesReturn, 8, '销售退货为流入正数');
|
||
assert.equal(seen.length, 4, '应发出 4 条单据查询');
|
||
assert.ok(seen.every((s) => s.includes('@from')), '每条查询都带 @from 参数');
|
||
assert.deepEqual(AVAILABLE_FLOW_FIELDS.sort(), ['purchaseIn', 'purchaseReturn', 'salesOut', 'salesReturn']);
|
||
});
|
||
|
||
test('fetchFlows 空仓库列表直接返回空 Map', async () => {
|
||
const map = await fetchFlows(fakeCloudDb(() => []), [], '2026-09-26 01:00:00');
|
||
assert.equal(map.size, 0);
|
||
});
|
||
|
||
test('deriveAndStoreOpeningSnapshot:全部仓库用实时库存扣流水回推,并保留基线仓库名', async () => {
|
||
const upserts = [];
|
||
const cloudDb = fakeCloudDb((sql, params) => {
|
||
if (sql.includes('jst_inventoryInfo')) {
|
||
return [
|
||
{ wmsCoId: 101, qty: '520', skuCount: '13' },
|
||
{ wmsCoId: 102, qty: '300', skuCount: '9' },
|
||
{ wmsCoId: 103, qty: '10', skuCount: '2' },
|
||
];
|
||
}
|
||
if (sql.includes('jst_purchaseItem')) return [{ wmsCoId: 102, qty: '60' }];
|
||
if (sql.includes('jst_purchaseoutItem')) return [{ wmsCoId: 102, qty: '10' }];
|
||
if (sql.includes('jst_OrderItem')) return [{ wmsCoId: 102, qty: '40' }];
|
||
if (sql.includes('jst_returnAndRefundItem')) return [];
|
||
return [];
|
||
});
|
||
const snapshotDb = {
|
||
async upsertSnapshot(bizDate, rows) {
|
||
upserts.push({ bizDate, rows });
|
||
return rows.length;
|
||
},
|
||
};
|
||
|
||
const result = await deriveAndStoreOpeningSnapshot({
|
||
cloudDb,
|
||
snapshotDb,
|
||
baseline: BASELINE,
|
||
openingDate: '2026-09-25',
|
||
latestBizDate: '2026-09-26',
|
||
takenAt: null,
|
||
});
|
||
|
||
assert.equal(result.count, 3);
|
||
assert.equal(result.derivedCount, 3);
|
||
assert.equal(upserts.length, 1);
|
||
assert.equal(upserts[0].bizDate, '2026-09-25');
|
||
|
||
const byWh = new Map(upserts[0].rows.map((r) => [r.wmsCoId, r]));
|
||
// 仓A:无流水 → 等于实时库存 520
|
||
assert.equal(byWh.get(101).closingQty, 520);
|
||
// 仓B:300 − 净流入(60 − 10 − 40) = 300 − 10 = 290
|
||
assert.equal(byWh.get(102).closingQty, 290);
|
||
// 仓C:无流水 → 等于实时库存 10
|
||
assert.equal(byWh.get(103).closingQty, 10);
|
||
// 带回基线仓库名,保证后续按 wmsCoId 读取正确
|
||
assert.equal(byWh.get(102).warehouse, '仓B主仓');
|
||
assert.equal(byWh.get(102).dbWarehouse, '仓B');
|
||
});
|
||
|
||
test('fetchFlows 缺少数据库实例参数时立即抛错(防静默空结果)', async () => {
|
||
await assert.rejects(
|
||
() => fetchFlows([101, 102], '2026-09-26 01:00:00'),
|
||
/第一个参数必须是数据库实例/
|
||
);
|
||
});
|