'use strict'; /** * 每日进销存 —— 流水列取数(业务库原生口径) * * 业务库 JSReportReplica 能还原的 4 个流水列(其余 8 列缺单据表,保持「—」): * 采购进仓(+): rpt.jst_purchase × jst_purchaseItem,按 po_date,状态 Confirmed/Finished,取明细 qty * (明细 inQty 全为 0 未同步,故用 qty) * 采购退货(−): rpt.jst_purchaseout × jst_purchaseoutItem,按 io_date * 销售出仓(−): rpt.jst_Orders × jst_OrderItem,按 send_date,排除 Delete/Cancelled * (与「库存周转天数」的发货口径一致;与聚水潭进销存导出的出库单口径有偏差, * 因为销售出库单明细数量在业务库未同步,无法精确还原) * 销售退货(+): rpt.jst_returnAndRefundInfo × jst_returnAndRefundItem,按 as_date, * 类型限定 普通退货/拒收退货/换货/维修,取明细 r_qty(实收数量; * qty 字段大多未同步,不能用) * * 符号约定与页面一致:流入为正、流出为负(前端负数标红)。 * 因为还有 8 类单据(调拨/盘点/加工/其它出入库)在业务库无表, * 「期末 ≠ 期初 + 流水之和」,差值即缺失单据的影响。 */ const FLOW_QUERIES = [ { field: 'purchaseIn', sign: 1, sql: ` SELECT p.wms_co_id AS wmsCoId, SUM(CAST(ISNULL(pi.qty, 0) AS DECIMAL(18, 2))) AS qty FROM rpt.jst_purchase p JOIN rpt.jst_purchaseItem pi ON pi.po_id = p.po_id WHERE p.wms_co_id IN (%WAREHOUSES%) AND p.po_date >= @from AND ISNULL(p.status, '') IN ('Confirmed', 'Finished') GROUP BY p.wms_co_id`, }, { field: 'purchaseReturn', sign: -1, sql: ` SELECT o.wms_co_id AS wmsCoId, SUM(CAST(ISNULL(oi.qty, 0) AS DECIMAL(18, 2))) AS qty FROM rpt.jst_purchaseout o JOIN rpt.jst_purchaseoutItem oi ON oi.io_id = o.io_id WHERE o.wms_co_id IN (%WAREHOUSES%) AND o.io_date >= @from GROUP BY o.wms_co_id`, }, { field: 'salesOut', sign: -1, sql: ` SELECT o.wms_co_id AS wmsCoId, SUM(CAST(ISNULL(oi.qty, 0) AS DECIMAL(18, 2))) AS qty FROM rpt.jst_Orders o JOIN rpt.jst_OrderItem oi ON oi.o_id = o.o_id WHERE o.wms_co_id IN (%WAREHOUSES%) AND o.send_date >= @from AND ISNULL(o.status, '') NOT IN ('Delete', 'Cancelled') GROUP BY o.wms_co_id`, }, { field: 'salesReturn', sign: 1, sql: ` SELECT r.wms_co_id AS wmsCoId, SUM(CAST(ISNULL(ri.r_qty, 0) AS DECIMAL(18, 2))) AS qty FROM rpt.jst_returnAndRefundInfo r JOIN rpt.jst_returnAndRefundItem ri ON ri.as_id = r.as_id WHERE r.wms_co_id IN (%WAREHOUSES%) AND r.as_date >= @from AND ISNULL(r.type, '') IN (N'普通退货', N'拒收退货', N'换货', N'维修') GROUP BY r.wms_co_id`, }, ]; /** mssql 不支持数组参数,按位展开成 @p0,@p1... */ function buildInClause(values, prefix) { const params = {}; const clause = values .map((v, i) => { params[`${prefix}${i}`] = v; return `@${prefix}${i}`; }) .join(','); return { clause, params }; } /** 业务库可还原的流水字段(带符号:流入 +1 / 流出 −1) */ const AVAILABLE_FLOW_FIELDS = FLOW_QUERIES.map((q) => q.field); /** * 拉取自 fromText(含)以来各仓的流水合计。 * @returns {Promise>} key = wmsCoId 字符串,value = { purchaseIn, purchaseReturn, salesOut, salesReturn }(带符号,无记录的字段不出现) */ async function fetchFlows(cloudDb, wmsCoIds, fromText) { if (!cloudDb || typeof cloudDb.query !== 'function') { throw new TypeError('fetchFlows 第一个参数必须是数据库实例(防呆:调用漏传 cloudDb 时立即报错而非静默返回空)'); } const map = new Map(); if (!wmsCoIds.length || !fromText) return map; for (const q of FLOW_QUERIES) { const { clause, params } = buildInClause(wmsCoIds, 'w'); const sqlText = q.sql.replace('%WAREHOUSES%', clause); const rows = await cloudDb.query(sqlText, { ...params, from: fromText }, { requestTimeout: 120000 }); for (const r of rows) { const key = String(r.wmsCoId); const entry = map.get(key) || {}; const qty = Number(r.qty) || 0; entry[q.field] = q.sign * qty; map.set(key, entry); } } return map; } module.exports = { AVAILABLE_FLOW_FIELDS, FLOW_QUERIES, fetchFlows, buildInClause, };