import { documentTemplates, documentTemplateCategories, templateVariables, type DocumentTemplate, type InsertDocumentTemplate, type DocumentTemplateCategory, type InsertDocumentTemplateCategory, type TemplateVariable, type InsertTemplateVariable, } from "@shared/schema"; import * as storage from "../storage/documents.storage"; const VARIABLE_REGEX = /\{\{\s*([^}]+?)\s*\}\}/g; export interface TemplateWithVariables extends DocumentTemplate { variables: TemplateVariable[]; category?: DocumentTemplateCategory | null; } export class DocumentTemplateService { // ── Categories ───────────────────────────────────────────────────────────── async listCategories(organizationId: number): Promise { return storage.listDocumentTemplateCategories(organizationId); } async getCategory(id: number, organizationId: number): Promise { return storage.getDocumentTemplateCategory(id, organizationId); } async createCategory(data: InsertDocumentTemplateCategory): Promise { return storage.createDocumentTemplateCategory(data); } async updateCategory( id: number, organizationId: number, updates: Partial, ): Promise { return storage.updateDocumentTemplateCategory(id, organizationId, updates); } async deleteCategory(id: number, organizationId: number): Promise { return storage.deleteDocumentTemplateCategory(id, organizationId); } // ── Templates ────────────────────────────────────────────────────────────── async list(filters: { organizationId: number; status?: string; categoryId?: number; formId?: number; }): Promise { return storage.listDocumentTemplates(filters); } async getById(id: number, organizationId: number): Promise { const template = await storage.getDocumentTemplate(id, organizationId); if (!template) return undefined; const variables = await storage.listTemplateVariables(id); const category = template.categoryId ? await this.getCategory(template.categoryId, organizationId) : null; return { ...template, variables, category: category || undefined }; } async getByIdRaw(id: number, organizationId: number): Promise { return storage.getDocumentTemplate(id, organizationId); } async create(data: InsertDocumentTemplate & { variables?: InsertTemplateVariable[] }): Promise { const template = await storage.createDocumentTemplate(data); const variables: TemplateVariable[] = []; if (data.variables && data.variables.length > 0) { for (const v of data.variables) { variables.push(await storage.createTemplateVariable({ ...v, templateId: template.id })); } } return { ...template, variables }; } async update( id: number, organizationId: number, updates: Partial & { variables?: Array & { id?: number }> }, ): Promise { const template = await storage.updateDocumentTemplate(id, organizationId, updates); if (!template) return undefined; if (updates.variables) { const existing = await storage.listTemplateVariables(id); const incomingIds = updates.variables.filter((v) => v.id && v.id > 0).map((v) => v.id!); const toDelete = existing.filter((e) => !incomingIds.includes(e.id)); for (const v of toDelete) { await storage.deleteTemplateVariable(v.id, id); } for (let i = 0; i < updates.variables.length; i++) { const v = updates.variables[i]; const variableData: InsertTemplateVariable = { templateId: id, code: v.code || `var_${i}`, label: v.label || v.code || `var_${i}`, description: v.description || null, source: v.source || 'static', mapping: v.mapping || {}, defaultValue: v.defaultValue || null, sortOrder: v.sortOrder ?? i, }; if (v.id && v.id > 0) { await storage.updateTemplateVariable(v.id, id, variableData); } else { await storage.createTemplateVariable(variableData); } } } return this.getById(id, organizationId); } async delete(id: number, organizationId: number): Promise { await storage.deleteTemplateVariablesByTemplate(id); await storage.deleteDocumentTemplate(id, organizationId); } async duplicate(id: number, organizationId: number, newName?: string): Promise { const source = await this.getById(id, organizationId); if (!source) throw new Error('Шаблон не найден'); const { id: _id, createdAt, updatedAt, status, ...rest } = source; const template = await storage.createDocumentTemplate({ ...rest, name: newName || `${source.name} (копия)`, status: 'draft', organizationId, createdBy: source.createdBy, }); const variables: TemplateVariable[] = []; for (const v of source.variables) { const { id: _vid, templateId, createdAt: _vCreatedAt, ...vRest } = v; variables.push( await storage.createTemplateVariable({ ...vRest, templateId: template.id, }), ); } return { ...template, variables }; } // ── Variables ────────────────────────────────────────────────────────────── async listVariables(templateId: number, organizationId: number): Promise { // Убедимся, что шаблон принадлежит организации const template = await storage.getDocumentTemplate(templateId, organizationId); if (!template) throw new Error('Шаблон не найден'); return storage.listTemplateVariables(templateId); } async parseVariablesFromContent(content: string): Promise { const codes = new Set(); let match: RegExpExecArray | null; while ((match = VARIABLE_REGEX.exec(content)) !== null) { codes.add(match[1].trim()); } return Array.from(codes); } async autoCreateVariablesFromContent(templateId: number, organizationId: number): Promise { const template = await storage.getDocumentTemplate(templateId, organizationId); if (!template) throw new Error('Шаблон не найден'); const codes = await this.parseVariablesFromContent(template.content); const existing = await storage.listTemplateVariables(templateId); const existingCodes = new Set(existing.map((v) => v.code)); const created: TemplateVariable[] = []; for (let i = 0; i < codes.length; i++) { const code = codes[i]; if (existingCodes.has(code)) continue; const variable = await storage.createTemplateVariable({ templateId, code, label: code, source: 'static', mapping: {}, defaultValue: null, sortOrder: existing.length + i, }); created.push(variable); } return created; } }