Files
crm.clientright.ru/DOC_TEMPLATE_VARIABLES_INSERTION.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

17 KiB
Raw Blame History

🔧 ВСТАВКА ПЕРЕМЕННЫХ В ШАБЛОН: ГДЕ И КАК?

🎯 ВОПРОС: ГДЕ ВСТАВЛЯТЬ ПЕРЕМЕННЫЕ В DOCX?


📋 ВАРИАНТЫ

ВАРИАНТ 1: ВРУЧНУЮ В ONLYOFFICE/NEXTCLOUD ✍️

Как работает:

  1. Пользователь открывает шаблон в OnlyOffice
  2. Вручную пишет переменные: {CLIENT_NAME}, {DATE}, {AMOUNT}
  3. Сохраняет в Nextcloud
  4. В CRM настраивает маппинг: CLIENT_NAME$PROJECT_PROJECTNAME$

Плюсы:

  • Полный контроль над расположением переменных
  • Можно использовать в любом месте документа
  • Гибкость форматирования

Минусы:

  • Нужно помнить названия переменных
  • Легко ошибиться в написании
  • Нет автодополнения

ВАРИАНТ 2: ЧЕРЕЗ ИНТЕРФЕЙС CRM (ВСТАВКА КНОПКОЙ) 🖱️

Как работает:

  1. Пользователь открывает шаблон в OnlyOffice (через CRM)
  2. В CRM показывается панель с переменными
  3. Нажимает на переменную → она вставляется в курсор в OnlyOffice
  4. Или копирует переменную и вставляет вручную

Плюсы:

  • Не нужно помнить названия
  • Нет опечаток
  • Визуальный выбор

Минусы:

  • Нужна интеграция OnlyOffice API
  • Сложнее реализация

ВАРИАНТ 3: ГИБРИДНЫЙ (РЕКОМЕНДУЕТСЯ)

Как работает:

  1. В OnlyOffice: Пользователь вручную пишет переменные {CLIENT_NAME}
  2. В CRM: При сохранении шаблона показывается список найденных переменных
  3. В CRM: Настраивается маппинг для каждой переменной
  4. В CRM: Можно добавить новые переменные, которых нет в документе

Плюсы:

  • Простота использования
  • Гибкость
  • Автоматическое обнаружение переменных

Минусы:

  • ⚠️ Нужно парсить DOCX для поиска переменных

🏆 РЕКОМЕНДУЕМЫЙ ПОДХОД: ГИБРИДНЫЙ


📝 СЦЕНАРИЙ РАБОТЫ

Шаг 1: Создание/редактирование шаблона в OnlyOffice

Пользователь открывает шаблон в OnlyOffice и пишет:

┌─────────────────────────────────────────────┐
│  ПРЕТЕНЗИЯ                                  │
│                                             │
│  Кому: УК "Жилищник"                       │
│  От: {CLIENT_NAME}                         │
│                                             │
│  Дата: {DATE}                               │
│                                             │
│  Текст претензии:                           │
│  {CLAIM_TEXT}                               │
│                                             │
│  Требования:                                │
│  1. Возместить ущерб в размере {AMOUNT} ₽  │
│                                             │
│  С уважением,                               │
│  {CLIENT_NAME}                              │
└─────────────────────────────────────────────┘

Пользователь вручную пишет: {CLIENT_NAME}, {DATE}, {AMOUNT}, {CLAIM_TEXT}


Шаг 2: Сохранение и настройка маппинга в CRM

После сохранения в OnlyOffice, в CRM показывается:

┌─────────────────────────────────────────────────────────┐
│  Настройка маппинга для шаблона "Претензия"            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Найдены переменные в шаблоне:                          │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │ Переменная    │ Поле модуля              │ [×]  │  │
│  ├──────────────────────────────────────────────────┤  │
│  │ CLIENT_NAME   │ [Выбрать поле ▼]         │ [×]  │  │
│  │ DATE          │ [Выбрать поле ▼]         │ [×]  │  │
│  │ AMOUNT        │ [Выбрать поле ▼]         │ [×]  │
│  │ CLAIM_TEXT    │ [Выбрать поле ▼]         │ [×]  │
│  └──────────────────────────────────────────────────┘  │
│                                                         │
│  [+ Добавить переменную]                                │
│                                                         │
│  [💾 Сохранить маппинг]                                 │
│                                                         │
└─────────────────────────────────────────────────────────┘

