CSV file upload to WFM folder implemeted

This commit is contained in:
laxmanhalaki 2026-03-09 15:45:04 +05:30
parent f679317d4a
commit b6ca3c7f9f
12 changed files with 330 additions and 19 deletions

View File

@ -1 +1 @@
import{a as s}from"./index-yOqi1S1C.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-CX5oLBI_.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
import{a as s}from"./index-CULgQ-8S.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-CX5oLBI_.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@
<!-- Preload essential fonts and icons -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<script type="module" crossorigin src="/assets/index-yOqi1S1C.js"></script>
<script type="module" crossorigin src="/assets/index-CULgQ-8S.js"></script>
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-BTBPSQfW.js">

View File

@ -1082,4 +1082,31 @@ export class DealerClaimController {
return ResponseHandler.error(res, 'Failed to download invoice CSV', 500, errorMessage);
}
}
/**
* Re-trigger WFM CSV push (Step 7)
* POST /api/v1/dealer-claims/:requestId/wfm/retrigger
*/
async retriggerWFMPush(req: Request, res: Response): Promise<void> {
try {
const { requestId: identifier } = req.params;
const workflow = await this.findWorkflowByIdentifier(identifier);
if (!workflow) {
return ResponseHandler.error(res, 'Workflow request not found', 404);
}
const requestId = (workflow as any).id || (workflow as any).requestId;
await this.dealerClaimService.pushWFMCSV(requestId);
return ResponseHandler.success(res, {
message: 'WFM CSV push re-triggered successfully'
}, 'WFM push re-triggered');
} catch (error: any) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('[DealerClaimController] Error re-triggering WFM push:', error);
return ResponseHandler.error(res, 'Failed to re-trigger WFM push', 500, errorMessage);
}
}
}

View File

@ -0,0 +1,50 @@
import { QueryInterface, DataTypes } from 'sequelize';
/**
* Helper function to check if a column exists in a table
*/
async function columnExists(
queryInterface: QueryInterface,
tableName: string,
columnName: string
): Promise<boolean> {
try {
const tableDescription = await queryInterface.describeTable(tableName);
return columnName in tableDescription;
} catch (error) {
return false;
}
}
export async function up(queryInterface: QueryInterface): Promise<void> {
const tableName = 'claim_invoices';
// Add wfm_push_status
if (!(await columnExists(queryInterface, tableName, 'wfm_push_status'))) {
await queryInterface.addColumn(tableName, 'wfm_push_status', {
type: DataTypes.STRING(20),
allowNull: true,
defaultValue: 'PENDING'
});
}
// Add wfm_push_error
if (!(await columnExists(queryInterface, tableName, 'wfm_push_error'))) {
await queryInterface.addColumn(tableName, 'wfm_push_error', {
type: DataTypes.TEXT,
allowNull: true
});
}
}
export async function down(queryInterface: QueryInterface): Promise<void> {
const tableName = 'claim_invoices';
if (await columnExists(queryInterface, tableName, 'wfm_push_status')) {
await queryInterface.removeColumn(tableName, 'wfm_push_status');
}
if (await columnExists(queryInterface, tableName, 'wfm_push_error')) {
await queryInterface.removeColumn(tableName, 'wfm_push_error');
}
}

View File

