Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
16 KiB
16 KiB
📦 ПЛАН СОЗДАНИЯ МОДУЛЯ DocTemplate
🎯 ЦЕЛЬ
Создать отдельный модуль DocTemplate для генерации документов из шаблонов Nextcloud с переменными модулей CRM, аналогично PDFMaker.
📁 СТРУКТУРА МОДУЛЯ
modules/DocTemplate/
├── DocTemplate.php # Основной класс модуля
├── schema.xml # Схема БД
├── language/ # Языковые файлы
│ └── ru_ru.lang.php
├── models/ # Модели
│ ├── Module.php # Модель модуля
│ ├── Record.php # Модель записи
│ ├── Template.php # Модель шаблона
│ ├── TemplateGenerator.php # Генератор документов
│ ├── VariableProcessor.php # Обработчик переменных
│ └── FieldMapper.php # Маппер полей
├── views/ # Представления
│ ├── List.php # Список шаблонов
│ ├── Detail.php # Детальный вид шаблона
│ ├── Edit.php # Редактирование шаблона
│ └── Generate.php # Генерация документа
├── actions/ # Действия
│ ├── GenerateDocument.php # Генерация документа
│ ├── ListTemplates.php # Список шаблонов для модуля
│ ├── PreviewVariables.php # Предпросмотр переменных
│ └── SaveMapping.php # Сохранение маппинга
├── resources/ # Ресурсы
│ ├── DocTemplate.js # JS для UI
│ ├── DocTemplate.css # Стили
│ └── images/ # Иконки
└── helpers/ # Вспомогательные классы
└── NextcloudClient.php # Клиент Nextcloud
🗄️ СТРУКТУРА БД
Таблица: vtiger_doctemplate
Хранит шаблоны и их настройки:
CREATE TABLE `vtiger_doctemplate` (
`templateid` int(11) NOT NULL AUTO_INCREMENT,
`templatename` varchar(255) NOT NULL,
`filename` varchar(255) NOT NULL, -- Имя файла в Nextcloud
`module` varchar(100) NOT NULL, -- Для какого модуля
`description` text,
`mapping` longtext, -- JSON маппинг переменных
`is_active` tinyint(1) DEFAULT '1',
`owner` int(11) NOT NULL,
`createdtime` datetime NOT NULL,
`modifiedtime` datetime NOT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`templateid`)
) ENGINE=InnoDB;
Таблица: vtiger_doctemplate_seq
Счетчик для ID:
CREATE TABLE `vtiger_doctemplate_seq` (
`id` int(11) NOT NULL DEFAULT '1'
) ENGINE=InnoDB;
Таблица: vtiger_doctemplate_settings
Настройки шаблона:
CREATE TABLE `vtiger_doctemplate_settings` (
`templateid` int(11) NOT NULL,
`output_folder` varchar(255), -- Папка для сохранения
`file_naming` varchar(255), -- Правило именования файлов
`auto_open` tinyint(1) DEFAULT '1', -- Автоматически открывать
`owner` int(11) NOT NULL,
`sharingtype` char(7) DEFAULT 'public',
PRIMARY KEY (`templateid`)
) ENGINE=InnoDB;
Таблица: vtiger_doctemplate_mappings
Маппинг переменных (альтернатива JSON в основной таблице):
CREATE TABLE `vtiger_doctemplate_mappings` (
`mappingid` int(11) NOT NULL AUTO_INCREMENT,
`templateid` int(11) NOT NULL,
`template_variable` varchar(100) NOT NULL, -- CLIENT_NAME
`module` varchar(100) NOT NULL, -- PROJECT
`fieldname` varchar(100) NOT NULL, -- projectname или $PROJECT_PROJECTNAME$
`field_type` varchar(50), -- direct, related, function, constant
`related_module` varchar(100), -- Для связанных модулей
`function_name` varchar(100), -- Для функций
`default_value` text, -- Значение по умолчанию
`sequence` int(11) DEFAULT '0',
PRIMARY KEY (`mappingid`),
KEY `templateid` (`templateid`)
) ENGINE=InnoDB;
📝 ОСНОВНОЙ КЛАСС МОДУЛЯ
DocTemplate.php
<?php
class DocTemplate {
public $log;
public $db;
public $name = 'DocTemplate';
public $id;
public $moduleName = 'DocTemplate';
public $parentName = 'Tools';
public function __construct() {
global $log;
$this->log = $log;
$this->db = PearDatabase::getInstance();
$this->id = getTabId("DocTemplate");
}
/**
* Обработчик событий модуля
*/
public function vtlib_handler($modulename, $event_type) {
switch ($event_type) {
case 'module.postinstall':
$this->executeSql();
$this->addCustomLinks();
break;
case 'module.enabled':
case 'module.postupdate':
$this->addCustomLinks();
break;
case 'module.disabled':
case 'module.preuninstall':
$this->deleteCustomLinks();
break;
}
}
/**
* Выполнение SQL из schema.xml
*/
private function executeSql() {
// Логика выполнения SQL
}
/**
* Добавление кастомных ссылок в модули
*/
private function addCustomLinks() {
// Добавить кнопку "Создать из шаблона" в модули
$modules = ['Project', 'HelpDesk', 'Contacts'];
foreach ($modules as $moduleName) {
$module = Vtiger_Module::getInstance($moduleName);
if ($module) {
$module->addLink(
'DETAILVIEWBASIC',
'Создать из шаблона',
"javascript:DocTemplate.showTemplateDialog('{$moduleName}', '{$recordId}')",
'icon-document',
10
);
}
}
}
private function deleteCustomLinks() {
// Удаление ссылок
}
}
🔧 КЛЮЧЕВЫЕ КОМПОНЕНТЫ
1. TemplateGenerator.php
class DocTemplate_TemplateGenerator_Model {
/**
* Генерация документа из шаблона
*/
public function generate($module, $recordId, $templateId) {
// 1. Получить шаблон
$template = DocTemplate_Template_Model::getInstanceById($templateId);
// 2. Получить данные записи (как PDFMaker)
$focus = CRMEntity::getInstance($module);
$focus->retrieve_entity_info($recordId, $module);
$focus->id = $recordId;
// 3. Получить маппинг
$mapping = $template->getMapping();
// 4. Построить переменные
$variables = $this->buildVariables($mapping, $focus, $module);
// 5. Скачать шаблон из Nextcloud
$templateContent = $this->downloadTemplate($template->getFilename());
// 6. Заменить переменные
$content = $this->replaceVariables($templateContent, $variables);
// 7. Сохранить документ
return $this->saveDocument($content, $module, $recordId, $template);
}
/**
* Построение переменных из маппинга
*/
private function buildVariables($mapping, $focus, $module) {
$variables = [];
$processor = new DocTemplate_VariableProcessor_Model();
foreach ($mapping as $templateVar => $config) {
$variables[$templateVar] = $processor->process($config, $focus, $module);
}
return $variables;
}
/**
* Замена переменных в шаблоне
*/
private function replaceVariables($content, $variables) {
// Для DOCX через PHPWord
if (pathinfo($template->getFilename(), PATHINFO_EXTENSION) == 'docx') {
return $this->replaceDocxVariables($content, $variables);
}
// Для других форматов - простая замена
foreach ($variables as $var => $value) {
$content = str_replace('{' . $var . '}', $value, $content);
$content = str_replace('{{' . $var . '}}', $value, $content);
}
return $content;
}
}
2. VariableProcessor.php
class DocTemplate_VariableProcessor_Model {
/**
* Обработка переменной из маппинга
*/
public function process($config, $focus, $module) {
if (is_string($config)) {
// Простое поле: 'projectname' или '$PROJECT_PROJECTNAME$'
return $this->getFieldValue($config, $focus, $module);
}
if (is_array($config)) {
$type = $config['type'] ?? 'direct';
switch ($type) {
case 'direct':
return $this->getFieldValue($config['field'], $focus, $module);
case 'related':
return $this->getRelatedFieldValue(
$focus->id,
$config['module'],
$config['field']
);
case 'function':
return $this->callFunction(
$config['function'],
$config['params'] ?? [],
$focus,
$module
);
case 'constant':
return $config['value'];
default:
return '';
}
}
return '';
}
/**
* Получение значения поля (поддержка формата PDFMaker)
*/
private function getFieldValue($fieldConfig, $focus, $module) {
// Если формат PDFMaker: $PROJECT_PROJECTNAME$
if (preg_match('/\$([A-Z]+)_([A-Z_]+)\$/', $fieldConfig, $matches)) {
$varModule = $matches[1];
$fieldName = strtolower($matches[2]);
if ($varModule == strtoupper($module)) {
return $focus->column_fields[$fieldName] ?? '';
} else {
return $this->getRelatedFieldValue($focus->id, $varModule, $fieldName);
}
}
// Простое поле
return $focus->column_fields[$fieldConfig] ?? '';
}
}
🎨 UI КОМПОНЕНТЫ
1. Кнопка в детальном виде
JavaScript: DocTemplate.js
DocTemplate = {
/**
* Показать диалог выбора шаблона
*/
showTemplateDialog: function(module, recordId) {
// 1. Получить список шаблонов для модуля
AppConnector.request({
module: 'DocTemplate',
action: 'ListTemplates',
module_name: module,
record_id: recordId
}).then(function(response) {
if (response.success) {
// 2. Показать модальное окно
DocTemplate.showTemplateSelector(response.result.templates, module, recordId);
}
});
},
/**
* Показать селектор шаблонов
*/
showTemplateSelector: function(templates, module, recordId) {
// Создать модальное окно с выбором шаблона
// При выборе → вызвать generateDocument
},
/**
* Генерация документа
*/
generateDocument: function(templateId, module, recordId) {
// Показать индикатор загрузки
AppConnector.request({
module: 'DocTemplate',
action: 'GenerateDocument',
template_id: templateId,
module_name: module,
record_id: recordId
}).then(function(response) {
if (response.success) {
// Открыть документ в OnlyOffice
window.open(response.result.fileUrl, '_blank');
} else {
Vtiger_Helper_Js.showPnotify(response.error.message);
}
});
}
};
📋 ПЛАН РЕАЛИЗАЦИИ
Этап 1: Базовая структура
- ✅ Создать структуру папок
- ✅ Создать
DocTemplate.php - ✅ Создать
schema.xml - ✅ Создать базовые модели
Этап 2: Интеграция с PDFMaker
- ✅ Использовать логику получения данных через
CRMEntity - ✅ Поддержка формата переменных PDFMaker
- ✅ Использовать
PDFMaker_Fields_Modelдля списка полей
Этап 3: Генерация документов
- ✅ Интеграция с Nextcloud (скачивание шаблонов)
- ✅ Замена переменных через PHPWord
- ✅ Сохранение в S3
Этап 4: UI
- ✅ Кнопка в детальном виде модулей
- ✅ Модальное окно выбора шаблона
- ✅ Предпросмотр переменных (опционально)
Этап 5: Тестирование
- ✅ Тестирование на реальных данных
- ✅ Проверка производительности
- ✅ Обработка ошибок
🔗 ИНТЕГРАЦИЯ С МОДУЛЯМИ
Добавление кнопки в модули
// В DocTemplate.php
private function addCustomLinks() {
$modules = ['Project', 'HelpDesk', 'Contacts', 'Accounts'];
foreach ($modules as $moduleName) {
$module = Vtiger_Module::getInstance($moduleName);
if ($module) {
$module->addLink(
'DETAILVIEWBASIC',
'LBL_CREATE_FROM_TEMPLATE',
"javascript:DocTemplate.showTemplateDialog('{$moduleName}', jQuery('#recordId').val())",
'icon-document',
10
);
}
}
}
📊 ПРЕИМУЩЕСТВА ОТДЕЛЬНОГО МОДУЛЯ
- ✅ Изолированность - не влияет на другие модули
- ✅ Расширяемость - легко добавлять функции
- ✅ Управление - можно включать/выключать
- ✅ Права доступа - настройка через профили
- ✅ Версионирование - можно обновлять отдельно
- ✅ Логирование - отдельные логи модуля
🚀 ГОТОВ НАЧАТЬ РЕАЛИЗАЦИЮ!
С чего начнем?
- Создать базовую структуру модуля?
- Создать schema.xml с таблицами?
- Реализовать TemplateGenerator с интеграцией PDFMaker?