const DocumentTemplateRepository = require('../../repositories/template/DocumentTemplateRepository');
const UnitOfWork = require('../../database/UnitOfWork');
const { logger } = require('../../middleware/logger');
class DocumentTemplateService {
async listTemplates() {
logger.info('DocumentTemplateService.listTemplates:start');
try {
const templates = await DocumentTemplateRepository.findAll();
logger.info('DocumentTemplateService.listTemplates:success', { count: templates.length });
return templates.map(t => ({
...t,
lang: t.lang
}));
} catch (error) {
logger.error('DocumentTemplateService.listTemplates:error', { error: error.message });
throw error;
}
}
async uploadTemplate(data) {
logger.info('DocumentTemplateService.uploadTemplate:start', { name: data.name, type: data.type });
const uow = new UnitOfWork();
try {
await uow.start();
const allowed = ['personal','company','both'];
const user_type = allowed.includes(data.user_type || data.userType) ? (data.user_type || data.userType) : 'both';
const created = await DocumentTemplateRepository.create({ ...data, user_type }, uow.connection);
await uow.commit();
logger.info('DocumentTemplateService.uploadTemplate:success', { id: created.id });
return created;
} catch (err) {
logger.error('DocumentTemplateService.uploadTemplate:error', { error: err.message });
await uow.rollback(err);
throw err;
}
}
async getTemplate(id) {
logger.info('DocumentTemplateService.getTemplate:start', { id });
try {
const template = await DocumentTemplateRepository.findById(id);
if (!template) {
logger.warn('DocumentTemplateService.getTemplate:not_found', { id });
return null;
}
logger.debug('DocumentTemplateService.getTemplate:meta', { id, storageKey: template.storageKey, lang: template.lang, version: template.version });
logger.info('DocumentTemplateService.getTemplate:success', { id });
return { ...template, lang: template.lang };
} catch (error) {
logger.error('DocumentTemplateService.getTemplate:error', { id, error: error.message });
throw error;
}
}
async deleteTemplate(id) {
logger.info('DocumentTemplateService.deleteTemplate:start', { id });
const uow = new UnitOfWork();
try {
await uow.start();
await DocumentTemplateRepository.delete(id, uow.connection);
await uow.commit();
logger.info('DocumentTemplateService.deleteTemplate:success', { id });
return true;
} catch (err) {
logger.error('DocumentTemplateService.deleteTemplate:error', { id, error: err.message });
await uow.rollback(err);
throw err;
}
}
async updateTemplate(id, data) {
logger.info('DocumentTemplateService.updateTemplate:start', { id });
const uow = new UnitOfWork();
try {
await uow.start();
const current = await DocumentTemplateRepository.findById(id, uow.connection);
if (!current) {
logger.warn('DocumentTemplateService.updateTemplate:not_found', { id });
await uow.rollback();
return null;
}
const allowed = ['personal','company','both'];
if (data.userType && !allowed.includes(data.userType)) delete data.userType;
if (data.user_type && !allowed.includes(data.user_type)) delete data.user_type;
const newVersion = (current.version || 1) + 1;
await DocumentTemplateRepository.update(
id,
{ ...data, version: newVersion, user_type: data.user_type || data.userType || current.user_type },
uow.connection
);
const updated = await DocumentTemplateRepository.findById(id, uow.connection);
await uow.commit();
logger.info('DocumentTemplateService.updateTemplate:success', { id, version: newVersion });
return updated;
} catch (err) {
logger.error('DocumentTemplateService.updateTemplate:error', { id, error: err.message });
await uow.rollback(err);
throw err;
}
}
async updateTemplateState(id, state) {
logger.info('DocumentTemplateService.updateTemplateState:start', { id, state });
const uow = new UnitOfWork();
try {
await uow.start();
await DocumentTemplateRepository.updateState(id, state, uow.connection);
const updated = await DocumentTemplateRepository.findById(id, uow.connection);
await uow.commit();
logger.info('DocumentTemplateService.updateTemplateState:success', { id, state });
return updated;
} catch (err) {
logger.error('DocumentTemplateService.updateTemplateState:error', { id, state, error: err.message });
await uow.rollback(err);
throw err;
}
}
async getActiveTemplatesForUserType(userType, templateType = null) {
logger.info('DocumentTemplateService.getActiveTemplatesForUserType:start', { userType, templateType });
try {
const rows = await DocumentTemplateRepository.findActiveByUserType(userType, templateType);
logger.info('DocumentTemplateService.getActiveTemplatesForUserType:success', { count: rows.length });
return rows.map(t => ({ ...t, lang: t.lang }));
} catch (error) {
logger.error('DocumentTemplateService.getActiveTemplatesForUserType:error', { error: error.message });
throw error;
}
}
// Convenience: return the most recent active template for a user type (by createdAt desc)
async getLatestActiveForUserType(userType, templateType = 'contract') {
logger.info('DocumentTemplateService.getLatestActiveForUserType:start', { userType, templateType });
try {
const list = await DocumentTemplateRepository.findActiveByUserType(userType, templateType);
const latest = Array.isArray(list) && list.length ? list[0] : null;
logger.info('DocumentTemplateService.getLatestActiveForUserType:result', { found: !!latest, id: latest?.id });
return latest;
} catch (error) {
logger.error('DocumentTemplateService.getLatestActiveForUserType:error', { error: error.message });
throw error;
}
}
// NEW: Retrieve Profit Planet signature (company stamp) as
tag
// Fallback order:
// 1) Active stamp for provided companyId
// 2) Any stamp for provided companyId
// 3) Any active stamp globally
// 4) Any stamp globally
async getProfitPlanetSignatureTag({ companyId = null, maxW = 300, maxH = 300 } = {}) {
const uow = new UnitOfWork();
const result = { tag: '', reason: 'not_started' };
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:enter', { companyId, maxW, maxH });
try {
await uow.start();
const conn = uow.connection;
const safeRowMeta = (row) => {
if (!row) return null;
return {
has_image: !!row.image_base64,
mime: row.mime_type,
image_len: row.image_base64 ? row.image_base64.length : 0,
image_head: row.image_base64 ? row.image_base64.slice(0, 30) : ''
};
};
// Helper to run single-row query safely
const fetchOne = async (sql, params, reasonCode) => {
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:query:start', { reasonCode, sql, params });
try {
const started = Date.now();
const [rows] = await conn.execute(sql, params);
const ms = Date.now() - started;
const rowCount = rows ? rows.length : 0;
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:query:result', {
reasonCode,
duration_ms: ms,
rowCount,
firstRow: safeRowMeta(rows && rows[0])
});
if (rows && rows[0] && rows[0].image_base64) {
const mime = rows[0].mime_type || 'image/png';
const dataUri = `data:${mime};base64,${rows[0].image_base64}`;
result.tag = `
`;
result.reason = reasonCode;
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:query:match', {
reasonCode,
mime,
dataUri_len: dataUri.length,
dataUri_head: dataUri.slice(0, 45)
});
return true;
}
} catch (e) {
logger.warn('DocumentTemplateService.getProfitPlanetSignatureTag:query_error', { reason: reasonCode, error: e.message });
}
return false;
};
if (companyId) {
if (await fetchOne(
'SELECT mime_type,image_base64 FROM company_stamps WHERE company_id = ? AND is_active = 1 ORDER BY id DESC LIMIT 1',
[companyId],
'company_active'
)) { await uow.commit(); logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:exit', { reason: result.reason }); return result; }
if (await fetchOne(
'SELECT mime_type,image_base64 FROM company_stamps WHERE company_id = ? ORDER BY is_active DESC, id DESC LIMIT 1',
[companyId],
'company_any'
)) { await uow.commit(); logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:exit', { reason: result.reason }); return result; }
}
if (await fetchOne(
'SELECT mime_type,image_base64 FROM company_stamps WHERE is_active = 1 ORDER BY id DESC LIMIT 1',
[],
'global_active'
)) { await uow.commit(); logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:exit', { reason: result.reason }); return result; }
if (await fetchOne(
'SELECT mime_type,image_base64 FROM company_stamps ORDER BY is_active DESC, id DESC LIMIT 1',
[],
'global_any'
)) { await uow.commit(); logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:exit', { reason: result.reason }); return result; }
result.reason = 'not_found';
await uow.commit();
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:not_found', { companyId });
return result;
} catch (err) {
result.reason = 'error';
logger.error('DocumentTemplateService.getProfitPlanetSignatureTag:error', { error: err.message, companyId });
try { await uow.rollback(err); } catch (rbErr) {
logger.error('DocumentTemplateService.getProfitPlanetSignatureTag:rollback_error', { error: rbErr.message });
}
return result;
} finally {
logger.debug('DocumentTemplateService.getProfitPlanetSignatureTag:final', { reason: result.reason, hasTag: !!result.tag });
}
}
}
module.exports = new DocumentTemplateService();