Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
17 KiB
17 KiB
🏗️ АРХИТЕКТУРА СИСТЕМЫ ГЕНЕРАЦИИ ДОКУМЕНТОВ ИЗ ШАБЛОНОВ
🎯 ЦЕЛЬ
Создать надежную систему генерации документов из шаблонов Nextcloud с автоматическим заполнением переменных из модулей CRM.
📋 КОНЦЕПЦИЯ
Основная идея:
- Шаблоны хранятся в Nextcloud
/Templates/ - Маппинг переменных - конфигурация, которая связывает переменные шаблона с полями модулей CRM
- Генератор - получает данные из модуля, подставляет в шаблон, сохраняет документ
- UI - кнопка в детальном виде записи для выбора шаблона и генерации
🏛️ АРХИТЕКТУРА (3 СЛОЯ)
СЛОЙ 1: КОНФИГУРАЦИЯ (Маппинг переменных)
Файл: crm_extensions/file_storage/config/template_mappings.php
Структура:
return [
'Project' => [
'pretenziya.docx' => [
// Прямые поля модуля
'CLIENT_NAME' => 'projectname', // поле projectname из vtiger_project
'DATE' => 'createdtime',
'AMOUNT' => 'cf_1885',
// Связанные модули (через отношения)
'CONTACT_NAME' => [
'module' => 'Contacts',
'relation' => 'Contacts', // название связи
'field' => 'lastname'
],
// Вычисляемые поля
'FULL_DATE' => [
'type' => 'function',
'function' => 'formatDate',
'params' => ['createdtime', 'd.m.Y']
],
// Константы
'COMPANY_NAME' => [
'type' => 'constant',
'value' => 'ООО "Клиент Право"'
],
// Кастомные функции
'CLAIM_TEXT' => [
'type' => 'custom',
'handler' => 'getClaimTextFromProject'
]
],
'iskovoe_zayavlenie.docx' => [
// другой маппинг для другого шаблона
]
],
'HelpDesk' => [
'pretenziya.docx' => [
'CLIENT_NAME' => 'ticket_title',
'DESCRIPTION' => 'description',
// ...
]
]
];
Преимущества:
- ✅ Централизованная конфигурация
- ✅ Легко добавлять новые шаблоны
- ✅ Поддержка связанных модулей
- ✅ Вычисляемые поля
- ✅ Кастомные функции
СЛОЙ 2: ДВИЖОК ГЕНЕРАЦИИ
Файл: crm_extensions/file_storage/api/generate_from_template.php
Алгоритм:
1. Получить параметры:
- module (Project, HelpDesk, etc.)
- recordId (ID записи)
- templateName (pretenziya.docx)
2. Загрузить маппинг для module + templateName
3. Получить данные записи:
- vtws_retrieve() или Vtiger_Record_Model
- Получить все поля модуля
- Получить связанные записи (если нужно)
4. Обработать маппинг:
- Прямые поля → взять из данных записи
- Связанные модули → получить через отношения
- Вычисляемые → вызвать функцию
- Константы → подставить значение
- Кастомные → вызвать handler
5. Скачать шаблон из Nextcloud (WebDAV)
6. Заполнить переменные (PHPWord для DOCX)
7. Сохранить в S3 в папку проекта/записи
8. Вернуть результат (JSON или редирект на OnlyOffice)
Обработка ошибок:
- Если поле не найдено → подставить пустую строку или значение по умолчанию
- Если связанная запись не найдена → пропустить или подставить "Не указано"
- Если шаблон не найден → вернуть ошибку
- Логирование всех операций
СЛОЙ 3: UI ИНТЕГРАЦИЯ
Вариант A: Кнопка в детальном виде
Файл: modules/{Module}/views/Detail.php или через JavaScript
JavaScript:
// Добавить кнопку "Создать из шаблона"
function showTemplateDialog(module, recordId) {
// 1. Получить список шаблонов для модуля
fetch(`/crm_extensions/file_storage/api/get_templates_for_module.php?module=${module}`)
.then(r => r.json())
.then(templates => {
// 2. Показать диалог выбора шаблона
// 3. При выборе → вызвать generate_from_template.php
});
}
Вариант B: Отдельная страница/модальное окно
Файл: modules/{Module}/actions/TemplateGenerator.php
Преимущества:
- Можно показать предпросмотр переменных
- Можно редактировать значения перед генерацией
- Можно выбрать несколько шаблонов
🔧 ДЕТАЛИ РЕАЛИЗАЦИИ
1. Получение данных модуля
// Вариант 1: Через vtws_retrieve (Webservice API)
$recordData = vtws_retrieve($recordId, $current_user);
// Вариант 2: Через Record Model (рекомендуется)
$recordModel = Vtiger_Record_Model::getInstanceById($recordId, $module);
$recordData = $recordModel->getData();
// Вариант 3: Прямой SQL (если нужны все поля включая кастомные)
$adb = PearDatabase::getInstance();
$result = $adb->pquery("SELECT * FROM vtiger_project WHERE projectid = ?", [$recordId]);
$recordData = $adb->fetchByAssoc($result);
Рекомендация: Использовать Record Model, т.к.:
- ✅ Учитывает права доступа
- ✅ Форматирует значения (даты, валюты)
- ✅ Работает с кастомными полями
- ✅ Поддерживает связанные модули
2. Обработка связанных модулей
// Получить связанную запись
function getRelatedRecord($recordModel, $relationName, $targetModule, $fieldName) {
$relationModel = $recordModel->getRelation($relationName);
if (!$relationModel) {
return null;
}
$relatedRecords = $relationModel->getRelatedRecords();
if (empty($relatedRecords)) {
return null;
}
// Берем первую связанную запись
$relatedRecord = $relatedRecords[0];
$relatedModel = Vtiger_Record_Model::getInstanceById($relatedRecord['id'], $targetModule);
return $relatedModel->get($fieldName);
}
// Использование:
$contactName = getRelatedRecord($projectModel, 'Contacts', 'Contacts', 'lastname');
3. Вычисляемые поля
// Функции-обработчики
class TemplateVariableProcessors {
public static function formatDate($value, $format = 'd.m.Y') {
if (empty($value)) return '';
$timestamp = strtotime($value);
return date($format, $timestamp);
}
public static function formatCurrency($value, $currency = 'RUB') {
if (empty($value)) return '0,00 ₽';
return number_format($value, 2, ',', ' ') . ' ₽';
}
public static function concatFields($recordData, $fields) {
$parts = [];
foreach ($fields as $field) {
if (!empty($recordData[$field])) {
$parts[] = $recordData[$field];
}
}
return implode(' ', $parts);
}
// Кастомная функция для получения текста претензии из проекта
public static function getClaimTextFromProject($recordId) {
// Логика получения текста претензии
// Например, из связанных тикетов HelpDesk
return 'Текст претензии...';
}
}
4. Обработка ошибок и валидация
class TemplateGenerator {
public function generate($module, $recordId, $templateName) {
try {
// 1. Валидация параметров
$this->validateParams($module, $recordId, $templateName);
// 2. Проверка существования записи
$recordModel = Vtiger_Record_Model::getInstanceById($recordId, $module);
if (!$recordModel) {
throw new Exception("Запись не найдена");
}
// 3. Проверка маппинга
$mapping = $this->getMapping($module, $templateName);
if (empty($mapping)) {
throw new Exception("Маппинг не найден для {$module}/{$templateName}");
}
// 4. Получение данных
$variables = $this->buildVariables($recordModel, $mapping);
// 5. Генерация документа
$result = $this->createDocument($templateName, $variables, $module, $recordId);
return ['success' => true, 'fileUrl' => $result['url']];
} catch (Exception $e) {
error_log("Template generation error: " . $e->getMessage());
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
private function buildVariables($recordModel, $mapping) {
$variables = [];
$recordData = $recordModel->getData();
foreach ($mapping as $templateVar => $config) {
try {
if (is_string($config)) {
// Прямое поле
$variables[$templateVar] = $recordData[$config] ?? '';
} elseif (is_array($config)) {
// Обработка сложных конфигураций
$variables[$templateVar] = $this->processComplexMapping($recordModel, $config);
}
} catch (Exception $e) {
// Если ошибка - подставляем пустую строку или значение по умолчанию
error_log("Variable {$templateVar} error: " . $e->getMessage());
$variables[$templateVar] = $config['default'] ?? '';
}
}
return $variables;
}
}
📊 СТРУКТУРА ФАЙЛОВ
crm_extensions/file_storage/
├── config/
│ └── template_mappings.php # Маппинг переменных
├── api/
│ ├── generate_from_template.php # Основной endpoint генерации
│ ├── get_templates_for_module.php # Список шаблонов для модуля
│ └── preview_template_variables.php # Предпросмотр переменных
├── lib/
│ ├── TemplateGenerator.php # Класс генератора
│ ├── TemplateVariableProcessors.php # Обработчики переменных
│ └── TemplateMappingLoader.php # Загрузчик маппингов
└── js/
└── template_generator.js # UI компонент
🎨 UI КОМПОНЕНТ
Вариант 1: Модальное окно
// В детальном виде модуля
function showTemplateGenerator(module, recordId) {
// 1. Загрузить список шаблонов
fetch(`/crm_extensions/file_storage/api/get_templates_for_module.php?module=${module}`)
.then(r => r.json())
.then(data => {
if (!data.success) {
alert('Ошибка загрузки шаблонов');
return;
}
// 2. Показать модальное окно с выбором шаблона
const modal = new Vtiger_Modal({
title: 'Создать документ из шаблона',
content: buildTemplateSelector(data.templates),
onSelect: (templateName) => {
// 3. Предпросмотр переменных (опционально)
showVariablePreview(module, recordId, templateName);
},
onGenerate: (templateName) => {
// 4. Генерация документа
generateDocument(module, recordId, templateName);
}
});
modal.show();
});
}
function generateDocument(module, recordId, templateName) {
const url = `/crm_extensions/file_storage/api/generate_from_template.php?` +
`module=${module}&` +
`recordId=${recordId}&` +
`templateName=${encodeURIComponent(templateName)}`;
// Показать индикатор загрузки
showLoadingIndicator();
fetch(url)
.then(r => r.json())
.then(result => {
hideLoadingIndicator();
if (result.success) {
// Открыть документ в OnlyOffice
window.open(result.fileUrl, '_blank');
} else {
alert('Ошибка: ' + result.error);
}
});
}
🔒 БЕЗОПАСНОСТЬ И ПРОИЗВОДИТЕЛЬНОСТЬ
Безопасность:
- ✅ Проверка прав доступа к записи
- ✅ Валидация параметров (module, recordId, templateName)
- ✅ Санитизация переменных перед подстановкой
- ✅ Логирование всех операций
Производительность:
- ✅ Кеширование маппингов (Redis)
- ✅ Кеширование шаблонов (скачанных из Nextcloud)
- ✅ Асинхронная генерация для больших документов
- ✅ Оптимизация запросов к БД (один запрос вместо множества)
🚀 ПЛАН ВНЕДРЕНИЯ
Этап 1: Базовая функциональность
- Создать структуру файлов
- Реализовать
TemplateGeneratorс базовой логикой - Создать простой маппинг для одного модуля (Project)
- Протестировать на одном шаблоне
Этап 2: Расширенная функциональность
- Добавить поддержку связанных модулей
- Добавить вычисляемые поля
- Добавить кастомные функции
- Создать UI компонент
Этап 3: Оптимизация и масштабирование
- Добавить кеширование
- Оптимизировать запросы
- Добавить предпросмотр переменных
- Добавить логирование и мониторинг
❓ ВОПРОСЫ ДЛЯ УТОЧНЕНИЯ
- Какие модули приоритетны? (Project, HelpDesk, Contacts?)
- Какие шаблоны нужны в первую очередь? (претензии, иски, жалобы?)
- Нужен ли предпросмотр переменных перед генерацией?
- Нужна ли возможность редактировать переменные перед генерацией?
- Как обрабатывать ошибки? (показывать пользователю, логировать, отправлять уведомления?)
💡 РЕКОМЕНДАЦИИ
-
Начать с простого:
- Один модуль (Project)
- Один шаблон (pretenziya.docx)
- Прямые поля без связей
-
Постепенно усложнять:
- Добавить связанные модули
- Добавить вычисляемые поля
- Добавить больше шаблонов
-
Тестировать на реальных данных:
- Использовать реальные проекты
- Проверять форматирование
- Проверять производительность
-
Документировать:
- Маппинг переменных
- Кастомные функции
- Примеры использования
Готов начать реализацию! С чего начнем? 🚀