From 41b444a190767740b929b6eb7a626e3316ad62dd Mon Sep 17 00:00:00 2001 From: seaznCode Date: Wed, 14 Jan 2026 18:11:22 +0100 Subject: [PATCH] feat: enhance contract upload process to handle missing templates gracefully and return skipped uploads --- .../DocumentTemplateController.js | 4 +- .../documents/ContractUploadController.js | 105 ++++++++++++------ 2 files changed, 72 insertions(+), 37 deletions(-) diff --git a/controller/documentTemplate/DocumentTemplateController.js b/controller/documentTemplate/DocumentTemplateController.js index db7a056..7ade45d 100644 --- a/controller/documentTemplate/DocumentTemplateController.js +++ b/controller/documentTemplate/DocumentTemplateController.js @@ -1459,7 +1459,9 @@ exports.previewLatestForMe = async (req, res) => { // Find the latest active template for this user type const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', contractType); if (!latest) { - return res.status(404).json({ error: 'No active template found for your user type' }); + logger.info('[previewLatestForMe] no active template', { userId: targetUserId, userType, contractType }); + // Return 200 with empty body so clients can show a friendly empty state. + return res.status(200).send(''); } // Fetch template HTML from storage diff --git a/controller/documents/ContractUploadController.js b/controller/documents/ContractUploadController.js index 2bf66da..917b0d2 100644 --- a/controller/documents/ContractUploadController.js +++ b/controller/documents/ContractUploadController.js @@ -50,30 +50,39 @@ class ContractUploadController { } const uploads = []; - // Primary contract - uploads.push(await ContractUploadService.uploadContract({ - userId, - file, - documentType: 'contract', - contractCategory: 'personal', - unitOfWork, - contractData, - signatureImage: signatureMeta ? signatureMeta.base64 : null, - contract_type: 'contract', - user_type: 'personal' - })); + const skipped = []; + const tryUpload = async ({ contract_type, fileOverride }) => { + try { + const result = await ContractUploadService.uploadContract({ + userId, + file: fileOverride, + documentType: 'contract', + contractCategory: 'personal', + unitOfWork, + contractData, + signatureImage: signatureMeta ? signatureMeta.base64 : null, + contract_type, + user_type: 'personal' + }); + uploads.push({ contract_type, ...result }); + return true; + } catch (e) { + const msg = (e && e.message) ? String(e.message) : ''; + if (msg.includes('No active') && msg.includes('template found')) { + skipped.push({ contract_type, reason: 'no_active_template' }); + logger.info('[ContractUploadController] template missing, skipping', { userId, contract_type }); + return false; + } + throw e; + } + }; - // GDPR contract (auto-generated from latest GDPR template) - uploads.push(await ContractUploadService.uploadContract({ - userId, - documentType: 'contract', - contractCategory: 'personal', - unitOfWork, - contractData, - signatureImage: signatureMeta ? signatureMeta.base64 : null, - contract_type: 'gdpr', - user_type: 'personal' - })); + const didUploadContract = await tryUpload({ contract_type: 'contract', fileOverride: file }); + const didUploadGdpr = await tryUpload({ contract_type: 'gdpr', fileOverride: undefined }); + + if (!didUploadContract && !didUploadGdpr) { + throw new Error('No active documents are available at this moment.'); + } // Cleanup standalone signature record after contracts are saved if (signatureMeta) { @@ -83,7 +92,7 @@ class ContractUploadController { await unitOfWork.commit(); logger.info(`[ContractUploadController] uploadPersonalContract success for userId: ${userId}`); - res.json({ success: true, uploads, downloadUrls: uploads.map(u => u.url || null) }); + res.json({ success: true, uploads, skipped, downloadUrls: uploads.map(u => u.url || null) }); } catch (error) { logger.error(`[ContractUploadController] uploadPersonalContract error for userId: ${userId}`, { error }); await unitOfWork.rollback(error); @@ -116,24 +125,48 @@ class ContractUploadController { logger.info('[ContractUploadController] signature stored for company user', { userId, bytes: signatureMeta.size }); } - const result = await ContractUploadService.uploadContract({ - userId, - file, - documentType: 'contract', - contractCategory: 'company', - unitOfWork, - contractData, - signatureImage: signatureMeta ? signatureMeta.base64 : null, - contract_type: 'contract', - user_type: 'company' - }); + const uploads = []; + const skipped = []; + const tryUpload = async ({ contract_type, fileOverride }) => { + try { + const result = await ContractUploadService.uploadContract({ + userId, + file: fileOverride, + documentType: 'contract', + contractCategory: 'company', + unitOfWork, + contractData, + signatureImage: signatureMeta ? signatureMeta.base64 : null, + contract_type, + user_type: 'company' + }); + uploads.push({ contract_type, ...result }); + return true; + } catch (e) { + const msg = (e && e.message) ? String(e.message) : ''; + if (msg.includes('No active') && msg.includes('template found')) { + skipped.push({ contract_type, reason: 'no_active_template' }); + logger.info('[ContractUploadController] template missing, skipping', { userId, contract_type }); + return false; + } + throw e; + } + }; + + const didUploadContract = await tryUpload({ contract_type: 'contract', fileOverride: file }); + const didUploadGdpr = await tryUpload({ contract_type: 'gdpr', fileOverride: undefined }); + + if (!didUploadContract && !didUploadGdpr) { + throw new Error('No active documents are available at this moment.'); + } + await unitOfWork.commit(); if (signatureMeta) { await repo.deleteSignatureDocumentsForUser(userId); logger.info('[ContractUploadController] signature cleanup completed for company user', { userId }); } logger.info(`[ContractUploadController] uploadCompanyContract success for userId: ${userId}`); - res.json({ success: true, upload: result, downloadUrl: result.url || null }); + res.json({ success: true, uploads, skipped, downloadUrls: uploads.map(u => u.url || null) }); } catch (error) { logger.error(`[ContractUploadController] uploadCompanyContract error for userId: ${userId}`, { error }); await unitOfWork.rollback(error);