'use strict'; const express = require('express'); const router = express.Router(); const axios = require('axios'); const FormData = require('form-data'); const ExcelJS = require('exceljs'); const { verifyToken } = require('../middleware/auth'); const https = require('https'); const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // ── helpers ──────────────────────────────────────────────────────────────── function safeDate(d) { if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) throw new Error(`Invalid date: ${d}`); return d; } // HSN/DOC SQL uses CONVERT(VARCHAR(6),date,112) = YYYYMM; TCS wants MMYYYY function toMMYYYY(fp) { const s = String(fp || '').replace(/\D/g, ''); if (s.length === 6) { const yr = parseInt(s.slice(0, 4), 10); if (yr >= 2000 && yr <= 2099) return s.slice(4, 6) + s.slice(0, 4); } return fp; } function r2(n) { const num = parseFloat(String(n ?? '0')); if (isNaN(num)) return 0; // Use Number() on toFixed result to strip trailing zeros and prevent scientific notation return parseFloat(num.toFixed(2)); } // Map SAP unit codes → valid GST UQC codes (TCS validates against GSTN list) const VALID_UQC = new Set([ 'BAG','BAL','BDL','BKL','BOU','BOX','BTL','BUN','CAN','CBM','CCM','CMS','CTN', 'DOZ','DRM','GGK','GMS','GRS','GYD','KGS','KLR','KME','KNO','KWT','LTR','MBE', 'MLT','MTR','MTS','NOS','OTH','PAC','PCS','PRS','QTL','ROL','SET','SQC','SQF', 'SQM','TBS','TGM','TON','TUB','UGS','UNT','YDS', ]); const UQC_MAP = { KG:'KGS', GM:'GMS', GRM:'GMS', LT:'LTR', L:'LTR', LITRE:'LTR', MT:'MTR', M:'MTR', METRE:'MTR', QTL:'QTL', TON:'TON', TNE:'TON', PC:'PCS', PCS:'PCS', PEC:'PCS', EA:'NOS', EACH:'NOS', NO:'NOS', NOS:'NOS', ROLL:'ROL', ROL:'ROL', ROLE:'ROL', ROOL:'ROL', PKT:'PAC', PACK:'PAC', PAC:'PAC', BTL:'BTL', BOT:'BTL', TUB:'TUB', CAN:'CAN', CTN:'CTN', BAG:'BAG', BDL:'BDL', BOX:'BOX', SET:'SET', SQM:'SQM', SQF:'SQF', SQC:'SQC', CBM:'CBM', MTR:'MTR', MTS:'MTS', KGS:'KGS', LTR:'LTR', GMS:'GMS', }; function validUQC(code) { const up = String(code || '').toUpperCase().trim(); const mapped = UQC_MAP[up] || up; return VALID_UQC.has(mapped) ? mapped : 'OTH'; } // ── SQL builders ────────────────────────────────────────────────────────── function sqlB2B(from, to) { return ` WITH TaxData AS ( SELECT T_DOC1.DocEntry, T_DOC1.LineNum, CAST(MAX(T_TAX1.BaseSum) AS DECIMAL(11,2)) AS ItemTaxableValue, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END AS TaxRate, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS CGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS SGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS IGST_Amt, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) AS rchrg FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV1 T_DOC1 ON T_TAX1.SrcArrType=12 AND T_TAX1.SrcLineNum=T_DOC1.LineNum AND T_DOC1.DocEntry=T_ODOC.DocEntry WHERE T_ODOC.GSTTranTyp='GA' AND T_ODOC.DocStatus<>'C' AND T_ODOC.InvntSttus<>'C' GROUP BY T_DOC1.DocEntry, T_DOC1.LineNum UNION ALL SELECT T_DOC1.DocEntry, T_TAX1.SrcLineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV2 T_DOC2 ON T_DOC2.DocEntry=T_ODOC.DocEntry AND T_DOC2.LineNum=T_TAX1.SrcLineNum AND T_TAX1.SrcGrpNum=T_DOC2.GroupNum INNER JOIN INV1 T_DOC1 ON T_DOC1.DocEntry=T_DOC2.DocEntry AND T_DOC1.LineNum=T_DOC2.LineNum WHERE T_ODOC.GSTTranTyp='GA' GROUP BY T_DOC1.DocEntry, T_TAX1.SrcLineNum, T_TAX1.SrcGrpNum UNION ALL SELECT T_DOC3.DocEntry, T_DOC3.LineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV3 T_DOC3 ON T_DOC3.DocEntry=T_ODOC.DocEntry AND T_DOC3.LineNum=T_TAX1.SrcLineNum WHERE T_ODOC.GSTTranTyp='GA' AND (T_TAX1.BaseSum>0 OR T_OSTT.NfTaxId=-60) GROUP BY T_DOC3.DocEntry, T_DOC3.LineNum ), ITS AS ( SELECT DocEntry, TaxRate, SUM(ItemTaxableValue) AS TotalTaxableValue, SUM(CGST_Amt) AS CGST_Amt, SUM(SGST_Amt) AS SGST_Amt, SUM(IGST_Amt) AS IGST_Amt, CASE WHEN MAX(rchrg)='Y' THEN 'Y' ELSE 'N' END AS ReverseCharge FROM TaxData GROUP BY DocEntry, TaxRate ) SELECT CASE WHEN EXISTS (SELECT 1 FROM INV1 T_DE WHERE T_DE.DocEntry=T_ODOC.DocEntry AND T_DE.AcctCode IN ('4110202001','4110202003')) THEN 'DE' WHEN T_DOC12.CountryB<>'IN' THEN 'EXP' WHEN T_ODOC.U_CustomerCategory='Indirect Export' THEN 'B2B' WHEN T_DOC12.BpGSTN IS NULL AND T_DOC12.CountryS='IN' THEN 'B2CS' ELSE 'B2B' END AS trans_type_code, RIGHT('0'+CAST(MONTH(T_ODOC.DocDate) AS VARCHAR(2)),2)+CAST(YEAR(T_ODOC.DocDate) AS VARCHAR(4)) AS fp, ROW_NUMBER() OVER(ORDER BY T_DOC12.DocEntry) AS ack_sr_no, T_DOC12.LocGSTN AS self_gstin, CASE WHEN T_ODOC.CANCELED='Y' THEN 'C' ELSE 'A' END AS resultflag, 'R' AS invoice_type, T_DOC12.BpGSTN AS cp_gstin, T_ODOC.CardName AS cp_gstin_name, CONCAT(T_NNM1.BeginStr, T_ODOC.DocNum) AS invoice_no, FORMAT(T_ODOC.DocDate,'dd-MM-yyyy') AS invoice_dt, T_ODOC.DocTotal AS invoice_val, ITS.TaxRate AS rate_of_tax, ITS.CGST_Amt AS cgst_amnt, ITS.SGST_Amt AS sgst_amnt, ITS.IGST_Amt AS igst_amnt, ITS.TotalTaxableValue AS taxable_val, CASE WHEN T_DOC12.BpGSTN IS NOT NULL THEN LEFT(T_DOC12.BpGSTN,2) ELSE T_OCST_S.GSTCode END AS pos, ITS.ReverseCharge AS rchrg FROM ITS INNER JOIN OINV T_ODOC ON ITS.DocEntry=T_ODOC.DocEntry INNER JOIN INV12 T_DOC12 ON T_DOC12.DocEntry=T_ODOC.DocEntry INNER JOIN NNM1 T_NNM1 ON T_NNM1.Series=T_ODOC.Series LEFT JOIN OCST T_OCST_S ON T_OCST_S.Code=T_DOC12.StateS WHERE T_ODOC.CANCELED='N' AND T_ODOC.DocDate>='${from}' AND T_ODOC.DocDate<='${to}' AND ( EXISTS (SELECT 1 FROM INV1 T_DE WHERE T_DE.DocEntry=T_ODOC.DocEntry AND T_DE.AcctCode IN ('4110202001','4110202003')) OR ( T_DOC12.CountryB='IN' AND (T_DOC12.BpGSTN IS NOT NULL OR T_ODOC.U_CustomerCategory='Indirect Export') AND T_OCST_S.GSTCode IS NOT NULL ) ) ORDER BY ITS.DocEntry, ITS.TaxRate`; } function sqlB2CL(from, to) { return ` WITH TaxData AS ( SELECT T_DOC1.DocEntry, T_DOC1.LineNum, CAST(MAX(T_TAX1.BaseSum) AS DECIMAL(11,2)) AS ItemTaxableValue, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END AS TaxRate, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS CGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS SGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS IGST_Amt, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) AS rchrg FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV1 T_DOC1 ON T_TAX1.SrcArrType=12 AND T_TAX1.SrcLineNum=T_DOC1.LineNum AND T_DOC1.DocEntry=T_ODOC.DocEntry WHERE T_ODOC.GSTTranTyp='GA' AND T_ODOC.DocStatus<>'C' AND T_ODOC.InvntSttus<>'C' GROUP BY T_DOC1.DocEntry, T_DOC1.LineNum UNION ALL SELECT T_DOC1.DocEntry, T_TAX1.SrcLineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV2 T_DOC2 ON T_DOC2.DocEntry=T_ODOC.DocEntry AND T_DOC2.LineNum=T_TAX1.SrcLineNum AND T_TAX1.SrcGrpNum=T_DOC2.GroupNum INNER JOIN INV1 T_DOC1 ON T_DOC1.DocEntry=T_DOC2.DocEntry AND T_DOC1.LineNum=T_DOC2.LineNum WHERE T_ODOC.GSTTranTyp='GA' GROUP BY T_DOC1.DocEntry, T_TAX1.SrcLineNum, T_TAX1.SrcGrpNum UNION ALL SELECT T_DOC3.DocEntry, T_DOC3.LineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV3 T_DOC3 ON T_DOC3.DocEntry=T_ODOC.DocEntry AND T_DOC3.LineNum=T_TAX1.SrcLineNum WHERE T_ODOC.GSTTranTyp='GA' AND (T_TAX1.BaseSum>0 OR T_OSTT.NfTaxId=-60) GROUP BY T_DOC3.DocEntry, T_DOC3.LineNum ), ITS AS ( SELECT DocEntry, TaxRate, SUM(ItemTaxableValue) AS TotalTaxableValue, SUM(CGST_Amt) AS CGST_Amt, SUM(SGST_Amt) AS SGST_Amt, SUM(IGST_Amt) AS IGST_Amt, CASE WHEN MAX(rchrg)='Y' THEN 'Y' ELSE 'N' END AS ReverseCharge FROM TaxData GROUP BY DocEntry, TaxRate ) SELECT 'B2CL' AS trans_type_code, RIGHT('0'+CAST(MONTH(T_ODOC.DocDate) AS VARCHAR(2)),2)+CAST(YEAR(T_ODOC.DocDate) AS VARCHAR(4)) AS fp, ROW_NUMBER() OVER(ORDER BY T_DOC12.DocEntry) AS ack_sr_no, T_DOC12.LocGSTN AS self_gstin, CASE WHEN T_ODOC.CANCELED='Y' THEN 'C' ELSE 'A' END AS resultflag, 'R' AS invoice_type, CONCAT(T_NNM1.BeginStr, T_ODOC.DocNum) AS invoice_no, FORMAT(T_ODOC.DocDate,'dd-MM-yyyy') AS invoice_dt, T_ODOC.DocTotal AS invoice_val, ITS.TaxRate AS rate_of_tax, ITS.CGST_Amt AS cgst_amnt, ITS.SGST_Amt AS sgst_amnt, ITS.IGST_Amt AS igst_amnt, ITS.TotalTaxableValue AS taxable_val, T_OCST_S.GSTCode AS pos FROM ITS INNER JOIN OINV T_ODOC ON ITS.DocEntry=T_ODOC.DocEntry INNER JOIN INV12 T_DOC12 ON T_DOC12.DocEntry=T_ODOC.DocEntry INNER JOIN NNM1 T_NNM1 ON T_NNM1.Series=T_ODOC.Series LEFT JOIN OCST T_OCST_S ON T_OCST_S.Code=T_DOC12.StateS WHERE T_ODOC.CANCELED='N' AND T_ODOC.DocDate>='${from}' AND T_ODOC.DocDate<='${to}' AND T_DOC12.CountryB='IN' AND T_DOC12.BpGSTN IS NULL AND T_OCST_S.GSTCode IS NOT NULL AND T_ODOC.DocTotal > 100000 AND ITS.IGST_Amt > 0 ORDER BY ITS.DocEntry, ITS.TaxRate`; } function sqlB2CS(from, to) { return ` WITH TaxData AS ( SELECT T_DOC1.DocEntry, T_DOC1.LineNum, CAST(MAX(T_TAX1.BaseSum) AS DECIMAL(11,2)) AS ItemTaxableValue, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END AS TaxRate, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS CGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS SGST_Amt, CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)) AS IGST_Amt, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) AS rchrg FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV1 T_DOC1 ON T_TAX1.SrcArrType=12 AND T_TAX1.SrcLineNum=T_DOC1.LineNum AND T_DOC1.DocEntry=T_ODOC.DocEntry WHERE T_ODOC.GSTTranTyp='GA' AND T_ODOC.DocStatus<>'C' AND T_ODOC.InvntSttus<>'C' GROUP BY T_DOC1.DocEntry, T_DOC1.LineNum UNION ALL SELECT T_DOC1.DocEntry, T_TAX1.SrcLineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV2 T_DOC2 ON T_DOC2.DocEntry=T_ODOC.DocEntry AND T_DOC2.LineNum=T_TAX1.SrcLineNum AND T_TAX1.SrcGrpNum=T_DOC2.GroupNum INNER JOIN INV1 T_DOC1 ON T_DOC1.DocEntry=T_DOC2.DocEntry AND T_DOC1.LineNum=T_DOC2.LineNum WHERE T_ODOC.GSTTranTyp='GA' GROUP BY T_DOC1.DocEntry, T_TAX1.SrcLineNum, T_TAX1.SrcGrpNum UNION ALL SELECT T_DOC3.DocEntry, T_DOC3.LineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId IN(-110,-150) THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN T_TAX1.VatSum ELSE 0 END) AS DECIMAL(13,2)), MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV3 T_DOC3 ON T_DOC3.DocEntry=T_ODOC.DocEntry AND T_DOC3.LineNum=T_TAX1.SrcLineNum WHERE T_ODOC.GSTTranTyp='GA' AND (T_TAX1.BaseSum>0 OR T_OSTT.NfTaxId=-60) GROUP BY T_DOC3.DocEntry, T_DOC3.LineNum ), ITS AS ( SELECT DocEntry, TaxRate, SUM(ItemTaxableValue) AS TotalTaxableValue, SUM(CGST_Amt) AS CGST_Amt, SUM(SGST_Amt) AS SGST_Amt, SUM(IGST_Amt) AS IGST_Amt, CASE WHEN MAX(rchrg)='Y' THEN 'Y' ELSE 'N' END AS ReverseCharge FROM TaxData GROUP BY DocEntry, TaxRate ) SELECT 'B2CS' AS trans_type_code, RIGHT('0'+CAST(MONTH(T_ODOC.DocDate) AS VARCHAR(2)),2)+CAST(YEAR(T_ODOC.DocDate) AS VARCHAR(4)) AS fp, ROW_NUMBER() OVER(ORDER BY T_DOC12.DocEntry) AS ack_sr_no, T_DOC12.LocGSTN AS self_gstin, CASE WHEN T_ODOC.CANCELED='Y' THEN 'C' ELSE 'A' END AS resultflag, 'R' AS invoice_type, CONCAT(T_NNM1.BeginStr, T_ODOC.DocNum) AS invoice_no, FORMAT(T_ODOC.DocDate,'dd-MM-yyyy') AS invoice_dt, T_ODOC.DocTotal AS invoice_val, ITS.TaxRate AS rate_of_tax, ITS.CGST_Amt AS cgst_amnt, ITS.SGST_Amt AS sgst_amnt, ITS.IGST_Amt AS igst_amnt, ITS.TotalTaxableValue AS taxable_val, T_OCST_S.GSTCode AS pos FROM ITS INNER JOIN OINV T_ODOC ON ITS.DocEntry=T_ODOC.DocEntry INNER JOIN INV12 T_DOC12 ON T_DOC12.DocEntry=T_ODOC.DocEntry INNER JOIN NNM1 T_NNM1 ON T_NNM1.Series=T_ODOC.Series LEFT JOIN OCST T_OCST_S ON T_OCST_S.Code=T_DOC12.StateS WHERE T_ODOC.CANCELED='N' AND T_ODOC.DocDate>='${from}' AND T_ODOC.DocDate<='${to}' AND T_DOC12.CountryB='IN' AND T_DOC12.BpGSTN IS NULL AND T_OCST_S.GSTCode IS NOT NULL AND (T_ODOC.DocTotal<=100000 OR (T_ODOC.DocTotal>100000 AND ITS.CGST_Amt>0)) ORDER BY ITS.DocEntry, ITS.TaxRate`; } // EXP — no invoice_type in SELECT (TCS throws NREQ error if present) function sqlExport(from, to) { return ` WITH TaxData AS ( SELECT T_DOC1.DocEntry, T_DOC1.LineNum, CAST(MAX(T_TAX1.BaseSum) AS DECIMAL(11,2)) AS ItemTaxableValue, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END AS TaxRate, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) AS rchrg FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV1 T_DOC1 ON T_TAX1.SrcArrType=12 AND T_TAX1.SrcLineNum=T_DOC1.LineNum AND T_DOC1.DocEntry=T_ODOC.DocEntry WHERE T_ODOC.GSTTranTyp='GA' AND T_ODOC.DocStatus<>'C' AND T_ODOC.InvntSttus<>'C' GROUP BY T_DOC1.DocEntry, T_DOC1.LineNum UNION ALL SELECT T_DOC1.DocEntry, T_TAX1.SrcLineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV2 T_DOC2 ON T_DOC2.DocEntry=T_ODOC.DocEntry AND T_DOC2.LineNum=T_TAX1.SrcLineNum AND T_TAX1.SrcGrpNum=T_DOC2.GroupNum INNER JOIN INV1 T_DOC1 ON T_DOC1.DocEntry=T_DOC2.DocEntry AND T_DOC1.LineNum=T_DOC2.LineNum WHERE T_ODOC.GSTTranTyp='GA' GROUP BY T_DOC1.DocEntry, T_TAX1.SrcLineNum, T_TAX1.SrcGrpNum UNION ALL SELECT T_DOC3.DocEntry, T_DOC3.LineNum, 0, CASE WHEN MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END)=0 THEN MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) + MAX(CASE WHEN T_OSTT.AbsId IN(-110,-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) ELSE MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) END, MAX(CASE WHEN T_TAX1.RvsChrgPrc>0 THEN 'Y' ELSE 'N' END) FROM TAX1 T_TAX1 INNER JOIN OSTA T1 ON T_TAX1.StaCode=T1.Code INNER JOIN OSTT T_OSTT ON T1.Type=T_OSTT.AbsId INNER JOIN OTAX T_OTAX ON T_OTAX.AbsEntry=T_TAX1.AbsEntry INNER JOIN OINV T_ODOC ON T_OTAX.SrcObjType=13 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN INV3 T_DOC3 ON T_DOC3.DocEntry=T_ODOC.DocEntry AND T_DOC3.LineNum=T_TAX1.SrcLineNum WHERE T_ODOC.GSTTranTyp='GA' AND (T_TAX1.BaseSum>0 OR T_OSTT.NfTaxId=-60) GROUP BY T_DOC3.DocEntry, T_DOC3.LineNum ), ITS AS ( SELECT DocEntry, TaxRate, SUM(ItemTaxableValue) AS TotalTaxableValue, CASE WHEN MAX(rchrg)='Y' THEN 'Y' ELSE 'N' END AS ReverseCharge FROM TaxData GROUP BY DocEntry, TaxRate ) SELECT 'EXP' AS trans_type_code, RIGHT('0'+CAST(MONTH(T_ODOC.DocDate) AS VARCHAR(2)),2)+CAST(YEAR(T_ODOC.DocDate) AS VARCHAR(4)) AS fp, ROW_NUMBER() OVER(ORDER BY T_DOC12.DocEntry) AS ack_sr_no, T_DOC12.LocGSTN AS self_gstin, CASE WHEN T_ODOC.CANCELED='Y' THEN 'C' ELSE 'A' END AS resultflag, 'WOPAY' AS export_type_desc, CONCAT(T_NNM1.BeginStr, T_ODOC.DocNum) AS invoice_no, FORMAT(T_ODOC.DocDate,'dd-MM-yyyy') AS invoice_dt, T_ODOC.DocTotal AS invoice_val, ITS.TaxRate AS rate_of_tax, ITS.TotalTaxableValue AS taxable_val FROM ITS INNER JOIN OINV T_ODOC ON ITS.DocEntry=T_ODOC.DocEntry INNER JOIN INV12 T_DOC12 ON T_DOC12.DocEntry=T_ODOC.DocEntry INNER JOIN NNM1 T_NNM1 ON T_NNM1.Series=T_ODOC.Series LEFT JOIN OCST T_OCST_S ON T_OCST_S.Code=T_DOC12.StateS WHERE T_ODOC.CANCELED='N' AND T_ODOC.DocDate>='${from}' AND T_ODOC.DocDate<='${to}' AND T_DOC12.ImpORExp='Y' AND T_DOC12.CountryB<>'IN' AND T_DOC12.BpGSTN IS NULL AND T_OCST_S.GSTCode IS NULL AND NOT EXISTS (SELECT 1 FROM INV1 T_DE WHERE T_DE.DocEntry=T_ODOC.DocEntry AND T_DE.AcctCode IN ('4110202001','4110202003')) ORDER BY ITS.DocEntry, ITS.TaxRate`; } function sqlHsn(from, to) { return ` ;WITH INV_TAX AS ( SELECT DocEntry, LineNum, SUM(CASE WHEN staType = '-120' THEN TaxSum ELSE 0 END) AS iamt, SUM(CASE WHEN staType = '-110' THEN TaxSum ELSE 0 END) AS camt, SUM(CASE WHEN staType = '-100' THEN TaxSum ELSE 0 END) AS samt, SUM(CASE WHEN staType = '10' THEN TaxSum ELSE 0 END) AS tcs FROM INV4 GROUP BY DocEntry, LineNum ), RIN_TAX AS ( SELECT DocEntry, LineNum, SUM(CASE WHEN staType = '-120' THEN TaxSum ELSE 0 END) AS iamt, SUM(CASE WHEN staType = '-110' THEN TaxSum ELSE 0 END) AS camt, SUM(CASE WHEN staType = '-100' THEN TaxSum ELSE 0 END) AS samt, SUM(CASE WHEN staType = '10' THEN TaxSum ELSE 0 END) AS tcs FROM RIN4 GROUP BY DocEntry, LineNum ), HSN_Lines AS ( -- AR Invoice SELECT CONCAT('INV-', OINV.DocEntry) AS doc_key, CONVERT(VARCHAR(6), OINV.DocDate, 112) AS fp, INV12.LocGSTN AS self_gstin, REPLACE(OCHP.ChapterID, '.', '') AS hsn_sc, CASE WHEN OCHP.ChapterID NOT LIKE '99%' THEN 'Goods' ELSE 'Service' END AS gstr1Desc, CASE WHEN ISNULL(INV12.BpGSTN, '') = '' THEN 'B2C' ELSE 'B2B' END AS section, ISNULL(INV1.unitMsr, 'OTH') AS uqc, INV1.Quantity AS qty, INV1.LineTotal + INV1.VatSum AS val, INV1.LineTotal AS txval, ISNULL(T.iamt, 0) AS iamt, ISNULL(T.camt, 0) AS camt, ISNULL(T.samt, 0) AS samt, ISNULL(T.tcs, 0) AS tcs FROM OINV INNER JOIN INV1 ON OINV.DocEntry = INV1.DocEntry LEFT JOIN INV_TAX T ON OINV.DocEntry = T.DocEntry AND INV1.LineNum = T.LineNum INNER JOIN INV12 ON OINV.DocEntry = INV12.DocEntry INNER JOIN OITM ON INV1.ItemCode = OITM.ItemCode INNER JOIN OCHP ON OITM.ChapterID = OCHP.AbsEntry WHERE OINV.DocDate BETWEEN '${from}' AND '${to}' AND OINV.CANCELED = 'N' UNION ALL -- AR Credit Memo SELECT CONCAT('CM-', ORIN.DocEntry) AS doc_key, CONVERT(VARCHAR(6), ORIN.DocDate, 112) AS fp, RIN12.LocGSTN AS self_gstin, REPLACE(OCHP.ChapterID, '.', '') AS hsn_sc, CASE WHEN OCHP.ChapterID NOT LIKE '99%' THEN 'Goods' ELSE 'Service' END AS gstr1Desc, CASE WHEN ISNULL(RIN12.BpGSTN, '') = '' THEN 'B2C' ELSE 'B2B' END AS section, ISNULL(RIN1.unitMsr, 'OTH') AS uqc, -RIN1.Quantity AS qty, -(RIN1.LineTotal + RIN1.VatSum) AS val, -RIN1.LineTotal AS txval, -ISNULL(T.iamt, 0) AS iamt, -ISNULL(T.camt, 0) AS camt, -ISNULL(T.samt, 0) AS samt, -ISNULL(T.tcs, 0) AS tcs FROM ORIN INNER JOIN RIN1 ON ORIN.DocEntry = RIN1.DocEntry LEFT JOIN RIN_TAX T ON ORIN.DocEntry = T.DocEntry AND RIN1.LineNum = T.LineNum INNER JOIN RIN12 ON ORIN.DocEntry = RIN12.DocEntry INNER JOIN OITM ON RIN1.ItemCode = OITM.ItemCode INNER JOIN OCHP ON OITM.ChapterID = OCHP.AbsEntry WHERE ORIN.DocDate BETWEEN '${from}' AND '${to}' AND ORIN.CANCELED = 'N' ), HSN_Agg AS ( SELECT fp, self_gstin, hsn_sc, gstr1Desc, section, uqc, SUM(qty) AS qty_n, SUM(val) AS val_n, SUM(txval) AS txval_n, SUM(iamt) AS iamt_n, SUM(camt) AS camt_n, SUM(samt) AS samt_n, SUM(tcs) AS tcs_n, CASE WHEN SUM(txval) = 0 THEN 0 WHEN ROUND( (SUM(iamt) + SUM(camt) + SUM(samt)) * 100.0 / SUM(txval), 0 ) IN (0, 5, 12, 18, 28) THEN ROUND( (SUM(iamt) + SUM(camt) + SUM(samt)) * 100.0 / SUM(txval), 0 ) ELSE 0 END AS rate_of_tax_n FROM HSN_Lines GROUP BY fp, self_gstin, hsn_sc, gstr1Desc, section, uqc ) SELECT ROW_NUMBER() OVER (ORDER BY hsn_sc, uqc, section, rate_of_tax_n) AS ack_sr_no, fp, self_gstin, hsn_sc, gstr1Desc, section, uqc, -- num: occurrence index of this HSN/SAC code — 1 if the code appears only -- once, 1,2,... if the same code repeats across different UQC/rate/section rows ROW_NUMBER() OVER (PARTITION BY hsn_sc ORDER BY uqc, section, rate_of_tax_n) AS num, -- Return values as formatted strings to avoid scientific notation LTRIM(STR(qty_n, 20, 3)) AS qty, LTRIM(STR(val_n, 20, 2)) AS val, LTRIM(STR(txval_n, 20, 2)) AS txval, LTRIM(STR(iamt_n, 20, 2)) AS iamt, LTRIM(STR(camt_n, 20, 2)) AS camt, LTRIM(STR(samt_n, 20, 2)) AS samt, LTRIM(STR(tcs_n, 20, 2)) AS tcs, LTRIM(STR(rate_of_tax_n, 20, 2)) AS rate_of_tax FROM HSN_Agg ORDER BY hsn_sc, uqc, section, rate_of_tax_n;`; } function sqlDocs(from, to) { return ` WITH DocStatus AS ( SELECT T0.DocEntry, T0.DocNum, T0.Series, T0.DocDate, 'Invoice' AS DocumentType, CASE WHEN T0.CANCELED='Y' OR CM.BaseEntry IS NOT NULL THEN 1 ELSE 0 END AS IsCanceled FROM OINV T0 LEFT JOIN (SELECT DISTINCT R1.BaseEntry FROM RIN1 R1 INNER JOIN ORIN R0 ON R0.DocEntry=R1.DocEntry WHERE R1.BaseType=13 AND R0.CANCELED='N') CM ON CM.BaseEntry=T0.DocEntry WHERE T0.DocDate>='${from}' AND T0.DocDate<'${to}' UNION ALL SELECT D0.DocEntry, D0.DocNum, D0.Series, D0.DocDate, 'Delivery', CASE WHEN D0.CANCELED='Y' THEN 1 ELSE 0 END FROM ODLN D0 INNER JOIN NNM1 N1 ON N1.Series=D0.Series WHERE N1.SeriesName LIKE '%FOC%' AND D0.DocDate>='${from}' AND D0.DocDate<'${to}' UNION ALL SELECT W0.DocEntry, W0.DocNum, W0.Series, W0.DocDate, CASE WHEN N1.SeriesName LIKE '%JW%' THEN 'JW' WHEN N1.SeriesName LIKE '%IT%' AND N1.SeriesName NOT LIKE '%ITR%' THEN 'IT' END, CASE WHEN W0.CANCELED='Y' THEN 1 ELSE 0 END FROM OWTR W0 INNER JOIN NNM1 N1 ON N1.Series=W0.Series WHERE (N1.SeriesName LIKE '%JW%' OR (N1.SeriesName LIKE '%IT%' AND N1.SeriesName NOT LIKE '%ITR%')) AND W0.DocDate>='${from}' AND W0.DocDate<'${to}' UNION ALL SELECT P0.DocEntry, P0.DocNum, P0.Series, P0.DocDate, 'AP Invoice (RCM)', CASE WHEN P0.CANCELED='Y' THEN 1 ELSE 0 END FROM OPCH P0 INNER JOIN NNM1 N1 ON N1.Series=P0.Series WHERE N1.SeriesName LIKE '%rev%' AND P0.DocDate>='${from}' AND P0.DocDate<'${to}' UNION ALL SELECT R0.DocEntry, R0.DocNum, R0.Series, R0.DocDate, 'AR Credit Memo', CASE WHEN R0.CANCELED='Y' THEN 1 ELSE 0 END FROM ORIN R0 INNER JOIN NNM1 N1 ON N1.Series=R0.Series WHERE N1.SeriesName LIKE '%ARCN%' AND R0.DocDate>='${from}' AND R0.DocDate<'${to}' ) SELECT CONVERT(char(6),DocDate,112) AS fp, Series, DocumentType, CASE WHEN DocumentType='AR Credit Memo' THEN CONCAT('CN-',MIN(DocNum)) WHEN DocumentType IN ('Invoice','Delivery') THEN CONCAT('1511',MIN(DocNum)) ELSE CAST(MIN(DocNum) AS varchar(20)) END AS sr_from, CASE WHEN DocumentType='AR Credit Memo' THEN CONCAT('CN-',MAX(DocNum)) WHEN DocumentType IN ('Invoice','Delivery') THEN CONCAT('1511',MAX(DocNum)) ELSE CAST(MAX(DocNum) AS varchar(20)) END AS sr_to, COUNT(*) AS totnum, SUM(IsCanceled) AS canceled, COUNT(*)-SUM(IsCanceled) AS net_issue FROM DocStatus GROUP BY CONVERT(char(6),DocDate,112), Series, DocumentType ORDER BY fp, Series, DocumentType`; } // ── route handlers ──────────────────────────────────────────────────────── async function handleQuery(req, res, sqlFn) { try { const from = safeDate(req.query.from || ''); const to = safeDate(req.query.to || ''); const { getPool } = require('../services/sqlPool'); const pool = await getPool(); const result = await pool.request().query(sqlFn(from, to)); const data = result.recordset || []; // Normalize all numeric-looking values to actual JS numbers // prevents DECIMAL(18,2) from being stringified as "1.568E7" downstream const normalized = data.map(row => { const out = {}; for (const [k, v] of Object.entries(row)) { if (v !== null && v !== undefined && typeof v === 'string' && /^-?[\d.]+([eE][+-]?\d+)?$/.test(v)) { out[k] = parseFloat(v); } else { out[k] = v; } } return out; }); res.json({ success: true, data: normalized }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } } router.get('/b2b', verifyToken, (req, res) => handleQuery(req, res, sqlB2B)); router.get('/b2cl', verifyToken, (req, res) => handleQuery(req, res, sqlB2CL)); router.get('/b2cs', verifyToken, (req, res) => handleQuery(req, res, sqlB2CS)); router.get('/export', verifyToken, (req, res) => handleQuery(req, res, sqlExport)); router.get('/hsn', verifyToken, (req, res) => handleQuery(req, res, sqlHsn)); router.get('/docs', verifyToken, (req, res) => handleQuery(req, res, sqlDocs)); // ── TCS token ───────────────────────────────────────────────────────────── async function getTcsToken() { const baseUrl = process.env.TCS_GSP_BASE_URL || 'https://g31.tcsgsp.in'; const username = process.env.TCS_GSP_USERNAME || ''; const password = process.env.TCS_GSP_PASSWORD || ''; const resp = await axios.get( `${baseUrl}/Tax-Tool-Core/services/accessMgmt/generateToken`, { headers: { 'Content-Type': 'application/json', username, password }, httpsAgent } ); const token = resp.data?.access_token || resp.data?.token || resp.data?.accessToken; if (!token) throw new Error(`TCS token missing. Response: ${JSON.stringify(resp.data)}`); console.log('[TCS] Token obtained successfully'); return token; } // ── Build TCS-format Excel workbook ─────────────────────────────────────── async function buildTcsExcel({ b2b = [], b2cl = [], b2cs = [], exp = [], hsn = [], docs = [] }, gstin) { const wb = new ExcelJS.Workbook(); // Columns that must never render as scientific notation in Excel/TCS const NUMERIC_COLS = new Set([ 'fp','ack_sr_no','num','doc_num', 'invoice_val','taxable_val','rate_of_tax', 'igst_amnt','cgst_amnt','sgst_amnt','cess_amnt', 'diff_igst_amnt','diff_cgst_amnt','diff_sgst_amnt','diff_cess_amnt', 'diff_value','diff_percent','items_serial_no', 'txval','rt','iamt','camt','samt','csamt', 'qty','val','totnum','cancel','net_issue','pos', ]); function addSheet(name, headers, rows) { const ws = wb.addWorksheet(name); ws.addRow(headers); rows.forEach(r => { const rowData = headers.map(h => { const v = r[h]; return (v === null || v === undefined) ? null : v; }); const excelRow = ws.addRow(rowData); // Force plain decimal format — prevents 1.568502177E7 → "SANITY FAILED" headers.forEach((h, colIdx) => { if (NUMERIC_COLS.has(h)) { const cell = excelRow.getCell(colIdx + 1); if (typeof cell.value === 'number') { cell.numFmt = '0.##'; // no trailing zeros, never scientific } } }); }); } addSheet('B2B', ['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','invoice_type','cp_gstin','cp_gstin_name','invoice_no','invoice_dt','invoice_val','taxable_val','items_serial_no','rate_of_tax','igst_amnt','cgst_amnt','sgst_amnt','cess_amnt','pos','rchrg','is_gstn_for_ecom_app','gstn_for_ecom_app','or_cp_gstin','orignal_invoice_no','orignal_invoice_dt'], b2b.map((r,i)=>({trans_type_code:'B2B',fp:Number(r.fp),ack_sr_no:i+1,self_gstin:gstin,system_cd:null,branch_cd:null,vertical_cd:null,resultflag:r.resultflag||'A',invoice_type:r.trans_type_code==='DE'?'DE':'R',cp_gstin:r.cp_gstin||null,cp_gstin_name:r.cp_gstin_name||null,invoice_no:r.invoice_no,invoice_dt:r.invoice_dt,invoice_val:r.invoice_val,taxable_val:r.taxable_val,items_serial_no:r.items_serial_no||1,rate_of_tax:r.rate_of_tax,igst_amnt:r.igst_amnt||null,cgst_amnt:r.cgst_amnt||null,sgst_amnt:r.sgst_amnt||null,cess_amnt:r.cess_amnt||null,pos:r.pos,rchrg:r.rchrg||'N',is_gstn_for_ecom_app:r.is_gstn_for_ecom_app||'N',gstn_for_ecom_app:r.gstn_for_ecom_app||null,or_cp_gstin:null,orignal_invoice_no:null,orignal_invoice_dt:null})) ); addSheet('B2BA',['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','invoice_type','cp_gstin','cp_gstin_name','invoice_no','invoice_dt','invoice_val','taxable_val','items_serial_no','rate_of_tax','igst_amnt','cgst_amnt','sgst_amnt','cess_amnt','pos','rchrg','is_gstn_for_ecom_app','gstn_for_ecom_app','or_cp_gstin','orignal_invoice_no','orignal_invoice_dt','diff_percent'],[]); addSheet('B2CL', ['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','invoice_type','invoice_no','invoice_dt','invoice_val','taxable_val','items_serial_no','rate_of_tax','igst_amnt','cess_amnt','pos','is_gstn_for_ecom_app','gstn_for_ecom_app','orignal_invoice_no','orignal_invoice_dt','diff_percent'], b2cl.map((r,i)=>({trans_type_code:'B2CL',fp:Number(r.fp),ack_sr_no:i+1,self_gstin:gstin,system_cd:null,branch_cd:null,vertical_cd:null,resultflag:r.resultflag||'A',invoice_type:null,invoice_no:r.invoice_no,invoice_dt:r.invoice_dt,invoice_val:r.invoice_val,taxable_val:r.taxable_val,items_serial_no:r.items_serial_no||1,rate_of_tax:r.rate_of_tax,igst_amnt:r.igst_amnt||null,cess_amnt:r.cess_amnt||null,pos:r.pos,is_gstn_for_ecom_app:r.is_gstn_for_ecom_app||'N',gstn_for_ecom_app:r.gstn_for_ecom_app||null,orignal_invoice_no:null,orignal_invoice_dt:null,diff_percent:0.65})) ); addSheet('B2CLA',['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','invoice_type','invoice_no','invoice_dt','invoice_val','taxable_val','items_serial_no','rate_of_tax','igst_amnt','cess_amnt','pos','is_gstn_for_ecom_app','gstn_for_ecom_app','orignal_invoice_no','orignal_invoice_dt','diff_percent'],[]); addSheet('CDNR',['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','cp_gstin','cp_gstin_name','type_of_note','reason_code','is_pre_gst_regime','dbt_crdt_note_no','dbt_crdt_note_dt','dbt_crdt_note_val','invoice_no','invoice_dt','diff_value','items_serial_no','rate_of_tax','diff_igst_amnt','diff_cgst_amnt','diff_sgst_amnt','diff_cess_amnt','or_cp_gstin','or_dbt_crdt_note_no','or_dbt_crdt_note_dt'],[]); addSheet('CDNRA',['ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','cp_gstin','cp_gstin_name','type_of_note','reason_code','is_pre_gst_regime','dbt_crdt_note_no','dbt_crdt_note_dt','dbt_crdt_note_val','invoice_no','invoice_dt','diff_value','items_serial_no','rate_of_tax','diff_igst_amnt','diff_cgst_amnt','diff_sgst_amnt','diff_cess_amnt','or_cp_gstin','or_dbt_crdt_note_no','or_dbt_crdt_note_dt','diff_percent'],[]); addSheet('CDNUR',['fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','trans_cat_type','type_of_note','reason_code','is_pre_gst_regime','dbt_crdt_note_no','dbt_crdt_note_dt','dbt_crdt_note_val','invoice_no','invoice_dt','diff_value','items_serial_no','rate_of_tax','diff_igst_amnt','diff_cess_amnt','or_dbt_crdt_note_no','or_dbt_crdt_note_dt','diff_percent'],[]); addSheet('CDNURA',['fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','trans_cat_type','type_of_note','reason_code','is_pre_gst_regime','dbt_crdt_note_no','dbt_crdt_note_dt','dbt_crdt_note_val','invoice_no','invoice_dt','diff_value','items_serial_no','rate_of_tax','diff_igst_amnt','diff_cess_amnt','or_dbt_crdt_note_no','or_dbt_crdt_note_dt','diff_percent'],[]); addSheet('EXP', ['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','export_type_desc','invoice_no','invoice_dt','invoice_val','taxable_val','port_code','export_shipping_bill_no','export_shipping_bill_dt','rate_of_tax','igst_amnt','cess_amnt','orignal_invoice_no','orignal_invoice_dt','diff_percent'], exp.map((r,i)=>({trans_type_code:'EXP',fp:Number(r.fp),ack_sr_no:i+1,self_gstin:gstin,system_cd:null,branch_cd:null,vertical_cd:null,resultflag:r.resultflag||'A',export_type_desc:r.export_type_desc||'WOPAY',invoice_no:r.invoice_no,invoice_dt:r.invoice_dt,invoice_val:r.invoice_val,taxable_val:r.taxable_val,port_code:null,export_shipping_bill_no:r.export_shipping_bill_no||null,export_shipping_bill_dt:r.export_shipping_bill_dt||null,rate_of_tax:r.rate_of_tax,igst_amnt:r.igst_amnt||null,cess_amnt:r.cess_amnt||null,orignal_invoice_no:null,orignal_invoice_dt:null,diff_percent:0.65})) ); addSheet('EXPA',['trans_type_code','fp','ack_sr_no','self_gstin','system_cd','branch_cd','vertical_cd','resultflag','export_type_desc','invoice_no','invoice_dt','invoice_val','taxable_val','port_code','export_shipping_bill_no','export_shipping_bill_dt','rate_of_tax','igst_amnt','cess_amnt','orignal_invoice_no','orignal_invoice_dt','diff_percent'],[]); const b2csGrouped = {}; b2cs.forEach(r => { const key = `${r.pos}_${r.rate_of_tax}`; if (!b2csGrouped[key]) { b2csGrouped[key] = { pos: r.pos, rt: r.rate_of_tax, txval: 0, iamt: 0, camt: 0, samt: 0, sply_ty: Number(r.igst_amnt) > 0 ? 'INTER' : 'INTRA', }; } b2csGrouped[key].txval += Number(r.taxable_val) || 0; b2csGrouped[key].iamt += Number(r.igst_amnt) || 0; b2csGrouped[key].camt += Number(r.cgst_amnt) || 0; b2csGrouped[key].samt += Number(r.sgst_amnt) || 0; }); addSheet('B2CS', ['trans_type_code','fp','ack_sr_no','self_gstin','sply_ty','pos','txval','rt','iamt','camt','samt','csamt','typ','etin'], Object.values(b2csGrouped).map((r,i)=>({trans_type_code:'B2CS',fp:b2cs[0]?Number(b2cs[0].fp):null,ack_sr_no:i+1,self_gstin:gstin,sply_ty:r.sply_ty,pos:Number(r.pos),txval:Math.round(r.txval*100)/100,rt:Number(r.rt),iamt:r.iamt>0?Math.round(r.iamt*100)/100:null,camt:r.camt>0?Math.round(r.camt*100)/100:null,samt:r.samt>0?Math.round(r.samt*100)/100:null,csamt:null,typ:'OE',etin:null})) ); addSheet('B2CSA',['trans_type_code','fp','ack_sr_no','self_gstin','sply_ty','pos','txval','rt','iamt','camt','samt','csamt','typ','etin','omon','opos'],[]); addSheet('Nil',['trans_type_code','fp','ack_sr_no','self_gstin','sply_ty','nil_amt','expt_amt','ngsup_amt'],[]); // Aggregate HSN by (hsn_sc, uqc, rate_of_tax) — TCS requires this combination to be unique const hsnAgg = {}; hsn.forEach(r => { const uqc = validUQC(r.uqc); const rt = r.rate_of_tax || 0; const section = r.section || 'B2B'; const key = `${r.hsn_sc}_${uqc}_${rt}_${section}`; if (!hsnAgg[key]) { hsnAgg[key] = { fp: r.fp, hsn_sc: r.hsn_sc, gstr1Desc: r.gstr1Desc || 'Goods', uqc, rate_of_tax: rt, section, qty: 0, val: 0, txval: 0, iamt: 0, camt: 0, samt: 0, }; } hsnAgg[key].qty += parseFloat(r.qty) || 0; hsnAgg[key].val += parseFloat(r.val) || 0; hsnAgg[key].txval += parseFloat(r.txval) || 0; hsnAgg[key].iamt += parseFloat(r.iamt) || 0; hsnAgg[key].camt += parseFloat(r.camt) || 0; hsnAgg[key].samt += parseFloat(r.samt) || 0; }); // num: occurrence index of this HSN/SAC code among the rows being uploaded — // 1 if the code appears once, 1,2,... if the same code repeats across // different UQC/rate/section rows (verified against hsn_sc + uqc + rate_of_tax) const hsnRowsArr = Object.values(hsnAgg) .sort((a, b) => (a.hsn_sc < b.hsn_sc ? -1 : a.hsn_sc > b.hsn_sc ? 1 : a.uqc < b.uqc ? -1 : a.uqc > b.uqc ? 1 : a.section< b.section? -1 : a.section> b.section? 1 : a.rate_of_tax - b.rate_of_tax)); const hsnSeen = {}; addSheet('HSN', ['trans_type_code','fp','ack_sr_no','self_gstin','num','hsn_sc','gstr1Desc','uqc','qty','val','txval','iamt','camt','samt','csamt','rate_of_tax','section'], hsnRowsArr.map((r, i) => { hsnSeen[r.hsn_sc] = (hsnSeen[r.hsn_sc] || 0) + 1; return { trans_type_code: 'HSNSAC', fp: r.fp ? Number(toMMYYYY(r.fp)) : null, ack_sr_no: i + 1, self_gstin: gstin, num: hsnSeen[r.hsn_sc], hsn_sc: r.hsn_sc, gstr1Desc: r.gstr1Desc, uqc: r.uqc, qty: r2(r.qty), val: r2(r.val), txval: r2(r.txval), iamt: r.iamt ? r2(r.iamt) : null, camt: r.camt ? r2(r.camt) : null, samt: r.samt ? r2(r.samt) : null, csamt: null, rate_of_tax: r.rate_of_tax, section: r.section || 'B2B', }; }) ); const DOC_TYPE_MAP={'Invoice':'Invoices for outward supply','AR Credit Memo':'Credit Notes','Delivery':'Delivery Challan in cases other than by way of supply (excluding job work, supply on approval, liquid gas )','AP Invoice (RCM)':'Invoices for inward supply from unregistered person','JW':'Delivery Challan for job work','IT':'Delivery Challan for supply on approval'}; const DOC_NUM_MAP={'Invoice':1,'AR Credit Memo':4,'Delivery':12,'AP Invoice (RCM)':2,'JW':9,'IT':10}; addSheet('DOC', ['trans_type_code','fp','ack_sr_no','self_gstin','doc_num','num','docType','sr_from','sr_to','totnum','cancel','net_issue'], docs.map((r,i)=>({trans_type_code:'DOCISSUE',fp:r.fp?Number(toMMYYYY(r.fp)):null,ack_sr_no:i+1,self_gstin:gstin,doc_num:DOC_NUM_MAP[r.DocumentType]||1,num:1,docType:DOC_TYPE_MAP[r.DocumentType]||r.DocumentType,sr_from:r.sr_from,sr_to:r.sr_to,totnum:r.totnum,cancel:r.canceled,net_issue:r.net_issue})) ); addSheet('AT_ATA',['trans_type_code','fp','ack_sr_no','self_gstin','sply_ty','pos','ad_amt','rt','iamt','camt','samt','csamt','omon','or_pos','or_sply_ty'],[]); addSheet('TXPD_TXPDA',['trans_type_code','fp','ack_sr_no','self_gstin','sply_ty','pos','ad_amt','rt','iamt','camt','samt','csamt','omon','or_sply_ty','or_pos'],[]); return wb.xlsx.writeBuffer(); } // ── postSummaryRaw: for HSN rows whose numeric fields are already plain // decimal STRINGS from .toFixed(). Uses plain JSON.stringify — no replacer // that could reconvert those strings back to numbers (and trigger scientific // notation again). async function postSummaryRaw(baseUrl, clientCode, endpoint, mappingCd, templateCd, gstin, period, token, rows) { const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/${endpoint}` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=SALESSUMMARY`; console.log(`[TCS] ${endpoint} URL:`, url, '| rows:', rows.length); // Plain stringify — values are already formatted strings, must NOT be reconverted to numbers const resp = await axios.post(url, JSON.stringify(rows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log(`[TCS] ${endpoint} status:`, resp.status, JSON.stringify(resp.data)); return resp.data; } // Replacer: keep numbers as numbers (no quotes) but round to 2dp. // JSON.stringify never uses scientific notation for finite numbers below 1e21. function tcsJson(rows) { return JSON.stringify(rows, (_k, v) => { if (typeof v === 'number' && isFinite(v)) return parseFloat(v.toFixed(2)); return v; }); } async function postSummary(baseUrl, clientCode, endpoint, mappingCd, templateCd, gstin, period, token, rows) { const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/${endpoint}` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=SALESSUMMARY`; console.log(`[TCS] ${endpoint} URL:`, url, '| rows:', rows.length); const resp = await axios.post(url, tcsJson(rows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log(`[TCS] ${endpoint} status:`, resp.status, JSON.stringify(resp.data)); return resp.data; } const DOC_TYPE_MAP = { 'Invoice': 'Invoices for outward supply', 'AR Credit Memo': 'Credit Notes', 'Delivery': 'Delivery Challan in cases other than by way of supply (excluding job work, supply on approval, liquid gas )', 'AP Invoice (RCM)': 'Invoices for inward supply from unregistered person', 'JW': 'Delivery Challan for job work', 'IT': 'Delivery Challan for supply on approval', }; const DOC_NUM_MAP = { 'Invoice': 1, 'AR Credit Memo': 4, 'Delivery': 12, 'AP Invoice (RCM)': 2, 'JW': 9, 'IT': 10 }; // ═══════════════════════════════════════════════════════════════════════════ // POST /upload-tcs // Uploads all GSTR-1 sections via their respective TCS JSON APIs: // B2B/B2CL/EXP → salesInvUpld (transactional) // B2CS → salesSummaryUpldB2csb2csa (summary) // HSN → salesSummaryUpldAtatatxpd (summary) — uses postSummaryRaw // DOC → salesSummaryUpldDocumentsIssued (summary) // ═══════════════════════════════════════════════════════════════════════════ router.post('/upload-tcs', verifyToken, async (req, res) => { try { const { period, b2b = [], b2cl = [], export: exp = [], b2cs = [], hsn = [], docs = [] } = req.body; if (!period) return res.status(400).json({ success: false, message: 'period required (MMYYYY)' }); const baseUrl = process.env.TCS_GSP_BASE_URL || 'https://g31.tcsgsp.in'; const gstin = process.env.TCS_GSP_GSTIN || ''; const clientCode = process.env.TCS_GSP_CLIENT_CODE || ''; const mappingCd = process.env.TCS_GSP_MAPPING_CD || 'MFHSIDTN0000005'; const templateCd = process.env.TCS_GSP_TEMPLATE_CD || 'SalesInvoiceData802'; const missing = []; if (!gstin) missing.push('TCS_GSP_GSTIN'); if (!clientCode) missing.push('TCS_GSP_CLIENT_CODE'); if (missing.length) return res.status(400).json({ success: false, message: `Missing .env: ${missing.join(', ')}` }); const token = await getTcsToken(); let srNo = 0; const mappedB2B = b2b.map(row => ({ trans_type_code: 'B2B', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, system_cd: '', branch_cd: '', vertical_cd: '', resultflag: row.resultflag || 'A', export_type_desc: '', invoice_type: row.trans_type_code === 'DE' ? 'DE' : 'R', cp_gstin: row.cp_gstin || '', cp_gstin_name: row.cp_gstin_name || '', invoice_no: row.invoice_no, invoice_dt: row.invoice_dt, invoice_val: row.invoice_val, taxable_val: row.taxable_val, export_shipping_bill_no: '', export_shipping_bill_dt: '', items_serial_no: row.items_serial_no || 1, rate_of_tax: row.rate_of_tax, igst_amnt: row.igst_amnt || '', cgst_amnt: row.cgst_amnt || '', sgst_amnt: row.sgst_amnt || '', cess_amnt: row.cess_amnt || '', pos: String(row.pos || '').padStart(2, '0'), rchrg: row.rchrg || 'N', is_gstn_for_ecom_app: row.is_gstn_for_ecom_app || 'N', gstn_for_ecom_app: row.gstn_for_ecom_app || '', or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', diff_percent: 0.65, })); const mappedB2CL = b2cl.map(row => ({ trans_type_code: 'B2CL', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, system_cd: '', branch_cd: '', vertical_cd: '', resultflag: row.resultflag || 'A', export_type_desc: '', invoice_type: '', cp_gstin: '', cp_gstin_name: '', invoice_no: row.invoice_no, invoice_dt: row.invoice_dt, invoice_val: row.invoice_val, taxable_val: row.taxable_val, export_shipping_bill_no: '', export_shipping_bill_dt: '', items_serial_no: row.items_serial_no || 1, rate_of_tax: row.rate_of_tax, igst_amnt: row.igst_amnt || '', cgst_amnt: '', sgst_amnt: '', cess_amnt: row.cess_amnt || '', pos: String(row.pos || '').padStart(2, '0'), rchrg: '', is_gstn_for_ecom_app: row.is_gstn_for_ecom_app || 'N', gstn_for_ecom_app: row.gstn_for_ecom_app || '', or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', diff_percent: 0.65, })); // EXP — invoice_type field MUST NOT be included const mappedExp = exp.map(row => ({ trans_type_code: 'EXP', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, system_cd: '', branch_cd: '', vertical_cd: '', resultflag: row.resultflag || 'A', export_type_desc: row.export_type_desc || 'WOPAY', cp_gstin: '', cp_gstin_name: '', invoice_no: row.invoice_no, invoice_dt: row.invoice_dt, invoice_val: row.invoice_val, taxable_val: row.taxable_val, export_shipping_bill_no: row.export_shipping_bill_no || '', export_shipping_bill_dt: row.export_shipping_bill_dt || '', items_serial_no: '', rate_of_tax: row.rate_of_tax, igst_amnt: row.igst_amnt || '', cgst_amnt: '', sgst_amnt: '', cess_amnt: '', pos: '', rchrg: '', is_gstn_for_ecom_app: '', gstn_for_ecom_app: '', or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', diff_percent: 0.65, })); const allRows = [...mappedB2B, ...mappedB2CL, ...mappedExp]; const results = {}; const errors = []; // ── 1. Transactional: B2B + B2CL + EXP ────────────────────────────── if (allRows.length) { const jsonUrl = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/salesInvUpld` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=SALES`; console.log('[TCS] Invoice URL:', jsonUrl); console.log('[TCS] B2B:', mappedB2B.length, '| B2CL:', mappedB2CL.length, '| EXP:', mappedExp.length); const r = await axios.post(jsonUrl, allRows, { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log('[TCS] Invoice status:', r.status, JSON.stringify(r.data)); results.invoices = r.data; if (r.data?.statusCd === 400 || r.data?.statusCd === '400') { const fe = Object.entries(r.data).filter(([k])=>k!=='statusCd').map(([k,v])=>`${k}: ${v}`).join(' | '); errors.push(`Invoices (B2B/B2CL/EXP): ${fe}`); } } // ── 2. B2CS summary ────────────────────────────────────────────────── if (b2cs.length) { const grouped = {}; b2cs.forEach(r => { const key = `${r.pos}_${r.rate_of_tax}`; if (!grouped[key]) grouped[key] = { pos: r.pos, rt: r.rate_of_tax, txval: 0, iamt: 0, camt: 0, samt: 0, sply_ty: Number(r.igst_amnt) > 0 ? 'INTER' : 'INTRA' }; grouped[key].txval += Number(r.taxable_val) || 0; grouped[key].iamt += Number(r.igst_amnt) || 0; grouped[key].camt += Number(r.cgst_amnt) || 0; grouped[key].samt += Number(r.sgst_amnt) || 0; }); const b2csSummary = Object.values(grouped).map((r, i) => ({ trans_type_code: 'B2CS', fp: Number(b2cs[0]?.fp) || null, // integer e.g. 42026 ack_sr_no: i + 1, self_gstin: gstin, sply_ty: r.sply_ty, pos: String(r.pos), // 2-char string e.g. "23" txval: Math.round(r.txval * 100) / 100, rt: Number(r.rt), iamt: r.iamt > 0 ? Math.round(r.iamt * 100) / 100 : 0, camt: r.camt > 0 ? Math.round(r.camt * 100) / 100 : 0, samt: r.samt > 0 ? Math.round(r.samt * 100) / 100 : 0, csamt: 0, typ: 'OE', })); const d = await postSummary(baseUrl, clientCode, 'salesSummaryUpldB2csb2csa', 'MFHGSFTN0000714', 'GSTR1SummaryFormB2CSB2CSA835', gstin, period, token, b2csSummary); results.b2cs = d; if (d?.statusCd === 400 || d?.statusCd === '400') { const fe = Object.entries(d).filter(([k])=>k!=='statusCd').map(([k,v])=>`${k}: ${v}`).join(' | '); errors.push(`B2CS: ${fe}`); } } // ── 3. HSN summary ─────────────────────────────────────────────────── if (hsn.length) { const hsnAggJson = {}; hsn.forEach(r => { const uqc = validUQC(r.uqc); const rt = r.rate_of_tax || 0; const section = r.section || 'B2B'; const key = `${r.hsn_sc}_${uqc}_${rt}_${section}`; if (!hsnAggJson[key]) hsnAggJson[key] = { fp: r.fp, hsn_sc: r.hsn_sc, gstr1Desc: r.gstr1Desc || 'Goods', uqc, rate_of_tax: rt, section, qty: 0, val: 0, txval: 0, iamt: 0, camt: 0, samt: 0, }; hsnAggJson[key].qty += Number(r.qty) || 0; hsnAggJson[key].val += Number(r.val) || 0; hsnAggJson[key].txval += Number(r.txval) || 0; hsnAggJson[key].iamt += Number(r.iamt) || 0; hsnAggJson[key].camt += Number(r.camt) || 0; hsnAggJson[key].samt += Number(r.samt) || 0; }); // num: occurrence index of this HSN/SAC code among the rows being uploaded — // 1 if the code appears once, 1,2,... if the same code repeats across // different UQC/rate/section rows (verified against hsn_sc + uqc + rate_of_tax) const hsnRowsSorted = Object.values(hsnAggJson) .sort((a, b) => (a.hsn_sc < b.hsn_sc ? -1 : a.hsn_sc > b.hsn_sc ? 1 : a.uqc < b.uqc ? -1 : a.uqc > b.uqc ? 1 : a.section< b.section? -1 : a.section> b.section? 1 : a.rate_of_tax - b.rate_of_tax)); const hsnSeenJson = {}; // All numeric fields serialized as plain decimal STRINGS via .toFixed() // so JSON.stringify never sees a raw float and cannot produce scientific notation const hsnRows = hsnRowsSorted.map((r, i) => { hsnSeenJson[r.hsn_sc] = (hsnSeenJson[r.hsn_sc] || 0) + 1; return { fp: toMMYYYY(r.fp), ack_sr_no: i + 1, self_gstin: gstin, num: hsnSeenJson[r.hsn_sc], hsn_sc: r.hsn_sc, gstr1Desc: r.gstr1Desc, uqc: r.uqc, qty: Number(parseFloat(r.qty || 0).toFixed(2)), val: parseFloat(r.val || 0).toFixed(2), // string "15685021.77" txval: parseFloat(r.txval || 0).toFixed(2), // string "15685021.77" iamt: parseFloat(r.iamt || 0).toFixed(2), camt: parseFloat(r.camt || 0).toFixed(2), samt: parseFloat(r.samt || 0).toFixed(2), csamt: '', rate_of_tax: parseFloat(r.rate_of_tax || 0).toFixed(2), section: r.section || 'B2B', }; }); // Log first 2 rows so you can verify no scientific notation before it hits TCS console.log('[TCS] HSN payload sample:', JSON.stringify(hsnRows.slice(0, 2), null, 2)); // Use postSummaryRaw — plain JSON.stringify, no replacer that could reconvert strings const d = await postSummaryRaw(baseUrl, clientCode, 'salesSummaryUpldAtatatxpd', 'MFHGSFTN0000716', 'GSTR1SummaryFormHSNSACSummary837', gstin, period, token, hsnRows); results.hsn = d; if (d?.statusCd === 400 || d?.statusCd === '400') { const fe = Object.entries(d).filter(([k])=>k!=='statusCd').map(([k,v])=>`${k}: ${v}`).join(' | '); errors.push(`HSN: ${fe}`); } } // ── 4. Documents Issued summary ────────────────────────────────────── if (docs.length) { const docRows = docs.map((r, i) => ({ fp: Number(toMMYYYY(r.fp)) || null, // integer e.g. 42026 ack_sr_no: i + 1, // integer self_gstin: gstin, doc_num: DOC_NUM_MAP[r.DocumentType] || 1, // integer num: 1, // integer docType: DOC_TYPE_MAP[r.DocumentType] || r.DocumentType, sr_from: String(r.sr_from), sr_to: String(r.sr_to), totnum: parseInt(r.totnum, 10) || 0, // integer cancel: parseInt(r.canceled, 10) || 0, // integer net_issue: parseInt(r.net_issue, 10) || 0, // integer })); const d = await postSummary(baseUrl, clientCode, 'salesSummaryUpldDocumentsIssued', 'MFHGSFTN0000717', 'GSTR1SummaryFormDocumentsIssuedSummary838', gstin, period, token, docRows); results.docs = d; if (d?.statusCd === 400 || d?.statusCd === '400') { const fe = Object.entries(d).filter(([k])=>k!=='statusCd').map(([k,v])=>`${k}: ${v}`).join(' | '); errors.push(`DOC: ${fe}`); } } if (!Object.keys(results).length) { return res.status(400).json({ success: false, message: 'No data to upload' }); } if (errors.length) { return res.status(400).json({ success: false, message: errors.join(' | '), data: results }); } const primaryAck = results.invoices?.ackNo || results.b2cs?.ackNo || results.hsn?.ackNo || results.docs?.ackNo; return res.json({ success: true, data: { ackNo: primaryAck, ...results }, method: 'JSON' }); } catch (err) { const msg = err.response?.data ? `[${err.response.status}] ${JSON.stringify(err.response.data)}` : err.message; console.error('[TCS] Error:', msg); res.status(500).json({ success: false, message: msg }); } }); // ── POST /upload-tcs-excel — Excel format upload ────────────────────────── router.post('/upload-tcs-excel', verifyToken, async (req, res) => { try { const { period, b2b = [], b2cl = [], b2cs = [], export: exp = [], hsn = [], docs = [] } = req.body; if (!period) return res.status(400).json({ success: false, message: 'period required' }); const baseUrl = process.env.TCS_GSP_BASE_URL || 'https://g31.tcsgsp.in'; const gstin = process.env.TCS_GSP_GSTIN || ''; const clientCode = process.env.TCS_GSP_CLIENT_CODE || ''; const mappingCd = process.env.TCS_GSP_MAPPING_CD || 'MFHSIDTN0000005'; const templateCd = process.env.TCS_GSP_TEMPLATE_CD || 'SalesInvoiceData802'; const token = await getTcsToken(); const excelBuf = await buildTcsExcel({ b2b, b2cl, b2cs, exp, hsn, docs }, gstin); const form = new FormData(); form.append('file', excelBuf, { filename: `GSTR1_${period}.xlsx`, contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', knownLength: excelBuf.length }); form.append('UpldGstinLst', gstin); const uploadUrl = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/salesInvUpldForEXCEL/${clientCode}/${mappingCd}/${templateCd}/${gstin}/EXCEL/${period}?oprFlag=SALES`; console.log('[TCS] Excel Upload URL:', uploadUrl); const uploadResp = await axios.post(uploadUrl, form, { headers: { ...form.getHeaders(), gstin, clientCode, Authorization: `Bearer ${token}` }, maxContentLength: Infinity, maxBodyLength: Infinity, httpsAgent, }); console.log('[TCS] Excel Status :', uploadResp.status); console.log('[TCS] Excel Response:', JSON.stringify(uploadResp.data, null, 2)); const tcsData = uploadResp.data; if (tcsData?.statusCd === 400 || tcsData?.statusCd === '400') { const fieldErrors = Object.entries(tcsData).filter(([k])=>k!=='statusCd').map(([k,v])=>`${k}: ${v}`).join(' | '); return res.status(400).json({ success: false, message: `TCS Excel error — ${fieldErrors}`, raw: tcsData }); } res.json({ success: true, data: tcsData, method: 'EXCEL' }); } catch (err) { const msg = err.response?.data ? JSON.stringify(err.response.data) : err.message; res.status(500).json({ success: false, message: msg }); } }); module.exports = router;