CRM автоматически:

  1. Парсит DOCX файл
  2. Находит все переменные {VAR_NAME}
  3. Показывает их в списке для настройки маппинга

🔧 РЕАЛИЗАЦИЯ: ПАРСИНГ ПЕРЕМЕННЫХ ИЗ DOCX

1. Поиск переменных в шаблоне

class DocTemplate_VariableExtractor_Model {
    
    /**
     * Извлечь все переменные из DOCX шаблона
     */
    public function extractVariables($docxContent) {
        $variables = [];
        
        // Сохранить во временный файл
        $tempFile = tempnam(sys_get_temp_dir(), 'template_') . '.docx';
        file_put_contents($tempFile, $docxContent);
        
        try {
            // Загрузить через PHPWord
            $phpWord = \PhpOffice\PhpWord\IOFactory::load($tempFile);
            
            // Найти все переменные в формате {VAR_NAME} или {{VAR_NAME}}
            $pattern = '/\{([A-Z_][A-Z0-9_]*)\}|\{\{([A-Z_][A-Z0-9_]*)\}\}/';
            
            // Обойти все секции и элементы
            foreach ($phpWord->getSections() as $section) {
                foreach ($section->getElements() as $element) {
                    $text = $this->extractTextFromElement($element);
                    preg_match_all($pattern, $text, $matches);
                    
                    // Собрать все найденные переменные
                    foreach ($matches[1] as $var) {
                        if (!empty($var) && !in_array($var, $variables)) {
                            $variables[] = $var;
                        }
                    }
                    foreach ($matches[2] as $var) {
                        if (!empty($var) && !in_array($var, $variables)) {
                            $variables[] = $var;
                        }
                    }
                }
            }
            
        } catch (Exception $e) {
            error_log("Error extracting variables: " . $e->getMessage());
        } finally {
            unlink($tempFile);
        }
        
        return $variables;
    }
    
    /**
     * Извлечь текст из элемента PHPWord
     */
    private function extractTextFromElement($element) {
        $text = '';
        
        if ($element instanceof \PhpOffice\PhpWord\Element\Text) {
            $text = $element->getText();
        } elseif ($element instanceof \PhpOffice\PhpWord\Element\TextRun) {
            foreach ($element->getElements() as $textElement) {
                if ($textElement instanceof \PhpOffice\PhpWord\Element\Text) {
                    $text .= $textElement->getText();
                }
            }
        }
        
        return $text;
    }
}

2. Автоматическое обнаружение при сохранении

class DocTemplate_Save_Action {
    
    public function process(Vtiger_Request $request) {
        $templateId = $request->get('record');
        $template = DocTemplate_Template_Model::getInstanceById($templateId);
        
        // Если шаблон уже существует в Nextcloud
        if ($template->getFilename()) {
            // 1. Скачать шаблон из Nextcloud
            $templateContent = $this->downloadFromNextcloud($template->getFilename());
            
            // 2. Извлечь переменные
            $extractor = new DocTemplate_VariableExtractor_Model();
            $variables = $extractor->extractVariables($templateContent);
            
            // 3. Сохранить найденные переменные
            $template->set('detected_variables', json_encode($variables));
            $template->save();
            
            // 4. Если маппинг не настроен - показать форму настройки
            if (empty($template->getMapping())) {
                return [
                    'success' => true,
                    'variables_detected' => $variables,
                    'show_mapping_editor' => true
                ];
            }
        }
        
        return ['success' => true];
    }
}

3. UI: Автоматическое заполнение маппинга

JavaScript:

