'use strict'; const express = require('express'); const router = express.Router(); const axios = require('axios'); 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; } function r2(n) { const num = parseFloat(String(n ?? '0')); return isNaN(num) ? 0 : parseFloat(num.toFixed(2)); } 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(); return VALID_UQC.has(UQC_MAP[up] || up) ? (UQC_MAP[up] || up) : 'OTH'; } // ── SQL builders ────────────────────────────────────────────────────────── function sqlPurchaseB2B(from, to) { return ` SELECT fp, trans_type_code, ack_sr_no, self_gstin, system_cd, branch_cd, vertical_cd, result_flag, cp_gstin, cp_gstin_name, invoice_type, invoice_no, invoice_dt, MAX(invoice_val) AS invoice_val, SUM(taxable_val) AS taxable_val, items_serial_no, MAX(rate_of_tax) AS rate_of_tax, SUM(igst_amnt) AS igst_amnt, SUM(cgst_amnt) AS cgst_amnt, SUM(sgst_amnt) AS sgst_amnt, SUM(cess_amnt) AS cess_amnt, pos, rchrg, eligibility_of_itc, SUM(tot_tax_avlbl_itc_igst) AS tot_tax_avlbl_itc_igst, SUM(tot_tax_avlbl_itc_cgst) AS tot_tax_avlbl_itc_cgst, SUM(tot_tax_avlbl_itc_sgst) AS tot_tax_avlbl_itc_sgst, SUM(total_tax_itc_cess) AS total_tax_itc_cess FROM ( SELECT FORMAT(T_ODOC.[DocDate], 'MMyyyy') AS fp, 'B2B' AS trans_type_code, NULL AS ack_sr_no, T_DOC12.[LocGSTN] AS self_gstin, NULL AS system_cd, NULL AS branch_cd, NULL AS vertical_cd, NULL AS result_flag, T_DOC12.[BpGSTN] AS cp_gstin, T_ODOC.[CardName] AS cp_gstin_name, CASE WHEN T_DOC12.[ImpORExp] = 'Y' THEN 'IMPG' WHEN T_LINE.[FlagReverseChange] = 'Y' THEN 'RCM' ELSE 'B2B' END AS invoice_type, T_ODOC.[NumAtCard] AS invoice_no, CONVERT(VARCHAR(10), T_ODOC.[TaxDate], 120) AS invoice_dt, CAST(T_ODOC.[DocTotal] AS DECIMAL(13,2)) AS invoice_val, CAST(T_LINE.[ItemTaxableValue] AS DECIMAL(13,2)) AS taxable_val, NULL AS items_serial_no, CASE WHEN T_LINE.[IGST_RATE] > 0 THEN T_LINE.[IGST_RATE] ELSE T_LINE.[CGST_RATE] + T_LINE.[SGST_RATE] END AS rate_of_tax, CAST(T_LINE.[IGST_AMOUNT] AS DECIMAL(13,2)) AS igst_amnt, CAST(T_LINE.[CGST_AMOUNT] AS DECIMAL(13,2)) AS cgst_amnt, CAST(T_LINE.[SGST_AMOUNT] AS DECIMAL(13,2)) AS sgst_amnt, CAST(T_LINE.[CESS_AMOUNT] AS DECIMAL(13,2)) AS cess_amnt, LEFT(T_DOC12.[BpGSTN], 2) AS pos, T_LINE.[FlagReverseChange] AS rchrg, CASE WHEN T_LINE.[Item_GST_Type] = 'C' THEN 'Capital Goods' WHEN T_LINE.[Item_GST_Type] = 'IP' THEN 'Inputs' WHEN T_LINE.[Item_GST_Type] = 'IS' THEN 'Input Services' WHEN T_LINE.[FlagReverseChange] = 'Y' THEN 'Ineligible' ELSE 'All other ITC' END AS eligibility_of_itc, CAST(T_LINE.[IGST_AMOUNT] - T_LINE.[IGST_DEBIT_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_igst, CAST(T_LINE.[CGST_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_cgst, CAST(T_LINE.[SGST_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_sgst, CAST(T_LINE.[CESS_AMOUNT] AS DECIMAL(13,2)) AS total_tax_itc_cess FROM ( -- Part 1: Item Lines (PCH1 / SrcArrType = 12) SELECT MIN(CASE WHEN T_ODOC.[DocType]='I' AND T_OITM.[ItemClass]=2 THEN 'G' ELSE 'S' END) AS [ItemType], MIN(T_DOC1.[ItemCode]) AS [ItemCode], MIN(T_DOC1.[Dscription]) AS [ItemDescription], MIN(T_OITM.[ItmsGrpCod]) AS [ItmsGrpCod], MIN(T_OITB.[ItmsGrpNam]) AS [ItmsGrpNam], MIN(CASE WHEN T_DOC1.[SacEntry] IS NULL THEN REPLACE(T_OCHP.[ChapterID],'.','') ELSE T_OSAC.[ServCode] END) AS [HSN], MIN(T_DOC1.[U_HSN]) AS [HSN_Code], MIN(CASE WHEN T_ODOC.[DocType]='S' THEN NULL ELSE T_DOC1.[Quantity] END) AS [ItemQuantity], MIN(T_DOC1.[UomCode]) AS [ItemUOM], MIN(CASE WHEN T_DOC1.[AssblValue]<>0 THEN T_DOC1.[AssblValue] ELSE CEILING(CASE WHEN T_DOC1.[Rate]>0 THEN CASE WHEN T_OADM.[DirectRate]='Y' THEN T_DOC1.[PriceBefDi]*T_DOC1.[Rate] ELSE T_DOC1.[PriceBefDi]/T_DOC1.[Rate] END ELSE T_DOC1.[PriceBefDi] END*100)/100 END) AS [ItemPrice], MIN((CASE WHEN T_DOC1.[AssblValue]<>0 THEN T_DOC1.[AssblValue] ELSE CEILING(CASE WHEN T_DOC1.[Rate]>0 THEN CASE WHEN T_OADM.[DirectRate]='Y' THEN T_DOC1.[PriceBefDi]*T_DOC1.[Rate] ELSE T_DOC1.[PriceBefDi]/T_DOC1.[Rate] END ELSE T_DOC1.[PriceBefDi] END*100)/100 END) * CASE WHEN T_ODOC.[DocType]='S' THEN 1.0 ELSE T_DOC1.[Quantity] END - T_TAX1.[BaseSum]) AS [LineDiscount], MAX(T_TAX1.[BaseSum]) AS [ItemTaxableValue], MAX(CASE WHEN T_OSTT.[AbsId]=-120 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [IGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-120 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [IGST_AMOUNT], 0.0 AS [IGST_DEBIT_AMOUNT], MAX(CASE WHEN T_OSTT.[AbsId]=-100 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [CGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-100 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [CGST_AMOUNT], MAX(CASE WHEN (T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150) AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [SGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [SGST_AMOUNT], MAX(CASE WHEN T_OSTT.[AbsId]=-130 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [CESS_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-130 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [CESS_AMOUNT], MAX(T_TAX1.[BaseSum])+SUM(CASE WHEN T_OSTT.[NfTaxId]=-60 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [LineTotalIncludeGST], MAX(CASE WHEN T_TAX1.[RvsChrgPrc]>0 THEN 'Y' ELSE 'N' END) AS [FlagReverseChange], MAX(CASE WHEN T_ODOC.[DocType]='I' THEN T_OITM.[GstTaxCtg] ELSE '' END) AS [Item_GST_Type], MIN(T_DOC1.[DocEntry]) AS [DocEntry], MIN(T_DOC1.[LineNum]) AS [LineNum], MIN(T_TAX1.[SrcGrpNum]) AS [GroupNum], MIN(T_TAX1.[SrcArrType]) AS [ArrayType] FROM OADM T_OADM, 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 OPCH T_ODOC ON T_OTAX.[SrcObjType]=18 AND T_OTAX.[SrcObjAbs]=T_ODOC.[DocEntry] INNER JOIN PCH1 T_DOC1 ON T_OTAX.[SrcObjType]=18 AND T_TAX1.[SrcArrType]=12 AND T_TAX1.[SrcLineNum]=T_DOC1.[LineNum] AND T_OTAX.[SrcObjAbs]=T_DOC1.[DocEntry] LEFT JOIN OITM T_OITM ON T_OITM.[ItemCode]=T_DOC1.[ItemCode] AND T_ODOC.[DocType]='I' INNER JOIN OITB T_OITB ON T_OITM.[ItmsGrpCod]=T_OITB.[ItmsGrpCod] LEFT JOIN OCHP T_OCHP ON T_OCHP.[AbsEntry]=T_OITM.[ChapterID] LEFT JOIN OSAC T_OSAC ON T_DOC1.[SacEntry]=T_OSAC.[AbsEntry] WHERE T_ODOC.[GSTTranTyp]='GA' GROUP BY T_DOC1.[DocEntry], T_DOC1.[LineNum] UNION ALL -- Part 2: Line-level Freight (PCH2 / SrcArrType = 13) SELECT 'S','','','','', MIN(CASE WHEN T_DOC1.[SacEntry] IS NULL THEN REPLACE(T_OCHP.[ChapterID],'.','') ELSE T_OSAC.[ServCode] END), MIN(T_DOC1.[U_HSN]), NULL,'',MAX(T_TAX1.[BaseSum]),0.0,MAX(T_TAX1.[BaseSum]), MAX(CASE WHEN T_OSTT.[AbsId]=-120 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-120 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), 0.0, MAX(CASE WHEN T_OSTT.[AbsId]=-100 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-100 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN (T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150) AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN T_OSTT.[AbsId]=-130 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-130 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(T_TAX1.[BaseSum])+SUM(CASE WHEN T_OSTT.[NfTaxId]=-60 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN T_TAX1.[RvsChrgPrc]>0 THEN 'Y' ELSE 'N' END), '', MIN(T_OTAX.[SrcObjAbs]),MIN(T_TAX1.[SrcLineNum]),MIN(T_TAX1.[SrcGrpNum]),MIN(T_TAX1.[SrcArrType]) FROM OADM T_OADM, 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 OPCH T_ODOC ON T_OTAX.[SrcObjType]=18 AND T_OTAX.[SrcObjAbs]=T_ODOC.[DocEntry] INNER JOIN PCH2 T_DOC2 ON T_OTAX.[SrcObjType]=18 AND T_TAX1.[SrcArrType]=13 AND T_TAX1.[SrcLineNum]=T_DOC2.[LineNum] AND T_OTAX.[SrcObjAbs]=T_DOC2.[DocEntry] AND T_TAX1.[SrcGrpNum]=T_DOC2.[GroupNum] INNER JOIN PCH1 T_DOC1 ON T_DOC1.[DocEntry]=T_DOC2.[DocEntry] AND T_DOC1.[LineNum]=T_DOC2.[LineNum] LEFT JOIN OITM T_OITM ON T_OITM.[ItemCode]=T_DOC1.[ItemCode] AND T_ODOC.[DocType]='I' LEFT JOIN OCHP T_OCHP ON T_OCHP.[AbsEntry]=T_OITM.[ChapterID] LEFT JOIN OSAC T_OSAC ON T_DOC1.[SacEntry]=T_OSAC.[AbsEntry] WHERE T_ODOC.[GSTTranTyp]='GA' GROUP BY T_OTAX.[SrcObjAbs], T_TAX1.[SrcLineNum], T_TAX1.[SrcGrpNum] UNION ALL -- Part 3: Header-level Freight (PCH3 / SrcArrType = 14) SELECT 'S','','','','', MAX(T_OEXD.[SacCode]),'', NULL,'',MAX(T_TAX1.[BaseSum]),0.0,MAX(ROUND(T_TAX1.[BaseSum],2)), MAX(CASE WHEN T_OSTT.[AbsId]=-120 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-120 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), 0.0, MAX(CASE WHEN T_OSTT.[AbsId]=-100 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-100 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN (T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150) AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN T_OSTT.[AbsId]=-130 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END), SUM(CASE WHEN T_OSTT.[AbsId]=-130 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(T_TAX1.[BaseSum])+SUM(CASE WHEN T_OSTT.[NfTaxId]=-60 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END), MAX(CASE WHEN T_TAX1.[RvsChrgPrc]>0 THEN 'Y' ELSE 'N' END), '', MIN(T_DOC3.[DocEntry]),MIN(T_DOC3.[LineNum]),MIN(T_TAX1.[SrcGrpNum]),MIN(T_TAX1.[SrcArrType]) 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 OPCH T_ODOC ON T_OTAX.[SrcObjType]=18 AND T_OTAX.[SrcObjAbs]=T_ODOC.[DocEntry] INNER JOIN PCH3 T_DOC3 ON T_OTAX.[SrcObjType]=18 AND T_TAX1.[SrcArrType]=14 AND T_TAX1.[SrcLineNum]=T_DOC3.[LineNum] AND T_OTAX.[SrcObjAbs]=T_DOC3.[DocEntry] INNER JOIN OEXD T_OEXD ON T_DOC3.[ExpnsCode]=T_OEXD.[ExpnsCode] WHERE T_ODOC.[GSTTranTyp]='GA' GROUP BY T_OTAX.[SrcObjAbs], T_TAX1.[SrcLineNum], T_TAX1.[SrcGrpNum] ) AS T_LINE INNER JOIN OPCH T_ODOC ON T_LINE.[DocEntry] = T_ODOC.[DocEntry] INNER JOIN PCH12 T_DOC12 ON T_DOC12.[DocEntry] = T_ODOC.[DocEntry] INNER JOIN NNM1 T_NNM1 ON T_NNM1.[Series] = T_ODOC.[Series] WHERE T_ODOC.[CANCELED] = 'N' AND LEN(T_DOC12.[BpGSTN]) = 15 AND T_DOC12.[BpGSTN] LIKE '[0-9][0-9]%' and T_ODOC.[NumAtCard] is not null AND T_ODOC.[DocDate] >= '${from}' AND T_ODOC.[DocDate] <= '${to}' ) AS RAW_GSTR2 GROUP BY fp, trans_type_code, ack_sr_no, self_gstin, system_cd, branch_cd, vertical_cd, result_flag, cp_gstin, cp_gstin_name, invoice_type, invoice_no, invoice_dt, items_serial_no, pos, rchrg, eligibility_of_itc ORDER BY fp, cp_gstin, invoice_no, items_serial_no`; } function sqlPurchaseB2BUR(from, to) { return ` SELECT fp, trans_type_code, ack_sr_no, self_gstin, cp_gstin, cp_gstin_name, invoice_type, invoice_category, invoice_no, invoice_dt, payment_vchr_no, payment_vchr_dt, unique_ref_no, MAX(invoice_val) AS invoice_val, SUM(taxable_val) AS taxable_val, items_serial_no, MAX(rate_of_tax) AS rate_of_tax, SUM(igst_amnt) AS igst_amnt, SUM(cgst_amnt) AS cgst_amnt, SUM(sgst_amnt) AS sgst_amnt, SUM(cess_amnt) AS cess_amnt, pos, rchrg, eligibility_of_itc, SUM(tot_tax_avlbl_itc_igst) AS tot_tax_avlbl_itc_igst, SUM(tot_tax_avlbl_itc_cgst) AS tot_tax_avlbl_itc_cgst, SUM(tot_tax_avlbl_itc_sgst) AS tot_tax_avlbl_itc_sgst, SUM(total_tax_itc_cess) AS total_tax_itc_cess, orig_invoice_no, orig_invoice_dt FROM ( SELECT FORMAT(T_ODOC.[DocDate], 'MMyyyy') AS fp, 'B2BUR' AS trans_type_code, NULL AS ack_sr_no, T_DOC12.[LocGSTN] AS self_gstin, '' AS cp_gstin, T_ODOC.[CardName] AS cp_gstin_name, 'B2BUR' AS invoice_type, 'R' AS invoice_category, T_ODOC.[NumAtCard] AS invoice_no, CONVERT(VARCHAR(10), T_ODOC.[TaxDate], 120) AS invoice_dt, '' AS payment_vchr_no, NULL AS payment_vchr_dt, '' AS unique_ref_no, CAST(T_ODOC.[DocTotal] AS DECIMAL(13,2)) AS invoice_val, CAST(T_LINE.[ItemTaxableValue] AS DECIMAL(13,2)) AS taxable_val, NULL AS items_serial_no, CASE WHEN T_LINE.[IGST_RATE] > 0 THEN T_LINE.[IGST_RATE] ELSE T_LINE.[CGST_RATE] + T_LINE.[SGST_RATE] END AS rate_of_tax, CAST(T_LINE.[IGST_AMOUNT] AS DECIMAL(13,2)) AS igst_amnt, CAST(T_LINE.[CGST_AMOUNT] AS DECIMAL(13,2)) AS cgst_amnt, CAST(T_LINE.[SGST_AMOUNT] AS DECIMAL(13,2)) AS sgst_amnt, CAST(T_LINE.[CESS_AMOUNT] AS DECIMAL(13,2)) AS cess_amnt, ISNULL(NULLIF(LEFT(T_DOC12.[LocGSTN], 2), ''), '') AS pos, T_LINE.[FlagReverseChange] AS rchrg, CASE WHEN T_LINE.[Item_GST_Type] = 'C' THEN 'Capital Goods' WHEN T_LINE.[Item_GST_Type] = 'IP' THEN 'Inputs' WHEN T_LINE.[Item_GST_Type] = 'IS' THEN 'Input Services' WHEN T_LINE.[FlagReverseChange] = 'Y' THEN 'Ineligible' ELSE 'All other ITC' END AS eligibility_of_itc, CAST(T_LINE.[IGST_AMOUNT] - T_LINE.[IGST_DEBIT_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_igst, CAST(T_LINE.[CGST_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_cgst, CAST(T_LINE.[SGST_AMOUNT] AS DECIMAL(13,2)) AS tot_tax_avlbl_itc_sgst, CAST(T_LINE.[CESS_AMOUNT] AS DECIMAL(13,2)) AS total_tax_itc_cess, '' AS orig_invoice_no, NULL AS orig_invoice_dt FROM ( SELECT MIN(CASE WHEN T_ODOC.[DocType]='I' AND T_OITM.[ItemClass]=2 THEN 'G' ELSE 'S' END) AS [ItemType], MIN(T_DOC1.[ItemCode]) AS [ItemCode], MIN(T_DOC1.[Dscription]) AS [ItemDescription], MIN(T_OITM.[ItmsGrpCod]) AS [ItmsGrpCod], MIN(T_OITB.[ItmsGrpNam]) AS [ItmsGrpNam], MIN(CASE WHEN T_DOC1.[SacEntry] IS NULL THEN REPLACE(T_OCHP.[ChapterID],'.','') ELSE T_OSAC.[ServCode] END) AS [HSN], MIN(T_DOC1.[U_HSN]) AS [HSN_Code], MIN(CASE WHEN T_ODOC.[DocType]='S' THEN NULL ELSE T_DOC1.[Quantity] END) AS [ItemQuantity], MIN(T_DOC1.[UomCode]) AS [ItemUOM], MIN(CASE WHEN T_DOC1.[AssblValue]<>0 THEN T_DOC1.[AssblValue] ELSE CEILING(CASE WHEN T_DOC1.[Rate]>0 THEN CASE WHEN T_OADM.[DirectRate]='Y' THEN T_DOC1.[PriceBefDi]*T_DOC1.[Rate] ELSE T_DOC1.[PriceBefDi]/T_DOC1.[Rate] END ELSE T_DOC1.[PriceBefDi] END*100)/100 END) AS [ItemPrice], MIN((CASE WHEN T_DOC1.[AssblValue]<>0 THEN T_DOC1.[AssblValue] ELSE CEILING(CASE WHEN T_DOC1.[Rate]>0 THEN CASE WHEN T_OADM.[DirectRate]='Y' THEN T_DOC1.[PriceBefDi]*T_DOC1.[Rate] ELSE T_DOC1.[PriceBefDi]/T_DOC1.[Rate] END ELSE T_DOC1.[PriceBefDi] END*100)/100 END) * CASE WHEN T_ODOC.[DocType]='S' THEN 1.0 ELSE T_DOC1.[Quantity] END - T_TAX1.[BaseSum]) AS [LineDiscount], MAX(T_TAX1.[BaseSum]) AS [ItemTaxableValue], MAX(CASE WHEN T_OSTT.[AbsId]=-120 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [IGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-120 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [IGST_AMOUNT], 0.0 AS [IGST_DEBIT_AMOUNT], MAX(CASE WHEN T_OSTT.[AbsId]=-100 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [CGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-100 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [CGST_AMOUNT], MAX(CASE WHEN (T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150) AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [SGST_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-110 OR T_OSTT.[AbsId]=-150 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [SGST_AMOUNT], MAX(CASE WHEN T_OSTT.[AbsId]=-130 AND T_ODOC.[DutyStatus]='Y' THEN T_TAX1.[VatPercent] ELSE 0 END) AS [CESS_RATE], SUM(CASE WHEN T_OSTT.[AbsId]=-130 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [CESS_AMOUNT], MAX(T_TAX1.[BaseSum])+SUM(CASE WHEN T_OSTT.[NfTaxId]=-60 THEN ROUND(T_TAX1.[VatSum],2) ELSE 0 END) AS [LineTotalIncludeGST], MAX(CASE WHEN T_TAX1.[RvsChrgPrc]>0 THEN 'Y' ELSE 'N' END) AS [FlagReverseChange], MAX(CASE WHEN T_ODOC.[DocType]='I' THEN T_OITM.[GstTaxCtg] ELSE '' END) AS [Item_GST_Type], MIN(T_DOC1.[DocEntry]) AS [DocEntry], MIN(T_DOC1.[LineNum]) AS [LineNum], MIN(T_TAX1.[SrcGrpNum]) AS [GroupNum], MIN(T_TAX1.[SrcArrType]) AS [ArrayType] FROM OADM T_OADM, 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 OPCH T_ODOC ON T_OTAX.[SrcObjType]=18 AND T_OTAX.[SrcObjAbs]=T_ODOC.[DocEntry] INNER JOIN PCH1 T_DOC1 ON T_OTAX.[SrcObjType]=18 AND T_TAX1.[SrcArrType]=12 AND T_TAX1.[SrcLineNum]=T_DOC1.[LineNum] AND T_OTAX.[SrcObjAbs]=T_DOC1.[DocEntry] LEFT JOIN OITM T_OITM ON T_OITM.[ItemCode]=T_DOC1.[ItemCode] AND T_ODOC.[DocType]='I' INNER JOIN OITB T_OITB ON T_OITM.[ItmsGrpCod]=T_OITB.[ItmsGrpCod] LEFT JOIN OCHP T_OCHP ON T_OCHP.[AbsEntry]=T_OITM.[ChapterID] LEFT JOIN OSAC T_OSAC ON T_DOC1.[SacEntry]=T_OSAC.[AbsEntry] WHERE T_ODOC.[GSTTranTyp]='GA' GROUP BY T_DOC1.[DocEntry], T_DOC1.[LineNum] ) AS T_LINE INNER JOIN OPCH T_ODOC ON T_LINE.[DocEntry] = T_ODOC.[DocEntry] INNER JOIN PCH12 T_DOC12 ON T_DOC12.[DocEntry] = T_ODOC.[DocEntry] INNER JOIN NNM1 T_NNM1 ON T_NNM1.[Series] = T_ODOC.[Series] WHERE T_ODOC.[CANCELED] = 'N' AND T_LINE.[FlagReverseChange] = 'Y' AND NOT (LEN(T_DOC12.[BpGSTN]) = 15 AND T_DOC12.[BpGSTN] LIKE '[0-9][0-9]%') AND T_ODOC.[NumAtCard] is not null AND T_ODOC.[DocDate] >= '${from}' AND T_ODOC.[DocDate] <= '${to}' ) AS RAW_GSTR2 GROUP BY fp, trans_type_code, ack_sr_no, self_gstin, cp_gstin, cp_gstin_name, invoice_type, invoice_category, invoice_no, invoice_dt, payment_vchr_no, payment_vchr_dt, unique_ref_no, items_serial_no, pos, rchrg, eligibility_of_itc, orig_invoice_no, orig_invoice_dt ORDER BY fp, invoice_no, items_serial_no`; } function sqlPurchaseHSN(from, to) { return ` WITH TaxLines AS ( SELECT T_DOC1.DocEntry, T_DOC1.LineNum, MIN(CASE WHEN T_DOC1.SacEntry IS NULL THEN REPLACE(T_OCHP.ChapterID,'.','') ELSE T_OSAC.ServCode END) AS hsn_code, MIN(CASE WHEN T_ODOC.DocType='S' THEN NULL ELSE T_DOC1.Quantity END) AS qty, MIN(T_DOC1.UomCode) AS uom, MIN(T_DOC1.Dscription) AS item_desc, CAST(MAX(T_TAX1.BaseSum) AS DECIMAL(13,2)) AS ItemTaxableValue, MAX(CASE WHEN T_OSTT.AbsId=-120 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) AS IGST_RATE, CAST(SUM(CASE WHEN T_OSTT.AbsId=-120 THEN ROUND(T_TAX1.VatSum,2) ELSE 0 END) AS DECIMAL(13,2)) AS IGST_AMT, MAX(CASE WHEN T_OSTT.AbsId=-100 AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) AS CGST_RATE, CAST(SUM(CASE WHEN T_OSTT.AbsId=-100 THEN ROUND(T_TAX1.VatSum,2) ELSE 0 END) AS DECIMAL(13,2)) AS CGST_AMT, MAX(CASE WHEN (T_OSTT.AbsId=-110 OR T_OSTT.AbsId=-150) AND T_ODOC.DutyStatus='Y' THEN T_TAX1.VatPercent ELSE 0 END) AS SGST_RATE, CAST(SUM(CASE WHEN T_OSTT.AbsId=-110 OR T_OSTT.AbsId=-150 THEN ROUND(T_TAX1.VatSum,2) ELSE 0 END) AS DECIMAL(13,2)) AS SGST_AMT, CAST(SUM(CASE WHEN T_OSTT.AbsId=-130 THEN ROUND(T_TAX1.VatSum,2) ELSE 0 END) AS DECIMAL(13,2)) AS CESS_AMT 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 OPCH T_ODOC ON T_OTAX.SrcObjType=18 AND T_OTAX.SrcObjAbs=T_ODOC.DocEntry INNER JOIN PCH1 T_DOC1 ON T_TAX1.SrcArrType=12 AND T_TAX1.SrcLineNum=T_DOC1.LineNum AND T_DOC1.DocEntry=T_ODOC.DocEntry LEFT JOIN OITM T_OITM ON T_OITM.ItemCode=T_DOC1.ItemCode AND T_ODOC.DocType='I' LEFT JOIN OCHP T_OCHP ON T_OCHP.AbsEntry=T_OITM.ChapterID LEFT JOIN OSAC T_OSAC ON T_DOC1.SacEntry=T_OSAC.AbsEntry WHERE T_ODOC.GSTTranTyp='GA' GROUP BY T_DOC1.DocEntry, T_DOC1.LineNum ) SELECT FORMAT(T_ODOC.DocDate, 'MMyyyy') AS fp, ISNULL(T_LINE.hsn_code, '') AS hsn_sc, MIN(ISNULL(T_LINE.item_desc, 'Goods')) AS item_desc, ISNULL(T_LINE.uom, '') AS uom, CAST(SUM(T_LINE.qty) AS DECIMAL(15,3)) AS qty, CAST(SUM(T_LINE.ItemTaxableValue + T_LINE.IGST_AMT + T_LINE.CGST_AMT + T_LINE.SGST_AMT + T_LINE.CESS_AMT) AS DECIMAL(15,2)) AS val, CAST(SUM(T_LINE.ItemTaxableValue) AS DECIMAL(15,2)) AS txval, CAST(SUM(T_LINE.IGST_AMT) AS DECIMAL(15,2)) AS iamt, CAST(SUM(T_LINE.CGST_AMT) AS DECIMAL(15,2)) AS camt, CAST(SUM(T_LINE.SGST_AMT) AS DECIMAL(15,2)) AS samt, CAST(SUM(T_LINE.CESS_AMT) AS DECIMAL(15,2)) AS csamt FROM TaxLines T_LINE INNER JOIN OPCH T_ODOC ON T_LINE.DocEntry = T_ODOC.DocEntry INNER JOIN PCH12 T_DOC12 ON T_DOC12.DocEntry = T_ODOC.DocEntry WHERE T_ODOC.CANCELED = 'N' AND LEN(T_DOC12.BpGSTN) = 15 AND T_DOC12.BpGSTN LIKE '[0-9][0-9]%' AND T_ODOC.DocDate >= '${from}' AND T_ODOC.DocDate <= '${to}' AND T_LINE.hsn_code is not null GROUP BY FORMAT(T_ODOC.DocDate, 'MMyyyy'), T_LINE.hsn_code, T_LINE.uom ORDER BY fp, hsn_sc`; } // ── query handler ────────────────────────────────────────────────────────── 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 || []; 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 }); } } function sqlCDN(from, to) { return ` WITH CN_RAW AS ( SELECT (T_ODOC."DocDate") as "InvoiceDate", (T_ODOC."DocNum") as "InvoiceNo", (T_ODOC."NumAtCard") as "BillNo", (T_ODOC."TaxDate") as "BillDate", (T_ODOC."CardName") as "SupplierName", (T_DOC12."BpGSTN") as "SupplierGSTIN", (T_DOC12."BpStateCod") as "StatePOS", T_LINE.*, (T_ODOC."DocTotal") * -1 as "Invoice_Value" FROM ( SELECT MIN (T_DOC1."ItemCode") as "ItemCode", -1 * MAX (T_TAX1."BaseSum") as "ItemTaxableValue", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -120 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "IGST_AMOUNT", 0.0 as "IGST_DEBIT_AMOUNT", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -100 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "CGST_AMOUNT", -1 * SUM (CASE WHEN (T_OSTT."AbsId" = -110 OR T_OSTT."AbsId" = -150) THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "SGST_AMOUNT", -1 * (MAX (T_TAX1."BaseSum") + SUM (CASE WHEN T_OSTT."NfTaxId" = -60 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END)) AS "LineTotalIncludeGST", MIN (T_DOC1."DocEntry") as "DocEntry", MIN (T_DOC1."LineNum") as "LineNum", MIN (T_TAX1."SrcGrpNum") as "GroupNum", MIN (T_TAX1."SrcArrType") AS "ArrayType" FROM "OADM" T_OADM, "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 ORPC T_ODOC on (T_OTAX."SrcObjType" = 19 and T_OTAX."SrcObjAbs" = T_ODOC."DocEntry") INNER JOIN RPC1 T_DOC1 on (T_OTAX."SrcObjType" = 19 and T_TAX1."SrcArrType" = 12 and T_TAX1."SrcLineNum" = T_DOC1."LineNum" AND T_OTAX."SrcObjAbs" = T_DOC1."DocEntry") WHERE T_ODOC."GSTTranTyp" = 'GA' GROUP BY T_DOC1."DocEntry", T_DOC1."LineNum" UNION ALL SELECT '' as "ItemCode", -1 * MAX (T_TAX1."BaseSum") as "ItemTaxableValue", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -120 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "IGST_AMOUNT", 0.0 as "IGST_DEBIT_AMOUNT", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -100 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "CGST_AMOUNT", -1 * SUM (CASE WHEN (T_OSTT."AbsId" = -110 OR T_OSTT."AbsId" = -150) THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "SGST_AMOUNT", -1 * (MAX (T_TAX1."BaseSum") + SUM (CASE WHEN T_OSTT."NfTaxId" = -60 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END)) AS "LineTotalIncludeGST", MIN (T_OTAX."SrcObjAbs") as "DocEntry", MIN (T_TAX1."SrcLineNum") as "LineNum", MIN (T_TAX1."SrcGrpNum") as "GroupNum", MIN (T_TAX1."SrcArrType") AS "ArrayType" FROM "OADM" T_OADM, "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 ORPC T_ODOC on (T_OTAX."SrcObjType" = 19 and T_OTAX."SrcObjAbs" = T_ODOC."DocEntry") INNER JOIN RPC2 T_DOC2 on (T_OTAX."SrcObjType" = 19 and T_TAX1."SrcArrType" = 13 and T_TAX1."SrcLineNum" = T_DOC2."LineNum" AND T_OTAX."SrcObjAbs" = T_DOC2."DocEntry" AND T_TAX1."SrcGrpNum" = T_DOC2."GroupNum") INNER JOIN RPC1 T_DOC1 on (T_DOC1."DocEntry" = T_DOC2."DocEntry" AND T_DOC1."LineNum" = T_DOC2."LineNum") WHERE T_ODOC."GSTTranTyp" = 'GA' GROUP BY T_OTAX."SrcObjAbs", T_TAX1."SrcLineNum", T_TAX1."SrcGrpNum" UNION ALL SELECT '' as "ItemCode", -1 * MAX (ROUND(T_TAX1."BaseSum",2)) as "ItemTaxableValue", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -120 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "IGST_AMOUNT", 0.0 as "IGST_DEBIT_AMOUNT", -1 * SUM (CASE WHEN T_OSTT."AbsId" = -100 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "CGST_AMOUNT", -1 * SUM (CASE WHEN (T_OSTT."AbsId" = -110 OR T_OSTT."AbsId" = -150) THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END) AS "SGST_AMOUNT", -1 * (MAX (T_TAX1."BaseSum") + SUM (CASE WHEN T_OSTT."NfTaxId" = -60 THEN ROUND(T_TAX1."VatSum",2) ELSE 0 END)) AS "LineTotalIncludeGST", MIN (T_DOC3."DocEntry") as "DocEntry", MIN (T_DOC3."LineNum") as "LineNum", MIN (T_TAX1."SrcGrpNum") as "GroupNum", MIN (T_TAX1."SrcArrType") AS "ArrayType" 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 ORPC T_ODOC on (T_OTAX."SrcObjType" = 19 and T_OTAX."SrcObjAbs" = T_ODOC."DocEntry") INNER JOIN RPC3 T_DOC3 on (T_OTAX."SrcObjType" = 19 and T_TAX1."SrcArrType" = 14 and T_TAX1."SrcLineNum" = T_DOC3."LineNum" AND T_OTAX."SrcObjAbs" = T_DOC3."DocEntry") INNER JOIN OEXD T_OEXD on (T_DOC3."ExpnsCode" = T_OEXD."ExpnsCode") WHERE T_ODOC."GSTTranTyp" = 'GA' GROUP BY T_OTAX."SrcObjAbs", T_TAX1."SrcLineNum", T_TAX1."SrcGrpNum" ) T_LINE INNER JOIN ORPC T_ODOC on (T_LINE."DocEntry" = T_ODOC."DocEntry") INNER JOIN RPC12 T_DOC12 on (T_DOC12."DocEntry" = T_ODOC."DocEntry") WHERE T_ODOC.CANCELED = 'N' AND T_ODOC."DocDate" >= '${from}' AND T_ODOC."DocDate" <= '${to}' ) SELECT "InvoiceDate" AS invoice_date, "InvoiceNo" AS invoice_no, "BillNo" AS bill_no, "BillDate" AS bill_date, "SupplierName" AS supplier_name, "SupplierGSTIN" AS supplier_gstin, "StatePOS" AS state_pos, '' AS remark, CONVERT(NUMERIC(20,2), ABS(SUM("ItemTaxableValue"))) AS taxable_value, CONVERT(NUMERIC(20,2), CASE WHEN SUM("ItemTaxableValue") = 0 THEN 0 ELSE SUM("IGST_AMOUNT") / SUM("ItemTaxableValue") * 100 END ) AS igst_rate, CONVERT(NUMERIC(20,2), ABS(SUM("IGST_AMOUNT"))) AS igst_amount, CONVERT(NUMERIC(20,2), ABS(SUM("IGST_DEBIT_AMOUNT"))) AS igst_debit_amount, CONVERT(NUMERIC(20,2), CASE WHEN SUM("ItemTaxableValue") = 0 THEN 0 ELSE SUM("CGST_AMOUNT") / SUM("ItemTaxableValue") * 100 END ) AS cgst_rate, CONVERT(NUMERIC(20,2), ABS(SUM("CGST_AMOUNT"))) AS cgst_amount, CONVERT(NUMERIC(20,2), CASE WHEN SUM("ItemTaxableValue") = 0 THEN 0 ELSE SUM("SGST_AMOUNT") / SUM("ItemTaxableValue") * 100 END ) AS sgst_rate, CONVERT(NUMERIC(20,2), ABS(SUM("SGST_AMOUNT"))) AS sgst_amount, CONVERT(NUMERIC(20,2), ABS(SUM("LineTotalIncludeGST"))) AS line_total_inc_gst FROM CN_RAW GROUP BY "InvoiceDate", "InvoiceNo", "BillNo", "BillDate", "SupplierName", "SupplierGSTIN", "StatePOS" UNION ALL SELECT T0.[RefDate] AS invoice_date, T0.[Number] AS invoice_no, T0.[Ref2] AS bill_no, T0.[TaxDate] AS bill_date, S.[CardName] AS supplier_name, S.[GSTRegnNo] AS supplier_gstin, S.[State] AS state_pos, T0.Memo AS remark, CONVERT(NUMERIC(20,2), MAX(S.[Debit]) -(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))) ) AS taxable_value, CONVERT(NUMERIC(20,2), MAX(ISNULL(IGST.[Credit],0)) / CASE WHEN (MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))))=0 THEN 1 ELSE MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))) END * 100 ) AS igst_rate, CONVERT(NUMERIC(20,2), MAX(ISNULL(IGST.[Credit],0))) AS igst_amount, CONVERT(NUMERIC(20,2), ISNULL(MAX(IGST_Debit.[Debit]),0)) AS igst_debit_amount, CONVERT(NUMERIC(20,2), MAX(ISNULL(CGST.[Credit],0)) / CASE WHEN (MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))))=0 THEN 1 ELSE MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))) END * 100 ) AS cgst_rate, CONVERT(NUMERIC(20,2), MAX(ISNULL(CGST.[Credit],0))) AS cgst_amount, CONVERT(NUMERIC(20,2), MAX(ISNULL(SGST.[Credit],0)) / CASE WHEN (MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))))=0 THEN 1 ELSE MAX(S.[Debit])-(MAX(ISNULL(IGST.[Credit],0))+MAX(ISNULL(CGST.[Credit],0))+MAX(ISNULL(SGST.[Credit],0))) END * 100 ) AS sgst_rate, CONVERT(NUMERIC(20,2), MAX(ISNULL(SGST.[Credit],0))) AS sgst_amount, CONVERT(NUMERIC(20,2), ISNULL(MAX(S.[Debit]),0)) AS line_total_inc_gst FROM OJDT T0 INNER JOIN JDT1 T1 ON T0.[TransId] = T1.[TransId] INNER JOIN OACT T2 ON T1.[Account] = T2.[AcctCode] LEFT JOIN ( SELECT J.[TransId], O.[CardName], C.[GSTRegnNo], C.[State], J.[Debit] FROM JDT1 J INNER JOIN OCRD O ON O.[CardCode] = J.[ShortName] INNER JOIN CRD1 C ON C.[CardCode] = O.[CardCode] AND C.[AdresType] = 'S' WHERE J.[Line_ID] = 0 ) S ON S.[TransId] = T1.[TransId] LEFT JOIN (SELECT [Credit],[TransId] FROM JDT1 WHERE [ShortName]='1330037014') SGST ON SGST.[TransId] = T1.[TransId] LEFT JOIN (SELECT [Credit],[TransId] FROM JDT1 WHERE [ShortName]='1330037009') CGST ON CGST.[TransId] = T1.[TransId] LEFT JOIN (SELECT [Credit],[TransId] FROM JDT1 WHERE [ShortName]='1330037002') IGST ON IGST.[TransId] = T1.[TransId] LEFT JOIN (SELECT [Debit], [TransId] FROM JDT1 WHERE [ShortName]='1330037004') IGST_Debit ON IGST_Debit.[TransId] = T1.[TransId] WHERE T0.U_CNote = 'Y' AND T0.[RefDate] >= '${from}' AND T0.[RefDate] <= '${to}' AND LEN(S.[GSTRegnNo]) = 15 AND S.[GSTRegnNo] LIKE '[0-9][0-9]%' GROUP BY T0.[RefDate], T0.[Number], T0.[Ref2], T0.[TaxDate], S.[CardName], S.[GSTRegnNo], S.[State], T0.Memo ORDER BY invoice_date, invoice_no`; } router.get('/b2b', verifyToken, (req, res) => handleQuery(req, res, sqlPurchaseB2B)); router.get('/b2bur', verifyToken, (req, res) => handleQuery(req, res, sqlPurchaseB2BUR)); router.get('/hsn', verifyToken, (req, res) => handleQuery(req, res, sqlPurchaseHSN)); router.get('/cdn', verifyToken, (req, res) => handleQuery(req, res, sqlCDN)); // ── upload helpers ───────────────────────────────────────────────────────── // TCS: alphanumeric + hyphen + slash only, max 16 chars function sanitizeInvoiceNo(s) { return String(s || '').replace(/[^A-Za-z0-9\-\/]/g, '').slice(0, 16); } // Valid GSTIN: 2 digits + 5 uppercase + 4 digits + 1 uppercase + 1 digit/letter + Z + 1 alnum function isValidGstin(g) { return /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]Z[0-9A-Z]$/.test(String(g || '').trim().toUpperCase()); } // ── TCS helpers ──────────────────────────────────────────────────────────── 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)}`); return token; } function tcsJson(rows) { return JSON.stringify(rows, (_k, v) => { if (typeof v === 'number' && isFinite(v)) return parseFloat(v.toFixed(2)); return v; }); } // ── POST /upload-tcs ─────────────────────────────────────────────────────── // Uploads GSTR-2 purchase data via TCS JSON APIs: // B2B/IMPG/RCM → purchaseInvUpld (PurchaseInvoiceData807 / MFHSPDTN0000004) // HSN → purchaseSummaryUpldHsnSummary (GSTR2SummaryFormHSNSummary842 / MFHGSFTN0000721) router.post('/upload-tcs', verifyToken, async (req, res) => { try { const { period, b2b = [], b2bur = [], hsn = [], cdn = [] } = 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 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(); const results = {}; const errors = []; // invoice_dt: YYYY-MM-DD → DD-MM-YYYY const toTcsDate = d => { const m = String(d || '').match(/^(\d{4})-(\d{2})-(\d{2})/); return m ? `${m[3]}-${m[2]}-${m[1]}` : (d || ''); }; // eligibility_of_itc codes: IP / CP / IS / NO / 17(5) const ITC_CODE = { 'Capital Goods': 'CP', 'Inputs': 'IP', 'Input Services': 'IS', 'Ineligible': 'NO', 'All other ITC': 'IP', }; // invoice_type: TCS accepts R / SEWP / SEWOP / DE const INV_TYPE = { B2B: 'R', RCM: 'R', IMPG: 'R' }; // ── 1. B2B / IMPG / RCM ───────────────────────────────────────────── if (b2b.length) { let srNo = 0; const validB2b = b2b.filter(row => row.invoice_type === 'IMPG' || isValidGstin(row.cp_gstin)); const skippedB2b = b2b.filter(row => row.invoice_type !== 'IMPG' && !isValidGstin(row.cp_gstin)); console.log(`[TCS-GSTR2] B2B: ${b2b.length} total, ${skippedB2b.length} skipped (invalid GSTIN)`); if (skippedB2b.length) { console.log('[TCS-GSTR2] Skipped invoices (invalid cp_gstin):', skippedB2b.map(r => `${r.invoice_no} (gstin="${r.cp_gstin}")`).join(', ')); } // IMPG rows use a different field set than B2B/RCM (per TCS portal // exception report): no invoice_no/dt/val, invoice_type, cgst/sgst, // pos, rchrg, or CGST/SGST ITC — but require Bill of Entry details // (bill_entry_exprt_no/dt/val) and is_pur_frm_sez. const mappedRows = validB2b.map(row => { if (row.invoice_type === 'IMPG') { return { trans_type_code: 'IMPG', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, result_flag: 'A', cp_gstin: row.cp_gstin || '', cp_gstin_name: row.cp_gstin_name || '', taxable_val: r2(row.taxable_val), items_serial_no: parseInt(row.items_serial_no, 10) || 1, rate_of_tax: r2(row.rate_of_tax), igst_amnt: r2(row.igst_amnt), cess_amnt: r2(row.cess_amnt), eligibility_of_itc: ITC_CODE[row.eligibility_of_itc] || 'IP', tot_tax_avlbl_itc_igst: r2(row.tot_tax_avlbl_itc_igst), total_tax_itc_cess: r2(row.total_tax_itc_cess), is_pur_frm_sez: row.is_pur_frm_sez || 'N', bill_entry_exprt_no: row.bill_entry_no || '', bill_entry_exprt_dt: toTcsDate(row.bill_entry_dt), bill_entry_exprt_val: r2(row.bill_entry_val), or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', }; } return { trans_type_code: 'B2B', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, result_flag: 'A', invoice_type: INV_TYPE[row.invoice_type] || 'R', cp_gstin: row.cp_gstin || '', cp_gstin_name: row.cp_gstin_name || '', invoice_no: sanitizeInvoiceNo(row.invoice_no), invoice_dt: toTcsDate(row.invoice_dt), invoice_val: r2(row.invoice_val), taxable_val: r2(row.taxable_val), items_serial_no: parseInt(row.items_serial_no, 10) || 1, rate_of_tax: r2(row.rate_of_tax), igst_amnt: r2(row.igst_amnt), cgst_amnt: r2(row.cgst_amnt), sgst_amnt: r2(row.sgst_amnt), cess_amnt: r2(row.cess_amnt), pos: (String(row.pos||'').trim()||'6').padStart(2,'0'), rchrg: row.rchrg || 'N', eligibility_of_itc: ITC_CODE[row.eligibility_of_itc] || 'IP', tot_tax_avlbl_itc_igst: r2(row.tot_tax_avlbl_itc_igst), tot_tax_avlbl_itc_cgst: r2(row.tot_tax_avlbl_itc_cgst), tot_tax_avlbl_itc_sgst: r2(row.tot_tax_avlbl_itc_sgst), total_tax_itc_cess: r2(row.total_tax_itc_cess), or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', }; }); const mappingCd = 'MFHSPDTN0000004'; const templateCd = 'PurchaseInvoiceData807'; const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/purchaseInvUpld` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=PURCHASE`; console.log('[TCS-GSTR2] B2B URL:', url, '| rows:', mappedRows.length); // Use tcsJson to ensure all numbers are clean 2dp floats (no scientific notation) const r = await axios.post(url, tcsJson(mappedRows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log('[TCS-GSTR2] B2B 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/IMPG/RCM): ${fe}`); } } // ── 1b. B2BUR (purchases from unregistered suppliers, RCM) ────────── if (b2bur.length) { let srNo = 0; const mappedUrRows = b2bur.map(row => { const itcCode = ITC_CODE[row.eligibility_of_itc] || 'IP'; const itcZero = itcCode === 'NO'; const pos = String(row.pos || '06').trim().padStart(2, '0'); return { trans_type_code: 'B2BUR', fp: row.fp, ack_sr_no: ++srNo, self_gstin: gstin, result_flag: 'A', supply_type: (parseFloat(row.igst_amnt) > 0) ? 'INTER' : 'INTRA', cp_gstin: '', cp_gstin_name: row.cp_gstin_name || '', invoice_no: sanitizeInvoiceNo(row.invoice_no), invoice_dt: toTcsDate(row.invoice_dt), invoice_val: r2(row.invoice_val), taxable_val: r2(row.taxable_val), items_serial_no: parseInt(row.items_serial_no, 10) || 1, rate_of_tax: r2(row.rate_of_tax), igst_amnt: r2(row.igst_amnt), cgst_amnt: r2(row.cgst_amnt), sgst_amnt: r2(row.sgst_amnt), cess_amnt: r2(row.cess_amnt), pos, rchrg: row.rchrg || 'Y', eligibility_of_itc: itcCode, tot_tax_avlbl_itc_igst: itcZero ? 0 : r2(row.tot_tax_avlbl_itc_igst), tot_tax_avlbl_itc_cgst: itcZero ? 0 : r2(row.tot_tax_avlbl_itc_cgst), tot_tax_avlbl_itc_sgst: itcZero ? 0 : r2(row.tot_tax_avlbl_itc_sgst), total_tax_itc_cess: itcZero ? 0 : r2(row.total_tax_itc_cess), or_cp_gstin: '', orignal_invoice_no: '', orignal_invoice_dt: '', }; }); const mappingCd = 'MFHSPDTN0000004'; const templateCd = 'PurchaseInvoiceData807'; const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/purchaseInvUpld` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=PURCHASE`; console.log('[TCS-GSTR2] B2BUR URL:', url, '| rows:', mappedUrRows.length); const ru = await axios.post(url, tcsJson(mappedUrRows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log('[TCS-GSTR2] B2BUR status:', ru.status, JSON.stringify(ru.data)); results.b2bur = ru.data; if (ru.data?.statusCd === 400 || ru.data?.statusCd === '400') { const fe = Object.entries(ru.data).filter(([k]) => k !== 'statusCd').map(([k, v]) => `${k}: ${v}`).join(' | '); errors.push(`B2BUR: ${fe}`); } } // Sanitize desc: TCS only allows alphanumeric + space + - / (max 30 chars) const sanitizeDesc = d => String(d || 'Goods') .replace(/[^A-Za-z0-9 \-\/]/g, ' ') .replace(/\s+/g, ' ') .trim() .slice(0, 30) || 'Goods'; // ── 2. HSN summary ─────────────────────────────────────────────────── if (hsn.length) { // Aggregate by (hsn_sc, uqc) — rate_of_tax NOT a GSTR-2 HSN field const hsnAgg = {}; hsn.forEach(r => { const uqc = validUQC(r.uom); const key = `${r.hsn_sc || r.hsn_code || ''}_${uqc}`; if (!hsnAgg[key]) hsnAgg[key] = { fp: r.fp, hsn_sc: r.hsn_sc || r.hsn_code || '', item_desc: r.item_desc || 'Goods', uqc, qty: 0, val: 0, txval: 0, iamt: 0, camt: 0, samt: 0, csamt: 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; hsnAgg[key].csamt += parseFloat(r.csamt) || 0; }); // Build rows and ensure (desc, uqc) uniqueness by appending suffix on collision const seenDescUqc = {}; const hsnRows = Object.values(hsnAgg).map((r, i) => { let desc = sanitizeDesc(r.item_desc); const duKey = `${desc}|${r.uqc}`; if (seenDescUqc[duKey] !== undefined) { seenDescUqc[duKey]++; const suffix = String(seenDescUqc[duKey]); desc = desc.slice(0, 30 - suffix.length - 1) + '-' + suffix; } else { seenDescUqc[duKey] = 0; } return { fp: r.fp ? Number(r.fp) : null, ack_sr_no: i + 1, self_gstin: gstin, num: i + 1, hsn_sc: String(r.hsn_sc || ''), desc, uqc: r.uqc, qty: r2(r.qty), val: (r.val || 0).toFixed(2), txval: (r.txval || 0).toFixed(2), iamt: (r.iamt || 0).toFixed(2), camt: (r.camt || 0).toFixed(2), samt: (r.samt || 0).toFixed(2), csamt: '0.00', }; }); const mappingCd = 'MFHGSFTN0000721'; const templateCd = 'GSTR2SummaryFormHSNSummary842'; const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/purchaseSummaryUpldHsnSummary` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=PURCHASESUMMARY`; console.log('[TCS-GSTR2] HSN URL:', url, '| rows:', hsnRows.length); const r = await axios.post(url, tcsJson(hsnRows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log('[TCS-GSTR2] HSN status:', r.status, JSON.stringify(r.data)); results.hsn = 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(`HSN: ${fe}`); } } // ── 3. CDN (Credit/Debit Notes received) ──────────────────────────── if (cdn.length) { let srNo = 0; const validCdn = cdn.filter(row => isValidGstin(row.supplier_gstin)); console.log(`[TCS-GSTR2] CDN: ${cdn.length} total, ${cdn.length - validCdn.length} skipped (invalid GSTIN)`); const cdnRows = validCdn.map(row => { // Derive fp (MMYYYY) from invoice_date (YYYY-MM-DD) const dt = String(row.invoice_date || ''); const fp = dt.length >= 7 ? dt.slice(5, 7) + dt.slice(0, 4) : period; const abs = v => Math.abs(parseFloat(v) || 0); const VALID_GST_RATES = [0, 0.1, 0.25, 1, 1.5, 3, 5, 6, 7.5, 12, 18, 28]; const snapRate = r => VALID_GST_RATES.reduce((a, b) => Math.abs(b - r) < Math.abs(a - r) ? b : a); const rawRate = abs(row.igst_rate) || (abs(row.cgst_rate) + abs(row.sgst_rate)); const rate = snapRate(parseFloat(rawRate.toFixed(2))); const issueReason = String(row.remark || 'Post Sale Discount').replace(/[^\x20-\x7E]/g, '').trim().slice(0, 50) || 'Post Sale Discount'; return { trans_type_code: 'CDN', fp, ack_sr_no: ++srNo, self_gstin: gstin, system_cd: '', branch_cd: '', vertical_cd: '', result_flag: 'A', cp_gstin: String(row.supplier_gstin || '').trim().toUpperCase(), cp_gstin_name: row.supplier_name || '', type_of_note: 'C', db_cr_issue_reason: issueReason, is_pre_gst_regime: 'N', dbt_crdt_note_no: sanitizeInvoiceNo(row.bill_no || row.invoice_no), dbt_crdt_note_dt: toTcsDate(row.invoice_date), dbt_crdt_note_val: parseFloat(abs(row.line_total_inc_gst).toFixed(2)), invoice_no: '', invoice_dt: '', diff_value: parseFloat(abs(row.taxable_value).toFixed(2)), items_serial_no: 1, rate_of_tax: rate, diff_igst_amnt: parseFloat(abs(row.igst_amount).toFixed(2)), diff_cgst_amnt: parseFloat(abs(row.cgst_amount).toFixed(2)), diff_sgst_amnt: parseFloat(abs(row.sgst_amount).toFixed(2)), diff_cess_amnt: 0, eligibility_of_itc: 'IP', tot_tax_avlbl_itc_igst: parseFloat((abs(row.igst_amount) - abs(row.igst_debit_amount || 0)).toFixed(2)), tot_tax_avlbl_itc_cgst: parseFloat(abs(row.cgst_amount).toFixed(2)), tot_tax_avlbl_itc_sgst: parseFloat(abs(row.sgst_amount).toFixed(2)), total_tax_itc_cess: 0, or_cp_gstin: '', or_dbt_crdt_note_no: '', or_dbt_crdt_note_dt: '', }; }); const mappingCd = 'MFHSPDTN0000004'; const templateCd = 'PurchaseInvoiceData807'; const url = `${baseUrl}/Tax-Tool-Core/services/auth/invoiceUpload/purchaseInvUpld` + `/${clientCode}/${mappingCd}/${templateCd}/${gstin}/JSON/${period}?oprFlag=PURCHASE`; console.log('[TCS-GSTR2] CDN URL:', url, '| rows:', cdnRows.length); const rc = await axios.post(url, tcsJson(cdnRows), { headers: { 'Content-Type': 'application/json', gstin, clientCode, Authorization: `Bearer ${token}` }, httpsAgent, }); console.log('[TCS-GSTR2] CDN status:', rc.status, JSON.stringify(rc.data)); results.cdn = rc.data; if (rc.data?.statusCd === 400 || rc.data?.statusCd === '400') { const fe = Object.entries(rc.data).filter(([k]) => k !== 'statusCd').map(([k, v]) => `${k}: ${v}`).join(' | '); errors.push(`CDN: ${fe}`); } } const ackNos = [results.invoices?.ackNo, results.b2bur?.ackNo, results.hsn?.ackNo, results.cdn?.ackNo].filter(Boolean); if (errors.length) { res.json({ success: false, message: errors.join(' | '), data: results }); } else { res.json({ success: true, message: `Uploaded successfully. Ack: ${ackNos.join(', ')}`, data: results }); } } catch (err) { console.error('[TCS-GSTR2] Error:', err.response?.data || err.message); res.status(500).json({ success: false, message: err.response?.data ? JSON.stringify(err.response.data) : err.message }); } }); module.exports = router;