Compare commits

..

2 Commits

4 changed files with 200 additions and 23 deletions

View File

@ -79,6 +79,16 @@ WFM_INCOMING_GST_CLAIMS_PATH=WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS_GST
WFM_INCOMING_NON_GST_CLAIMS_PATH=WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS_NON_GST WFM_INCOMING_NON_GST_CLAIMS_PATH=WFM-QRE\\INCOMING\\WFM_MAIN\\DLR_INC_CLAIMS_NON_GST
WFM_OUTGOING_GST_CLAIMS_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_GST WFM_OUTGOING_GST_CLAIMS_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_GST
WFM_OUTGOING_NON_GST_CLAIMS_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_NON_GST WFM_OUTGOING_NON_GST_CLAIMS_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_NON_GST
WFM_FORM16_CREDIT_INCOMING_PATH=WFM-QRE\\INCOMING\\WFM_MAIN\\FORM16_CRDT
WFM_FORM16_DEBIT_INCOMING_PATH=WFM-QRE\\INCOMING\\WFM_MAIN\\FORM16_DBT
WFM_FORM16_CREDIT_OUTGOING_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\FORM16_CRDT
WFM_FORM16_DEBIT_OUTGOING_PATH=WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\FORM16_DBT
# WFM Archive Configuration (INCOMING)
WFM_ARCHIVE_GST_CLAIMS_PATH=WFM-QRE\\INCOMING\\WFM_ARACHIVE\\DLR_INC_CLAIMS_GST
WFM_ARCHIVE_NON_GST_CLAIMS_PATH=WFM-QRE\\INCOMING\\WFM_ARACHIVE\\DLR_INC_CLAIMS_NON_GST
WFM_FORM16_CREDIT_ARCHIVE_PATH=WFM-QRE\\INCOMING\\WFM_ARACHIVE\\FORM16_CRDT
WFM_FORM16_DEBIT_ARCHIVE_PATH=WFM-QRE\\INCOMING\\WFM_ARACHIVE\\FORM16_DBT
# TAT Monitoring # TAT Monitoring
TAT_CHECK_INTERVAL_MINUTES=30 TAT_CHECK_INTERVAL_MINUTES=30
@ -119,9 +129,13 @@ SAP_DISABLE_SSL_VERIFY=false
# WFM_INCOMING_CLAIMS_PATH=WFM-QRE\INCOMING\WFM_MAIN\DLR_INC_CLAIMS # WFM_INCOMING_CLAIMS_PATH=WFM-QRE\INCOMING\WFM_MAIN\DLR_INC_CLAIMS
# WFM_OUTGOING_CLAIMS_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\DLR_INC_CLAIMS # WFM_OUTGOING_CLAIMS_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\DLR_INC_CLAIMS
# Form 16 credit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_CRDT # Form 16 credit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_CRDT
# Form 16 debit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_DEBT # Form 16 debit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_DBT
# Form 16 SAP responses (outgoing): OUTGOING/WFM_SAP_MAIN/FORM16_CRDT # Form 16 SAP responses (outgoing): OUTGOING/WFM_SAP_MAIN/FORM16_CRDT
# WFM_FORM16_CREDIT_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\FORM16_CRDT # WFM_FORM16_CREDIT_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\FORM16_CRDT
# WFM_FORM16_DEBIT_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\FORM16_DEBT # WFM_FORM16_DEBIT_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\FORM16_DBT
# WFM_FORM16_OUTGOING_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\FORM16_CRDT # WFM_FORM16_OUTGOING_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\FORM16_CRDT
# WFM Archive configuration examples (if overrides are needed)
# WFM_ARCHIVE_GST_CLAIMS_PATH=WFM-QRE\INCOMING\WFM_ARACHIVE\DLR_INC_CLAIMS_GST
# WFM_FORM16_CREDIT_ARCHIVE_PATH=WFM-QRE\INCOMING\WFM_ARACHIVE\FORM16_CRDT

View File