DocTemplate = {
    
    /**
     * Показать редактор маппинга с автозаполнением
     */
    showMappingEditor: function(templateId, detectedVariables) {
        // 1. Получить список полей модуля
        AppConnector.request({
            module: 'DocTemplate',
            action: 'GetModuleFields',
            template_id: templateId
        }).then(function(response) {
            var fields = response.result.fields;
            var module = response.result.module;
            
            // 2. Показать форму с найденными переменными
            var html = '<div class="mapping-editor">';
            
            detectedVariables.forEach(function(variable) {
                // Попытаться угадать поле по названию переменной
                var suggestedField = DocTemplate.suggestField(variable, fields);
                
                html += '<div class="mapping-row">' +
                    '<input type="text" value="' + variable + '" readonly>' +
                    '<select class="field-selector">' +
                    '<option value="">-- Выберите поле --</option>';
                
                // Добавить варианты полей
                fields.forEach(function(field) {
                    var selected = (field.key === suggestedField) ? 'selected' : '';
                    html += '<option value="' + field.key + '" ' + selected + '>' + 
                            field.label + '</option>';
                });
                
                html += '</select>' +
                    '<button onclick="DocTemplate.removeMappingRow(this)">×</button>' +
                    '</div>';
            });
            
            html += '</div>';
            
            // Показать модальное окно
            DocTemplate.showModal('Настройка маппинга', html);
        });
    },
    
    /**
     * Предложить поле по названию переменной
     */
    suggestField: function(variable, fields) {
        // Простая эвристика:
        // CLIENT_NAME → PROJECTNAME, CONTACTNAME
        // DATE → CREATEDTIME, MODIFIEDTIME
        // AMOUNT → CF_1885, TOTAL
        
        var suggestions = {
            'CLIENT_NAME': ['PROJECTNAME', 'CONTACTNAME', 'ACCOUNTNAME'],
            'DATE': ['CREATEDTIME', 'MODIFIEDTIME'],
            'AMOUNT': ['CF_1885', 'TOTAL', 'AMOUNT']
        };
        
        var varUpper = variable.toUpperCase();
        if (suggestions[varUpper]) {
            for (var i = 0; i < fields.length; i++) {
                var fieldKey = fields[i].key.toUpperCase();
                if (suggestions[varUpper].some(function(s) {
                    return fieldKey.indexOf(s) !== -1;
                })) {
                    return fields[i].key;
                }
            }
        }
        
        return null;
    }
};

🎨 АЛЬТЕРНАТИВА: ВСТАВКА ЧЕРЕЗ ONLYOFFICE API

Вариант: Интеграция с OnlyOffice Document Server

Если нужна вставка переменных прямо в OnlyOffice:

// В OnlyOffice редакторе
function insertVariable(variable) {
    // Получить API OnlyOffice
    var oDocument = Api.GetDocument();
    var oRange = oDocument.GetRange();
    
    // Вставить переменную в текущую позицию курсора
    oRange.InsertText('{' + variable + '}');
}

Но это требует:

  • Настройку OnlyOffice Document Server API
  • Сложную интеграцию
  • Не всегда работает стабильно

РЕКОМЕНДУЕМЫЙ ПОДХОД

1. Пользователь пишет переменные вручную в OnlyOffice

{CLIENT_NAME}
{DATE}
{AMOUNT}

2. CRM автоматически находит переменные при сохранении

// Парсинг DOCX → находит {CLIENT_NAME}, {DATE}, {AMOUNT}

3. CRM показывает форму настройки маппинга

CLIENT_NAME → [Выбрать поле: $PROJECT_PROJECTNAME$]
DATE → [Выбрать поле: $PROJECT_CREATEDTIME$]
AMOUNT → [Выбрать поле: $PROJECT_CF_1885$]

4. Сохранение маппинга в БД


🔄 ПРОЦЕСС РАБОТЫ (ПОЛНЫЙ ЦИКЛ)

1. Пользователь создает шаблон в CRM
   ↓
2. Открывается пустой DOCX в OnlyOffice
   ↓
3. Пользователь пишет текст и переменные:
   "От: {CLIENT_NAME}, Дата: {DATE}"
   ↓
4. Сохраняет в Nextcloud
   ↓
5. В CRM автоматически обнаруживаются переменные:
   CLIENT_NAME, DATE
   ↓
6. Показывается форма настройки маппинга:
   CLIENT_NAME → $PROJECT_PROJECTNAME$
   DATE → $PROJECT_CREATEDTIME$
   ↓
7. Сохраняется маппинг
   ↓
8. Готово! Можно генерировать документы

🎯 ИТОГОВОЕ РЕШЕНИЕ

Вставка переменных:

  • Вручную в OnlyOffice - пишет {CLIENT_NAME}
  • CRM автоматически находит - парсит DOCX
  • CRM предлагает маппинг - выбор поля из списка

Преимущества:

  • Простота использования
  • Нет сложной интеграции с OnlyOffice API
  • Гибкость - можно использовать любые названия переменных
  • Автоматизация - не нужно вручную настраивать каждую переменную

🚀 ГОТОВ РЕАЛИЗОВАТЬ!

Подходит такой подход? Или хочешь другой вариант?