Files
crm.clientright.ru/DOC_TEMPLATE_MODULE_PLAN.md
Fedor 01c4fe80b5 chore: snapshot current working tree changes
Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
2026-03-26 14:19:01 +03:00

16 KiB
Raw Blame History

📦 ПЛАН СОЗДАНИЯ МОДУЛЯ 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: Базовая структура

  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. Обработка ошибок

🔗 ИНТЕГРАЦИЯ С МОДУЛЯМИ

Добавление кнопки в модули

// В 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?