credit note format change, handeling versions
This commit is contained in:
parent
9f3327ce38
commit
b3dcaca697
@ -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 generation: run26asMatchAndCreditNote only (on Form 16A submit, match 26AS Section 194Q/Booking F/O → CN-F-16-{dealerCode}-{FY}-{quarter}, ledger, CSV to WFM FORM_16).
|
||||
* Debit: generateForm16DebitNoteForCreditNote (manual) and process26asUploadAggregation (auto when 26AS total drops); DN-F-16-{dc}-{fy}-{q}.
|
||||
* 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).
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
@ -416,23 +416,49 @@ function form16FyCompact(financialYear: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Form 16 credit note number: CN-F-16-DC-FY-Q (CN=credit note, F=form, 16, DC=dealer code, FY=financial year, Q=quarter)
|
||||
* Sanitize certificate number for use in note numbers (alphanumeric and single hyphens only).
|
||||
*/
|
||||
export function formatForm16CreditNoteNumber(dealerCode: string, financialYear: string, quarter: string): string {
|
||||
const dc = (dealerCode || '').trim().replace(/\s+/g, '-') || 'XX';
|
||||
const fy = form16FyCompact(financialYear) || 'XX';
|
||||
const q = normalizeQuarter(quarter) || 'X';
|
||||
return `CN-F-16-${dc}-${fy}-${q}`;
|
||||
function sanitizeCertificateNumber(raw: string): string {
|
||||
const s = (raw || '').trim().replace(/\s+/g, '-').replace(/[^A-Za-z0-9-]/g, '') || '';
|
||||
return s || 'XX';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form 16 debit note number: DN-F-16-DC-FY-Q (DN=debit note, F=form, 16, DC=dealer code, FY=financial year, Q=quarter)
|
||||
* Form 16 credit note number: CN-F-16-{certificateNumber}-{dealerCode}-{FY}-{quarter}-V{version}
|
||||
* Supports revised 26AS / Form 16 resubmission versioning.
|
||||
*/
|
||||
export function formatForm16DebitNoteNumber(dealerCode: string, financialYear: string, quarter: string): string {
|
||||
export function formatForm16CreditNoteNumber(
|
||||
dealerCode: string,
|
||||
financialYear: string,
|
||||
quarter: string,
|
||||
certificateNumber: string,
|
||||
version: number = 1
|
||||
): string {
|
||||
const cert = sanitizeCertificateNumber(certificateNumber);
|
||||
const dc = (dealerCode || '').trim().replace(/\s+/g, '-') || 'XX';
|
||||
const fy = form16FyCompact(financialYear) || 'XX';
|
||||
const q = normalizeQuarter(quarter) || 'X';
|
||||
return `DN-F-16-${dc}-${fy}-${q}`;
|
||||
const v = Math.max(1, Math.floor(version));
|
||||
return `CN-F-16-${cert}-${dc}-${fy}-${q}-V${v}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form 16 debit note number: DN-F-16-{creditNoteCertificateNumber}-{dc}-{fy}-{q}-V{version}
|
||||
* Uses the certificate number of the credit note being reversed (same Form 16A certificate that led to that credit note).
|
||||
*/
|
||||
export function formatForm16DebitNoteNumber(
|
||||
dealerCode: string,
|
||||
financialYear: string,
|
||||
quarter: string,
|
||||
version: number = 1,
|
||||
creditNoteCertificateNumber: string = ''
|
||||
): string {
|
||||
const cert = sanitizeCertificateNumber(creditNoteCertificateNumber) || 'XX';
|
||||
const dc = (dealerCode || '').trim().replace(/\s+/g, '-') || 'XX';
|
||||
const fy = form16FyCompact(financialYear) || 'XX';
|
||||
const q = normalizeQuarter(quarter) || 'X';
|
||||
const v = Math.max(1, Math.floor(version));
|
||||
return `DN-F-16-${cert}-${dc}-${fy}-${q}-V${v}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -520,9 +546,11 @@ async function run26asMatchAndCreditNote(submission: Form16aSubmission): Promise
|
||||
}
|
||||
}
|
||||
|
||||
// Dealer code from submission (set at create from users.employee_number)
|
||||
// Dealer code, certificate number and version from submission (for revised 26AS / Form 16 versioning)
|
||||
const dealerCode = (sub.dealerCode || '').toString().trim();
|
||||
const cnNumber = formatForm16CreditNoteNumber(dealerCode, financialYear, quarter);
|
||||
const certificateNumber = (sub.form16aNumber || '').toString().trim();
|
||||
const version = typeof sub.version === 'number' && sub.version >= 1 ? sub.version : 1;
|
||||
const cnNumber = formatForm16CreditNoteNumber(dealerCode, financialYear, quarter, certificateNumber, version);
|
||||
const now = new Date();
|
||||
const creditNote = await Form16CreditNote.create({
|
||||
submissionId: submission.id,
|
||||
@ -734,6 +762,14 @@ export async function createSubmission(
|
||||
logger.info(
|
||||
`[Form16] Submission match complete: requestId=${requestId}, status=${validationStatus}${validationNotes ? `, notes=${validationNotes}` : ''}${creditNoteNumber ? `, creditNote=${creditNoteNumber}` : ''}.`
|
||||
);
|
||||
// When credit note is issued (completed), set workflow status to CLOSED so the request appears on Closed requests page
|
||||
if (validationStatus === 'success' && creditNoteNumber) {
|
||||
const workflow = await WorkflowRequest.findOne({ where: { requestId }, attributes: ['requestId', 'status'] });
|
||||
if (workflow && (workflow as any).status !== WorkflowStatus.CLOSED) {
|
||||
await workflow.update({ status: WorkflowStatus.CLOSED });
|
||||
logger.info(`[Form16] Request ${requestId} set to CLOSED (credit note issued).`);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(
|
||||
`[Form16] 26AS match/credit note error for requestId=${requestId}: Form 16A TAN=${(submission as any).tanNumber}, FY=${(submission as any).financialYear}, Quarter=${(submission as any).quarter}, TDS amount=${(submission as any).tdsAmount}. Error:`,
|
||||
@ -1272,7 +1308,7 @@ export async function getCreditNoteById(creditNoteId: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* RE only. Generate debit note for a credit note (Form 16). Creates Form16DebitNote with DN-F-16-DC-FY-Q format and pushes CSV to WFM INCOMING/WFM_MAIN/FORM_16 for SAP.
|
||||
* 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,
|
||||
@ -1282,16 +1318,18 @@ export async function generateForm16DebitNoteForCreditNote(
|
||||
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'] }],
|
||||
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 from submission (set at Form 16 submit from users.employee_number)
|
||||
// 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 dnNumber = formatForm16DebitNoteNumber(dealerCode || 'UNKNOWN', financialYear, 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,
|
||||
@ -2032,12 +2070,14 @@ export async function process26asUploadAggregation(uploadLogId: number): Promise
|
||||
});
|
||||
if (creditNote) {
|
||||
const amount = parseFloat(String((creditNote as any).amount ?? 0));
|
||||
const submission = await Form16aSubmission.findByPk((creditNote as any).submissionId, { attributes: ['dealerCode'] });
|
||||
// Dealer code from submission (set at Form 16 submit from users.employee_number)
|
||||
const submission = await Form16aSubmission.findByPk((creditNote as any).submissionId, { attributes: ['dealerCode', 'version', 'form16aNumber'] });
|
||||
// Dealer code, version and certificate number from submission (DN uses same cert as the credit note being reversed)
|
||||
const dealerCode = submission ? ((submission as any).dealerCode || '').toString().trim() : '';
|
||||
const version = typeof (submission as any)?.version === 'number' && (submission as any).version >= 1 ? (submission as any).version : 1;
|
||||
const creditNoteCertNumber = submission ? ((submission as any).form16aNumber || '').toString().trim() : '';
|
||||
const cnFy = (creditNote as any).financialYear || fy;
|
||||
const cnQuarter = (creditNote as any).quarter || q;
|
||||
const debitNum = formatForm16DebitNoteNumber(dealerCode || 'XX', cnFy, cnQuarter);
|
||||
const debitNum = formatForm16DebitNoteNumber(dealerCode || 'XX', cnFy, cnQuarter, version, creditNoteCertNumber);
|
||||
const now = new Date();
|
||||
const debit = await Form16DebitNote.create({
|
||||
creditNoteId: creditNote.id,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user