Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
468 lines
16 KiB
Markdown
468 lines
16 KiB
Markdown
# 📦 ПЛАН СОЗДАНИЯ МОДУЛЯ 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`**
|
||
|
||
Хранит шаблоны и их настройки:
|
||
|
||
```sql
|
||
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:
|
||
|
||
```sql
|
||
CREATE TABLE `vtiger_doctemplate_seq` (
|
||
`id` int(11) NOT NULL DEFAULT '1'
|
||
) ENGINE=InnoDB;
|
||
```
|
||
|
||
### **Таблица: `vtiger_doctemplate_settings`**
|
||
|
||
Настройки шаблона:
|
||
|
||
```sql
|
||
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 в основной таблице):
|
||
|
||
```sql
|
||
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
|
||
<?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**
|
||
|
||
```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**
|
||
|
||
```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`**
|
||
|
||
```javascript
|
||
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: Базовая структура**
|
||
1. ✅ Создать структуру папок
|
||
2. ✅ Создать `DocTemplate.php`
|
||
3. ✅ Создать `schema.xml`
|
||
4. ✅ Создать базовые модели
|
||
|
||
### **Этап 2: Интеграция с PDFMaker**
|
||
1. ✅ Использовать логику получения данных через `CRMEntity`
|
||
2. ✅ Поддержка формата переменных PDFMaker
|
||
3. ✅ Использовать `PDFMaker_Fields_Model` для списка полей
|
||
|
||
### **Этап 3: Генерация документов**
|
||
1. ✅ Интеграция с Nextcloud (скачивание шаблонов)
|
||
2. ✅ Замена переменных через PHPWord
|
||
3. ✅ Сохранение в S3
|
||
|
||
### **Этап 4: UI**
|
||
1. ✅ Кнопка в детальном виде модулей
|
||
2. ✅ Модальное окно выбора шаблона
|
||
3. ✅ Предпросмотр переменных (опционально)
|
||
|
||
### **Этап 5: Тестирование**
|
||
1. ✅ Тестирование на реальных данных
|
||
2. ✅ Проверка производительности
|
||
3. ✅ Обработка ошибок
|
||
|
||
---
|
||
|
||
## 🔗 ИНТЕГРАЦИЯ С МОДУЛЯМИ
|
||
|
||
### **Добавление кнопки в модули**
|
||
|
||
```php
|
||
// В 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
|
||
);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 ПРЕИМУЩЕСТВА ОТДЕЛЬНОГО МОДУЛЯ
|
||
|
||
1. ✅ **Изолированность** - не влияет на другие модули
|
||
2. ✅ **Расширяемость** - легко добавлять функции
|
||
3. ✅ **Управление** - можно включать/выключать
|
||
4. ✅ **Права доступа** - настройка через профили
|
||
5. ✅ **Версионирование** - можно обновлять отдельно
|
||
6. ✅ **Логирование** - отдельные логи модуля
|
||
|
||
---
|
||
|
||
## 🚀 ГОТОВ НАЧАТЬ РЕАЛИЗАЦИЮ!
|
||
|
||
**С чего начнем?**
|
||
1. Создать базовую структуру модуля?
|
||
2. Создать schema.xml с таблицами?
|
||
3. Реализовать TemplateGenerator с интеграцией PDFMaker?
|