1224 lines
66 KiB
JavaScript
1224 lines
66 KiB
JavaScript
'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; |