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

431 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔧 ВСТАВКА ПЕРЕМЕННЫХ В ШАБЛОН: ГДЕ И КАК?
## 🎯 ВОПРОС: ГДЕ ВСТАВЛЯТЬ ПЕРЕМЕННЫЕ В 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. Поиск переменных в шаблоне**
```php
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. Автоматическое обнаружение при сохранении**
```php
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:**
```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:
```javascript
// В 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 автоматически находит переменные при сохранении**
```php
// Парсинг 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
- Гибкость - можно использовать любые названия переменных
- Автоматизация - не нужно вручную настраивать каждую переменную
---
## 🚀 ГОТОВ РЕАЛИЗОВАТЬ!
**Подходит такой подход?** Или хочешь другой вариант?