feat: enhance contract upload process to handle missing templates gracefully and return skipped uploads

This commit is contained in:
seaznCode 2026-01-14 18:11:22 +01:00
parent 4aead9bedc
commit 41b444a190
2 changed files with 72 additions and 37 deletions

View File

@ -1459,7 +1459,9 @@ exports.previewLatestForMe = async (req, res) => {
// Find the latest active template for this user type // Find the latest active template for this user type
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', contractType); const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', contractType);
if (!latest) { 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 // Fetch template HTML from storage

View File

@ -50,30 +50,39 @@ class ContractUploadController {
} }
const uploads = []; const uploads = [];
// Primary contract const skipped = [];
uploads.push(await ContractUploadService.uploadContract({ const tryUpload = async ({ contract_type, fileOverride }) => {
try {
const result = await ContractUploadService.uploadContract({
userId, userId,
file, file: fileOverride,
documentType: 'contract', documentType: 'contract',
contractCategory: 'personal', contractCategory: 'personal',
unitOfWork, unitOfWork,
contractData, contractData,
signatureImage: signatureMeta ? signatureMeta.base64 : null, signatureImage: signatureMeta ? signatureMeta.base64 : null,
contract_type: 'contract', contract_type,
user_type: 'personal' 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) const didUploadContract = await tryUpload({ contract_type: 'contract', fileOverride: file });
uploads.push(await ContractUploadService.uploadContract({ const didUploadGdpr = await tryUpload({ contract_type: 'gdpr', fileOverride: undefined });
userId,
documentType: 'contract', if (!didUploadContract && !didUploadGdpr) {
contractCategory: 'personal', throw new Error('No active documents are available at this moment.');
unitOfWork, }
contractData,
signatureImage: signatureMeta ? signatureMeta.base64 : null,
contract_type: 'gdpr',
user_type: 'personal'
}));
// Cleanup standalone signature record after contracts are saved // Cleanup standalone signature record after contracts are saved
if (signatureMeta) { if (signatureMeta) {
@ -83,7 +92,7 @@ class ContractUploadController {
await unitOfWork.commit(); await unitOfWork.commit();
logger.info(`[ContractUploadController] uploadPersonalContract success for userId: ${userId}`); 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) { } catch (error) {
logger.error(`[ContractUploadController] uploadPersonalContract error for userId: ${userId}`, { error }); logger.error(`[ContractUploadController] uploadPersonalContract error for userId: ${userId}`, { error });
await unitOfWork.rollback(error); await unitOfWork.rollback(error);
@ -116,24 +125,48 @@ class ContractUploadController {
logger.info('[ContractUploadController] signature stored for company user', { userId, bytes: signatureMeta.size }); logger.info('[ContractUploadController] signature stored for company user', { userId, bytes: signatureMeta.size });
} }
const uploads = [];
const skipped = [];
const tryUpload = async ({ contract_type, fileOverride }) => {
try {
const result = await ContractUploadService.uploadContract({ const result = await ContractUploadService.uploadContract({
userId, userId,
file, file: fileOverride,
documentType: 'contract', documentType: 'contract',
contractCategory: 'company', contractCategory: 'company',
unitOfWork, unitOfWork,
contractData, contractData,
signatureImage: signatureMeta ? signatureMeta.base64 : null, signatureImage: signatureMeta ? signatureMeta.base64 : null,
contract_type: 'contract', contract_type,
user_type: 'company' 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(); await unitOfWork.commit();
if (signatureMeta) { if (signatureMeta) {
await repo.deleteSignatureDocumentsForUser(userId); await repo.deleteSignatureDocumentsForUser(userId);
logger.info('[ContractUploadController] signature cleanup completed for company user', { userId }); logger.info('[ContractUploadController] signature cleanup completed for company user', { userId });
} }
logger.info(`[ContractUploadController] uploadCompanyContract success for userId: ${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) { } catch (error) {
logger.error(`[ContractUploadController] uploadCompanyContract error for userId: ${userId}`, { error }); logger.error(`[ContractUploadController] uploadCompanyContract error for userId: ${userId}`, { error });
await unitOfWork.rollback(error); await unitOfWork.rollback(error);