@ -25,8 +25,6 @@
"seed:demo-requests": "ts-node -r tsconfig-paths/register src/scripts/seed-demo-requests.ts", "seed:demo-requests": "ts-node -r tsconfig-paths/register src/scripts/seed-demo-requests.ts",
"seed:demo-dealers": "ts-node -r tsconfig-paths/register src/scripts/seed-demo-dealers.ts", "seed:demo-dealers": "ts-node -r tsconfig-paths/register src/scripts/seed-demo-dealers.ts",
"cleanup:dealer-claims": "ts-node -r tsconfig-paths/register src/scripts/cleanup-dealer-claims.ts", "cleanup:dealer-claims": "ts-node -r tsconfig-paths/register src/scripts/cleanup-dealer-claims.ts",
"clear:form16-and-demo": "ts-node -r tsconfig-paths/register src/scripts/clear-form16-and-demo-data.ts",
"clear:26as": "ts-node -r tsconfig-paths/register src/scripts/clear-26as-data.ts",
"redis:start": "docker run -d --name redis-workflow -p 6379:6379 redis:7-alpine", "redis:start": "docker run -d --name redis-workflow -p 6379:6379 redis:7-alpine",
"redis:stop": "docker rm -f redis-workflow", "redis:stop": "docker rm -f redis-workflow",
"test": "jest --passWithNoTests --forceExit", "test": "jest --passWithNoTests --forceExit",
@ -110,4 +108,4 @@
"node": ">=22.0.0", "node": ">=22.0.0",
"npm": ">=10.0.0" "npm": ">=10.0.0"
} }
} }

View File

@ -0,0 +1,65 @@
import { wfmFileService } from '../services/wfmFile.service';
import logger from '../utils/logger';
import dotenv from 'dotenv';
import fs from 'fs';
import path from 'path';
// Load environment variables
dotenv.config();
async function testWfmArchiveFinal() {
try {
console.log('Starting WFM Final Verification Test...');
// 1. Test Claim CSV Archiving (GST)
const claimData = [{
TRNS_UNIQ_NO: 'TEST_FINAL_123',
CLAIM_NUMBER: 'CLI_FINAL_001',
DEALER_CODE: '6059',
CLAIM_AMT: 1000.00
}];
const claimFileName = `ARCHIVE_FINAL_CLAIM_${Date.now()}.csv`;
console.log(`Testing Claim CSV: ${claimFileName}`);
const claimFilePath = await wfmFileService.generateIncomingClaimCSV(claimData, claimFileName, false); // false = GST
console.log(`Main Claim CSV: ${claimFilePath}`);
const expectedArchivePath = claimFilePath.replace('WFM_MAIN', 'WFM_ARACHIVE');
console.log(`Archive Claim CSV expected at: ${expectedArchivePath}`);
if (fs.existsSync(claimFilePath) && fs.existsSync(expectedArchivePath)) {
console.log('✅ Claim CSV and Archive verified!');
} else {
if (!fs.existsSync(claimFilePath)) console.error('❌ Main Claim CSV not found!');
if (!fs.existsSync(expectedArchivePath)) console.error('❌ Archive Claim CSV not found!');
}
// 2. Test Form 16 CSV Archiving (Credit)
const form16Data = [{
DEALER_CODE: '6059',
QUARTER: 'Q1',
YEAR: '2024-25',
AMOUNT: 500.00
}];
const form16FileName = `ARCHIVE_FINAL_FORM16_${Date.now()}.csv`;
console.log(`\nTesting Form 16 CSV: ${form16FileName}`);
const form16FilePath = await wfmFileService.generateForm16IncomingCSV(form16Data, form16FileName, 'credit');
console.log(`Main Form 16 CSV: ${form16FilePath}`);
const expectedForm16ArchivePath = form16FilePath.replace('WFM_MAIN', 'WFM_ARACHIVE');
console.log(`Archive Form 16 CSV expected at: ${expectedForm16ArchivePath}`);
if (fs.existsSync(form16FilePath) && fs.existsSync(expectedForm16ArchivePath)) {
console.log('✅ Form 16 CSV and Archive verified!');
} else {
if (!fs.existsSync(form16FilePath)) console.error('❌ Main Form 16 CSV not found!');
if (!fs.existsSync(expectedForm16ArchivePath)) console.error('❌ Archive Form 16 CSV not found!');
}
} catch (error) {
console.error('❌ Verification failed:', error);
}
}
testWfmArchiveFinal();

