Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
431 lines
17 KiB
Markdown
431 lines
17 KiB
Markdown
# 🔧 ВСТАВКА ПЕРЕМЕННЫХ В ШАБЛОН: ГДЕ И КАК?
|
||
|
||
## 🎯 ВОПРОС: ГДЕ ВСТАВЛЯТЬ ПЕРЕМЕННЫЕ В 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
|
||
- Гибкость - можно использовать любые названия переменных
|
||
- Автоматизация - не нужно вручную настраивать каждую переменную
|
||
|
||
---
|
||
|
||
## 🚀 ГОТОВ РЕАЛИЗОВАТЬ!
|
||
|
||
**Подходит такой подход?** Или хочешь другой вариант?
|