diff --git a/form 16 build (6 march).zip b/form 16 build (6 march).zip deleted file mode 100644 index 9182b8e..0000000 Binary files a/form 16 build (6 march).zip and /dev/null differ diff --git a/src/services/form16.service.ts b/src/services/form16.service.ts index a62c194..d265a76 100644 --- a/src/services/form16.service.ts +++ b/src/services/form16.service.ts @@ -709,8 +709,7 @@ function sanitizeForm16PdfCertSegment(text: string): string { /** * PDF file name after successful 26AS match + credit note: - * [TAN]_[AY]_[Quarter]_[Name and address of deductor]_[Certificate][_Vn].pdf - * Revised submissions (version > 1) append _V2, _V3, ... + * [TAN]_[Assessment Year]_[Quarter]_[Name and address of deductor]_[Certificate].pdf */ function buildForm16CreditNoteSuccessPdfFileName(sub: Form16aSubmission): string { const tan = normalizeTanNumber(String(sub.tanNumber || '')) @@ -730,13 +729,12 @@ function buildForm16CreditNoteSuccessPdfFileName(sub: Form16aSubmission): string if (!nameAddr) nameAddr = String(sub.deductorName || 'Deductor').trim(); let deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, 150); 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}${ver}`; + let base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}`; if (base.length > 220) { const over = base.length - 220; const shorter = Math.max(20, deductorSan.length - over - 5); deductorSan = sanitizeForm16PdfDeductorSegment(nameAddr, shorter); - base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}${ver}`; + base = `${tan}_${ay}_${q}_${deductorSan}_${certSan}`; } return `${base}.pdf`; } @@ -747,6 +745,13 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: { oldRelativePath: string; }): Promise { 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(); if (!oldPathNorm || oldPathNorm.includes('..') || !oldPathNorm.startsWith('requests/')) { logger.warn('[Form16] Skip PDF rename: invalid storage path', { oldPathNorm }); @@ -754,16 +759,39 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: { } 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); + 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 { const result = await gcsStorageService.renameRequestDocumentFile({ oldRelativePath: oldPathNorm, 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 }); + logger.info('[Form16] Submission documentUrl updated after rename', { + submissionId, + requestId, + }); const doc = await Document.findOne({ where: { requestId, filePath: oldPathNorm }, @@ -778,6 +806,13 @@ async function renameForm16SubmissionPdfAfterCreditNote(params: { filePath: fp, storageUrl: su, }); + logger.info('[Form16] Document metadata updated after rename', { + requestId, + submissionId, + documentId: (doc as any).id || null, + oldPathNorm, + newPath: fp, + }); } else { 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 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'] }); if (workflow && (workflow as any).status !== WorkflowStatus.CLOSED) { await workflow.update({ status: WorkflowStatus.CLOSED }); @@ -1343,6 +1384,17 @@ export async function createSubmission( requestId, 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) { logger.error( diff --git a/src/services/gcsStorage.service.ts b/src/services/gcsStorage.service.ts index afdba83..d1febd3 100644 --- a/src/services/gcsStorage.service.ts +++ b/src/services/gcsStorage.service.ts @@ -36,6 +36,28 @@ class GCSStorageService { private bucketName: 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() { // Check if Google Secret Manager should be used const useGoogleSecretManager = process.env.USE_GOOGLE_SECRET_MANAGER === 'true'; @@ -507,6 +529,14 @@ class GCSStorageService { }): Promise<{ storageUrl: string; filePath: string; fileName: string }> { const { oldRelativePath } = options; 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 === '..') { throw new Error('Invalid new file name'); } @@ -517,18 +547,24 @@ class GCSStorageService { const oldNorm = oldRelativePath.replace(/\\/g, '/'); const dir = path.posix.dirname(oldNorm); 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()) { - const fullOld = path.join(UPLOAD_DIR, ...oldNorm.split('/').filter(Boolean)); - const fullNew = path.join(UPLOAD_DIR, ...newRelativePath.split('/').filter(Boolean)); - if (!fs.existsSync(fullOld)) { - throw new Error(`Local file not found: ${oldNorm}`); - } - fs.mkdirSync(path.dirname(fullNew), { recursive: true }); - fs.renameSync(fullOld, fullNew); - const storageUrl = `/uploads/${newRelativePath.replace(/\\/g, '/')}`; - logger.info('[GCS] Renamed local Form 16 document', { oldNorm, newRelativePath }); - return { storageUrl, filePath: newRelativePath.replace(/\\/g, '/'), fileName: newFileName }; + logger.info('[GCS] renameRequestDocumentFile using local storage mode'); + return this.renameLocalStoredFile(oldNorm, newRelativePath, newFileName); + } + + // Important: if upload previously fell back to local storage, rename must also happen locally + // even when GCS is configured in the current environment. + if (localOldExists) { + logger.info('[GCS] renameRequestDocumentFile detected local-stored file; using local rename'); + return this.renameLocalStoredFile(oldNorm, newRelativePath, newFileName); } if (!this.storage) { @@ -539,12 +575,19 @@ class GCSStorageService { const bucket = this.storage.bucket(this.bucketName); const oldFile = bucket.file(oldNorm); const [exists] = await oldFile.exists(); + logger.info('[GCS] renameRequestDocumentFile GCS existence check', { + oldNorm, + exists, + newRelativePath, + }); if (!exists) { throw new Error(`GCS file not found: ${oldNorm}`); } const newFile = bucket.file(newRelativePath); await oldFile.copy(newFile); + logger.info('[GCS] renameRequestDocumentFile copy success', { from: oldNorm, to: newRelativePath }); await oldFile.delete(); + logger.info('[GCS] renameRequestDocumentFile delete old success', { oldNorm }); let publicUrl: string; try {