View File

@ -7,42 +7,73 @@ const DEFAULT_CLAIMS_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'DL
const DEFAULT_CLAIMS_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_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_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_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'); const DEFAULT_FORM16_CREDIT_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM16_CRDT');
const DEFAULT_FORM16_DEBIT_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM16_DBT');
/** /**
* WFM File Service * WFM File Service
* Handles generation and storage of CSV files in the WFM folder structure. * 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. * Dealer claims use DLR_INC_CLAIMS; Form 16 uses:
* - FORM16_CRDT (credit) and FORM16_DEBT (debit) under INCOMING/WFM_MAIN
* - FORM16_CRDT (credit) and FORM16_DBT (debit) under OUTGOING/WFM_SAP_MAIN
* Paths are cross-platform; set WFM_BASE_PATH (and optional path overrides) in .env for production. * Paths are cross-platform; set WFM_BASE_PATH (and optional path overrides) in .env for production.
*/ */
export class WFMFileService { export class WFMFileService {
private basePath: string; private basePath: string;
// --- INCOMING PATHS (WFM_MAIN / WFM_ARACHIVE) ---
private incomingGstClaimsPath: string; private incomingGstClaimsPath: string;
private incomingArchiveGstClaimsPath: string;
private incomingNonGstClaimsPath: string; private incomingNonGstClaimsPath: string;
private incomingArchiveNonGstClaimsPath: string;
private form16IncomingCreditPath: string;
private incomingArchiveForm16CreditPath: string;
private form16IncomingDebitPath: string;
private incomingArchiveForm16DebitPath: string;
// --- OUTGOING PATHS (WFM_SAP_MAIN) ---
private outgoingGstClaimsPath: string; private outgoingGstClaimsPath: string;
private outgoingNonGstClaimsPath: string; private outgoingNonGstClaimsPath: string;
/** Form 16 credit notes: INCOMING/WFM_MAIN/FORM16_CRDT */
private form16IncomingCreditPath: string; /** Form 16 credit responses: OUTGOING/WFM_SAP_MAIN/FORM16_CRDT */
/** Form 16 debit notes: INCOMING/WFM_MAIN/FORM16_DEBT */ private form16OutgoingCreditPath: string;
private form16IncomingDebitPath: string; /** Form 16 debit responses: OUTGOING/WFM_SAP_MAIN/FORM16_DBT */
/** Form 16: OUTGOING/WFM_SAP_MAIN/FORM16_CRDT (SAP responses) */ private form16OutgoingDebitPath: string;
private form16OutgoingPath: string;
constructor() { constructor() {
this.basePath = process.env.WFM_BASE_PATH || 'C:\\WFM'; 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'; // Initialize Incoming Paths from .env or defaults
this.outgoingGstClaimsPath = process.env.WFM_OUTGOING_GST_CLAIMS_PATH || 'WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_GST'; this.incomingGstClaimsPath = process.env.WFM_INCOMING_GST_CLAIMS_PATH || DEFAULT_CLAIMS_INCOMING + '_GST';
this.outgoingNonGstClaimsPath = process.env.WFM_OUTGOING_NON_GST_CLAIMS_PATH || 'WFM-QRE\\OUTGOING\\WFM_SAP_MAIN\\DLR_INC_CLAIMS_NON_GST'; this.incomingArchiveGstClaimsPath = process.env.WFM_ARCHIVE_GST_CLAIMS_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'DLR_INC_CLAIMS_GST');
this.incomingNonGstClaimsPath = process.env.WFM_INCOMING_NON_GST_CLAIMS_PATH || DEFAULT_CLAIMS_INCOMING + '_NON_GST';
this.incomingArchiveNonGstClaimsPath = process.env.WFM_ARCHIVE_NON_GST_CLAIMS_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'DLR_INC_CLAIMS_NON_GST');
// Backwards-compatible: support legacy WFM_FORM16_INCOMING_PATH if specific credit/debit paths are not set // Backwards-compatible: support legacy WFM_FORM16_INCOMING_PATH if specific credit/debit paths are not set
const legacyForm16Incoming = process.env.WFM_FORM16_INCOMING_PATH; const legacyForm16Incoming = process.env.WFM_FORM16_INCOMING_PATH;
this.form16IncomingCreditPath = this.form16IncomingCreditPath =
process.env.WFM_FORM16_CREDIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_CREDIT_INCOMING; process.env.WFM_FORM16_CREDIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_CREDIT_INCOMING;
this.incomingArchiveForm16CreditPath = process.env.WFM_FORM16_CREDIT_ARCHIVE_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'FORM16_CRDT');
this.form16IncomingDebitPath = this.form16IncomingDebitPath =
process.env.WFM_FORM16_DEBIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_DEBIT_INCOMING; process.env.WFM_FORM16_DEBIT_INCOMING_PATH || legacyForm16Incoming || DEFAULT_FORM16_DEBIT_INCOMING;
this.incomingArchiveForm16DebitPath = process.env.WFM_FORM16_DEBIT_ARCHIVE_PATH || path.join('WFM-QRE', 'INCOMING', 'WFM_ARACHIVE', 'FORM16_DBT');
this.form16OutgoingPath = process.env.WFM_FORM16_OUTGOING_PATH || DEFAULT_FORM16_OUTGOING; // Initialize Outgoing Paths from .env or defaults
this.outgoingGstClaimsPath = process.env.WFM_OUTGOING_GST_CLAIMS_PATH || DEFAULT_CLAIMS_OUTGOING + '_GST';
this.outgoingNonGstClaimsPath = process.env.WFM_OUTGOING_NON_GST_CLAIMS_PATH || DEFAULT_CLAIMS_OUTGOING + '_NON_GST';
// Outgoing: allow specific credit/debit overrides; fall back to legacy single path for credit
const legacyForm16Outgoing = process.env.WFM_FORM16_OUTGOING_PATH;
this.form16OutgoingCreditPath =
process.env.WFM_FORM16_CREDIT_OUTGOING_PATH || legacyForm16Outgoing || DEFAULT_FORM16_CREDIT_OUTGOING;
this.form16OutgoingDebitPath =
process.env.WFM_FORM16_DEBIT_OUTGOING_PATH || DEFAULT_FORM16_DEBIT_OUTGOING;
} }
/** /**
@ -70,7 +101,8 @@ export class WFMFileService {
const targetDir = path.join(this.basePath, targetPath); const targetDir = path.join(this.basePath, targetPath);
this.ensureDirectoryExists(targetDir); this.ensureDirectoryExists(targetDir);
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`); const fileNameWithExt = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;
const filePath = path.join(targetDir, fileNameWithExt);
// Simple CSV generation logic with pipe separator and no quotes // Simple CSV generation logic with pipe separator and no quotes
const headers = Object.keys(data[0] || {}).join('|'); const headers = Object.keys(data[0] || {}).join('|');
@ -80,6 +112,19 @@ export class WFMFileService {
fs.writeFileSync(filePath, csvContent); fs.writeFileSync(filePath, csvContent);
logger.info(`[WFMFileService] Generated CSV at: ${filePath}`); logger.info(`[WFMFileService] Generated CSV at: ${filePath}`);
// Archive copy
try {
const archivePathPrefix = isNonGst ? this.incomingArchiveNonGstClaimsPath : this.incomingArchiveGstClaimsPath;
const archiveDir = path.join(this.basePath, archivePathPrefix);
this.ensureDirectoryExists(archiveDir);
const archivePath = path.join(archiveDir, fileNameWithExt);
fs.writeFileSync(archivePath, csvContent);
logger.info(`[WFMFileService] Archived CSV copy at: ${archivePath}`);
} catch (archiveError) {
logger.error('[WFMFileService] Error saving archive copy:', archiveError);
// Don't throw archive error to avoid failing the main process
}
return filePath; return filePath;
} catch (error: any) { } catch (error: any) {
if (error.code === 'EBUSY' && retryCount < maxRetries) { if (error.code === 'EBUSY' && retryCount < maxRetries) {
@ -161,7 +206,8 @@ export class WFMFileService {
const targetDir = path.join(this.basePath, targetPath); const targetDir = path.join(this.basePath, targetPath);
this.ensureDirectoryExists(targetDir); this.ensureDirectoryExists(targetDir);
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`); const fileNameWithExt = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;
const filePath = path.join(targetDir, fileNameWithExt);
// Pipe separator, no double quotes (values as plain strings) // Pipe separator, no double quotes (values as plain strings)
const keys = Object.keys(data[0] || {}); const keys = Object.keys(data[0] || {});
@ -177,6 +223,19 @@ export class WFMFileService {
fs.writeFileSync(filePath, csvContent); fs.writeFileSync(filePath, csvContent);
logger.info(`[WFMFileService] Form 16 CSV generated at: ${filePath}`); logger.info(`[WFMFileService] Form 16 CSV generated at: ${filePath}`);
// Archive copy
try {
const archivePathPrefix = type === 'debit' ? this.incomingArchiveForm16DebitPath : this.incomingArchiveForm16CreditPath;
const archiveDir = path.join(this.basePath, archivePathPrefix);
this.ensureDirectoryExists(archiveDir);
const archivePath = path.join(archiveDir, fileNameWithExt);
fs.writeFileSync(archivePath, csvContent);
logger.info(`[WFMFileService] Form 16 archived copy at: ${archivePath}`);
} catch (archiveError) {
logger.error('[WFMFileService] Error saving Form 16 archive copy:', archiveError);
// Don't throw archive error to avoid failing the main process
}
return filePath; return filePath;
} catch (error: any) { } catch (error: any) {
if (error.code === 'EBUSY' && retryCount < maxRetries) { if (error.code === 'EBUSY' && retryCount < maxRetries) {
@ -197,10 +256,51 @@ export class WFMFileService {
} }
/** /**
* Get the absolute path for a Form 16 outgoing (response) file * Get the absolute path for a Form 16 outgoing (response) file.
* - Credit: WFM/WFM-QRE/OUTGOING/WFM_SAP_MAIN/FORM16_CRDT
* - Debit: WFM/WFM-QRE/OUTGOING/WFM_SAP_MAIN/FORM16_DBT
*/ */
getForm16OutgoingPath(fileName: string): string { getForm16OutgoingPath(fileName: string, type: 'credit' | 'debit' = 'credit'): string {
return path.join(this.basePath, this.form16OutgoingPath, fileName); const targetPath = type === 'debit' ? this.form16OutgoingDebitPath : this.form16OutgoingCreditPath;
return path.join(this.basePath, targetPath, fileName);
}
/**
* Read a Form 16 outgoing (SAP) response CSV and return rows as objects keyed by header:
* Expected columns (both credit and debit):
* - DMS_UNIQ_NO
* - CLAIM_NUMBER
* - DOC_NO
* - MSG_TYP
* - MESSAGE
*/
async readForm16OutgoingResponse(fileName: string, type: 'credit' | 'debit' = 'credit'): Promise<any[]> {
const filePath = this.getForm16OutgoingPath(fileName, type);
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 Form 16 response CSV (${type}): ${fileName}`, error);
return [];
}
} }
} }