debit CSV and details page fixed
This commit is contained in:
parent
b3dcaca697
commit
89beffee2e
3
.gitignore
vendored
3
.gitignore
vendored
@ -135,4 +135,5 @@ uploads/
|
|||||||
|
|
||||||
# GCP Service Account Key
|
# GCP Service Account Key
|
||||||
config/gcp-key.json
|
config/gcp-key.json
|
||||||
Jenkinsfile
|
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
|
# WARNING: Only use in development/testing environments
|
||||||
SAP_DISABLE_SSL_VERIFY=false
|
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).
|
# If unset: Windows defaults to C:\WFM; Linux/Mac defaults to <cwd>/wfm (paths are cross-platform).
|
||||||
# WFM_BASE_PATH=C:\WFM
|
# WFM_BASE_PATH=C:\WFM
|
||||||
# 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/debit note CSV: INCOMING/WFM_MAIN/FORM_16, OUTGOING/WFM_SAP_MAIN/FORM_16
|
# Form 16 credit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_CRDT
|
||||||
# WFM_FORM16_INCOMING_PATH=WFM-QRE\INCOMING\WFM_MAIN\Form_16
|
# Form 16 debit note CSV (incoming): INCOMING/WFM_MAIN/FORM16_DEBT
|
||||||
# WFM_FORM16_OUTGOING_PATH=WFM-QRE\OUTGOING\WFM_SAP_MAIN\Form_16
|
# 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",
|
"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: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",
|
||||||
|
|||||||
@ -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
|
* POST /api/v1/form16/26as/upload
|
||||||
* RE only. Upload a single TXT file containing 26AS data (all dealers). Data stored in tds_26as_entries.
|
* 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))
|
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)
|
// Dealer-only: pending submissions and pending quarters (Form 16 Pending Submissions page)
|
||||||
router.get(
|
router.get(
|
||||||
'/dealer/submissions',
|
'/dealer/submissions',
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* Form 16 (Form 16A TDS Credit) service.
|
* Form 16 (Form 16A TDS Credit) service.
|
||||||
* Quarter-based reconciliation: 26AS (aggregated by tan+fy+quarter), Form 16A match, credit/debit, ledger.
|
* 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).
|
* Credit note: run26asMatchAndCreditNote only (on Form 16A submit, match 26AS → CN-F-16-{...}, 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).
|
* Debit: process26asUploadAggregation only (when 26AS total drops for a SETTLED quarter); DN-F-16-{...}, CSV to WFM FORM_16.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
@ -579,31 +579,27 @@ async function run26asMatchAndCreditNote(submission: Form16aSubmission): Promise
|
|||||||
validationNotes: null,
|
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 {
|
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 trnsUniqNo = `F16-CN-${submission.id}-${creditNote.id}-${Date.now()}`;
|
||||||
const claimDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
const docDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||||
const csvRow = {
|
const fyCompact = form16FyCompact(financialYear) || '';
|
||||||
CREDIT_TYPE: 'Form16',
|
const finYearAndQuarter = fyCompact && quarter ? `FY_${fyCompact}_${quarter}` : '';
|
||||||
DEALER_CODE: dealerCode,
|
const csvRow: Record<string, string | number> = {
|
||||||
DEALER_NAME: dealerName,
|
|
||||||
AMOUNT: tdsAmount,
|
|
||||||
FINANCIAL_YEAR: financialYear,
|
|
||||||
QUARTER: quarter,
|
|
||||||
CREDIT_NOTE_NUMBER: cnNumber,
|
|
||||||
TRNS_UNIQ_NO: trnsUniqNo,
|
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`;
|
const fileName = `${cnNumber}.csv`;
|
||||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName);
|
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName, 'credit');
|
||||||
logger.info(`[Form16] Credit note CSV pushed to WFM FORM_16: ${cnNumber}`);
|
logger.info(`[Form16] Credit note CSV pushed to WFM FORM16_CRDT: ${cnNumber}`);
|
||||||
} catch (csvErr: any) {
|
} 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
|
// 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) ----------
|
// ---------- Non-submitted dealers (RE only) ----------
|
||||||
const QUARTERS = ['Q1', 'Q2', 'Q3', 'Q4'] as const;
|
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);
|
await setQuarterStatusDebitIssued(tanNumber, fy, q, debit.id);
|
||||||
debitsCreated++;
|
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 {
|
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 trnsUniqNo = `F16-DN-${creditNote.id}-${debit.id}-${Date.now()}`;
|
||||||
const claimDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
const docDate = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||||
const creditNoteIssueDate = (creditNote as any).issueDate
|
const fyCompact = form16FyCompact(cnFy) || '';
|
||||||
? new Date((creditNote as any).issueDate).toISOString().slice(0, 10).replace(/-/g, '')
|
const finYearAndQuarter = fyCompact && cnQuarter ? `FY ${fyCompact}_${cnQuarter}` : '';
|
||||||
: '';
|
const csvRow: Record<string, string | number> = {
|
||||||
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,
|
|
||||||
TRNS_UNIQ_NO: trnsUniqNo,
|
TRNS_UNIQ_NO: trnsUniqNo,
|
||||||
CLAIM_DATE: claimDate,
|
TDS_TRNS_ID: debitNum,
|
||||||
CREDIT_NOTE_DATE: creditNoteIssueDate,
|
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`;
|
const fileName = `${debitNum}.csv`;
|
||||||
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName);
|
await wfmFileService.generateForm16IncomingCSV([csvRow], fileName, 'debit');
|
||||||
logger.info(`[Form16] Debit note CSV pushed to WFM FORM_16: ${debitNum}`);
|
logger.info(`[Form16] Debit note CSV pushed to WFM FORM16_DEBT: ${debitNum}`);
|
||||||
} catch (csvErr: any) {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
const { notificationService } = await import('./notification.service');
|
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';
|
const title = 'Form 16 – 26AS data updated';
|
||||||
if (reUserIds.length > 0 && n.templateRe) {
|
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). */
|
/** 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_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_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_CREDIT_INCOMING = path.join('WFM-QRE', 'INCOMING', 'WFM_MAIN', 'FORM16_CRDT');
|
||||||
const DEFAULT_FORM16_OUTGOING = path.join('WFM-QRE', 'OUTGOING', 'WFM_SAP_MAIN', 'FORM_16');
|
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
|
* 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 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.
|
* Paths are cross-platform; set WFM_BASE_PATH (and optional path overrides) in .env for production.
|
||||||
*/
|
*/
|
||||||
export class WFMFileService {
|
export class WFMFileService {
|
||||||
@ -20,9 +21,11 @@ export class WFMFileService {
|
|||||||
private incomingNonGstClaimsPath: string;
|
private incomingNonGstClaimsPath: string;
|
||||||
private outgoingGstClaimsPath: string;
|
private outgoingGstClaimsPath: string;
|
||||||
private outgoingNonGstClaimsPath: string;
|
private outgoingNonGstClaimsPath: string;
|
||||||
/** Form 16: INCOMING/WFM_MAIN/FORM_16 */
|
/** Form 16 credit notes: INCOMING/WFM_MAIN/FORM16_CRDT */
|
||||||
private form16IncomingPath: string;
|
private form16IncomingCreditPath: string;
|
||||||
/** Form 16: OUTGOING/WFM_SAP_MAIN/FORM_16 */
|
/** 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;
|
private form16OutgoingPath: string;
|
||||||
|
|
||||||
constructor() {
|
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.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.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.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;
|
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).
|
* 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 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 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;
|
const maxRetries = 3;
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
|
|
||||||
while (retryCount <= maxRetries) {
|
while (retryCount <= maxRetries) {
|
||||||
try {
|
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);
|
this.ensureDirectoryExists(targetDir);
|
||||||
|
|
||||||
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`);
|
const filePath = path.join(targetDir, fileName.endsWith('.csv') ? fileName : `${fileName}.csv`);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user