feat: refactor template variable handling and add Profit Planet signature application logic
This commit is contained in:
parent
25c67783d4
commit
7b2eb4dbf0
@ -1515,7 +1515,7 @@ exports.previewLatestForUser = async (req, res) => {
|
|||||||
exports.previewLatestForMe = async (req, res) => {
|
exports.previewLatestForMe = async (req, res) => {
|
||||||
if (!req.user) return res.status(401).json({ error: 'Unauthorized' });
|
if (!req.user) return res.status(401).json({ error: 'Unauthorized' });
|
||||||
const targetUserId = req.user.id || req.user.userId;
|
const targetUserId = req.user.id || req.user.userId;
|
||||||
const userType = req.user.user_type || req.user.userType;
|
const userType = (req.user.user_type || req.user.userType || '').toString().toLowerCase();
|
||||||
if (!targetUserId || !userType) return res.status(400).json({ error: 'Invalid authenticated user' });
|
if (!targetUserId || !userType) return res.status(400).json({ error: 'Invalid authenticated user' });
|
||||||
|
|
||||||
const contractTypeParam = (req.query.contract_type || req.query.contractType || '').toString().toLowerCase();
|
const contractTypeParam = (req.query.contract_type || req.query.contractType || '').toString().toLowerCase();
|
||||||
@ -1545,132 +1545,20 @@ exports.previewLatestForMe = async (req, res) => {
|
|||||||
if (!fileObj.Body) return res.status(404).json({ error: 'Template file not available' });
|
if (!fileObj.Body) return res.status(404).json({ error: 'Template file not available' });
|
||||||
let html = await streamToString(fileObj.Body, latest.id);
|
let html = await streamToString(fileObj.Body, latest.id);
|
||||||
|
|
||||||
// Build variables from the authenticated user's DB data
|
// Build variables using the same logic as contract upload
|
||||||
const vars = {};
|
const uow = new UnitOfWork();
|
||||||
// initialize common placeholders to empty so they get stripped from template even when data is missing
|
await uow.start();
|
||||||
['fullName','address','zip_code','city','country','phone','fullAddress','companyFullAddress','companyName','registrationNumber','companyAddress','companyZipCode','companyCity','companyEmail','companyPhone','contactPersonName','contactPersonPhone','companyCompanyName','companyRegistrationNumber'].forEach(k => { vars[k] = ''; });
|
const vars = await ContractUploadService.buildTemplateVars({
|
||||||
const setIfEmpty = (key, val) => {
|
userId: targetUserId,
|
||||||
if (val === undefined || val === null) return;
|
user_type: userType,
|
||||||
const str = String(val).trim();
|
contractData: {},
|
||||||
if (!str) return;
|
unitOfWork: uow
|
||||||
if (!vars[key] || String(vars[key]).trim() === '') {
|
});
|
||||||
vars[key] = str;
|
await uow.commit();
|
||||||
}
|
|
||||||
};
|
|
||||||
// base fields
|
|
||||||
vars.email = req.user.email || '';
|
|
||||||
const d = new Date();
|
|
||||||
const pad = (n) => String(n).padStart(2, '0');
|
|
||||||
vars.currentDate = `${pad(d.getDate())}.${pad(d.getMonth() + 1)}.${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
||||||
|
|
||||||
if (userType === 'personal') {
|
|
||||||
try {
|
|
||||||
let p = null;
|
|
||||||
try {
|
|
||||||
const [rows] = await db.execute('SELECT * FROM personal_profiles WHERE user_id = ? LIMIT 1', [targetUserId]);
|
|
||||||
p = Array.isArray(rows) ? rows[0] : rows;
|
|
||||||
} catch (ignored) {}
|
|
||||||
|
|
||||||
// Fallback lookup via users.email join (personal_profiles has no email column)
|
|
||||||
if (!p && req.user.email) {
|
|
||||||
try {
|
|
||||||
const [rowsEmail] = await db.execute(
|
|
||||||
'SELECT pp.* FROM personal_profiles pp JOIN users u ON pp.user_id = u.id WHERE u.email = ? LIMIT 1',
|
|
||||||
[req.user.email]
|
|
||||||
);
|
|
||||||
p = Array.isArray(rowsEmail) ? rowsEmail[0] : rowsEmail;
|
|
||||||
} catch (ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p) {
|
|
||||||
setIfEmpty('fullName', `${p.first_name || ''} ${p.last_name || ''}`.trim());
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallbacks from authenticated user payload (helps dummy/local users without profile rows)
|
|
||||||
setIfEmpty('fullName', req.user.fullName || req.user.full_name);
|
|
||||||
setIfEmpty('fullName', `${req.user.first_name || ''} ${req.user.last_name || ''}`);
|
|
||||||
setIfEmpty('fullName', `${req.user.firstName || ''} ${req.user.lastName || ''}`);
|
|
||||||
setIfEmpty('address', req.user.address || req.user.street || req.user.street_address);
|
|
||||||
setIfEmpty('zip_code', req.user.zip_code || req.user.zip || req.user.postalCode || req.user.postal_code);
|
|
||||||
setIfEmpty('city', req.user.city || req.user.town);
|
|
||||||
setIfEmpty('country', req.user.country);
|
|
||||||
setIfEmpty('phone', req.user.phone || req.user.phone_secondary || req.user.mobile);
|
|
||||||
|
|
||||||
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(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('[previewLatestForMe] personal vars', { userId: targetUserId, found: !!p, vars });
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('[previewLatestForMe] personal profile lookup failed', e && e.message);
|
|
||||||
}
|
|
||||||
} else if (userType === 'company') {
|
|
||||||
try {
|
|
||||||
const [rows] = await db.execute('SELECT * FROM company_profiles WHERE user_id = ? LIMIT 1', [targetUserId]);
|
|
||||||
const c = rows && rows[0] ? rows[0] : null;
|
|
||||||
if (c) {
|
|
||||||
setIfEmpty('companyName', c.company_name);
|
|
||||||
setIfEmpty('registrationNumber', c.registration_number);
|
|
||||||
setIfEmpty('companyAddress', c.address);
|
|
||||||
setIfEmpty('address', c.address);
|
|
||||||
setIfEmpty('zip_code', c.zip_code);
|
|
||||||
setIfEmpty('city', c.city);
|
|
||||||
setIfEmpty('country', c.country);
|
|
||||||
setIfEmpty('contactPersonName', c.contact_person_name);
|
|
||||||
setIfEmpty('contactPersonPhone', c.contact_person_phone || c.phone);
|
|
||||||
setIfEmpty('companyEmail', c.email || c.company_email || c.contact_email || req.user.email);
|
|
||||||
setIfEmpty('companyPhone', c.phone || c.contact_person_phone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallbacks from authenticated user payload (helps dummy/local users without profile rows)
|
|
||||||
setIfEmpty('companyName', req.user.companyName || req.user.company_name || req.user.company);
|
|
||||||
setIfEmpty('registrationNumber', req.user.registrationNumber || req.user.registration_number || req.user.vatNumber);
|
|
||||||
setIfEmpty('companyAddress', req.user.address || req.user.street || req.user.street_address);
|
|
||||||
setIfEmpty('address', req.user.address || req.user.street || req.user.street_address);
|
|
||||||
setIfEmpty('zip_code', req.user.zip_code || req.user.zip || req.user.postalCode || req.user.postal_code);
|
|
||||||
setIfEmpty('city', req.user.city || req.user.town);
|
|
||||||
setIfEmpty('country', req.user.country);
|
|
||||||
setIfEmpty('contactPersonName', req.user.contactPersonName || req.user.contact_person_name);
|
|
||||||
setIfEmpty('contactPersonPhone', req.user.contactPersonPhone || req.user.contact_person_phone || req.user.companyPhone || req.user.phone);
|
|
||||||
setIfEmpty('companyEmail', req.user.companyEmail || req.user.email);
|
|
||||||
setIfEmpty('companyPhone', req.user.companyPhone || req.user.phone);
|
|
||||||
|
|
||||||
if (!vars.companyAddress) setIfEmpty('companyAddress', vars.address);
|
|
||||||
if (!vars.address) setIfEmpty('address', vars.companyAddress);
|
|
||||||
|
|
||||||
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(', ');
|
|
||||||
|
|
||||||
// Ensure template-prefixed company placeholders are populated
|
|
||||||
vars.companyCompanyName = vars.companyName || '';
|
|
||||||
vars.companyRegistrationNumber = vars.registrationNumber || '';
|
|
||||||
vars.companyZipCode = vars.zip_code || '';
|
|
||||||
vars.companyCity = vars.city || '';
|
|
||||||
|
|
||||||
// Signature/display name for company preview
|
|
||||||
vars.fullName = vars.contactPersonName || vars.companyName || vars.fullName || '';
|
|
||||||
logger.debug('[previewLatestForMe] company vars', { userId: targetUserId, found: !!c, vars });
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('[previewLatestForMe] company profile lookup failed', e && e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace placeholders
|
// Replace placeholders
|
||||||
Object.entries(vars).forEach(([k, v]) => {
|
Object.entries(vars).forEach(([k, v]) => {
|
||||||
html = html.replace(new RegExp(`{{\\s*${k}\\s*}}`, 'g'), String(v ?? ''));
|
html = html.replace(new RegExp(`{{\s*${k}\s*}}`, 'g'), String(v ?? ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show a friendly placeholder for signature in preview (not signed yet)
|
// Show a friendly placeholder for signature in preview (not signed yet)
|
||||||
|
|||||||
@ -50,7 +50,19 @@ async function streamToBuffer(body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillTemplate(template, data) {
|
function fillTemplate(template, data) {
|
||||||
return template.replace(/{{\s*(\w+)\s*}}/g, (_, key) => data[key] || '');
|
return template.replace(/{{(\w+)}}/g, (_, key) => data[key] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyProfitPlanetSignatureForHtml({ html, userId }) {
|
||||||
|
if (!html) return html;
|
||||||
|
if (!/{{\s*profitplanetSignature\s*}}/i.test(html)) return html;
|
||||||
|
try {
|
||||||
|
const { tag } = await DocumentTemplateService.getProfitPlanetSignatureTag({ maxW: 300, maxH: 300 });
|
||||||
|
return html.replace(/{{\s*profitplanetSignature\s*}}/gi, tag || '');
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('ContractUploadService.uploadContract:profitplanetSignature failed', { userId, msg: e.message });
|
||||||
|
return html.replace(/{{\s*profitplanetSignature\s*}}/gi, '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build placeholder variables by combining DB profile data with any contractData overrides (for both personal/company).
|
// Build placeholder variables by combining DB profile data with any contractData overrides (for both personal/company).
|
||||||
@ -311,7 +323,7 @@ class ContractUploadService {
|
|||||||
// Merge DB-derived vars with request data so placeholders fill like the preview endpoint
|
// Merge DB-derived vars with request data so placeholders fill like the preview endpoint
|
||||||
const vars = await buildTemplateVars({ userId, user_type, contractData, unitOfWork });
|
const vars = await buildTemplateVars({ userId, user_type, contractData, unitOfWork });
|
||||||
Object.entries(vars).forEach(([key, value]) => {
|
Object.entries(vars).forEach(([key, value]) => {
|
||||||
const re = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
const re = new RegExp(`{{\s*${key}\s*}}`, 'g');
|
||||||
htmlTemplate = htmlTemplate.replace(re, String(value ?? ''));
|
htmlTemplate = htmlTemplate.replace(re, String(value ?? ''));
|
||||||
});
|
});
|
||||||
if (signatureImage) {
|
if (signatureImage) {
|
||||||
@ -320,16 +332,8 @@ class ContractUploadService {
|
|||||||
htmlTemplate = htmlTemplate.replace(/{{\s*signatureImage\s*}}/g, tag);
|
htmlTemplate = htmlTemplate.replace(/{{\s*signatureImage\s*}}/g, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply Profit Planet signature stamp if placeholder exists
|
// Apply Profit Planet signature placeholder (if any)
|
||||||
try {
|
htmlTemplate = await applyProfitPlanetSignatureForHtml({ html: htmlTemplate, userId });
|
||||||
if (htmlTemplate.match(/{{\s*profitplanetSignature\s*}}/i)) {
|
|
||||||
const tag = await CompanyStampService.getProfitPlanetSignatureTag({ maxW: 300, maxH: 300 });
|
|
||||||
htmlTemplate = htmlTemplate.replace(/{{\s*profitplanetSignature\s*}}/gi, tag || '');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('ContractUploadService.uploadContract:profitplanetSignature failed', { userId, msg: e.message });
|
|
||||||
htmlTemplate = htmlTemplate.replace(/{{\s*profitplanetSignature\s*}}/gi, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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'] });
|
||||||
@ -392,4 +396,7 @@ class ContractUploadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reuse helper for preview endpoints
|
||||||
|
ContractUploadService.buildTemplateVars = buildTemplateVars;
|
||||||
|
|
||||||
module.exports = ContractUploadService;
|
module.exports = ContractUploadService;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user