From 9fd9c218df76a4e4cfcd3893b32c2ed87e7e0728 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Fri, 20 Feb 2026 20:39:36 +0530 Subject: [PATCH] inplemented the gst and non gst invoice generation flow and cost item table enhanced --- src/controllers/dealerClaim.controller.ts | 93 +++++- .../20260217-create-claim-invoice-items.ts | 25 ++ src/models/ClaimInvoiceItem.ts | 42 ++- src/routes/dealerClaim.routes.ts | 3 +- src/services/dealerClaim.service.ts | 53 ++- src/services/pwcIntegration.service.ts | 306 +++++++++--------- 6 files changed, 341 insertions(+), 181 deletions(-) diff --git a/src/controllers/dealerClaim.controller.ts b/src/controllers/dealerClaim.controller.ts index 8bd54d3..f82606d 100644 --- a/src/controllers/dealerClaim.controller.ts +++ b/src/controllers/dealerClaim.controller.ts @@ -11,6 +11,11 @@ import { sapIntegrationService } from '../services/sapIntegration.service'; import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; +import { WorkflowRequest } from '../models/WorkflowRequest'; +import { DealerClaimDetails } from '../models/DealerClaimDetails'; +import { ClaimInvoice } from '../models/ClaimInvoice'; +import { ClaimInvoiceItem } from '../models/ClaimInvoiceItem'; +import { ActivityType } from '../models/ActivityType'; export class DealerClaimController { private dealerClaimService = new DealerClaimService(); @@ -981,5 +986,91 @@ export class DealerClaimController { return ResponseHandler.error(res, error.message || 'Failed to test SAP budget block', 500); } } -} + /** + * Download Invoice CSV + * GET /api/v1/dealer-claims/:requestId/e-invoice/csv + */ + async downloadInvoiceCsv(req: Request, res: Response): Promise { + try { + const identifier = req.params.requestId; + // Use helper to find workflow + const workflow = await this.findWorkflowByIdentifier(identifier); + if (!workflow) { + return ResponseHandler.error(res, 'Workflow request not found', 404); + } + + const requestId = (workflow as any).requestId || (workflow as any).request_id; + const requestNumber = (workflow as any).requestNumber || (workflow as any).request_number; + + // Fetch related data + logger.info(`[DealerClaimController] Preparing CSV for requestId: ${requestId}`); + const [invoice, items, claimDetails, internalOrder] = await Promise.all([ + ClaimInvoice.findOne({ where: { requestId } }), + ClaimInvoiceItem.findAll({ where: { requestId }, order: [['slNo', 'ASC']] }), + DealerClaimDetails.findOne({ where: { requestId } }), + InternalOrder.findOne({ where: { requestId } }) + ]); + + logger.info(`[DealerClaimController] Found ${items.length} items to export for request ${requestNumber}`); + + let sapRefNo = ''; + if (claimDetails?.activityType) { + const activityType = await ActivityType.findOne({ where: { title: claimDetails.activityType } }); + sapRefNo = activityType?.sapRefNo || ''; + } + + // Construct CSV + const headers = [ + 'TRNS_UNIQ_NO', + 'CLAIM_NUMBER', + 'INV_NUMBER', + 'DEALER_CODE', + 'IO_NUMBER', + 'CLAIM_DOC_TYP', + 'CLAIM_DATE', + 'CLAIM_AMT', + 'GST_AMT', + 'GST_PERCENTAG' + ]; + + const rows = items.map(item => { + const trnsUniqNo = item.transactionCode || ''; + const claimNumber = requestNumber; + const invNumber = invoice?.invoiceNumber || ''; + const dealerCode = claimDetails?.dealerCode || ''; + const ioNumber = internalOrder?.ioNumber || ''; + const claimDocTyp = sapRefNo; + const claimDate = invoice?.createdAt ? new Date(invoice.createdAt).toISOString().split('T')[0] : ''; + const claimAmt = item.assAmt; + const totalTax = Number(item.igstAmt || 0) + Number(item.cgstAmt || 0) + Number(item.sgstAmt || 0) + Number(item.utgstAmt || 0); + const gstPercentag = item.gstRt; + + return [ + trnsUniqNo, + claimNumber, + invNumber, + dealerCode, + ioNumber, + claimDocTyp, + claimDate, + claimAmt, + totalTax.toFixed(2), + gstPercentag + ].join(','); + }); + + const csvContent = [headers.join(','), ...rows].join('\n'); + + res.setHeader('Content-Type', 'text/csv'); + res.setHeader('Content-Disposition', `attachment; filename="Invoice_${requestNumber}.csv"`); + + res.status(200).send(csvContent); + return; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + logger.error('[DealerClaimController] Error downloading invoice CSV:', error); + return ResponseHandler.error(res, 'Failed to download invoice CSV', 500, errorMessage); + } + } +} diff --git a/src/migrations/20260217-create-claim-invoice-items.ts b/src/migrations/20260217-create-claim-invoice-items.ts index e0221eb..cf0fe2d 100644 --- a/src/migrations/20260217-create-claim-invoice-items.ts +++ b/src/migrations/20260217-create-claim-invoice-items.ts @@ -70,6 +70,31 @@ module.exports = { type: DataTypes.DECIMAL(15, 2), allowNull: false, }, + utgst_amt: { + type: DataTypes.DECIMAL(15, 2), + allowNull: true, + defaultValue: 0, + }, + cgst_rate: { + type: DataTypes.DECIMAL(5, 2), + allowNull: true, + defaultValue: 0, + }, + sgst_rate: { + type: DataTypes.DECIMAL(5, 2), + allowNull: true, + defaultValue: 0, + }, + igst_rate: { + type: DataTypes.DECIMAL(5, 2), + allowNull: true, + defaultValue: 0, + }, + utgst_rate: { + type: DataTypes.DECIMAL(5, 2), + allowNull: true, + defaultValue: 0, + }, tot_item_val: { type: DataTypes.DECIMAL(15, 2), allowNull: false, diff --git a/src/models/ClaimInvoiceItem.ts b/src/models/ClaimInvoiceItem.ts index a2d3c18..d165033 100644 --- a/src/models/ClaimInvoiceItem.ts +++ b/src/models/ClaimInvoiceItem.ts @@ -18,14 +18,19 @@ interface ClaimInvoiceItemAttributes { igstAmt: number; cgstAmt: number; sgstAmt: number; + utgstAmt: number; totItemVal: number; isServc: string; + igstRate?: number; + cgstRate?: number; + sgstRate?: number; + utgstRate?: number; expenseIds?: string[]; createdAt: Date; updatedAt: Date; } -interface ClaimInvoiceItemCreationAttributes extends Optional { } +interface ClaimInvoiceItemCreationAttributes extends Optional { } class ClaimInvoiceItem extends Model implements ClaimInvoiceItemAttributes { public itemId!: string; @@ -43,8 +48,13 @@ class ClaimInvoiceItem extends Model 0 || Number(item.utgstAmt) > 0); - const igstAmt = isIGST ? (baseTotal * gstRate) / 100 : 0; - const cgstAmt = !isIGST ? (baseTotal * gstRate) / 200 : 0; - const sgstAmt = !isIGST ? (baseTotal * gstRate) / 200 : 0; - const gstAmt = igstAmt + cgstAmt + sgstAmt; + const finalIgstRate = isIGST ? (Number(item.igstRate) || gstRate) : 0; + const finalCgstRate = !isIGST ? (Number(item.cgstRate) || gstRate / 2) : 0; + const finalSgstRate = (!isIGST && !hasUtgst) ? (Number(item.sgstRate) || gstRate / 2) : 0; + const finalUtgstRate = (!isIGST && hasUtgst) ? (Number(item.utgstRate) || gstRate / 2) : 0; + + const finalIgstAmt = isIGST ? (Number(item.igstAmt) || (baseTotal * finalIgstRate) / 100) : 0; + const finalCgstAmt = !isIGST ? (Number(item.cgstAmt) || (baseTotal * finalCgstRate) / 100) : 0; + const finalSgstAmt = (!isIGST && !hasUtgst) ? (Number(item.sgstAmt) || (baseTotal * finalSgstRate) / 100) : 0; + const finalUtgstAmt = (!isIGST && hasUtgst) ? (Number(item.utgstAmt) || (baseTotal * finalUtgstRate) / 100) : 0; + const totalTaxAmt = finalIgstAmt + finalCgstAmt + finalSgstAmt + finalUtgstAmt; return { requestId, @@ -1501,18 +1520,18 @@ export class DealerClaimService { quantity, hsnCode: item.hsnCode || '', gstRate, - gstAmt: Number(item.gstAmt) || gstAmt, - cgstRate: Number(item.cgstRate) || cgstRate, - cgstAmt: Number(item.cgstAmt) || cgstAmt, - sgstRate: Number(item.sgstRate) || sgstRate, - sgstAmt: Number(item.sgstAmt) || sgstAmt, - igstRate: Number(item.igstRate) || igstRate, - igstAmt: Number(item.igstAmt) || igstAmt, - utgstRate: Number(item.utgstRate) || 0, - utgstAmt: Number(item.utgstAmt) || 0, + gstAmt: totalTaxAmt, + cgstRate: finalCgstRate, + cgstAmt: finalCgstAmt, + sgstRate: finalSgstRate, + sgstAmt: finalSgstAmt, + igstRate: finalIgstRate, + igstAmt: finalIgstAmt, + utgstRate: finalUtgstRate, + utgstAmt: finalUtgstAmt, cessRate: Number(item.cessRate) || 0, cessAmt: Number(item.cessAmt) || 0, - totalAmt: Number(item.totalAmt) || (baseTotal + gstAmt), + totalAmt: Number(item.totalAmt) || (baseTotal + totalTaxAmt), isService: !!item.isService, expenseDate: item.date instanceof Date ? item.date : (item.date ? new Date(item.date) : (completionData.activityCompletionDate || new Date())), }; diff --git a/src/services/pwcIntegration.service.ts b/src/services/pwcIntegration.service.ts index dc63463..2a7e814 100644 --- a/src/services/pwcIntegration.service.ts +++ b/src/services/pwcIntegration.service.ts @@ -10,6 +10,7 @@ import { DealerClaimDetails } from '../models/DealerClaimDetails'; import { DealerCompletionExpense } from '../models/DealerCompletionExpense'; import { DealerCompletionDetails } from '../models/DealerCompletionDetails'; import { ClaimInvoiceItem } from '../models/ClaimInvoiceItem'; +import { findDealerLocally, DealerInfo } from './dealer.service'; /** * PWC E-Invoice Integration Service @@ -91,10 +92,11 @@ export class PWCIntegrationService { if (!request) return { success: false, error: 'Request not found' }; const claimDetails = (request as any).claimDetails; - const dealer = await Dealer.findOne({ where: { dlrcode: claimDetails?.dealerCode } }); + const dealer: DealerInfo | null = await findDealerLocally(claimDetails?.dealerCode, claimDetails?.dealerEmail); const activity = await ActivityType.findOne({ where: { title: claimDetails?.activityType } }); if (!dealer || !activity) { + logger.warn(`[PWCIntegration] Dealer or Activity missing for request ${requestId}. Dealer lookup: ${claimDetails?.dealerCode} / ${claimDetails?.dealerEmail}`); return { success: false, error: 'Dealer or Activity details missing' }; } @@ -109,98 +111,8 @@ export class PWCIntegrationService { const formatQty = (val: number) => Number(val.toFixed(3)); const formatRate = (val: number) => Number(val.toFixed(2)); - // NEW LOGIC: Check for Non-GST Activity - const isNonGST = activity.taxationType === 'Non GST'; - - if (isNonGST) { - logger.info(`[PWC] Activity ${activity.title} is Non-GST. Skipping IRN generation and grossing up values.`); - - let totalNonGstVal = 0; - let nonGstSlNo = 1; - - // Clear existing items - await ClaimInvoiceItem.destroy({ where: { requestId } }); - - if (expenses && expenses.length > 0) { - for (const expense of expenses) { - // Gross Up Logic: Total Amount (Base + Tax) becomes the new Base Amount - // stored in 'totalAmt' or calculated from components - const grossAmount = Number(expense.totalAmt) || (Number(expense.amount) + Number(expense.gstAmt || 0) + Number(expense.cessAmt || 0)); - - // Transaction Code - const transactionCode = `${customInvoiceNumber}-${String(nonGstSlNo).padStart(2, '0')}`; - - await ClaimInvoiceItem.create({ - requestId, - invoiceNumber: customInvoiceNumber, - transactionCode: transactionCode, - slNo: nonGstSlNo, - description: expense.description || activity.title, - hsnCd: expense.hsnCode || activity.hsnCode || "998311", - qty: 1, - unit: "NOS", - unitPrice: formatAmount(grossAmount), - assAmt: formatAmount(grossAmount), // Taxable value is the gross amount - gstRt: 0, // 0% Tax - igstAmt: 0, - cgstAmt: 0, - sgstAmt: 0, - totItemVal: formatAmount(grossAmount), - isServc: "Y", // Non-GST reimbursements are generally treated as service/settlement - expenseIds: [expense.expenseId] - }); - - totalNonGstVal += grossAmount; - nonGstSlNo++; - } - } else { - // Fallback if no specific expenses (rare) - const fallbackAmount = finalAmount; - const transactionCode = `${customInvoiceNumber}-${String(nonGstSlNo).padStart(2, '0')}`; - - await ClaimInvoiceItem.create({ - requestId, - invoiceNumber: customInvoiceNumber, - transactionCode: transactionCode, - slNo: nonGstSlNo, - description: activity.title, - hsnCd: activity.hsnCode || "998311", - qty: 1, - unit: "NOS", - unitPrice: formatAmount(fallbackAmount), - assAmt: formatAmount(fallbackAmount), - gstRt: 0, - igstAmt: 0, - cgstAmt: 0, - sgstAmt: 0, - totItemVal: formatAmount(fallbackAmount), - isServc: "Y", - expenseIds: [] - }); - totalNonGstVal += fallbackAmount; - } - - // Return internal success response (No IRN) - return { - success: true, - irn: undefined, - ackNo: undefined, - ackDate: new Date(), - signedInvoice: undefined, - qrCode: undefined, // No QR for Non-GST - qrImage: undefined, - totalIgstAmt: 0, - totalCgstAmt: 0, - totalSgstAmt: 0, - totalAssAmt: formatAmount(totalNonGstVal) - }; - } - - // --- END Non-GST Logic --- - - // Existing GST Logic starts here... // Extract State Code from Dealer GSTIN - let dealerGst = (dealer as any).gst; + let dealerGst = dealer?.gstin; // HOTFIX: For PWC QA Environment, use a known valid GSTIN if dealer has the invalid test one // The test GSTIN 29AAACE3882D1ZZ is not registered in PWC QA Master, causing Error 701 @@ -220,14 +132,13 @@ export class PWCIntegrationService { // Try to extract from GSTIN (first 2 chars) if (dealerGst && dealerGst.length >= 2 && !isNaN(Number(dealerGst.substring(0, 2)))) { dealerStateCode = dealerGst.substring(0, 2); - } else if ((dealer as any).stateCode) { - dealerStateCode = (dealer as any).stateCode; + } else if (dealer?.state) { + // Approximate state code from state name or use 33 as default if it's RE state + dealerStateCode = dealer.state.toLowerCase().includes('tamil') ? "33" : "24"; } - // Fetch expenses if available (Moved to top) - // const expenses = await DealerCompletionExpense.findAll({ where: { requestId } }); - let itemList: any[] = []; + let claimInvoiceItemsToCreate: any[] = []; let totalAssAmt = 0; let totalIgstAmt = 0; let totalCgstAmt = 0; @@ -235,6 +146,7 @@ export class PWCIntegrationService { let totalInvVal = 0; const isIGST = dealerStateCode !== "33"; // If dealer state != Buyer state (33), it's IGST + const isNonGSTActivity = activity.taxationType === 'Non GST'; if (expenses && expenses.length > 0) { // Group expenses by HSN/SAC and GST Rate @@ -252,11 +164,6 @@ export class PWCIntegrationService { const amount = Number(expense.amount) || 0; const baseAmt = amount * qty; - const igst = isIGST ? (baseAmt * (gstRate / 100)) : 0; - const cgst = !isIGST ? (baseAmt * (gstRate / 200)) : 0; - const sgst = !isIGST ? (baseAmt * (gstRate / 200)) : 0; - const itemTotal = baseAmt + igst + cgst + sgst; - if (!groupedExpenses[groupKey]) { groupedExpenses[groupKey] = { hsnCd, @@ -266,9 +173,11 @@ export class PWCIntegrationService { igst: 0, cgst: 0, sgst: 0, + utgst: 0, itemTotal: 0, description: expense.description || activity.title, - expenseIds: [expense.expenseId] + expenseIds: [expense.expenseId], + hasUtgst: Number(expense.utgstRate || 0) > 0 || Number(expense.utgstAmt || 0) > 0 }; } else { const nextDesc = expense.description || activity.title; @@ -280,68 +189,98 @@ export class PWCIntegrationService { : updatedDesc; } groupedExpenses[groupKey].expenseIds.push(expense.expenseId); + if (Number(expense.utgstRate || 0) > 0 || Number(expense.utgstAmt || 0) > 0) { + groupedExpenses[groupKey].hasUtgst = true; + } } groupedExpenses[groupKey].baseAmt += baseAmt; - groupedExpenses[groupKey].igst += igst; - groupedExpenses[groupKey].cgst += cgst; - groupedExpenses[groupKey].sgst += sgst; - groupedExpenses[groupKey].itemTotal += itemTotal; }); - // Persistence: Delete old items and insert new ones for this request - await ClaimInvoiceItem.destroy({ where: { requestId } }); - itemList = Object.values(groupedExpenses).map((group: any, index: number) => { + // STRICT CALCULATION: Recalculate tax based on grouped baseAmt to satisfy PWC validation (Tax = Base * Rate) + const groupGstRate = Number(group.gstRate || 0); + const groupBaseAmt = Number(group.baseAmt || 0); + + let calcIgst = 0, calcCgst = 0, calcSgst = 0, calcUtgst = 0; + let calcIgstRate = 0, calcCgstRate = 0, calcSgstRate = 0, calcUtgstRate = 0; + + if (isIGST) { + calcIgst = Number((groupBaseAmt * groupGstRate / 100).toFixed(2)); + calcIgstRate = groupGstRate; + } else { + const halfRate = groupGstRate / 2; + const halfTax = Number((groupBaseAmt * halfRate / 100).toFixed(2)); + calcCgst = halfTax; + calcCgstRate = halfRate; + // Use UTGST if detected in any expense of this group, otherwise SGST + if (group.hasUtgst) { + calcUtgst = halfTax; + calcUtgstRate = halfRate; + } else { + calcSgst = halfTax; + calcSgstRate = halfRate; + } + } + + const calcTotalInvVal = Number((groupBaseAmt + calcIgst + calcCgst + calcSgst + calcUtgst).toFixed(2)); + // Accumulate overall totals - totalAssAmt += group.baseAmt; - totalIgstAmt += group.igst; - totalCgstAmt += group.cgst; - totalSgstAmt += group.sgst; - totalInvVal += group.itemTotal; + totalAssAmt += groupBaseAmt; + totalIgstAmt += calcIgst; + totalCgstAmt += calcCgst; + totalSgstAmt += calcSgst + calcUtgst; // Sum of SGST and UTGST for summary + totalInvVal += calcTotalInvVal; const slNo = index + 1; - - // Save to DB const transactionCode = `${customInvoiceNumber}-${String(slNo).padStart(2, '0')}`; - ClaimInvoiceItem.create({ + claimInvoiceItemsToCreate.push({ requestId, invoiceNumber: customInvoiceNumber, transactionCode: transactionCode, slNo: slNo, description: group.description, - hsnCd: group.hsnCd, // Use actual HSN from group + hsnCd: group.hsnCd, qty: 1, unit: "NOS", - unitPrice: formatAmount(group.baseAmt), - assAmt: formatAmount(group.baseAmt), - gstRt: formatRate(Number(group.gstRate)), - igstAmt: formatAmount(group.igst), - cgstAmt: formatAmount(group.cgst), - sgstAmt: formatAmount(group.sgst), - totItemVal: formatAmount(group.itemTotal), - isServc: group.isService ? "Y" : "N", // Use actual isService from group + unitPrice: formatAmount(groupBaseAmt), + assAmt: formatAmount(groupBaseAmt), + gstRt: formatRate(groupGstRate), + igstAmt: formatAmount(calcIgst), + cgstAmt: formatAmount(calcCgst), + sgstAmt: formatAmount(calcSgst), + utgstAmt: formatAmount(calcUtgst), + igstRate: formatRate(calcIgstRate), + cgstRate: formatRate(calcCgstRate), + sgstRate: formatRate(calcSgstRate), + utgstRate: formatRate(calcUtgstRate), + totItemVal: formatAmount(calcTotalInvVal), + isServc: group.isService ? "Y" : "N", expenseIds: group.expenseIds - }).catch((err: any) => logger.error(`[PWCIntegrationService] Error saving ClaimInvoiceItem:`, err)); + }); + + // PWC Payload Item format + const hsnForPwc = isNonGSTActivity ? group.hsnCd : "87141090"; // Force valid HSN for PWC Payload if GST + const isServcForPwc = isNonGSTActivity ? (group.isService ? "Y" : "N") : "N"; // 87141090 is Goods return { SlNo: String(slNo), PrdNm: group.description, PrdDesc: group.description, - HsnCd: "87141090", // Known working HSN from old invoice - IsServc: "N", // 87141090 is Goods + HsnCd: hsnForPwc, + IsServc: isServcForPwc, Qty: formatQty(1), Unit: "NOS", - UnitPrice: formatAmount(group.baseAmt), - TotAmt: formatAmount(group.baseAmt), + UnitPrice: formatAmount(groupBaseAmt), + TotAmt: formatAmount(groupBaseAmt), Discount: 0, - PreTaxVal: formatAmount(group.baseAmt), - AssAmt: formatAmount(group.baseAmt), - GstRt: formatRate(Number(group.gstRate)), - IgstAmt: formatAmount(group.igst), - CgstAmt: formatAmount(group.cgst), - SgstAmt: formatAmount(group.sgst), + PreTaxVal: formatAmount(groupBaseAmt), + AssAmt: formatAmount(groupBaseAmt), + GstRt: formatRate(groupGstRate), + IgstAmt: formatAmount(calcIgst), + CgstAmt: formatAmount(calcCgst), + SgstAmt: formatAmount(calcSgst + calcUtgst), CesRt: 0, CesAmt: 0, CesNonAdValAmt: 0, @@ -349,17 +288,22 @@ export class PWCIntegrationService { StateCesAmt: 0, StateCesNonAdValAmt: 0, OthChrg: 0, - TotItemVal: formatAmount(group.itemTotal) + TotItemVal: formatAmount(calcTotalInvVal) }; }); } else { // Fallback to single line item if no expenses found - const gstRate = Number(activity.gstRate || 18); + const gstRate = isNonGSTActivity ? 0 : Number(activity.gstRate || 18); const assAmt = finalAmount; - const igstAmt = isIGST ? (finalAmount * (gstRate / 100)) : 0; - const cgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0; - const sgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0; - const totalTax = igstAmt + cgstAmt + sgstAmt; + let igstAmt = 0, cgstAmt = 0, sgstAmt = 0; + + if (!isNonGSTActivity) { + igstAmt = isIGST ? (finalAmount * (gstRate / 100)) : 0; + cgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0; + sgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0; + } + const utgstAmt = 0; // Fallback assumes SGST for simplicity unless we detect state + const totalTax = igstAmt + cgstAmt + sgstAmt + utgstAmt; const totalItemVal = finalAmount + totalTax; totalAssAmt = assAmt; @@ -370,14 +314,11 @@ export class PWCIntegrationService { const slNo = 1; - // Persistence: Delete old items and insert single fallback item - await ClaimInvoiceItem.destroy({ where: { requestId } }); - const fallbackHsn = activity.hsnCode || activity.sacCode || "998311"; const fallbackIsService = this.isServiceHSN(fallbackHsn) === "Y"; const transactionCode = `${customInvoiceNumber}-${String(slNo).padStart(2, '0')}`; - ClaimInvoiceItem.create({ + claimInvoiceItemsToCreate.push({ requestId, invoiceNumber: customInvoiceNumber, transactionCode: transactionCode, @@ -392,19 +333,22 @@ export class PWCIntegrationService { igstAmt: formatAmount(igstAmt), cgstAmt: formatAmount(cgstAmt), sgstAmt: formatAmount(sgstAmt), + utgstAmt: 0, totItemVal: formatAmount(totalItemVal), isServc: fallbackIsService ? "Y" : "N", expenseIds: [] - }).catch((err: any) => logger.error(`[PWCIntegrationService] Error saving ClaimInvoiceItem (fallback):`, err)); + }); - const hsnCd = "87141090"; // Force valid HSN for PWC Payload + // PWC Payload Item format + const hsnForPwc = isNonGSTActivity ? fallbackHsn : "87141090"; // Force valid HSN for PWC Payload if GST + const isServcForPwc = isNonGSTActivity ? (fallbackIsService ? "Y" : "N") : "N"; // 87141090 is Goods itemList = [{ SlNo: String(slNo), PrdNm: activity.title, PrdDesc: activity.title, - HsnCd: hsnCd, - IsServc: "N", + HsnCd: hsnForPwc, + IsServc: isServcForPwc, Qty: formatQty(1), Unit: "NOS", UnitPrice: formatAmount(assAmt), @@ -427,6 +371,33 @@ export class PWCIntegrationService { }]; } + // NEW LOGIC: Check for Non-GST Activity + if (isNonGSTActivity) { + logger.info(`[PWC] Activity ${activity.title} is Non-GST. Skipping IRN generation.`); + + // Persistence for Non-GST (Awaited) + await ClaimInvoiceItem.destroy({ where: { requestId } }); + if (claimInvoiceItemsToCreate.length > 0) { + await ClaimInvoiceItem.bulkCreate(claimInvoiceItemsToCreate); + logger.info(`[PWCIntegration] Persisted ${claimInvoiceItemsToCreate.length} line items for Non-GST request ${requestId}`); + } + + // Return internal success response (No IRN) + return { + success: true, + irn: undefined, + ackNo: undefined, + ackDate: new Date(), + signedInvoice: undefined, + qrCode: undefined, // No QR for Non-GST + qrImage: undefined, + totalIgstAmt: formatAmount(totalIgstAmt), + totalCgstAmt: formatAmount(totalCgstAmt), + totalSgstAmt: formatAmount(totalSgstAmt), + totalAssAmt: formatAmount(totalAssAmt) + }; + } + // Construct PWC Payload - Aligned with sample format provided by user const payload = [ { @@ -456,16 +427,16 @@ export class PWCIntegrationService { }, SellerDtls: { Gstin: dealerGst, - LglNm: (dealer as any).dealership || 'Dealer', - TrdNm: (dealer as any).dealership || 'Dealer', - Addr1: (dealer as any).showroomAddress || "Address Line 1", - Loc: (dealer as any).location || "Location", - Pin: (dealerGst === validQaGst && String((dealer as any).showroomPincode || '').substring(0, 1) !== '3') + LglNm: dealer?.dealerName || 'Dealer', + TrdNm: dealer?.dealerName || 'Dealer', + Addr1: dealer?.city || "Address Line 1", + Loc: dealer?.city || "Location", + Pin: (dealerGst === validQaGst) ? 380001 - : Number((dealer as any).showroomPincode) || 600001, + : 600001, Stcd: dealerStateCode, - Ph: (dealer as any).dpContactNumber || "9998887776", - Em: (dealer as any).dealerPrincipalEmailId || "Supplier@inv.com" + Ph: dealer?.phone || "9998887776", + Em: dealer?.email || "Supplier@inv.com" }, BuyerDtls: { Gstin: "33AAACE3882D1ZZ", // Royal Enfield GST (Tamil Nadu) @@ -482,7 +453,7 @@ export class PWCIntegrationService { AssVal: formatAmount(totalAssAmt), IgstVal: formatAmount(totalIgstAmt), CgstVal: formatAmount(totalCgstAmt), - SgstVal: formatAmount(totalSgstAmt), + SgstVal: formatAmount(totalSgstAmt), // Sum of SGST and UTGST for summary TotInvVal: formatAmount(totalInvVal) } } @@ -544,6 +515,19 @@ export class PWCIntegrationService { return { success: false, error: errorMessage }; } + // Persistence for GST (Awaited) - Only if IRN generation was successful + await ClaimInvoiceItem.destroy({ where: { requestId } }); + if (claimInvoiceItemsToCreate.length > 0) { + await ClaimInvoiceItem.bulkCreate(claimInvoiceItemsToCreate); + logger.info(`[PWCIntegration] Persisted ${claimInvoiceItemsToCreate.length} line items for GST request ${requestId}`); + } + + // Update invoice with dealer GSTIN if available + if (dealer?.gstin) { + await ClaimInvoice.update({ consignorGsin: dealer.gstin }, { where: { requestId } }) + .catch(err => logger.error(`[PWCIntegration] Error updating invoice consignorGsin:`, err)); + } + return { success: true, irn,