242 lines
10 KiB
JavaScript
242 lines
10 KiB
JavaScript
const db = require('../../database/database');
|
|
const { logger } = require('../../middleware/logger');
|
|
|
|
class DocumentTemplateRepository {
|
|
async create(data, conn) {
|
|
logger.info('DocumentTemplateRepository.create:start', { name: data.name, type: data.type });
|
|
// ADDED: validate required fields + normalize optional fields
|
|
const required = ['name', 'type', 'storageKey', 'lang'];
|
|
for (const k of required) {
|
|
const v = data[k];
|
|
if (v === undefined || v === null || String(v).trim() === '') {
|
|
const err = new Error(`Invalid document template: missing field "${k}"`);
|
|
err.code = 'INVALID_TEMPLATE_DATA';
|
|
logger.error('DocumentTemplateRepository.create:invalid', { field: k });
|
|
throw err;
|
|
}
|
|
}
|
|
const name = String(data.name);
|
|
const type = String(data.type);
|
|
const storageKey = String(data.storageKey);
|
|
const description = data.description === undefined ? null : data.description; // avoid undefined bind
|
|
const lang = String(data.lang);
|
|
const allowedUserTypes = new Set(['personal', 'company', 'both']);
|
|
const user_type = allowedUserTypes.has(data.user_type || data.userType) ? (data.user_type || data.userType) : 'both';
|
|
const allowedContractTypes = new Set(['contract', 'gdpr']);
|
|
const contract_type = type === 'contract'
|
|
? (allowedContractTypes.has(data.contract_type || data.contractType) ? (data.contract_type || data.contractType) : null)
|
|
: null;
|
|
const finalContractType = type === 'contract' ? (contract_type || 'contract') : null;
|
|
|
|
const version = Number.isFinite(Number(data.version)) ? Math.max(1, Number(data.version)) : 1;
|
|
const state = (data.state === 'active' || data.state === 'inactive') ? data.state : 'inactive';
|
|
|
|
const query = `
|
|
INSERT INTO document_templates (name, type, contract_type, storageKey, description, lang, user_type, version, state, createdAt, updatedAt)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
|
`;
|
|
try {
|
|
if (conn) {
|
|
const [res] = await conn.execute(query, [name, type, finalContractType, storageKey, description, lang, user_type, version, state]);
|
|
const insertId = res && (res.insertId || res[0]?.insertId);
|
|
logger.info('DocumentTemplateRepository.create:success', { id: insertId || res.insertId });
|
|
return { id: insertId || res.insertId, name, type, contract_type: finalContractType, storageKey, description, lang, user_type, version, state };
|
|
}
|
|
const result = await db.execute(query, [name, type, finalContractType, storageKey, description, lang, user_type, version, state]);
|
|
const insertId = result && result.insertId ? result.insertId : (Array.isArray(result) && result[0] && result[0].insertId ? result[0].insertId : undefined);
|
|
logger.info('DocumentTemplateRepository.create:success', { id: insertId });
|
|
return { id: insertId, name, type, contract_type: finalContractType, storageKey, description, lang, user_type, version, state };
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.create:error', { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findAll(conn) {
|
|
logger.info('DocumentTemplateRepository.findAll:start');
|
|
const query = `SELECT * FROM document_templates ORDER BY createdAt DESC`;
|
|
try {
|
|
if (conn) {
|
|
const [rows] = await conn.execute(query);
|
|
logger.info('DocumentTemplateRepository.findAll:success');
|
|
return rows;
|
|
}
|
|
const result = await db.execute(query);
|
|
// db.execute may return rows directly or [rows, fields]
|
|
if (Array.isArray(result) && Array.isArray(result[0])) return result[0];
|
|
logger.info('DocumentTemplateRepository.findAll:success');
|
|
return result;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.findAll:error', { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findById(id, conn) {
|
|
logger.info('DocumentTemplateRepository.findById:start', { id });
|
|
const query = `SELECT * FROM document_templates WHERE id = ? LIMIT 1`;
|
|
try {
|
|
if (conn) {
|
|
const [rows] = await conn.execute(query, [id]);
|
|
logger.info('DocumentTemplateRepository.findById:success', { id });
|
|
return (rows && rows[0]) ? rows[0] : null;
|
|
}
|
|
const result = await db.execute(query, [id]);
|
|
if (Array.isArray(result) && Array.isArray(result[0])) {
|
|
return result[0][0] || null;
|
|
}
|
|
logger.info('DocumentTemplateRepository.findById:success', { id });
|
|
return (result && result[0]) ? result[0] : null;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.findById:error', { id, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async update(id, data, conn) {
|
|
logger.info('DocumentTemplateRepository.update:start', { id, data });
|
|
// data: { name, type, storageKey, description, lang, version }
|
|
const fields = [];
|
|
const values = [];
|
|
for (const key of ['name', 'type', 'contract_type', 'storageKey', 'description', 'lang', 'version', 'user_type']) {
|
|
if (data[key] !== undefined) {
|
|
fields.push(`${key} = ?`);
|
|
values.push(data[key]);
|
|
}
|
|
}
|
|
// Support camelCase input
|
|
if (data.userType !== undefined && data.user_type === undefined) {
|
|
fields.push(`user_type = ?`);
|
|
values.push(data.userType);
|
|
}
|
|
// Do not update state here
|
|
if (!fields.length) return false;
|
|
const query = `
|
|
UPDATE document_templates SET ${fields.join(', ')}, updatedAt = NOW()
|
|
WHERE id = ?
|
|
`;
|
|
values.push(id);
|
|
try {
|
|
if (conn) await conn.execute(query, values);
|
|
else await db.execute(query, values);
|
|
logger.info('DocumentTemplateRepository.update:success', { id });
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.update:error', { id, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateState(id, state, conn) {
|
|
logger.info('DocumentTemplateRepository.updateState:start', { id, state });
|
|
const query = `
|
|
UPDATE document_templates SET state = ?, updatedAt = NOW()
|
|
WHERE id = ?
|
|
`;
|
|
try {
|
|
if (conn) await conn.execute(query, [state, id]);
|
|
else await db.execute(query, [state, id]);
|
|
logger.info('DocumentTemplateRepository.updateState:success', { id, state });
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.updateState:error', { id, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Deactivate other active contract templates that would conflict with the one being activated.
|
|
// Conflict dimensions:
|
|
// - same type='contract'
|
|
// - same contract_type ('contract' | 'gdpr')
|
|
// - same lang
|
|
// - overlapping user_type (personal/company/both)
|
|
async deactivateOtherActiveContracts({ excludeId, contract_type, lang, user_type }, conn) {
|
|
logger.info('DocumentTemplateRepository.deactivateOtherActiveContracts:start', { excludeId, contract_type, lang, user_type });
|
|
const safeContractType = (contract_type === 'gdpr' || contract_type === 'contract') ? contract_type : 'contract';
|
|
const safeLang = (lang === 'en' || lang === 'de') ? lang : 'en';
|
|
const safeUserType = (user_type === 'personal' || user_type === 'company' || user_type === 'both') ? user_type : 'both';
|
|
|
|
// user_type overlap rules
|
|
const overlap = safeUserType === 'both'
|
|
? ['personal', 'company', 'both']
|
|
: [safeUserType, 'both'];
|
|
|
|
const query = `
|
|
UPDATE document_templates
|
|
SET state = 'inactive', updatedAt = NOW()
|
|
WHERE id <> ?
|
|
AND type = 'contract'
|
|
AND (
|
|
contract_type = ?
|
|
OR (contract_type IS NULL AND ? = 'contract')
|
|
)
|
|
AND lang = ?
|
|
AND state = 'active'
|
|
AND (
|
|
user_type IN (${overlap.map(() => '?').join(', ')})
|
|
OR user_type IS NULL
|
|
)
|
|
`;
|
|
const params = [excludeId, safeContractType, safeContractType, safeLang, ...overlap];
|
|
try {
|
|
if (conn) {
|
|
const [res] = await conn.execute(query, params);
|
|
logger.info('DocumentTemplateRepository.deactivateOtherActiveContracts:success', { affected: res?.affectedRows });
|
|
return res?.affectedRows || 0;
|
|
}
|
|
const res = await db.execute(query, params);
|
|
logger.info('DocumentTemplateRepository.deactivateOtherActiveContracts:success', { affected: res?.affectedRows });
|
|
return res?.affectedRows || 0;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.deactivateOtherActiveContracts:error', { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async delete(id, conn) {
|
|
logger.info('DocumentTemplateRepository.delete:start', { id });
|
|
const query = `DELETE FROM document_templates WHERE id = ?`;
|
|
try {
|
|
if (conn) await conn.execute(query, [id]);
|
|
else await db.execute(query, [id]);
|
|
logger.info('DocumentTemplateRepository.delete:success', { id });
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.delete:error', { id, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findActiveByUserType(userType, templateType = null, contractType = null, conn) {
|
|
logger.info('DocumentTemplateRepository.findActiveByUserType:start', { userType, templateType, contractType });
|
|
const safeType = (userType === 'personal' || userType === 'company') ? userType : 'personal';
|
|
let query = `SELECT * FROM document_templates WHERE state = 'active' AND (user_type = ? OR user_type = 'both')`;
|
|
const params = [safeType];
|
|
if (templateType) {
|
|
query += ` AND type = ?`;
|
|
params.push(templateType);
|
|
}
|
|
if (contractType && templateType === 'contract') {
|
|
query += ` AND contract_type = ?`;
|
|
params.push(contractType);
|
|
}
|
|
query += ` ORDER BY createdAt DESC`;
|
|
try {
|
|
if (conn) {
|
|
const [rows] = await conn.execute(query, params);
|
|
logger.info('DocumentTemplateRepository.findActiveByUserType:success', { count: rows.length });
|
|
return rows;
|
|
}
|
|
const result = await db.execute(query, params);
|
|
const rows = Array.isArray(result) && Array.isArray(result[0]) ? result[0] : result;
|
|
logger.info('DocumentTemplateRepository.findActiveByUserType:success', { count: rows.length });
|
|
return rows;
|
|
} catch (error) {
|
|
logger.error('DocumentTemplateRepository.findActiveByUserType:error', { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new DocumentTemplateRepository();
|