feat: implement buildTemplateVars function to enhance contract data handling with user profile integration
This commit is contained in:
parent
f286d44353
commit
4aead9bedc
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user