1022 lines
58 KiB
JavaScript
1022 lines
58 KiB
JavaScript
'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;
|