'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'), /第一个参数必须是数据库实例/ ); });