// 大货库存监控 - 出入库流水取数(本月 1 日 ~ 快照日) // // 业务库 JSReportReplica 里与「库存流水」相关的单据表: // 采购进仓 rpt.jst_purchase (wms_co_id, po_date, status) + rpt.jst_purchaseItem (po_id, inQty) // 采购退货 rpt.jst_purchaseout (wms_co_id, io_date, status) + rpt.jst_purchaseoutItem (io_id, qty) // 销售出仓 rpt.jst_salesOutboundInfo(wms_co_id, io_date, status) + rpt.jst_OrderItem (o_id, qty) // 销售退货 rpt.jst_returnAndRefundInfo(warehouse 名称) + rpt.jst_returnAndRefundItem(as_id, r_qty) // // 有效单据状态:只统计 Confirmed / Finished,排除 Cancelled / Delete / WaitConfirm。 // 业务库中不存在加工、盘点、调拨、其它出库任何单据表,这 6 列无法取数。 const cloudDb = require('../config/cloudDatabase'); // 单据有效状态(已确认/已完成),避免把取消单算进流水 const PURCHASE_STATUS = "'Confirmed','Finished'"; const PURCHASEOUT_STATUS = "'Confirmed'"; const SALESOUT_STATUS = "'Confirmed'"; const REFUND_STATUS = "'Confirmed'"; function toNumber(value) { if (value == null) return null; const n = Number(value); return Number.isFinite(n) ? n : null; } /** * 采购进仓:采购单已入库数量(inQty) */ async function fetchPurchaseIn(wmsCoId, periodStart, periodEnd) { const rows = await cloudDb.query( `SELECT ISNULL(SUM(CAST(pi.inQty AS DECIMAL(18, 2))), 0) AS v FROM rpt.jst_purchase p JOIN rpt.jst_purchaseItem pi ON pi.po_id = p.po_id WHERE p.wms_co_id = @wmsCoId AND p.po_date >= @periodStart AND p.po_date < @periodEnd AND p.status IN (${PURCHASE_STATUS})`, { wmsCoId, periodStart, periodEnd } ); return toNumber(rows[0]?.v); } /** * 采购退货:采购退货出库单数量 */ async function fetchPurchaseReturn(wmsCoId, periodStart, periodEnd) { const rows = await cloudDb.query( `SELECT ISNULL(SUM(CAST(oi.qty AS DECIMAL(18, 2))), 0) AS v FROM rpt.jst_purchaseout o JOIN rpt.jst_purchaseoutItem oi ON oi.io_id = o.io_id WHERE o.wms_co_id = @wmsCoId AND o.io_date >= @periodStart AND o.io_date < @periodEnd AND o.status IN (${PURCHASEOUT_STATUS})`, { wmsCoId, periodStart, periodEnd } ); return toNumber(rows[0]?.v); } /** * 销售出仓:销售出库单关联的订单明细数量 * 注意:jst_salesOutboundInfo.qty 是表头字段且恒为 0,真实件数在 jst_OrderItem.qty */ async function fetchSalesOut(wmsCoId, periodStart, periodEnd) { const rows = await cloudDb.query( `SELECT ISNULL(SUM(CAST(oi.qty AS DECIMAL(18, 2))), 0) AS v FROM rpt.jst_salesOutboundInfo s JOIN rpt.jst_OrderItem oi ON oi.o_id = s.o_id WHERE s.wms_co_id = @wmsCoId AND s.io_date >= @periodStart AND s.io_date < @periodEnd AND s.status IN (${SALESOUT_STATUS})`, { wmsCoId, periodStart, periodEnd } ); return toNumber(rows[0]?.v); } /** * 销售退货:退货退款单明细的实退数量(r_qty) * 该表 wms_co_id 大量为空(9 月 92821 单中 75094 单为空),改用 warehouse 名称匹配: * 名称形如「简尚-崔桥工厂仓销退仓」/「简尚-分销部睢宁专用仓主仓」,统一去掉「销退仓」「主仓」后比对。 */ async function fetchSalesReturn(dbWarehouse, periodStart, periodEnd) { if (!dbWarehouse) return null; const rows = await cloudDb.query( `SELECT ISNULL(SUM(CAST(ri.r_qty AS DECIMAL(18, 2))), 0) AS v FROM rpt.jst_returnAndRefundInfo r JOIN rpt.jst_returnAndRefundItem ri ON ri.as_id = r.as_id WHERE REPLACE(REPLACE(ISNULL(r.warehouse, ''), N'销退仓', N''), N'主仓', N'') = @whName AND r.created >= @periodStart AND r.created < @periodEnd AND r.status IN (${REFUND_STATUS})`, { whName: dbWarehouse, periodStart, periodEnd } ); return toNumber(rows[0]?.v); } /** * 仓库实物库存:聚水潭实时库存表 rpt.jst_inventoryInfo(按 wms_co_id 单仓统计) * 这是报表「期末数量」的取数口径 —— pm_stockMonitor 的 warehouse 仅是归属仓, * 其 jsStock 含已调拨到外仓的货,不能代表本仓实物库存。 */ async function fetchInventoryQty(wmsCoId) { if (!wmsCoId) return null; const rows = await cloudDb.query( `SELECT ISNULL(SUM(CAST(qty AS DECIMAL(18, 2))), 0) AS v, COUNT(CASE WHEN ISNULL(qty, 0) <> 0 THEN 1 END) AS skuCount, MAX(modified) AS takenAt FROM rpt.jst_inventoryInfo WHERE wms_co_id = @wmsCoId`, { wmsCoId } ); return { qty: toNumber(rows[0]?.v), skuCount: toNumber(rows[0]?.skuCount) || 0, takenAt: rows[0]?.takenAt ? new Date(rows[0].takenAt).toISOString() : null, }; } /** * 一次性取全部流水;任一子查询失败不影响其它字段(返回 null 由调用方降级为「—」) */ async function fetchFlowSummary({ wmsCoId, dbWarehouse, periodStart, periodEnd }) { const [purchaseIn, purchaseReturn, salesOut, salesReturn] = await Promise.all([ wmsCoId ? fetchPurchaseIn(wmsCoId, periodStart, periodEnd).catch(() => null) : null, wmsCoId ? fetchPurchaseReturn(wmsCoId, periodStart, periodEnd).catch(() => null) : null, wmsCoId ? fetchSalesOut(wmsCoId, periodStart, periodEnd).catch(() => null) : null, fetchSalesReturn(dbWarehouse, periodStart, periodEnd).catch(() => null), ]); return { purchaseIn, purchaseReturn, salesOut, salesReturn }; } module.exports = { fetchFlowSummary, fetchInventoryQty, };