Re_Backend/src/services/wfmFile.service.ts
2026-03-13 14:15:55 +05:30

208 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'fs';
import path from 'path';
import logger from '../utils/logger';
/** Default WFM folder names (joined with path.sep for current OS). */
const DEFAULT_CLAIMS_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'DLR_INC_CLAIMS');
const DEFAULT_CLAIMS_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'DLR_INC_CLAIMS');
const DEFAULT_FORM16_CREDIT_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'FORM16_CRDT');
const DEFAULT_FORM16_DEBIT_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'FORM16_DEBT');
const DEFAULT_FORM16_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM16_CRDT');
/**
* WFM File Service
* Handles generation and storage of CSV files in the WFM folder structure.
* Dealer claims use DLR_INC_CLAIMS; Form 16 uses FORM16_CRDT (credit) and FORM16_DEBT (debit) under INCOMING/WFM_MAIN.
* Paths are cross-platform; set WFM_BASE_PATH (and optional path overrides) in .env for production.
*/
export class WFMFileService {
private basePath: string;
private incomingGstClaimsPath: string;
private incomingNonGstClaimsPath: string;
private outgoingGstClaimsPath: string;
private outgoingNonGstClaimsPath: string;
/** Form 16 credit notes: INCOMING/WFM_MAIN/FORM16_CRDT */
private form16IncomingCreditPath: string;
/** Form 16 debit notes: INCOMING/WFM_MAIN/FORM16_DEBT */
private form16IncomingDebitPath: string;
/** Form 16: OUTGOING/WFM_SAP_MAIN/FORM16_CRDT (SAP responses) */
private form16OutgoingPath: string;
constructor() {
this.basePath = process.env.WFM_BASE_PATH || 'C:\\WFM';
this.incomingGstClaimsPath = process.env.WFM_INCOMING_GST_CLAIMS_PATH || 'WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS_GST';
this.incomingNonGstClaimsPath = process.env.WFM_INCOMING_NON_GST_CLAIMS_PATH || 'WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS_NON_GST';
this.outgoingGstClaimsPath = process.env.WFM_OUTGOING_GST_CLAIMS_PATH || 'WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_GST';
this.outgoingNonGstClaimsPath = process.env.WFM_OUTGOING_NON_GST_CLAIMS_PATH || 'WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_NON_GST';
// Backwards-compatible: support legacy WFM_FORM16_INCOMING_PATH if specific credit/debit paths are not set
const legacyForm16Incoming = process.env.WFM_FORM16_INCOMING_PATH;
this.form16IncomingCreditPath =
process.env.WFM_FORM16_CREDIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_CREDIT_INCOMING;
this.form16IncomingDebitPath =
process.env.WFM_FORM16_DEBIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_DEBIT_INCOMING;
this.form16OutgoingPath = process.env.WFM_FORM16_OUTGOING_PATH || DEFAULT_FORM16_OUTGOING;
}
/**
* 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, isNonGst: boolean = false): Promise<string> {
const maxRetries = 3;
let retryCount = 0;
while (retryCount <= maxRetries) {
try {
const targetPath = isNonGst ? this.incomingNonGstClaimsPath : this.incomingGstClaimsPath;
const targetDir = path.join(this.basePath, targetPath);
this.ensureDirectoryExists(targetDir);
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`);
// Simple CSV generation logic with pipe separator and no quotes
const headers = Object.keys(data[0] || {}).join('|');
const rows = data.map(item => Object.values(item).map(val => val === null || val === undefined ? '' : String(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, isNonGst: boolean = false): string {
const targetPath = isNonGst ? this.outgoingNonGstClaimsPath : this.outgoingGstClaimsPath;
return path.join(this.basePath, targetPath, fileName);
}
/**
* Get credit note details from outgoing CSV
*/
async getCreditNoteDetails(dealerCode: string, requestNumber: string, isNonGst: boolean = false): Promise<any[]> {
const fileName = `CN_${dealerCode}_${requestNumber}.csv`;
const filePath = this.getOutgoingPath(fileName, isNonGst);
try {
if (!fs.existsSync(filePath)) {
return [];
}
const fileContent = fs.readFileSync(filePath, 'utf-8');
const lines = fileContent.split('\n').filter(line => line.trim() !== '');
if (lines.length <= 1) return []; // Only headers or empty
const headers = lines[0].split('|');
const data = lines.slice(1).map(line => {
const values = line.split('|');
const row: any = {};
headers.forEach((header, index) => {
row[header.trim()] = values[index]?.trim() || '';
});
return row;
});
return data;
} catch (error) {
logger.error(`[WFMFileService] Error reading credit note CSV: ${fileName}`, error);
return [];
}
}
/**
* Generate a CSV file for Form 16 (credit/debit note) and store in the appropriate INCOMING/WFM_MAIN folder.
* - Credit: FORM16_CRDT
* - Debit: FORM16_DEBT
* Format: pipe (|) as column separator, no double quotes around values (SAP/WFM requirement).
* @param data Array of one or more row objects (keys become header; use UPPER_SNAKE_CASE for column names)
* @param fileName File name (e.g. CN-F-16-6282-24-25-Q1.csv or DN-F-16-6282-24-25-Q1.csv)
* @param type 'credit' (default) or 'debit' selects FORM16_CRDT vs FORM16_DEBT
*/
async generateForm16IncomingCSV(data: any[], fileName: string, type: 'credit' | 'debit' = 'credit'): Promise<string> {
const maxRetries = 3;
let retryCount = 0;
while (retryCount <= maxRetries) {
try {
const targetPath = type === 'debit' ? this.form16IncomingDebitPath : this.form16IncomingCreditPath;
const targetDir = path.join(this.basePath, targetPath);
this.ensureDirectoryExists(targetDir);
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`);
// Pipe separator, no double quotes (values as plain strings)
const keys = Object.keys(data[0] || {});
const headers = keys.join('|');
const rows = data.map(item =>
keys.map(key => {
const val = item[key];
return val === null || val === undefined ? '' : String(val);
}).join('|')
).join('\n');
const csvContent = `${headers}\n${rows}`;
fs.writeFileSync(filePath, csvContent);
logger.info(`[WFMFileService] Form 16 CSV generated at: ${filePath}`);
return filePath;
} catch (error: any) {
if (error.code === 'EBUSY' && retryCount < maxRetries) {
retryCount++;
const delay = retryCount * 1000;
logger.warn(`[WFMFileService] Form 16 file busy, retrying in ${delay}ms (${retryCount}/${maxRetries}): ${fileName}`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
if (error.code === 'EBUSY') {
throw new Error(`Form 16 file is locked. Please close '${fileName}' and try again.`);
}
logger.error('[WFMFileService] Error generating Form 16 incoming CSV:', error);
throw error;
}
}
throw new Error(`Failed to generate Form 16 CSV after ${maxRetries} retries.`);
}
/**
* Get the absolute path for a Form 16 outgoing (response) file
*/
getForm16OutgoingPath(fileName: string): string {
return path.join(this.basePath, this.form16OutgoingPath, fileName);
}
}
export const wfmFileService = new WFMFileService();