'use strict'; const express = require('express'); const router = express.Router(); const { verifyToken } = require('../middleware/auth'); const { getPool } = require('../services/sqlPool'); // ── Open Production Orders ──────────────────────────────────────────────── function buildOpenProductionOrdersSQL(from, to, status, segment) { const statusClause = status && status !== 'all' ? `AND OWOR.Status = '${status === 'Released' ? 'R' : 'P'}'` : `AND OWOR.Status IN ('R','P')`; const segmentClause = segment && segment !== 'all' ? `AND ( CASE WHEN OITM.U_ItemSubGroup LIKE '%SFG Equipment%' OR OITM.U_ItemSubGroup LIKE '%EQUIPMENT%' THEN 'Equipment' WHEN OITM.U_ItemSubGroup LIKE '%Consumable%' OR OITM.U_ItemSubGroup LIKE '%Consumables%' THEN 'Consumable' WHEN OITM.U_ItemSubGroup LIKE '%FG Stent%' THEN 'FG Stent' WHEN OITM.U_ItemSubGroup LIKE '%FG TT Device%' THEN 'FG TT Device' WHEN OITM.U_ItemSubGroup LIKE '%PLASTIC%' THEN 'PLASTIC' WHEN OITM.U_ItemSubGroup LIKE '%NEEDLE%' THEN 'NEEDLE' WHEN OITM.U_ItemSubGroup LIKE '%MOULDING%' THEN 'MOULDING' WHEN OITM.U_ItemSubGroup LIKE '%FG CAPD ACCESSORIES%' THEN 'FG CAPD ACCESSORIES' WHEN OITM.U_ItemSubGroup LIKE '%FG CAPD%' THEN 'FG CAPD' WHEN OITM.U_ItemSubGroup LIKE '%CAPD%' THEN 'CAPD' WHEN OITM.U_ItemSubGroup LIKE '%FG Equipment%' THEN 'FG Equipment' WHEN OITM.U_ItemSubGroup LIKE '%FG Blood Bag%' THEN 'FG Blood Bag' WHEN OITM.U_ItemSubGroup LIKE '%SFG Blood bag%' THEN 'SFG Blood bag' WHEN OITM.U_ItemSubGroup LIKE '%BLOOD BAG%' THEN 'BLOOD BAG' ELSE 'Other' END ) = '${segment.replace(/'/g, "''")}'` : ''; return ` SELECT DISTINCT OWOR.DocEntry, k.SeriesName, OWOR.DocNum, CASE WHEN OWOR.Type = 'S' THEN 'Standard' ELSE 'Other' END AS Type, CASE WHEN OWOR.Status = 'R' THEN 'Released' WHEN OWOR.Status = 'P' THEN 'Planned' ELSE 'Unknown' END AS Status, OWOR.ItemCode AS ProductCode, CASE WHEN OITM.U_ItemSubGroup LIKE '%SFG Equipment%' OR OITM.U_ItemSubGroup LIKE '%EQUIPMENT%' THEN 'Equipment' WHEN OITM.U_ItemSubGroup LIKE '%Consumable%' OR OITM.U_ItemSubGroup LIKE '%Consumables%' THEN 'Consumable' WHEN OITM.U_ItemSubGroup LIKE '%FG Stent%' THEN 'FG Stent' WHEN OITM.U_ItemSubGroup LIKE '%FG TT Device%' THEN 'FG TT Device' WHEN OITM.U_ItemSubGroup LIKE '%PLASTIC%' THEN 'PLASTIC' WHEN OITM.U_ItemSubGroup LIKE '%NEEDLE%' THEN 'NEEDLE' WHEN OITM.U_ItemSubGroup LIKE '%MOULDING%' THEN 'MOULDING' WHEN OITM.U_ItemSubGroup LIKE '%FG CAPD ACCESSORIES%' THEN 'FG CAPD ACCESSORIES' WHEN OITM.U_ItemSubGroup LIKE '%FG CAPD%' THEN 'FG CAPD' WHEN OITM.U_ItemSubGroup LIKE '%CAPD%' THEN 'CAPD' WHEN OITM.U_ItemSubGroup LIKE '%FG Equipment%' THEN 'FG Equipment' WHEN OITM.U_ItemSubGroup LIKE '%FG Blood Bag%' THEN 'FG Blood Bag' WHEN OITM.U_ItemSubGroup LIKE '%SFG Blood bag%' THEN 'SFG Blood bag' WHEN OITM.U_ItemSubGroup LIKE '%BLOOD BAG%' THEN 'BLOOD BAG' ELSE 'Other' END AS ProductSegment, OWOR.ProdName AS ProductName, OWOR.PlannedQty AS PlannedQuantity, OWOR.CmpltQty AS CompletedQty, CONVERT(VARCHAR(10), OWOR.PostDate, 105) AS PostDate, CONVERT(VARCHAR(10), OWOR.StartDate, 105) AS StartDate, CONVERT(VARCHAR(10), OWOR.DueDate, 105) AS DueDate FROM OWOR INNER JOIN WOR1 ON OWOR.DocEntry = WOR1.DocEntry INNER JOIN OITM ON OWOR.ItemCode = OITM.ItemCode LEFT JOIN NNM1 k ON k.Series = OWOR.Series WHERE OWOR.PostDate BETWEEN @from AND @to ${statusClause} AND OITM.U_ItemSubGroup <> 'FG Scrap' ${segmentClause} ORDER BY OWOR.DocNum DESC`; } // ── GET /api/reports/open-production-orders ─────────────────────────────── router.get('/open-production-orders', verifyToken, async (req, res) => { try { let { from, to, status = 'all', segment = 'all' } = req.query; // Default: last 15 days if (!from || !to) { const toDate = new Date(); toDate.setDate(toDate.getDate() - 1); const frDate = new Date(toDate); frDate.setDate(frDate.getDate() - 14); to = toDate.toISOString().split('T')[0]; from = frDate.toISOString().split('T')[0]; } const pool = await getPool(); const sql = buildOpenProductionOrdersSQL(from, to, status, segment); const r = await pool.request() .input('from', from) .input('to', to) .query(sql); res.json({ success: true, data: r.recordset || [], count: (r.recordset || []).length, from, to }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ── Revenue constants (mirrors chatbot.js) ──────────────────────────────── const EXCL_DOCS = `'552520145','552520146','552520147','552520465','552522652','552522653','552620186'`; const EXCL_TM = `'4110201001','4110202001','4110201003','4110202003','4110201007','4110201002','4110201005'`; const EXCL_PD = EXCL_TM + `,'4110202005','4110202007','4110201004'`; const EXCL_ED = `'4110201001','4110201003','4110201007','4110201002','4110201005','4110201004'`; const EXCL_EI = `'4110202001','4110202003','4110202005','4110202007'`; function buildRevSubq(dateWhere, isCN) { const tbl = isCN ? 'RIN1' : 'INV1'; const hdr = isCN ? 'ORIN' : 'OINV'; const sign = isCN ? '-' : ''; const extras = isCN ? '' : ` AND i.DocNum NOT IN (${EXCL_DOCS}) AND (t2.ItmsGrpNam IN ('FG CAPD','FG CAPD Accessories') OR (i.DocStatus<>'C' OR i.InvntSttus='O'))`; return ` SELECT CASE WHEN (t2.ItmsGrpNam='FG BLOOD BAG' OR (t2.ItmsGrpNam IN ('FG EQUIPMENT','SEMI FINISHED') AND t1.U_BagType='Equipment')) AND t.AcctCode NOT IN (${EXCL_TM}) THEN ${sign}t.LineTotal ELSE 0 END AS TM, CASE WHEN (t2.ItmsGrpNam IN ('FG CAPD','FG CAPD Accessories') OR t1.U_PDGroup='Equipment') AND t.AcctCode NOT IN (${EXCL_PD}) THEN ${sign}t.LineTotal ELSE 0 END AS PD, CASE WHEN t.AcctCode IN (${EXCL_ED}) THEN ${sign}t.LineTotal ELSE 0 END AS ED, CASE WHEN t.AcctCode IN (${EXCL_EI}) THEN ${sign}t.LineTotal ELSE 0 END AS EI FROM ${tbl} t LEFT JOIN OITM t1 ON t.ItemCode = t1.ItemCode LEFT JOIN OITB t2 ON t1.ItmsGrpCod = t2.ItmsGrpCod INNER JOIN ${hdr} i ON t.DocEntry = i.DocEntry WHERE ${dateWhere} ${extras}`; } // ── GET /api/reports/sales?asOf=YYYY-MM-DD&period=all|daily|monthly|yearly ─ router.get('/sales', verifyToken, async (req, res) => { try { const { period = 'all' } = req.query; const target = req.query.asOf ? new Date(req.query.asOf) : (() => { const d = new Date(); d.setDate(d.getDate() - 1); return d; })(); const toDate = target.toISOString().split('T')[0]; const dd = target.getDate(), mm = target.getMonth() + 1, yyyy = target.getFullYear(); const yearStart = (dd < 16 && mm <= 1) ? `${yyyy - 1}-01-16` : `${yyyy}-01-16`; const pMm = mm === 1 ? 12 : mm - 1; const pYyyy = mm === 1 ? yyyy - 1 : yyyy; const monthStart = dd < 16 ? `${pYyyy}-${String(pMm).padStart(2, '0')}-16` : `${yyyy}-${String(mm).padStart(2, '0')}-16`; const pt = period.toLowerCase(); const periods = []; if (pt === 'daily' || pt === 'all') periods.push([`Daily (${toDate})`, `i.DocDate = '${toDate}'`]); if (pt === 'monthly' || pt === 'all') periods.push([`Monthly (${monthStart} – ${toDate})`, `i.DocDate BETWEEN '${monthStart}' AND '${toDate}'`]); if (pt === 'yearly' || pt === 'all') periods.push([`Yearly (${yearStart} – ${toDate})`, `i.DocDate BETWEEN '${yearStart}' AND '${toDate}'`]); const pool = await getPool(); const rows = []; for (const [label, dw] of periods) { const sql = ` SELECT '${label}' AS Period, ROUND(SUM(TM), 2) AS TM_BloodBag, ROUND(SUM(PD), 2) AS PD_CAPD, ROUND(SUM(ED), 2) AS ExportDirect, ROUND(SUM(EI), 2) AS ExportIndirect, ROUND(SUM(TM)+SUM(PD)+SUM(ED)+SUM(EI), 2) AS Total FROM ( ${buildRevSubq(dw, false)} UNION ALL ${buildRevSubq(dw, true)} ) CK`; try { const r = await pool.request().query(sql); if (r.recordset?.[0]) rows.push(r.recordset[0]); } catch (e) { rows.push({ Period: label, error: e.message }); } } res.json({ success: true, data: rows, asOf: toDate }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ── GET /api/reports/fg-inventory?from=&to=&warehouse=&itemGroup= ───────── router.get('/fg-inventory', verifyToken, async (req, res) => { try { let { from, to, warehouse = '01', itemGroup = 'FG BLOOD BAG' } = req.query; if (!from || !to) { const d = new Date(); to = d.toISOString().split('T')[0]; const f = new Date(d.getFullYear(), d.getMonth(), 1); from = f.toISOString().split('T')[0]; } const pool = await getPool(); const r = await pool.request() .input('FromDate', from) .input('ToDate', to) .input('WhsCode', warehouse) .input('ItmsGrp', itemGroup) .execute('INVENTORY_FG_AVG_PRICE'); const data = r.recordset || []; res.json({ success: true, data, count: data.length, from, to, warehouse, itemGroup }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ── GET /api/reports/bom-cost?itemGroup= ───────────────────────────────── router.get('/bom-cost', verifyToken, async (req, res) => { try { const { itemGroup = 'FG BLOOD BAG' } = req.query; const pool = await getPool(); const r = await pool.request() .input('grp', itemGroup) .query(` SELECT T1.Code AS ItemCode, T0.ItemName, MIN(P.Price) AS Price FROM OITT T1 INNER JOIN OITM T0 ON T0.ItemCode = T1.Code LEFT JOIN ITM1 P ON P.ItemCode = T1.Code LEFT JOIN OITB T2 ON T2.ItmsGrpCod = T0.ItmsGrpCod WHERE T2.ItmsGrpNam = @grp AND P.Price > 0 GROUP BY T1.Code, T0.ItemName ORDER BY T1.Code`); const data = r.recordset || []; res.json({ success: true, data, count: data.length, itemGroup }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); module.exports = router; // Reused by routes/board.js so the Board Dashboard's Division Summary is // computed by EXACTLY the same logic as this sales report (verified to match // the management figures to the paisa). module.exports.buildRevSubq = buildRevSubq; module.exports.REV_CONST = { EXCL_DOCS, EXCL_TM, EXCL_PD, EXCL_ED, EXCL_EI };