feat: implement buildTemplateVars function to enhance contract data handling with user profile integration

This commit is contained in:
seaznCode 2026-01-14 16:57:58 +01:00
parent f286d44353
commit 4aead9bedc

View File

@ -3,6 +3,7 @@ const { uploadBuffer, s3: exoS3 } = require('../../utils/exoscaleUploader');
const PDFDocument = require('pdfkit'); const PDFDocument = require('pdfkit');
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3'); const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const DocumentTemplateService = require('../template/DocumentTemplateService'); const DocumentTemplateService = require('../template/DocumentTemplateService');
const db = require('../../database/database');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { logger } = require('../../middleware/logger'); const { logger } = require('../../middleware/logger');
@ -51,6 +52,116 @@ function fillTemplate(template, data) {
return template.replace(/{{(\w+)}}/g, (_, key) => data[key] || ''); return template.replace(/{{(\w+)}}/g, (_, key) => data[key] || '');
} }
// Build placeholder variables by combining DB profile data with any contractData overrides (for both personal/company).
async function buildTemplateVars({ userId, user_type, contractData = {}, unitOfWork }) {
const vars = {};
const setIfEmpty = (key, val) => {
if (val === undefined || val === null) return;
const str = String(val).trim();
if (!str) return;
if (!vars[key] || String(vars[key]).trim() === '') {
vars[key] = str;
}
};
// Initialize known placeholders to empty so replacement removes them if no value exists
[
'fullName','address','zip_code','city','country','phone','fullAddress','email',
'companyFullAddress','companyName','registrationNumber','companyAddress','companyZipCode','companyCity','companyEmail','companyPhone',
'contactPersonName','contactPersonPhone','companyCompanyName','companyRegistrationNumber','currentDate'
].forEach((k) => { vars[k] = ''; });
// Current date placeholder (matches preview controller format)
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
vars.currentDate = `${pad(now.getDate())}.${pad(now.getMonth() + 1)}.${now.getFullYear()} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
// Load base user row for email fallback
try {
const [userRows] = await unitOfWork.connection.query('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
const u = Array.isArray(userRows) ? userRows[0] : userRows;
if (u) {
setIfEmpty('email', u.email);
setIfEmpty('fullName', `${u.first_name || ''} ${u.last_name || ''}`);
setIfEmpty('address', u.address || u.street || u.street_address);
setIfEmpty('zip_code', u.zip_code || u.zip || u.postal_code || u.postalCode);
setIfEmpty('city', u.city || u.town);
setIfEmpty('country', u.country);
setIfEmpty('phone', u.phone || u.phone_secondary || u.mobile);
}
} catch (ignored) {}
if (user_type === 'personal') {
try {
const [rows] = await unitOfWork.connection.query('SELECT * FROM personal_profiles WHERE user_id = ? LIMIT 1', [userId]);
const p = Array.isArray(rows) ? rows[0] : rows;
if (p) {
setIfEmpty('fullName', `${p.first_name || ''} ${p.last_name || ''}`);
setIfEmpty('address', p.address);
setIfEmpty('zip_code', p.zip_code);
setIfEmpty('city', p.city);
setIfEmpty('country', p.country);
setIfEmpty('phone', p.phone || p.phone_secondary);
setIfEmpty('fullAddress', p.full_address);
}
} catch (ignored) {}
} else if (user_type === 'company') {
try {
const [rows] = await unitOfWork.connection.query('SELECT * FROM company_profiles WHERE user_id = ? LIMIT 1', [userId]);
const c = Array.isArray(rows) ? rows[0] : rows;
if (c) {
vars.companyName = c.company_name || '';
vars.registrationNumber = c.registration_number || '';
vars.companyAddress = c.address || '';
vars.address = vars.companyAddress;
vars.zip_code = c.zip_code || '';
vars.city = c.city || '';
vars.country = c.country || '';
vars.contactPersonName = c.contact_person_name || '';
vars.contactPersonPhone = c.contact_person_phone || c.phone || '';
vars.companyEmail = c.email || c.company_email || c.contact_email || vars.email || '';
vars.companyPhone = c.phone || c.contact_person_phone || '';
const parts = [];
if (vars.companyAddress) parts.push(vars.companyAddress);
const zipCity = [vars.zip_code, vars.city].filter(Boolean).join(' ');
if (zipCity) parts.push(zipCity);
vars.companyFullAddress = parts.join(', ');
// Prefixed variants used in templates
vars.companyCompanyName = vars.companyName;
vars.companyRegistrationNumber = vars.registrationNumber;
vars.companyZipCode = vars.zip_code;
vars.companyCity = vars.city;
// Display name fallback for signature blocks
setIfEmpty('fullName', vars.contactPersonName || vars.companyName);
}
} catch (ignored) {}
}
// Build fullAddress if still empty
if (!vars.fullAddress) {
const parts = [];
if (vars.address) parts.push(vars.address);
const zipCity = [vars.zip_code, vars.city].filter(Boolean).join(' ');
if (zipCity) parts.push(zipCity);
vars.fullAddress = parts.join(', ');
}
// Overlay contractData to allow request-provided values to win
if (contractData && typeof contractData === 'object') {
Object.entries(contractData).forEach(([key, value]) => {
if (value === undefined || value === null) return;
// Ignore nested objects like confirmations
if (typeof value === 'object') return;
vars[key] = String(value);
});
}
return vars;
}
const DEBUG_PDF_FILES = !!process.env.DEBUG_PDF_FILES; const DEBUG_PDF_FILES = !!process.env.DEBUG_PDF_FILES;
function ensureDebugDir() { function ensureDebugDir() {
const debugDir = path.join(__dirname, '../debug-pdf'); const debugDir = path.join(__dirname, '../debug-pdf');
@ -133,7 +244,8 @@ class ContractUploadService {
contractBody = `Contract for ${contractData.name}\nDate: ${contractData.date}\nEmail: ${contractData.email}\n\nTerms and Conditions...\n`; contractBody = `Contract for ${contractData.name}\nDate: ${contractData.date}\nEmail: ${contractData.email}\n\nTerms and Conditions...\n`;
} }
if (contractData && signatureImage) { const hasExplicitTemplate = !!(templateId || contractTemplate);
if (contractData && signatureImage && hasExplicitTemplate) {
logger.info('ContractUploadService.uploadContract:generating_pdf', { userId }); logger.info('ContractUploadService.uploadContract:generating_pdf', { userId });
// Generate styled PDF // Generate styled PDF
const doc = new PDFDocument({ const doc = new PDFDocument({
@ -193,7 +305,19 @@ class ContractUploadService {
const client = exoS3 || new S3Client({ region: process.env.EXOSCALE_REGION, endpoint: process.env.EXOSCALE_ENDPOINT, forcePathStyle: true, credentials: { accessKeyId: process.env.EXOSCALE_ACCESS_KEY, secretAccessKey: process.env.EXOSCALE_SECRET_KEY } }); const client = exoS3 || new S3Client({ region: process.env.EXOSCALE_REGION, endpoint: process.env.EXOSCALE_ENDPOINT, forcePathStyle: true, credentials: { accessKeyId: process.env.EXOSCALE_ACCESS_KEY, secretAccessKey: process.env.EXOSCALE_SECRET_KEY } });
const obj = await client.send(new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: tmpl.storageKey })); const obj = await client.send(new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: tmpl.storageKey }));
const htmlBuffer = await streamToBuffer(obj.Body); const htmlBuffer = await streamToBuffer(obj.Body);
const htmlTemplate = htmlBuffer.toString('utf-8'); let htmlTemplate = htmlBuffer.toString('utf-8');
// Merge DB-derived vars with request data so placeholders fill like the preview endpoint
const vars = await buildTemplateVars({ userId, user_type, contractData, unitOfWork });
Object.entries(vars).forEach(([key, value]) => {
const re = new RegExp(`{{\s*${key}\s*}}`, 'g');
htmlTemplate = htmlTemplate.replace(re, String(value ?? ''));
});
if (signatureImage) {
const base64 = String(signatureImage).startsWith('data:') ? String(signatureImage).split(',')[1] : String(signatureImage);
const tag = `<img src="data:image/png;base64,${base64}" style="max-width:200px;max-height:100px;">`;
htmlTemplate = htmlTemplate.replace(/{{\s*signatureImage\s*}}/g, tag);
}
// Render HTML to PDF via Puppeteer (closest to preview output) // Render HTML to PDF via Puppeteer (closest to preview output)
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
@ -225,6 +349,7 @@ class ContractUploadService {
documentType, documentType,
contractType: contract_type || 'contract', contractType: contract_type || 'contract',
objectStorageId: uploadResult.objectKey, objectStorageId: uploadResult.objectKey,
signatureBase64: null,
idType: null, idType: null,
idNumber: null, idNumber: null,
expiryDate: null, expiryDate: null,