Merge branch 'laxman_dev' of http://10.10.1.3:2010/sipl/re-workflow-be into CSD_DEV

This commit is contained in:
Arjun Mehar 2026-04-17 19:59:56 +05:30
commit 7d35a1d167
3 changed files with 111 additions and 16 deletions

Binary file not shown.

View File

@ -709,8 +709,7 @@ function sanitizeForm16PdfCertSegment(text: string): string {
/** /**
* PDF file name after successful 26AS match + credit note: * PDF file name after successful 26AS match + credit note:
* [TAN]_[AY]_[Quarter]_[Name and address of deductor]_[Certificate][_Vn].pdf * [TAN]_[Assessment Year]_[Quarter]_[Name and address of deductor]_[Certificate].pdf
* Revised submissions (version > 1) append _V2, _V3, ...
*/ */
function buildForm16CreditNoteSuccessPdfFileName(sub: Form16aSubmission): string { function buildForm16CreditNoteSuccessPdfFileName(sub: Form16aSubmission): string {
const tan = normalizeTanNumber(String(sub.tanNumber || '')) const tan = normalizeTanNumber(String(sub.tanNumber || ''))
@ -730,13 +729,12 @@ function buildForm16CreditNoteSuccessPdfFileName(sub: Form16aSubmission): string
if (!nameAddr) nameAddr = String(sub.deductorName || 'Deductor').trim(); if (!nameAddr) nameAddr = String(sub.deductorName || 'Deductor').trim();
let deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, 150); let deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, 150);
const certSan = sanitizeForm16PdfCertSegment(String(sub.form16aNumber || '')); const certSan = sanitizeForm16PdfCertSegment(String(sub.form16aNumber || ''));
const ver = typeof sub.version === 'number' && sub.version > 1 ? `_V${sub.version}` : ''; let base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}`;
let base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}${ver}`;
if (base.length > 220) { if (base.length > 220) {
const over = base.length - 220; const over = base.length - 220;
const shorter = Math.max(20, deductorSan.length - over - 5); const shorter = Math.max(20, deductorSan.length - over - 5);
deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, shorter); deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, shorter);
base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}${ver}`; base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}`;
} }
return `${base}.pdf`; return `${base}.pdf`;
} }
@ -747,6 +745,13 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: {
oldRelativePath: string; oldRelativePath: string;
}): Promise<void> { }): Promise<void> {
const { submissionId, requestId, oldRelativePath } = params; const { submissionId, requestId, oldRelativePath } = params;
logger.info('[Form16] PDF rename flow start', {
submissionId,
requestId,
oldRelativePath,
nodeEnv: process.env.NODE_ENV,
gcpBucket: process.env.GCP_BUCKET_NAME || null,
});
const oldPathNorm = String(oldRelativePath || '').replace(/\\/g, '/').trim(); const oldPathNorm = String(oldRelativePath || '').replace(/\\/g, '/').trim();
if (!oldPathNorm || oldPathNorm.includes('..') || !oldPathNorm.startsWith('requests/')) { if (!oldPathNorm || oldPathNorm.includes('..') || !oldPathNorm.startsWith('requests/')) {
logger.warn('[Form16] Skip PDF rename: invalid storage path', { oldPathNorm }); logger.warn('[Form16] Skip PDF rename: invalid storage path', { oldPathNorm });
@ -754,16 +759,39 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: {
} }
const sub = await Form16aSubmission.findByPk(submissionId); const sub = await Form16aSubmission.findByPk(submissionId);
if (!sub) return; if (!sub) {
logger.warn('[Form16] Skip PDF rename: submission not found', { submissionId, requestId });
return;
}
const newFileName = buildForm16CreditNoteSuccessPdfFileName(sub); const newFileName = buildForm16CreditNoteSuccessPdfFileName(sub);
logger.info('[Form16] PDF rename target name computed', {
submissionId,
requestId,
newFileName,
tanNumber: (sub as any).tanNumber || null,
financialYear: (sub as any).financialYear || null,
quarter: (sub as any).quarter || null,
form16aNumber: (sub as any).form16aNumber || null,
});
try { try {
const result = await gcsStorageService.renameRequestDocumentFile({ const result = await gcsStorageService.renameRequestDocumentFile({
oldRelativePath: oldPathNorm, oldRelativePath: oldPathNorm,
newFileName, newFileName,
}); });
logger.info('[Form16] Storage rename success', {
submissionId,
requestId,
oldPathNorm,
renamedFilePath: result.filePath,
renamedStorageUrlPrefix: String(result.storageUrl || '').slice(0, 120),
});
await sub.update({ documentUrl: result.storageUrl }); await sub.update({ documentUrl: result.storageUrl });
logger.info('[Form16] Submission documentUrl updated after rename', {
submissionId,
requestId,
});
const doc = await Document.findOne({ const doc = await Document.findOne({
where: { requestId, filePath: oldPathNorm }, where: { requestId, filePath: oldPathNorm },
@ -778,6 +806,13 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: {
filePath: fp, filePath: fp,
storageUrl: su, storageUrl: su,
}); });
logger.info('[Form16] Document metadata updated after rename', {
requestId,
submissionId,
documentId: (doc as any).id || null,
oldPathNorm,
newPath: fp,
});
} else { } else {
logger.warn('[Form16] PDF renamed; documents row not found for path', { requestId, oldPathNorm }); logger.warn('[Form16] PDF renamed; documents row not found for path', { requestId, oldPathNorm });
} }
@ -1333,6 +1368,12 @@ export async function createSubmission(
); );
// When credit note is issued (completed), set workflow status to CLOSED so the request appears on Closed requests page // When credit note is issued (completed), set workflow status to CLOSED so the request appears on Closed requests page
if (validationStatus === 'success' && creditNoteNumber) { if (validationStatus === 'success' && creditNoteNumber) {
logger.info('[Form16] Success path reached; triggering PDF rename', {
requestId,
submissionId: submission.id,
creditNoteNumber,
uploadFilePath,
});
const workflow = await WorkflowRequest.findOne({ where: { requestId }, attributes: ['requestId', 'status'] }); const workflow = await WorkflowRequest.findOne({ where: { requestId }, attributes: ['requestId', 'status'] });
if (workflow && (workflow as any).status !== WorkflowStatus.CLOSED) { if (workflow && (workflow as any).status !== WorkflowStatus.CLOSED) {
await workflow.update({ status: WorkflowStatus.CLOSED }); await workflow.update({ status: WorkflowStatus.CLOSED });
@ -1343,6 +1384,17 @@ export async function createSubmission(
requestId, requestId,
oldRelativePath: uploadFilePath.replace(/\\/g, '/'), oldRelativePath: uploadFilePath.replace(/\\/g, '/'),
}); });
logger.info('[Form16] PDF rename call completed', {
requestId,
submissionId: submission.id,
});
} else {
logger.info('[Form16] PDF rename not triggered (submission not successful)', {
requestId,
submissionId: submission.id,
validationStatus,
creditNoteNumber: creditNoteNumber || null,
});
} }
} catch (err: any) { } catch (err: any) {
logger.error( logger.error(

View File

@ -36,6 +36,28 @@ class GCSStorageService {
private bucketName: string = ''; private bucketName: string = '';
private projectId: string = ''; private projectId: string = '';
private getAbsoluteUploadPath(relativePath: string): string {
return path.join(UPLOAD_DIR, ...relativePath.replace(/\\/g, '/').split('/').filter(Boolean));
}
private renameLocalStoredFile(oldNorm: string, newRelativePath: string, newFileName: string): {
storageUrl: string;
filePath: string;
fileName: string;
} {
const fullOld = this.getAbsoluteUploadPath(oldNorm);
const fullNew = this.getAbsoluteUploadPath(newRelativePath);
if (!fs.existsSync(fullOld)) {
throw new Error(`Local file not found: ${oldNorm}`);
}
fs.mkdirSync(path.dirname(fullNew), { recursive: true });
fs.renameSync(fullOld, fullNew);
const normalizedPath = newRelativePath.replace(/\\/g, '/');
const storageUrl = `/uploads/${normalizedPath}`;
logger.info('[GCS] Renamed local Form 16 document', { oldNorm, newRelativePath: normalizedPath });
return { storageUrl, filePath: normalizedPath, fileName: newFileName };
}
constructor() { constructor() {
// Check if Google Secret Manager should be used // Check if Google Secret Manager should be used
const useGoogleSecretManager = process.env.USE_GOOGLE_SECRET_MANAGER === 'true'; const useGoogleSecretManager = process.env.USE_GOOGLE_SECRET_MANAGER === 'true';
@ -507,6 +529,14 @@ class GCSStorageService {
}): Promise<{ storageUrl: string; filePath: string; fileName: string }> { }): Promise<{ storageUrl: string; filePath: string; fileName: string }> {
const { oldRelativePath } = options; const { oldRelativePath } = options;
let newFileName = path.basename(String(options.newFileName || '').trim()); let newFileName = path.basename(String(options.newFileName || '').trim());
logger.info('[GCS] renameRequestDocumentFile called', {
oldRelativePath,
requestedNewFileName: options.newFileName,
sanitizedNewFileName: newFileName,
nodeEnv: process.env.NODE_ENV,
bucket: this.bucketName || null,
useGoogleSecretManager: process.env.USE_GOOGLE_SECRET_MANAGER || null,
});
if (!newFileName || newFileName === '.' || newFileName === '..') { if (!newFileName || newFileName === '.' || newFileName === '..') {
throw new Error('Invalid new file name'); throw new Error('Invalid new file name');
} }
@ -517,18 +547,24 @@ class GCSStorageService {
const oldNorm = oldRelativePath.replace(/\\/g, '/'); const oldNorm = oldRelativePath.replace(/\\/g, '/');
const dir = path.posix.dirname(oldNorm); const dir = path.posix.dirname(oldNorm);
const newRelativePath = `${dir}/${newFileName}`; const newRelativePath = `${dir}/${newFileName}`;
const localOldExists = fs.existsSync(this.getAbsoluteUploadPath(oldNorm));
logger.info('[GCS] renameRequestDocumentFile storage presence check', {
oldNorm,
newRelativePath,
localOldExists,
gcsConfigured: this.isConfigured(),
});
if (!this.isConfigured()) { if (!this.isConfigured()) {
const fullOld = path.join(UPLOAD_DIR, ...oldNorm.split('/').filter(Boolean)); logger.info('[GCS] renameRequestDocumentFile using local storage mode');
const fullNew = path.join(UPLOAD_DIR, ...newRelativePath.split('/').filter(Boolean)); return this.renameLocalStoredFile(oldNorm, newRelativePath, newFileName);
if (!fs.existsSync(fullOld)) { }
throw new Error(`Local file not found: ${oldNorm}`);
} // Important: if upload previously fell back to local storage, rename must also happen locally
fs.mkdirSync(path.dirname(fullNew), { recursive: true }); // even when GCS is configured in the current environment.
fs.renameSync(fullOld, fullNew); if (localOldExists) {
const storageUrl = `/uploads/${newRelativePath.replace(/\\/g, '/')}`; logger.info('[GCS] renameRequestDocumentFile detected local-stored file; using local rename');
logger.info('[GCS] Renamed local Form 16 document', { oldNorm, newRelativePath }); return this.renameLocalStoredFile(oldNorm, newRelativePath, newFileName);
return { storageUrl, filePath: newRelativePath.replace(/\\/g, '/'), fileName: newFileName };
} }
if (!this.storage) { if (!this.storage) {
@ -539,12 +575,19 @@ class GCSStorageService {
const bucket = this.storage.bucket(this.bucketName); const bucket = this.storage.bucket(this.bucketName);
const oldFile = bucket.file(oldNorm); const oldFile = bucket.file(oldNorm);
const [exists] = await oldFile.exists(); const [exists] = await oldFile.exists();
logger.info('[GCS] renameRequestDocumentFile GCS existence check', {
oldNorm,
exists,
newRelativePath,
});
if (!exists) { if (!exists) {
throw new Error(`GCS file not found: ${oldNorm}`); throw new Error(`GCS file not found: ${oldNorm}`);
} }
const newFile = bucket.file(newRelativePath); const newFile = bucket.file(newRelativePath);
await oldFile.copy(newFile); await oldFile.copy(newFile);
logger.info('[GCS] renameRequestDocumentFile copy success', { from: oldNorm, to: newRelativePath });
await oldFile.delete(); await oldFile.delete();
logger.info('[GCS] renameRequestDocumentFile delete old success', { oldNorm });
let publicUrl: string; let publicUrl: string;
try { try {