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; } } // 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();