@ -39,13 +39,15 @@ interface ClaimInvoiceAttributes {
pwcResponse?: any;
irpResponse?: any;
errorMessage?: string;
wfmPushStatus?: 'PENDING' | 'SUCCESS' | 'FAILED';
wfmPushError?: string | null;
generatedAt?: Date;
description?: string;
createdAt: Date;
updatedAt: Date;
}
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'dmsNumber' | 'invoiceDate' | 'irn' | 'ackNo' | 'ackDate' | 'signedInvoice' | 'signedInvoiceUrl' | 'dealerClaimNumber' | 'dealerClaimDate' | 'billingNo' | 'billingDate' | 'taxableValue' | 'cgstTotal' | 'sgstTotal' | 'igstTotal' | 'utgstTotal' | 'cessTotal' | 'tcsAmt' | 'roundOffAmt' | 'placeOfSupply' | 'totalValueInWords' | 'taxValueInWords' | 'creditNature' | 'consignorGsin' | 'gstinDate' | 'filePath' | 'qrCode' | 'qrImage' | 'pwcResponse' | 'irpResponse' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'dmsNumber' | 'invoiceDate' | 'irn' | 'ackNo' | 'ackDate' | 'signedInvoice' | 'signedInvoiceUrl' | 'dealerClaimNumber' | 'dealerClaimDate' | 'billingNo' | 'billingDate' | 'taxableValue' | 'cgstTotal' | 'sgstTotal' | 'igstTotal' | 'utgstTotal' | 'cessTotal' | 'tcsAmt' | 'roundOffAmt' | 'placeOfSupply' | 'totalValueInWords' | 'taxValueInWords' | 'creditNature' | 'consignorGsin' | 'gstinDate' | 'filePath' | 'qrCode' | 'qrImage' | 'pwcResponse' | 'irpResponse' | 'errorMessage' | 'wfmPushStatus' | 'wfmPushError' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
public invoiceId!: string;
@ -84,6 +86,8 @@ class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAtt
public pwcResponse?: any;
public irpResponse?: any;
public errorMessage?: string;
public wfmPushStatus?: 'PENDING' | 'SUCCESS' | 'FAILED';
public wfmPushError?: string | null;
public generatedAt?: Date;
public description?: string;
public createdAt!: Date;
@ -280,6 +284,17 @@ ClaimInvoice.init(
allowNull: true,
field: 'error_message',
},
wfmPushStatus: {
type: DataTypes.STRING(20),
allowNull: true,
defaultValue: 'PENDING',
field: 'wfm_push_status'
},
wfmPushError: {
type: DataTypes.TEXT,
allowNull: true,
field: 'wfm_push_error'
},
generatedAt: {
type: DataTypes.DATE,
allowNull: true,

View File

@ -98,6 +98,7 @@ router.put('/:requestId/io', authenticateToken, sapLimiter, validateParams(reque
*/
router.put('/:requestId/e-invoice', authenticateToken, sapLimiter, validateParams(requestIdParamsSchema), validateBody(updateEInvoiceSchema), asyncHandler(dealerClaimController.updateEInvoice.bind(dealerClaimController)));
router.get('/:requestId/e-invoice/pdf', authenticateToken, validateParams(requestIdParamsSchema), asyncHandler(dealerClaimController.downloadInvoicePdf.bind(dealerClaimController)));
router.post('/:requestId/wfm/retrigger', authenticateToken, validateParams(requestIdParamsSchema), asyncHandler(dealerClaimController.retriggerWFMPush.bind(dealerClaimController)));
/**
* @route PUT /api/v1/dealer-claims/:requestId/credit-note

View File

@ -162,6 +162,7 @@ async function runMigrations(): Promise<void> {
const m47 = require('../migrations/20260216-create-api-tokens');
const m48 = require('../migrations/20260216-add-qty-hsn-to-expenses'); // Added new migration
const m49 = require('../migrations/20260302-refine-dealer-claim-schema');
const m50 = require('../migrations/20260309-add-wfm-push-fields');
const migrations = [
{ name: '2025103000-create-users', module: m0 },
@ -216,6 +217,7 @@ async function runMigrations(): Promise<void> {
{ name: '20260216-create-api-tokens', module: m47 },
{ name: '20260216-add-qty-hsn-to-expenses', module: m48 }, // Added to array
{ name: '20260302-refine-dealer-claim-schema', module: m49 },
{ name: '20260309-add-wfm-push-fields', module: m50 },
];
// Dynamically import sequelize after secrets are loaded

View File

@ -52,6 +52,7 @@ import * as m46 from '../migrations/20260216-add-qty-hsn-to-expenses';
import * as m47 from '../migrations/20260217-add-is-service-to-expenses';
import * as m48 from '../migrations/20260217-create-claim-invoice-items';
import * as m49 from '../migrations/20260302-refine-dealer-claim-schema';
import * as m50 from '../migrations/20260309-add-wfm-push-fields';
interface Migration {
name: string;
@ -65,7 +66,8 @@ const migrations: Migration[] = [
{ name: '20260216-add-qty-hsn-to-expenses', module: m46 },
{ name: '20260217-add-is-service-to-expenses', module: m47 },
{ name: '20260217-create-claim-invoice-items', module: m48 },
{ name: '20260302-refine-dealer-claim-schema', module: m49 }
{ name: '20260302-refine-dealer-claim-schema', module: m49 },
{ name: '20260309-add-wfm-push-fields', module: m50 }
];
/**

View File

@ -0,0 +1,48 @@
import { wfmFileService } from '../services/wfmFile.service';
import logger from '../utils/logger';
import dotenv from 'dotenv';
import path from 'path';
// Load environment variables
dotenv.config();
async function testOfficialCSVGeneration() {
try {
console.log('Starting WFM Official CSV Generation Test...');
const officialData = [{
TRNS_UNIQ_NO: '1342774290',
CLAIM_NUMBER: 'CLI000012833733',
INV_NUMBER: 'INV007593742231',
DEALER_CODE: '6059',
IO_NUMBER: '439887',
CLAIM_DOC_TYP: 'ZMBS',
CLAIM_DATE: '20190627',
CLAIM_AMT: 3931.539,
GST_AMT: '185.00',
GST_PERCENTAG: 18
}];
const fileName = `OFFICIAL_TEST_${Date.now()}.csv`;
const filePath = await wfmFileService.generateIncomingClaimCSV(officialData, fileName);
console.log(`✅ Success! Official CSV generated at: ${filePath}`);
// Verify file existence and content
const fs = require('fs');
if (fs.existsSync(filePath)) {
console.log('File existence verified on disk.');
const content = fs.readFileSync(filePath, 'utf8');
console.log('--- CSV Content ---');
console.log(content);
console.log('-------------------');
} else {
console.error('❌ Error: File not found on disk!');
}
} catch (error) {
console.error('❌ Test failed:', error);
}
}
testOfficialCSVGeneration();

View File

@ -10,6 +10,7 @@ import { InternalOrder, IOStatus } from '../models/InternalOrder';
import { ClaimBudgetTracking, BudgetStatus } from '../models/ClaimBudgetTracking';
import { ClaimInvoice } from '../models/ClaimInvoice';
import { ClaimCreditNote } from '../models/ClaimCreditNote';
import { ClaimInvoiceItem } from '../models/ClaimInvoiceItem';
import { DealerCompletionExpense } from '../models/DealerCompletionExpense';
import { ApprovalLevel } from '../models/ApprovalLevel';
import { Participant } from '../models/Participant';
@ -24,6 +25,7 @@ import { generateRequestNumber } from '../utils/helpers';
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
import { sapIntegrationService } from './sapIntegration.service';
import { pwcIntegrationService } from './pwcIntegration.service';
import { wfmFileService } from './wfmFile.service';
import { findDealerLocally } from './dealer.service';
import { notificationService } from './notification.service';
import { activityService } from './activity.service';
@ -2080,6 +2082,11 @@ export class DealerClaimService {
logger.info(`[DealerClaimService] E-Invoice details manually updated for request: ${requestId}`);
}
// Generate CSV for WFM system (INCOMING\WFM_MAIN\DLR_INC_CLAIMS)
await this.pushWFMCSV(requestId).catch((err: Error) => {
logger.error('[DealerClaimService] Initial WFM push failed:', err);
});
// Generate PDF Invoice
try {
const { pdfService } = require('./pdf.service');
@ -3563,5 +3570,81 @@ export class DealerClaimService {
return plain;
});
}
/**
* Push CSV to WFM folder and track status
* This is used by both auto-trigger and manual re-trigger
*/
async pushWFMCSV(requestId: string): Promise<void> {
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
if (!invoice) {
throw new Error('Invoice not found');
}
try {
const [invoiceItems, claimDetails, internalOrder] = await Promise.all([
ClaimInvoiceItem.findAll({ where: { requestId } }),
DealerClaimDetails.findOne({ where: { requestId } }),
InternalOrder.findOne({ where: { requestId } })
]);
if (!claimDetails) {
throw new Error('Dealer claim details not found');
}
const requestNumber = (await WorkflowRequest.findByPk(requestId))?.requestNumber || 'UNKNOWN';
if (invoiceItems.length > 0) {
let sapRefNo = '';
if (claimDetails.activityType) {
const activity = await ActivityType.findOne({ where: { title: claimDetails.activityType } });
sapRefNo = activity?.sapRefNo || '';
}
const formatDate = (date: any) => {
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
};
const csvData = invoiceItems.map((item: any) => {
const totalTax = Number(item.igstAmt || 0) + Number(item.cgstAmt || 0) + Number(item.sgstAmt || 0) + Number(item.utgstAmt || 0);
return {
TRNS_UNIQ_NO: item.transactionCode || '',
CLAIM_NUMBER: requestNumber,
INV_NUMBER: invoice.invoiceNumber || '',
DEALER_CODE: claimDetails.dealerCode,
IO_NUMBER: internalOrder?.ioNumber || '',
CLAIM_DOC_TYP: sapRefNo,
CLAIM_DATE: formatDate(invoice.invoiceDate || new Date()),
CLAIM_AMT: item.assAmt,
GST_AMT: totalTax.toFixed(2),
GST_PERCENTAG: item.gstRt
};
});
await wfmFileService.generateIncomingClaimCSV(csvData, `CN_${claimDetails.dealerCode}_${requestNumber}.csv`);
await invoice.update({
wfmPushStatus: 'SUCCESS',
wfmPushError: null
});
logger.info(`[DealerClaimService] WFM CSV successfully pushed for request ${requestNumber}`);
} else {
logger.warn(`[DealerClaimService] No invoice items found for WFM push: ${requestNumber}`);
}
} catch (error: any) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error pushing to WFM';
await invoice.update({
wfmPushStatus: 'FAILED',
wfmPushError: errorMessage
});
throw error;
}
}
}

View File

@ -0,0 +1,83 @@
import fs from 'fs';
import path from 'path';
import logger from '../utils/logger';
/**
* WFM File Service
* Handles generation and storage of CSV files in the WFM folder structure
*/
export class WFMFileService {
private basePath: string;
private incomingClaimsPath: string;
private outgoingClaimsPath: string;
constructor() {
this.basePath = process.env.WFM_BASE_PATH || 'C:\\WFM';
this.incomingClaimsPath = process.env.WFM_INCOMING_CLAIMS_PATH || 'WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS';
this.outgoingClaimsPath = process.env.WFM_OUTGOING_CLAIMS_PATH || 'WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS';
}
/**
* Ensure the target directory exists
*/
private ensureDirectoryExists(dirPath: string): void {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
logger.info(`[WFMFileService] Created directory: ${dirPath}`);
}
}
/**
* Generate a CSV file for a credit note/claim and store it in the INCOMING folder
* @param data The data to be written to the CSV
* @param fileName The name of the file (e.g., CLAIM_12345.csv)
*/
async generateIncomingClaimCSV(data: any[], fileName: string): Promise<string> {
const maxRetries = 3;
let retryCount = 0;
while (retryCount <= maxRetries) {
try {
const targetDir = path.join(this.basePath, this.incomingClaimsPath);
this.ensureDirectoryExists(targetDir);
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`);
// Simple CSV generation logic
const headers = Object.keys(data[0] || {}).join(',');
const rows = data.map(item => Object.values(item).map(val => `"${val}"`).join(',')).join('\n');
const csvContent = `${headers}\n${rows}`;
fs.writeFileSync(filePath, csvContent);
logger.info(`[WFMFileService] Generated CSV at: ${filePath}`);
return filePath;
} catch (error: any) {
if (error.code === 'EBUSY' && retryCount < maxRetries) {
retryCount++;
const delay = retryCount * 1000;
logger.warn(`[WFMFileService] File busy/locked, retrying in ${delay}ms (Attempt ${retryCount}/${maxRetries}): ${fileName}`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
if (error.code === 'EBUSY') {
throw new Error(`File is locked or open in another program (e.g., Excel). Please close '${fileName}' and try again.`);
}
logger.error('[WFMFileService] Error generating incoming claim CSV:', error);
throw error;
}
}
throw new Error(`Failed to generate CSV after ${maxRetries} retries. Please ensure the file '${fileName}' is not open in any other application.`);
}
/**
* Get the absolute path for an outgoing claim file
*/
getOutgoingPath(fileName: string): string {
return path.join(this.basePath, this.outgoingClaimsPath, fileName);
}
}
export const wfmFileService = new WFMFileService();