debit CSV and details page fixed
This commit is contained in:
parent
b3dcaca697
commit
89beffee2e
1
.gitignore
vendored
1
.gitignore
vendored
@ -136,3 +136,4 @@ uploads/
|
||||
# GCP Service Account Key
|
||||
config/gcp-key.json
|
||||
Jenkinsfile
|
||||
clear-26as-data.ts
|
||||
11
env.example
11
env.example
@ -113,12 +113,15 @@ SAP_REQUESTER=REFMS
|
||||
# WARNING: Only use in development/testing environments
|
||||
SAP_DISABLE_SSL_VERIFY=false
|
||||
|
||||
# WFM file paths (base path; dealer claims use DLR_INC_CLAIMS, Form 16 uses FORM_16)
|
||||
# WFM file paths (base path; dealer claims use DLR_INC_CLAIMS, Form 16 uses FORM16_CRDT / FORM16_DEBT)
|
||||
# If unset: Windows defaults to C:\WFM; Linux/Mac defaults to <cwd>/wfm (paths are cross-platform).
|
||||
# WFM_BASE_PATH=C:\WFM
|
||||
# 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
|
||||
# Form 16 credit/debit note CSV: INCOMING/WFM_MAIN/FORM_16, OUTGOING/WFM_SAP_MAIN/FORM_16
|
||||
# WFM_FORM16_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\Form_16
|
||||
# WFM_FORM16_OUTGOING_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\Form_16
|
||||
# Form 16 credit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_CRDT
|
||||
# Form 16 debit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_DEBT
|
||||
# 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_DEBIT_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\FORM16_DEBT
|
||||
# WFM_FORM16_OUTGOING_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\FORM16_CRDT
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"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",
|
||||
"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:stop": "docker rm -f redis-workflow",
|
||||
"test": "jest --passWithNoTests --forceExit",
|
||||
|
||||
@ -442,33 +442,6 @@ export class Form16Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/form16/credit-notes/:id/generate-debit-note
|
||||
* RE only. Generate debit note for a credit note (dealer + credit note number + amount → SAP simulation → save debit note).
|
||||
*/
|
||||
async generateForm16DebitNote(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).user?.userId;
|
||||
if (!userId) return ResponseHandler.unauthorized(res, 'Authentication required');
|
||||
const creditNoteId = parseInt((req.params as { id: string }).id, 10);
|
||||
if (Number.isNaN(creditNoteId) || creditNoteId <= 0) {
|
||||
return ResponseHandler.error(res, 'Valid credit note id is required', 400);
|
||||
}
|
||||
const body = (req.body || {}) as { amount?: number };
|
||||
const amount = typeof body.amount === 'number' ? body.amount : parseFloat(String(body.amount || 0));
|
||||
const result = await form16Service.generateForm16DebitNoteForCreditNote(creditNoteId, userId, amount);
|
||||
return ResponseHandler.success(
|
||||
res,
|
||||
{ debitNote: result.debitNote, creditNote: result.creditNote },
|
||||
'Debit note generated'
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('[Form16Controller] generateForm16DebitNote error:', error);
|
||||
return ResponseHandler.error(res, errorMessage, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/form16/26as/upload
|
||||
* RE only. Upload a single TXT file containing 26AS data (all dealers). Data stored in tds_26as_entries.
|
||||
|
||||
@ -111,14 +111,6 @@ router.post(
|
||||
asyncHandler(form16Controller.sapSimulateDebitNote.bind(form16Controller))
|
||||
);
|
||||
|
||||
// RE only: generate debit note for a credit note (hits SAP simulation; replace with real SAP later).
|
||||
router.post(
|
||||
'/credit-notes/:id/generate-debit-note',
|
||||
requireForm16ReOnly,
|
||||
requireForm16SubmissionAccess,
|
||||
asyncHandler(form16Controller.generateForm16DebitNote.bind(form16Controller))
|
||||
);
|
||||
|
||||
// Dealer-only: pending submissions and pending quarters (Form 16 Pending Submissions page)
|
||||
router.get(
|
||||
'/dealer/submissions',
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
* Form 16 (Form 16A TDS Credit) service.
|
||||
* Quarter-based reconciliation: 26AS (aggregated by tan+fy+quarter), Form 16A match, credit/debit, ledger.
|
||||
*
|
||||
* Credit note: run26asMatchAndCreditNote only (on Form 16A submit, match 26AS → CN-F-16-{certificateNumber}-{dealerCode}-{FY}-{quarter}-V{version}, ledger, CSV to WFM FORM_16).
|
||||
* Debit: generateForm16DebitNoteForCreditNote (manual) and process26asUploadAggregation (auto when 26AS total drops); DN-F-16-{creditNoteCertificateNumber}-{dc}-{fy}-{q}-V{version} (uses the credit note’s certificate number).
|
||||
* Credit note: run26asMatchAndCreditNote only (on Form 16A submit, match 26AS → CN-F-16-{...}, ledger, CSV to WFM FORM_16).
|
||||
* Debit: process26asUploadAggregation only (when 26AS total drops for a SETTLED quarter); DN-F-16-{...}, CSV to WFM FORM_16.
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
@ -579,31 +579,27 @@ async function run26asMatchAndCreditNote(submission: Form16aSubmission): Promise
|
||||
validationNotes: null,
|
||||
});
|
||||
|
||||
// Push Form 16 credit note CSV to WFM INCOMING/WFM_MAIN/FORM_16 (pipe | separator, no double quotes)
|
||||
// Push Form 16 credit note incoming CSV to WFM INCOMING/WFM_MAIN/FORM16_CRDT (SAP credit note generation – exact fields only)
|
||||
try {
|
||||
const dealer = await Dealer.findOne({
|
||||
where: { [Op.or]: [{ salesCode: dealerCode }, { dlrcode: dealerCode }], isActive: true },
|
||||
attributes: ['dealership', 'dealerPrincipalName'],
|
||||
});
|
||||
const dealerName = dealer ? ((dealer as any).dealership || (dealer as any).dealerPrincipalName || dealerCode) : dealerCode;
|
||||
const trnsUniqNo = `F16-CN-${submission.id}-${creditNote.id}-${Date.now()}`;
|
||||
const claimDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const csvRow = {
|
||||
CREDIT_TYPE: 'Form16',
|
||||
DEALER_CODE: dealerCode,
|
||||
DEALER_NAME: dealerName,
|
||||
AMOUNT: tdsAmount,
|
||||
FINANCIAL_YEAR: financialYear,
|
||||
QUARTER: quarter,
|
||||
CREDIT_NOTE_NUMBER: cnNumber,
|
||||
const docDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const fyCompact = form16FyCompact(financialYear) || '';
|
||||
const finYearAndQuarter = fyCompact && quarter ? `FY_${fyCompact}_${quarter}` : '';
|
||||
const csvRow: Record<string, string | number> = {
|
||||
TRNS_UNIQ_NO: trnsUniqNo,
|
||||
CLAIM_DATE: claimDate,
|
||||
TDS_TRNS_ID: cnNumber,
|
||||
DEALER_CODE: dealerCode,
|
||||
TDS_TRNS_DOC_TYP: 'ZTDS',
|
||||
DLR_TAN_NO: tanNumber,
|
||||
'FIN_YEAR & QUARTER': finYearAndQuarter,
|
||||
DOC_DATE: docDate,
|
||||
TDS_AMT: Number(tdsAmount).toFixed(2),
|
||||
};
|
||||
const fileName = `${cnNumber}.csv`;
|
||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName);
|
||||
logger.info(`[Form16] Credit note CSV pushed to WFM FORM_16: ${cnNumber}`);
|
||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName, 'credit');
|
||||
logger.info(`[Form16] Credit note CSV pushed to WFM FORM16_CRDT: ${cnNumber}`);
|
||||
} catch (csvErr: any) {
|
||||
logger.error('[Form16] Failed to push credit note CSV to WFM FORM_16:', csvErr?.message || csvErr);
|
||||
logger.error('[Form16] Failed to push credit note CSV to WFM FORM16_CRDT:', csvErr?.message || csvErr);
|
||||
// Do not fail the flow; credit note and ledger are already created
|
||||
}
|
||||
|
||||
@ -1307,74 +1303,6 @@ export async function getCreditNoteById(creditNoteId: number) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* RE only. Generate debit note for a credit note (Form 16). Creates Form16DebitNote with DN-F-16-{creditNoteCertificateNumber}-{dc}-{fy}-{q}-V{v} and pushes CSV to WFM INCOMING/WFM_MAIN/FORM_16 for SAP.
|
||||
*/
|
||||
export async function generateForm16DebitNoteForCreditNote(
|
||||
creditNoteId: number,
|
||||
userId: string,
|
||||
amount: number
|
||||
): Promise<{ debitNote: Form16DebitNote; creditNote: Form16CreditNote }> {
|
||||
if (!amount || amount <= 0) throw new Error('Valid amount is required to generate debit note.');
|
||||
const creditNote = await Form16CreditNote.findByPk(creditNoteId, {
|
||||
attributes: ['id', 'creditNoteNumber', 'amount', 'financialYear', 'quarter', 'issueDate'],
|
||||
include: [{ model: Form16aSubmission, as: 'submission', attributes: ['id', 'dealerCode', 'version', 'form16aNumber'] }],
|
||||
});
|
||||
if (!creditNote || !(creditNote as any).submission) throw new Error('Credit note not found.');
|
||||
const existing = await Form16DebitNote.findOne({ where: { creditNoteId }, attributes: ['id'] });
|
||||
if (existing) throw new Error('A debit note already exists for this credit note.');
|
||||
// Dealer code, version and certificate number from the credit note's submission (DN uses same cert as the CN being reversed)
|
||||
const dealerCode = ((creditNote as any).submission?.dealerCode || '').toString().trim();
|
||||
const financialYear = (creditNote as any).financialYear || '';
|
||||
const quarter = (creditNote as any).quarter || '';
|
||||
const version = typeof (creditNote as any).submission?.version === 'number' && (creditNote as any).submission.version >= 1 ? (creditNote as any).submission.version : 1;
|
||||
const creditNoteCertNumber = ((creditNote as any).submission?.form16aNumber || '').toString().trim();
|
||||
const dnNumber = formatForm16DebitNoteNumber(dealerCode || 'UNKNOWN', financialYear, quarter, version, creditNoteCertNumber);
|
||||
const now = new Date();
|
||||
const debitNote = await Form16DebitNote.create({
|
||||
creditNoteId,
|
||||
debitNoteNumber: dnNumber,
|
||||
amount,
|
||||
issueDate: now,
|
||||
status: 'issued',
|
||||
reason: 'Debit note pushed to WFM FORM16 for SAP.',
|
||||
createdBy: userId,
|
||||
});
|
||||
|
||||
// Push Form 16 debit note CSV to WFM INCOMING/WFM_MAIN/FORM_16
|
||||
try {
|
||||
const dealer = await Dealer.findOne({
|
||||
where: { [Op.or]: [{ salesCode: dealerCode }, { dlrcode: dealerCode }], isActive: true },
|
||||
attributes: ['dealership', 'dealerPrincipalName'],
|
||||
});
|
||||
const dealerName = dealer ? ((dealer as any).dealership || (dealer as any).dealerPrincipalName || dealerCode) : dealerCode;
|
||||
const trnsUniqNo = `F16-DN-${creditNoteId}-${debitNote.id}-${Date.now()}`;
|
||||
const claimDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const creditNoteIssueDate = (creditNote as any).issueDate
|
||||
? new Date((creditNote as any).issueDate).toISOString().slice(0, 10).replace(/-/g, '')
|
||||
: '';
|
||||
const csvRow = {
|
||||
CREDIT_NOTE_NUMBER: (creditNote as any).creditNoteNumber,
|
||||
DEALER_CODE: dealerCode || 'UNKNOWN',
|
||||
DEALER_NAME: dealerName,
|
||||
AMOUNT: amount,
|
||||
FINANCIAL_YEAR: financialYear,
|
||||
QUARTER: quarter,
|
||||
DEBIT_NOTE_NUMBER: dnNumber,
|
||||
TRNS_UNIQ_NO: trnsUniqNo,
|
||||
CLAIM_DATE: claimDate,
|
||||
CREDIT_NOTE_DATE: creditNoteIssueDate,
|
||||
};
|
||||
const fileName = `${dnNumber}.csv`;
|
||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName);
|
||||
logger.info(`[Form16] Manual debit note CSV pushed to WFM FORM_16: ${dnNumber}`);
|
||||
} catch (csvErr: any) {
|
||||
logger.error('[Form16] Failed to push manual debit note CSV to WFM FORM_16:', csvErr?.message || csvErr);
|
||||
}
|
||||
|
||||
return { debitNote, creditNote };
|
||||
}
|
||||
|
||||
// ---------- Non-submitted dealers (RE only) ----------
|
||||
const QUARTERS = ['Q1', 'Q2', 'Q3', 'Q4'] as const;
|
||||
|
||||
@ -2100,35 +2028,28 @@ export async function process26asUploadAggregation(uploadLogId: number): Promise
|
||||
await setQuarterStatusDebitIssued(tanNumber, fy, q, debit.id);
|
||||
debitsCreated++;
|
||||
|
||||
// Push Form 16 debit note CSV to WFM INCOMING/WFM_MAIN/FORM_16
|
||||
// Push Form 16 debit note CSV to WFM INCOMING/WFM_MAIN/FORM16_DEBT (same column set as credit note / SAP expectation)
|
||||
try {
|
||||
const dealer = await Dealer.findOne({
|
||||
where: { [Op.or]: [{ salesCode: dealerCode }, { dlrcode: dealerCode }], isActive: true },
|
||||
attributes: ['dealership', 'dealerPrincipalName'],
|
||||
});
|
||||
const dealerName = dealer ? ((dealer as any).dealership || (dealer as any).dealerPrincipalName || dealerCode) : dealerCode;
|
||||
const trnsUniqNo = `F16-DN-${creditNote.id}-${debit.id}-${Date.now()}`;
|
||||
const claimDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const creditNoteIssueDate = (creditNote as any).issueDate
|
||||
? new Date((creditNote as any).issueDate).toISOString().slice(0, 10).replace(/-/g, '')
|
||||
: '';
|
||||
const csvRow = {
|
||||
CREDIT_NOTE_NUMBER: (creditNote as any).creditNoteNumber,
|
||||
DEALER_CODE: dealerCode || 'XX',
|
||||
DEALER_NAME: dealerName,
|
||||
AMOUNT: amount,
|
||||
FINANCIAL_YEAR: cnFy,
|
||||
QUARTER: cnQuarter,
|
||||
DEBIT_NOTE_NUMBER: debitNum,
|
||||
const docDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const fyCompact = form16FyCompact(cnFy) || '';
|
||||
const finYearAndQuarter = fyCompact && cnQuarter ? `FY ${fyCompact}_${cnQuarter}` : '';
|
||||
const csvRow: Record<string, string | number> = {
|
||||
TRNS_UNIQ_NO: trnsUniqNo,
|
||||
CLAIM_DATE: claimDate,
|
||||
CREDIT_NOTE_DATE: creditNoteIssueDate,
|
||||
TDS_TRNS_ID: debitNum,
|
||||
DEALER_CODE: dealerCode || 'XX',
|
||||
TDS_TRNS_DOC_TYP: 'ZTDS',
|
||||
'Org.Document Number': debit.id,
|
||||
DLR_TAN_NO: tanNumber,
|
||||
'FIN_YEAR & QUARTER': finYearAndQuarter,
|
||||
DOC_DATE: docDate,
|
||||
TDS_AMT: Number(amount).toFixed(2),
|
||||
};
|
||||
const fileName = `${debitNum}.csv`;
|
||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName);
|
||||
logger.info(`[Form16] Debit note CSV pushed to WFM FORM_16: ${debitNum}`);
|
||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName, 'debit');
|
||||
logger.info(`[Form16] Debit note CSV pushed to WFM FORM16_DEBT: ${debitNum}`);
|
||||
} catch (csvErr: any) {
|
||||
logger.error('[Form16] Failed to push debit note CSV to WFM FORM_16:', csvErr?.message || csvErr);
|
||||
logger.error('[Form16] Failed to push debit note CSV to WFM FORM16_DEBT:', csvErr?.message || csvErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,13 @@ export async function trigger26AsDataAddedNotification(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
const { notificationService } = await import('./notification.service');
|
||||
const reUserIds = await getReUserIdsFor26As();
|
||||
|
||||
// Base RE audience (admins / RE viewers). This helper already tries to exclude dealers,
|
||||
// but we defensively re-filter below so that 26AS notifications are never sent to dealers.
|
||||
const baseReUserIds = await getReUserIdsFor26As();
|
||||
const dealerUserIds = await getDealerUserIds();
|
||||
const dealerSet = new Set(dealerUserIds);
|
||||
const reUserIds = baseReUserIds.filter((id) => !dealerSet.has(id));
|
||||
|
||||
const title = 'Form 16 – 26AS data updated';
|
||||
if (reUserIds.length > 0 && n.templateRe) {
|
||||
|
||||
@ -5,13 +5,14 @@ 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_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'FORM_16');
|
||||
const DEFAULT_FORM16_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM_16');
|
||||
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 FORM_16 under INCOMING/WFM_MAIN and OUTGOING/WFM_SAP_MAIN.
|
||||
* 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 {
|
||||
@ -20,9 +21,11 @@ export class WFMFileService {
|
||||
private incomingNonGstClaimsPath: string;
|
||||
private outgoingGstClaimsPath: string;
|
||||
private outgoingNonGstClaimsPath: string;
|
||||
/** Form 16: INCOMING/WFM_MAIN/FORM_16 */
|
||||
private form16IncomingPath: string;
|
||||
/** Form 16: OUTGOING/WFM_SAP_MAIN/FORM_16 */
|
||||
/** 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() {
|
||||
@ -31,7 +34,14 @@ export class WFMFileService {
|
||||
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';
|
||||
this.form16IncomingPath = process.env.WFM_FORM16_INCOMING_PATH || DEFAULT_FORM16_INCOMING;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -133,18 +143,22 @@ export class WFMFileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a CSV file for Form 16 (credit/debit note) and store in INCOMING/WFM_MAIN/FORM_16.
|
||||
* 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): Promise<string> {
|
||||
async generateForm16IncomingCSV(data: any[], fileName: string, type: 'credit' | 'debit' = 'credit'): Promise<string> {
|
||||
const maxRetries = 3;
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
const targetDir = path.join(this.basePath, this.form16IncomingPath);
|
||||
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`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user