Добавлены скрипты для парсинга судебных документов и обновления проектов в CRM
- court_document_parser.py: парсер судебных документов с извлечением ФИО, номера дела, УИД, суда - court_parser_api.py: API для вызова парсера из n8n - pdf_court_parser.py: парсер PDF документов с извлечением текста - simple_project_updater.php: обновление проектов через CRM API - simple_project_updater_v2.php: обновленная версия с прямыми SQL запросами и S3Client - update_project_from_document.php: альтернативный скрипт обновления - test_input.json: тестовые данные для парсера - README файлы с документацией для всех скриптов Скрипты поддерживают: - Поиск проектов по ФИО, номеру дела, УИД, названию суда - Создание документов в CRM с загрузкой в S3 - Привязку документов к проектам - Логирование всех операций - Работу с n8n через SSH команды
This commit is contained in:
@@ -85,3 +85,5 @@
|
||||
---
|
||||
|
||||
**Последнее обновление:** 24 сентября 2025
|
||||
|
||||
|
||||
|
||||
@@ -120,3 +120,5 @@ curl -I "https://s3.twcstorage.ru/your_bucket/"
|
||||
---
|
||||
|
||||
**💡 Совет:** Всегда делайте backup перед изменениями!
|
||||
|
||||
|
||||
|
||||
99
crm_extensions/README_court_parser.md
Normal file
99
crm_extensions/README_court_parser.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Парсер судебных документов
|
||||
|
||||
Скрипт для извлечения структурированных данных из судебных документов.
|
||||
|
||||
## Файлы
|
||||
|
||||
- `court_document_parser.py` - основной скрипт парсера
|
||||
- `court_parser_api.py` - API endpoint для вызова из n8n
|
||||
- `test_input.json` - пример входных данных
|
||||
|
||||
## Использование
|
||||
|
||||
### 1. Прямой вызов скрипта
|
||||
|
||||
```bash
|
||||
# Чтение из файла
|
||||
python3 court_document_parser.py --input input.json --output output.json
|
||||
|
||||
# Чтение из stdin, вывод в stdout
|
||||
echo '{"combinedText": "..."}' | python3 court_document_parser.py --stdin --stdout
|
||||
|
||||
# Чтение из файла, вывод в stdout
|
||||
python3 court_document_parser.py --input input.json --stdout
|
||||
```
|
||||
|
||||
### 2. Вызов через API endpoint (для n8n)
|
||||
|
||||
```bash
|
||||
# Чтение из stdin, вывод в stdout
|
||||
echo '{"combinedText": "..."}' | python3 court_parser_api.py
|
||||
```
|
||||
|
||||
### 3. Вызов из n8n через SSH
|
||||
|
||||
В n8n используйте SSH node со следующими параметрами:
|
||||
- **Command**: `python3 /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/court_parser_api.py`
|
||||
- **Input**: JSON с полем `combinedText`
|
||||
|
||||
## Формат входных данных
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"combinedText": "Текст судебного документа..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Формат выходных данных
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"case_number": "М-5071/2025",
|
||||
"execution_number": null,
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"court": "Щелковский городской суд",
|
||||
"plaintiff": "Соколов Александр Владимирович",
|
||||
"defendant": "ООО СКИЛБОКС",
|
||||
"document_type": "ОПРЕДЕЛЕНИЕ",
|
||||
"document_date": "26 августа 2025 г.",
|
||||
"judge": "Пикулева Т.И.",
|
||||
"raw_text": "Исходный текст документа...",
|
||||
"extraction_timestamp": "2025-09-30T15:55:40.803597"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Извлекаемые поля
|
||||
|
||||
- **case_number** - номер дела
|
||||
- **execution_number** - номер исполнительного листа
|
||||
- **uid** - уникальный идентификатор (УИД)
|
||||
- **court** - название суда
|
||||
- **plaintiff** - ФИО истца
|
||||
- **defendant** - ФИО/название ответчика
|
||||
- **document_type** - тип документа (ОПРЕДЕЛЕНИЕ, РЕШЕНИЕ, и т.д.)
|
||||
- **document_date** - дата документа
|
||||
- **judge** - ФИО судьи
|
||||
|
||||
## Пример использования в n8n
|
||||
|
||||
1. Создайте SSH node
|
||||
2. Настройте подключение к серверу
|
||||
3. Установите команду: `python3 /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/court_parser_api.py`
|
||||
4. Передайте JSON с полем `combinedText` в stdin
|
||||
5. Получите структурированные данные в stdout
|
||||
|
||||
## Тестирование
|
||||
|
||||
```bash
|
||||
# Тест с примером
|
||||
python3 court_document_parser.py --input test_input.json --stdout
|
||||
```
|
||||
|
||||
## Требования
|
||||
|
||||
- Python 3.6+
|
||||
- Стандартные библиотеки Python (json, re, sys, argparse, datetime, typing)
|
||||
110
crm_extensions/README_pdf_parser.md
Normal file
110
crm_extensions/README_pdf_parser.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# PDF Парсер судебных документов
|
||||
|
||||
Скрипт для извлечения структурированных данных из PDF файлов судебных документов.
|
||||
|
||||
## Файлы
|
||||
|
||||
- `pdf_court_parser.py` - основной скрипт для парсинга PDF
|
||||
- `court_document_parser.py` - парсер текста (используется внутри)
|
||||
|
||||
## Установка зависимостей
|
||||
|
||||
```bash
|
||||
# Установка poppler-utils (рекомендуется)
|
||||
sudo apt-get install poppler-utils
|
||||
|
||||
# Установка Python библиотек
|
||||
pip3 install pdfplumber
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### 1. Прямой вызов скрипта
|
||||
|
||||
```bash
|
||||
# Парсинг PDF файла
|
||||
python3 pdf_court_parser.py "/path/to/document.pdf"
|
||||
```
|
||||
|
||||
### 2. Вызов из n8n через SSH
|
||||
|
||||
```bash
|
||||
# Команда для SSH node в n8n:
|
||||
python3 /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/pdf_court_parser.py "{{ $json.pdf_path }}"
|
||||
```
|
||||
|
||||
## Формат входных данных
|
||||
|
||||
В n8n передайте путь к PDF файлу:
|
||||
```json
|
||||
{
|
||||
"pdf_path": "/tmp/document.pdf"
|
||||
}
|
||||
```
|
||||
|
||||
## Формат выходных данных
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"case_number": "М-5071/2025",
|
||||
"execution_number": null,
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"court": "Щелковский городской суд",
|
||||
"plaintiff": "Соколов Александр Владимирович",
|
||||
"defendant": "ООО СКИЛБОКС",
|
||||
"document_type": "ОПРЕДЕЛЕНИЕ",
|
||||
"document_date": "26 августа 2025 г.",
|
||||
"judge": "Пикулева Т.И.",
|
||||
"pdf_file": "/tmp/document.pdf",
|
||||
"extracted_text_length": 1234,
|
||||
"raw_text": "Извлеченный текст...",
|
||||
"extraction_timestamp": "2025-09-30T16:08:33.294637"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Извлекаемые поля
|
||||
|
||||
- **case_number** - номер дела
|
||||
- **execution_number** - номер исполнительного листа
|
||||
- **uid** - уникальный идентификатор (УИД)
|
||||
- **court** - название суда
|
||||
- **plaintiff** - ФИО истца
|
||||
- **defendant** - ФИО/название ответчика
|
||||
- **document_type** - тип документа (ОПРЕДЕЛЕНИЕ, РЕШЕНИЕ, и т.д.)
|
||||
- **document_date** - дата документа
|
||||
- **judge** - ФИО судьи
|
||||
- **pdf_file** - путь к исходному PDF файлу
|
||||
- **extracted_text_length** - длина извлеченного текста
|
||||
|
||||
## Пример использования в n8n
|
||||
|
||||
1. **Загрузите PDF файл** в n8n (например, через HTTP Request)
|
||||
2. **Сохраните файл** во временную папку
|
||||
3. **Создайте SSH node** с командой:
|
||||
```bash
|
||||
python3 /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/pdf_court_parser.py "{{ $json.file_path }}"
|
||||
```
|
||||
4. **Получите структурированные данные** в следующем узле
|
||||
|
||||
## Поддерживаемые методы извлечения текста
|
||||
|
||||
1. **pdftotext** (poppler-utils) - основной метод
|
||||
2. **pdfplumber** - резервный метод
|
||||
3. **PyPDF2** - дополнительный метод
|
||||
|
||||
## Требования
|
||||
|
||||
- Python 3.6+
|
||||
- poppler-utils (рекомендуется)
|
||||
- pdfplumber (установлен)
|
||||
- Стандартные библиотеки Python
|
||||
|
||||
## Преимущества PDF парсинга
|
||||
|
||||
- ✅ Нет проблем с экранированием кавычек
|
||||
- ✅ Сохраняется форматирование текста
|
||||
- ✅ Работает с любыми PDF документами
|
||||
- ✅ Простая интеграция с n8n
|
||||
- ✅ Надежное извлечение текста
|
||||
93
crm_extensions/README_project_updater.md
Normal file
93
crm_extensions/README_project_updater.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Обновление проекта из судебного документа
|
||||
|
||||
## Описание
|
||||
Скрипт для автоматического обновления проекта в CRM на основе данных из судебного документа.
|
||||
|
||||
## Использование
|
||||
|
||||
### 1. Через аргумент командной строки
|
||||
```bash
|
||||
php simple_project_updater.php '[{"content": {...}, "file": "...", "file_name": "..."}]'
|
||||
```
|
||||
|
||||
### 2. Через stdin
|
||||
```bash
|
||||
echo '[{"content": {...}}]' | php simple_project_updater.php
|
||||
```
|
||||
|
||||
### 3. Из n8n через SSH
|
||||
```bash
|
||||
ssh user@server "cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions && echo '$(printf '%s' '{{ $json | toJson }}')' | php simple_project_updater.php"
|
||||
```
|
||||
|
||||
## Формат входных данных
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"description": "Описание документа",
|
||||
"case_number": "М-5071/2025",
|
||||
"writ_number": null,
|
||||
"court": "Щелковский городской суд Московской области",
|
||||
"plaintiff_fio": "Соколов Александр Владимирович",
|
||||
"defendant_fio": null,
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"document_title": "ОПРЕДЕЛЕНИЕ"
|
||||
},
|
||||
"file": "https://s3.twcstorage.ru/.../document.pdf",
|
||||
"file_name": "document.pdf"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Функциональность
|
||||
|
||||
### 1. Поиск проекта
|
||||
- По ФИО истца в названии проекта или описании
|
||||
- По номеру дела в номере проекта или описании
|
||||
- По УИД в описании
|
||||
|
||||
### 2. Добавление документа к проекту
|
||||
- Создание записи документа в CRM
|
||||
- Привязка документа к проекту
|
||||
- Логирование всех действий
|
||||
|
||||
### 3. Загрузка документа
|
||||
- Скачивание файла по URL
|
||||
- Создание записи документа в CRM
|
||||
- Привязка документа к проекту
|
||||
- Сохранение в S3 хранилище
|
||||
|
||||
## Логирование
|
||||
Все действия записываются в файл `logs/project_update.log`
|
||||
|
||||
## Пример использования
|
||||
|
||||
```bash
|
||||
# Тестовый запуск
|
||||
php simple_project_updater.php '[{
|
||||
"content": {
|
||||
"description": "Определение судьи...",
|
||||
"case_number": "М-5071/2025",
|
||||
"court": "Щелковский городской суд Московской области",
|
||||
"plaintiff_fio": "Соколов Александр Владимирович",
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"document_title": "ОПРЕДЕЛЕНИЕ"
|
||||
},
|
||||
"file": "https://s3.twcstorage.ru/.../document.pdf",
|
||||
"file_name": "document.pdf"
|
||||
}]'
|
||||
```
|
||||
|
||||
## Обработка ошибок
|
||||
- Валидация входных данных
|
||||
- Проверка существования проекта
|
||||
- Обработка ошибок загрузки файлов
|
||||
- Детальное логирование ошибок
|
||||
|
||||
## Требования
|
||||
- PHP 7.4+
|
||||
- Доступ к базе данных CRM
|
||||
- Права на запись в директорию логов
|
||||
- Доступ к S3 хранилищу
|
||||
125
crm_extensions/README_project_updater_v2.md
Normal file
125
crm_extensions/README_project_updater_v2.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Обновление проекта из судебного документа (v2)
|
||||
|
||||
## Описание
|
||||
Скрипт для автоматического обновления проекта в CRM на основе данных из судебного документа с использованием прямых SQL запросов и S3Client.
|
||||
|
||||
## Использование
|
||||
|
||||
### 1. Через stdin (рекомендуется)
|
||||
```bash
|
||||
echo '[{"content": {...}, "file": "...", "file_name": "..."}]' | php simple_project_updater_v2.php
|
||||
```
|
||||
|
||||
### 2. Из n8n через SSH
|
||||
```bash
|
||||
ssh user@server "cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions && echo '$(printf '%s' '{{ $json | toJson }}')' | php simple_project_updater_v2.php"
|
||||
```
|
||||
|
||||
### 3. Прямой вызов с JSON
|
||||
```bash
|
||||
php simple_project_updater_v2.php << 'EOF'
|
||||
[{"content": {"description": "Определение судьи...", "case_number": "М-5071/2025", "court": "Щелковский городской суд Московской области", "plaintiff_fio": "Соколов Александр Владимирович", "uid": "50RS0052-01-2025-007323-70", "document_title": "ОПРЕДЕЛЕНИЕ"}, "file": "https://s3.twcstorage.ru/.../document.pdf", "file_name": "document.pdf"}]
|
||||
EOF
|
||||
```
|
||||
|
||||
## Формат входных данных
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"content": {
|
||||
"description": "Описание документа",
|
||||
"case_number": "М-5071/2025",
|
||||
"writ_number": null,
|
||||
"court": "Щелковский городской суд Московской области",
|
||||
"plaintiff_fio": "Соколов Александр Владимирович",
|
||||
"defendant_fio": null,
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"document_title": "ОПРЕДЕЛЕНИЕ"
|
||||
},
|
||||
"file": "https://s3.twcstorage.ru/.../document.pdf",
|
||||
"file_name": "document.pdf"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Функциональность
|
||||
|
||||
### 1. Поиск проекта
|
||||
- По ФИО истца в названии проекта или описании
|
||||
- По номеру дела в номере проекта или описании
|
||||
- По УИД в описании
|
||||
- По названию суда в кастомных полях (cf_1499, cf_2278)
|
||||
|
||||
### 2. Создание документа
|
||||
- Прямые SQL запросы к vtiger_crmentity и vtiger_notes
|
||||
- Автоматическое обновление vtiger_crmentity_seq
|
||||
- Формирование полного S3 URL в поле filename
|
||||
|
||||
### 3. Загрузка в S3
|
||||
- Использование S3Client с авторизацией
|
||||
- Скачивание файла по URL
|
||||
- Загрузка в S3 с правильным ключом
|
||||
- Обновление метаданных (s3_bucket, s3_key, s3_etag, filesize)
|
||||
|
||||
### 4. Привязка к проекту
|
||||
- Создание связи в vtiger_senotesrel
|
||||
- Логирование всех действий
|
||||
|
||||
## Логирование
|
||||
Все действия записываются в файл `logs/project_update.log`
|
||||
|
||||
## Пример использования
|
||||
|
||||
```bash
|
||||
# Тестовый запуск
|
||||
echo '[{
|
||||
"content": {
|
||||
"description": "Определение судьи Щелковского городского суда Московской области Т.И. Пикулевой от 26 августа 2025 года о возврате искового заявления МОО «Клиентправ», действующего в интересах Соколова Александра Владимировича, к ООО «СКИЛБОКС» о взыскании денежных средств, неустойки, процентов, штрафа, компенсации морального вреда. Исковое заявление оставлено без движения 16 июля 2025 года, недостатки в срок не устранены, заявление возвращается. Разъяснено право на повторное обращение. Определение может быть обжаловано в Московский областной суд в течение 15 дней.",
|
||||
"case_number": "М-5071/2025",
|
||||
"court": "Щелковский городской суд Московской области",
|
||||
"plaintiff_fio": "Соколов Александр Владимирович",
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"document_title": "ОПРЕДЕЛЕНИЕ"
|
||||
},
|
||||
"file": "https://s3.twcstorage.ru/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/letters/58f29988-faac-4a2f-b9c1-41a2441240d6/ПК_e9572340-f3d0-0c4c-310b-edeb2996fd47.pdf_WITH_ENVELOPE.pdf",
|
||||
"file_name": "ПК_e9572340-f3d0-0c4c-310b-edeb2996fd47.pdf_WITH_ENVELOPE.pdf"
|
||||
}]' | php simple_project_updater_v2.php
|
||||
```
|
||||
|
||||
## Формат ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"project_id": 58462,
|
||||
"plaintiff_fio": "Соколов",
|
||||
"case_number": "М-5071/2025",
|
||||
"uid": "50RS0052-01-2025-007323-70",
|
||||
"document_id": "394196",
|
||||
"document_ws_id": "15x394196",
|
||||
"s3_url": null,
|
||||
"message": "Документ успешно создан через CRM API и добавлен к проекту"
|
||||
}
|
||||
```
|
||||
|
||||
## Обработка ошибок
|
||||
- Валидация входных данных
|
||||
- Проверка существования проекта
|
||||
- Обработка ошибок загрузки файлов
|
||||
- Детальное логирование ошибок
|
||||
- Автоматическое обновление последовательности ID
|
||||
|
||||
## Требования
|
||||
- PHP 7.4+
|
||||
- Доступ к базе данных CRM
|
||||
- Права на запись в директорию логов
|
||||
- Доступ к S3 хранилищу через S3Client
|
||||
- Файл конфигурации .env с S3 credentials
|
||||
|
||||
## Отличия от v1
|
||||
- Использует прямые SQL запросы вместо CRM API
|
||||
- Интегрирован с S3Client для загрузки файлов
|
||||
- Автоматически обновляет vtiger_crmentity_seq
|
||||
- Сохраняет полный S3 URL в поле filename
|
||||
- Более надежная обработка ошибок
|
||||
BIN
crm_extensions/__pycache__/court_document_parser.cpython-36.pyc
Normal file
BIN
crm_extensions/__pycache__/court_document_parser.cpython-36.pyc
Normal file
Binary file not shown.
247
crm_extensions/court_document_parser.py
Executable file
247
crm_extensions/court_document_parser.py
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Парсер судебных документов для извлечения структурированных данных
|
||||
Использование: python3 court_document_parser.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class CourtDocumentParser:
|
||||
"""Парсер судебных документов для извлечения структурированных данных"""
|
||||
|
||||
def __init__(self):
|
||||
# Паттерны для извлечения данных
|
||||
self.patterns = {
|
||||
# Номер дела
|
||||
'case_number': [
|
||||
r'№\s*([А-Яа-я0-9\-/]+)',
|
||||
r'дело\s*№\s*([А-Яа-я0-9\-/]+)',
|
||||
r'№\s*дела\s*([А-Яа-я0-9\-/]+)',
|
||||
r'([А-Яа-я0-9\-/]+)\s*№\s*([А-Яа-я0-9\-/]+)',
|
||||
],
|
||||
|
||||
# Номер исполнительного листа
|
||||
'execution_number': [
|
||||
r'исполнительный\s*лист\s*№\s*([А-Яа-я0-9\-/]+)',
|
||||
r'№\s*исполнительного\s*листа\s*([А-Яа-я0-9\-/]+)',
|
||||
r'ИЛ\s*№\s*([А-Яа-я0-9\-/]+)',
|
||||
],
|
||||
|
||||
# УИД
|
||||
'uid': [
|
||||
r'УИД\s*№\s*([0-9]{2}[А-Я]{2}[0-9]{3}-[0-9]{2}-[0-9]{4}-[0-9]{6}-[0-9]{2})',
|
||||
r'уникальный\s*идентификатор\s*([0-9]{2}[А-Я]{2}[0-9]{3}-[0-9]{2}-[0-9]{4}-[0-9]{6}-[0-9]{2})',
|
||||
r'([0-9]{2}[А-Я]{2}[0-9]{3}-[0-9]{2}-[0-9]{4}-[0-9]{6}-[0-9]{2})',
|
||||
r'УИД\s*№\s*([А-Яа-я0-9\-/]+)',
|
||||
],
|
||||
|
||||
# Суд
|
||||
'court': [
|
||||
r'([А-Яа-я\s]+городской\s*суд)',
|
||||
r'([А-Яа-я\s]+районный\s*суд)',
|
||||
r'([А-Яа-я\s]+областной\s*суд)',
|
||||
r'([А-Яа-я\s]+суд)',
|
||||
],
|
||||
|
||||
# ФИО истца
|
||||
'plaintiff': [
|
||||
r'в\s*интересах\s*([А-Я][а-я]+\s+[А-Я][а-я]+\s+[А-Я][а-я]+)',
|
||||
r'истец[а-я\s]*([А-Я][а-я]+\s+[А-Я][а-я]+\s+[А-Я][а-я]+)',
|
||||
r'([А-Я][а-я]+\s+[А-Я][а-я]+\s+[А-Я][а-я]+)\s*к\s*([А-Яа-я\s«»""]+)',
|
||||
],
|
||||
|
||||
# ФИО ответчика
|
||||
'defendant': [
|
||||
r'к\s*([А-Яа-я\s«»""]+?)\s*о\s*взыскании',
|
||||
r'ответчик[а-я\s]*([А-Яа-я\s«»""]+)',
|
||||
r'([А-Яа-я\s«»""]+?)\s*о\s*взыскании',
|
||||
],
|
||||
|
||||
# Название документа
|
||||
'document_type': [
|
||||
r'(ОПРЕДЕЛЕНИЕ|РЕШЕНИЕ|ПОСТАНОВЛЕНИЕ|ПРИКАЗ)',
|
||||
r'([А-Яа-я\s]+)\s*от\s*\d+',
|
||||
r'([А-Яа-я\s]+)\s*№\s*[А-Яа-я0-9\-/]+',
|
||||
],
|
||||
|
||||
# Дата документа
|
||||
'document_date': [
|
||||
r'(\d{1,2}\s+[а-я]+\s+\d{4}\s+г\.)',
|
||||
r'(\d{1,2}\.\d{1,2}\.\d{4})',
|
||||
r'от\s*(\d{1,2}\s+[а-я]+\s+\d{4}\s+г\.)',
|
||||
],
|
||||
|
||||
# Судья
|
||||
'judge': [
|
||||
r'судья[а-я\s]*([А-Я][а-я]+\s+[А-Я]\.[А-Я]\.)',
|
||||
r'([А-Я][а-я]+\s+[А-Я]\.[А-Я]\.)\s*судья',
|
||||
r'([А-Я][а-я]+\s+[А-Я]\.[А-Я]\.)',
|
||||
r'([А-Я][а-я]+\s+[А-Я]\.[А-Я]\.)',
|
||||
],
|
||||
}
|
||||
|
||||
def extract_data(self, text: str) -> Dict[str, Any]:
|
||||
"""Извлекает структурированные данные из текста документа"""
|
||||
result = {
|
||||
'case_number': None,
|
||||
'execution_number': None,
|
||||
'uid': None,
|
||||
'court': None,
|
||||
'plaintiff': None,
|
||||
'defendant': None,
|
||||
'document_type': None,
|
||||
'document_date': None,
|
||||
'judge': None,
|
||||
'raw_text': text,
|
||||
'extraction_timestamp': datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
# Нормализуем текст для поиска
|
||||
normalized_text = re.sub(r'\s+', ' ', text.strip())
|
||||
|
||||
# Извлекаем данные по каждому паттерну
|
||||
for field, patterns in self.patterns.items():
|
||||
for pattern in patterns:
|
||||
matches = re.findall(pattern, normalized_text, re.IGNORECASE)
|
||||
if matches:
|
||||
# Берем первое найденное совпадение
|
||||
if isinstance(matches[0], tuple):
|
||||
result[field] = matches[0][0] if matches[0][0] else matches[0][1]
|
||||
else:
|
||||
result[field] = matches[0]
|
||||
break
|
||||
|
||||
# Дополнительная обработка для специфических случаев
|
||||
self._post_process_result(result, normalized_text)
|
||||
|
||||
return result
|
||||
|
||||
def _post_process_result(self, result: Dict[str, Any], text: str):
|
||||
"""Дополнительная обработка результатов"""
|
||||
|
||||
# Очистка и нормализация данных
|
||||
for key in ['case_number', 'execution_number', 'uid', 'court', 'plaintiff', 'defendant', 'document_type', 'judge']:
|
||||
if result[key]:
|
||||
result[key] = result[key].strip()
|
||||
|
||||
# Специальная обработка для номера дела
|
||||
if result['case_number']:
|
||||
# Убираем лишние символы, оставляем только нужный формат
|
||||
result['case_number'] = re.sub(r'[^\w\-/]', '', result['case_number'])
|
||||
|
||||
# Специальная обработка для УИД
|
||||
if result['uid']:
|
||||
# Убираем лишние символы, оставляем только нужный формат
|
||||
result['uid'] = re.sub(r'[^\w\-]', '', result['uid'])
|
||||
|
||||
# Специальная обработка для суда
|
||||
if result['court']:
|
||||
# Убираем лишние слова и нормализуем
|
||||
result['court'] = re.sub(r'\s+(суд|суды|суда|суду|судом|суде)\s*$', '', result['court'], flags=re.IGNORECASE)
|
||||
result['court'] = re.sub(r'\s+судья[а-я\s]*', '', result['court'], flags=re.IGNORECASE)
|
||||
result['court'] = result['court'].strip()
|
||||
|
||||
# Специальная обработка для ответчика
|
||||
if result['defendant']:
|
||||
# Убираем кавычки и лишние символы
|
||||
result['defendant'] = re.sub(r'[«»""]', '', result['defendant'])
|
||||
result['defendant'] = re.sub(r'\s+о\s+взыскании.*$', '', result['defendant'], flags=re.IGNORECASE)
|
||||
result['defendant'] = result['defendant'].strip()
|
||||
|
||||
# Специальная обработка для истца
|
||||
if result['plaintiff']:
|
||||
# Убираем лишние слова
|
||||
result['plaintiff'] = re.sub(r'\s+к\s+.*$', '', result['plaintiff'])
|
||||
result['plaintiff'] = result['plaintiff'].strip()
|
||||
|
||||
# Специальная обработка для судьи
|
||||
if result['judge']:
|
||||
# Убираем лишние символы
|
||||
result['judge'] = re.sub(r'[^\w\s\.]', '', result['judge'])
|
||||
result['judge'] = result['judge'].strip()
|
||||
|
||||
def parse_documents(self, documents: List[Dict[str, str]]) -> List[Dict[str, Any]]:
|
||||
"""Парсит список документов"""
|
||||
results = []
|
||||
|
||||
for doc in documents:
|
||||
if 'combinedText' in doc:
|
||||
extracted_data = self.extract_data(doc['combinedText'])
|
||||
results.append(extracted_data)
|
||||
else:
|
||||
# Если нет combinedText, пытаемся найти текст в других полях
|
||||
text = None
|
||||
for key in ['text', 'content', 'body', 'message']:
|
||||
if key in doc:
|
||||
text = doc[key]
|
||||
break
|
||||
|
||||
if text:
|
||||
extracted_data = self.extract_data(text)
|
||||
results.append(extracted_data)
|
||||
else:
|
||||
results.append({
|
||||
'error': 'No text found in document',
|
||||
'raw_document': doc
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
"""Основная функция для запуска из командной строки"""
|
||||
parser = argparse.ArgumentParser(description='Парсер судебных документов')
|
||||
parser.add_argument('--input', '-i', help='Входной JSON файл')
|
||||
parser.add_argument('--output', '-o', help='Выходной JSON файл')
|
||||
parser.add_argument('--stdin', action='store_true', help='Читать из stdin')
|
||||
parser.add_argument('--stdout', action='store_true', help='Выводить в stdout')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Создаем парсер
|
||||
document_parser = CourtDocumentParser()
|
||||
|
||||
try:
|
||||
# Получаем входные данные
|
||||
if args.stdin:
|
||||
input_data = json.loads(sys.stdin.read())
|
||||
elif args.input:
|
||||
with open(args.input, 'r', encoding='utf-8') as f:
|
||||
input_data = json.load(f)
|
||||
else:
|
||||
# По умолчанию читаем из stdin
|
||||
input_data = json.loads(sys.stdin.read())
|
||||
|
||||
# Парсим документы
|
||||
if isinstance(input_data, list):
|
||||
results = document_parser.parse_documents(input_data)
|
||||
else:
|
||||
# Если передан один документ
|
||||
results = document_parser.parse_documents([input_data])
|
||||
|
||||
# Выводим результаты
|
||||
if args.stdout or not args.output:
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return 0
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Ошибка парсинга JSON: {e}", file=sys.stderr)
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
54
crm_extensions/court_parser_api.py
Executable file
54
crm_extensions/court_parser_api.py
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
API endpoint для парсера судебных документов
|
||||
Использование: python3 court_parser_api.py "текст документа"
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from court_document_parser import CourtDocumentParser
|
||||
|
||||
|
||||
def main():
|
||||
"""API endpoint для вызова из n8n через SSH"""
|
||||
|
||||
# Создаем парсер
|
||||
document_parser = CourtDocumentParser()
|
||||
|
||||
try:
|
||||
# Получаем текст документа из аргумента командной строки
|
||||
if len(sys.argv) < 2:
|
||||
error_result = {
|
||||
'error': 'Не указан текст документа',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
# Берем текст из первого аргумента
|
||||
document_text = sys.argv[1]
|
||||
|
||||
# Создаем объект документа
|
||||
input_data = {"combinedText": document_text}
|
||||
|
||||
# Парсим документ
|
||||
results = document_parser.parse_documents([input_data])
|
||||
|
||||
# Выводим результаты в stdout
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
error_result = {
|
||||
'error': f'Ошибка обработки: {str(e)}',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
207
crm_extensions/deploy_scripts/README.md
Normal file
207
crm_extensions/deploy_scripts/README.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Скрипты деплоя CRM
|
||||
|
||||
Этот набор скриптов предназначен для безопасного деплоя изменений из тестовой среды `crm-test.clientright.ru` в боевую среду `crm.clientright.ru`.
|
||||
|
||||
## Структура
|
||||
|
||||
- `deploy_to_production.sh` - Основной скрипт полного деплоя
|
||||
- `sync_crm_extensions.sh` - Скрипт синхронизации только CRM Extensions
|
||||
- `README.md` - Данная документация
|
||||
|
||||
## Основной скрипт деплоя
|
||||
|
||||
### Использование
|
||||
|
||||
```bash
|
||||
# Полный деплой (по умолчанию)
|
||||
./deploy_to_production.sh
|
||||
|
||||
# Создание только резервной копии
|
||||
./deploy_to_production.sh backup
|
||||
|
||||
# Синхронизация файлов без бэкапа
|
||||
./deploy_to_production.sh sync
|
||||
|
||||
# Проверка работоспособности
|
||||
./deploy_to_production.sh health
|
||||
|
||||
# Откат к последней резервной копии
|
||||
./deploy_to_production.sh rollback
|
||||
```
|
||||
|
||||
### Что делает полный деплой
|
||||
|
||||
1. **Проверка прав доступа** - Убеждается, что есть доступ к необходимым директориям
|
||||
2. **Создание резервной копии** - Создает полную резервную копию продакшн среды
|
||||
3. **Синхронизация файлов** - Копирует изменения из тестовой среды
|
||||
4. **Обновление прав доступа** - Устанавливает правильные права на файлы
|
||||
5. **Проверка работоспособности** - Проверяет доступность сайта после деплоя
|
||||
|
||||
### Синхронизируемые компоненты
|
||||
|
||||
- `crm_extensions/` - Все доработки CRM
|
||||
- `layouts/v7/skins/images` - Изображения интерфейса
|
||||
- `layouts/v7/lib` - JavaScript библиотеки
|
||||
- `modules/` - Модули CRM
|
||||
- `libraries/` - PHP библиотеки
|
||||
- `include/` - Включаемые файлы
|
||||
- `vtlib/` - Vtiger библиотеки
|
||||
- `packages/` - Пакеты расширений
|
||||
- `resources/` - Ресурсы
|
||||
- `.htaccess` - Конфигурация Apache
|
||||
|
||||
## Скрипт синхронизации CRM Extensions
|
||||
|
||||
### Использование
|
||||
|
||||
```bash
|
||||
# Синхронизация CRM Extensions (по умолчанию)
|
||||
./sync_crm_extensions.sh
|
||||
|
||||
# Создание резервной копии
|
||||
./sync_crm_extensions.sh backup
|
||||
|
||||
# Проверка синхронизации
|
||||
./sync_crm_extensions.sh verify
|
||||
```
|
||||
|
||||
### Что делает
|
||||
|
||||
1. **Проверка директорий** - Убеждается в существовании необходимых путей
|
||||
2. **Резервная копия** - Создает бэкап текущих CRM Extensions
|
||||
3. **Синхронизация** - Копирует новую версию из тестовой среды
|
||||
4. **Проверка** - Верифицирует успешность синхронизации
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Резервные копии
|
||||
|
||||
- Все резервные копии сохраняются в `/var/www/fastuser/data/backups/`
|
||||
- Исключаются кеш, временные файлы и логи
|
||||
- Автоматическое именование с временными метками
|
||||
- Возможность отката к любой резервной копии
|
||||
|
||||
### Права доступа
|
||||
|
||||
- Файлы: 644 (rw-r--r--)
|
||||
- Директории: 755 (rwxr-xr-x)
|
||||
- Исполняемые скрипты: 755 (rwxr-xr-x)
|
||||
- Кеш и временные файлы: 777 (rwxrwxrwx)
|
||||
- Владелец: fastuser:fastuser
|
||||
|
||||
### Исключения
|
||||
|
||||
Из синхронизации исключаются:
|
||||
- `/cache/` - Кеш файлы
|
||||
- `/test/templates_c/` - Скомпилированные шаблоны
|
||||
- `/logs/` - Логи
|
||||
- `/tmp/` - Временные файлы
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Логи
|
||||
|
||||
- Основной скрипт: `/var/www/fastuser/data/logs/deploy_YYYYMMDD_HHMMSS.log`
|
||||
- Цветной вывод в консоль
|
||||
- Детальная информация о каждом шаге
|
||||
|
||||
### Проверки работоспособности
|
||||
|
||||
- HTTP статус главной страницы
|
||||
- Наличие ключевых файлов
|
||||
- Размеры директорий
|
||||
- Права доступа
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Ежедневный деплой изменений
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts
|
||||
./deploy_to_production.sh
|
||||
```
|
||||
|
||||
### Быстрая синхронизация только Extensions
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts
|
||||
./sync_crm_extensions.sh
|
||||
```
|
||||
|
||||
### Экстренный откат
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts
|
||||
./deploy_to_production.sh rollback
|
||||
```
|
||||
|
||||
### Проверка состояния
|
||||
|
||||
```bash
|
||||
cd /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts
|
||||
./deploy_to_production.sh health
|
||||
```
|
||||
|
||||
## Автоматизация
|
||||
|
||||
### Cron задачи
|
||||
|
||||
Для автоматического деплоя можно добавить в crontab:
|
||||
|
||||
```bash
|
||||
# Ежедневный деплой в 2:00
|
||||
0 2 * * * /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts/deploy_to_production.sh
|
||||
|
||||
# Еженедельная проверка работоспособности
|
||||
0 6 * * 1 /var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/deploy_scripts/deploy_to_production.sh health
|
||||
```
|
||||
|
||||
### Уведомления
|
||||
|
||||
Скрипты можно модифицировать для отправки уведомлений:
|
||||
- Email при ошибках
|
||||
- Telegram уведомления
|
||||
- Slack интеграция
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
### Частые проблемы
|
||||
|
||||
1. **Ошибки прав доступа**
|
||||
```bash
|
||||
sudo chown -R fastuser:fastuser /var/www/fastuser/data/www/crm.clientright.ru
|
||||
```
|
||||
|
||||
2. **Проблемы с кешем**
|
||||
```bash
|
||||
rm -rf /var/www/fastuser/data/www/crm.clientright.ru/test/templates_c/*
|
||||
chmod -R 777 /var/www/fastuser/data/www/crm.clientright.ru/test/templates_c/
|
||||
```
|
||||
|
||||
3. **Ошибки Apache**
|
||||
```bash
|
||||
systemctl restart apache2
|
||||
tail -f /var/log/apache2/error.log
|
||||
```
|
||||
|
||||
### Восстановление из резервной копии
|
||||
|
||||
```bash
|
||||
# Список доступных резервных копий
|
||||
ls -la /var/www/fastuser/data/backups/
|
||||
|
||||
# Восстановление конкретной копии
|
||||
tar -xzf /var/www/fastuser/data/backups/crm_backup_YYYYMMDD_HHMMSS.tar.gz -C /var/www/fastuser/data/www/
|
||||
```
|
||||
|
||||
## Контакты
|
||||
|
||||
- **Автор**: Фёдор
|
||||
- **Дата создания**: 2025-09-26
|
||||
- **Версия**: 1.0
|
||||
|
||||
## Лицензия
|
||||
|
||||
Внутренний инструмент ClientRight CRM. Все права защищены.
|
||||
|
||||
|
||||
252
crm_extensions/deploy_scripts/deploy_to_production.sh
Executable file
252
crm_extensions/deploy_scripts/deploy_to_production.sh
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Скрипт деплоя изменений из тестовой среды в боевую
|
||||
# Автор: Фёдор
|
||||
# Дата: 2025-09-26
|
||||
|
||||
set -e # Остановка при ошибке
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Пути
|
||||
TEST_DIR="/var/www/fastuser/data/www/crm-test.clientright.ru"
|
||||
PROD_DIR="/var/www/fastuser/data/www/crm.clientright.ru"
|
||||
BACKUP_DIR="/var/www/fastuser/data/backups/crm_deployments"
|
||||
LOG_FILE="/var/www/fastuser/data/logs/deploy_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# Функция логирования
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Проверка прав доступа
|
||||
check_permissions() {
|
||||
log "Проверка прав доступа..."
|
||||
|
||||
if [ ! -d "$TEST_DIR" ]; then
|
||||
error "Тестовая директория не найдена: $TEST_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -d "$PROD_DIR" ]; then
|
||||
error "Продакшн директория не найдена: $PROD_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -w "$PROD_DIR" ]; then
|
||||
error "Нет прав на запись в продакшн директорию: $PROD_DIR"
|
||||
fi
|
||||
|
||||
success "Права доступа проверены"
|
||||
}
|
||||
|
||||
# Создание резервной копии
|
||||
create_backup() {
|
||||
log "Создание резервной копии продакшн среды..."
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_NAME="crm_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
|
||||
|
||||
# Исключаем кеш и временные файлы из бэкапа
|
||||
tar -czf "$BACKUP_PATH" \
|
||||
--exclude="$PROD_DIR/cache" \
|
||||
--exclude="$PROD_DIR/test/templates_c" \
|
||||
--exclude="$PROD_DIR/logs" \
|
||||
--exclude="$PROD_DIR/tmp" \
|
||||
-C "$(dirname "$PROD_DIR")" "$(basename "$PROD_DIR")"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
success "Резервная копия создана: $BACKUP_PATH"
|
||||
echo "$BACKUP_PATH" > "$BACKUP_DIR/latest_backup.txt"
|
||||
else
|
||||
error "Ошибка создания резервной копии"
|
||||
fi
|
||||
}
|
||||
|
||||
# Синхронизация файлов
|
||||
sync_files() {
|
||||
log "Синхронизация файлов из тестовой среды..."
|
||||
|
||||
# Список файлов и директорий для синхронизации
|
||||
SYNC_ITEMS=(
|
||||
"crm_extensions"
|
||||
"layouts/v7/skins/images"
|
||||
"layouts/v7/lib"
|
||||
"modules"
|
||||
"libraries"
|
||||
"include"
|
||||
"vtlib"
|
||||
"packages"
|
||||
"resources"
|
||||
".htaccess"
|
||||
)
|
||||
|
||||
for item in "${SYNC_ITEMS[@]}"; do
|
||||
if [ -e "$TEST_DIR/$item" ]; then
|
||||
log "Синхронизация: $item"
|
||||
|
||||
# Создаем директорию если не существует
|
||||
if [ -d "$TEST_DIR/$item" ]; then
|
||||
mkdir -p "$PROD_DIR/$(dirname "$item")"
|
||||
fi
|
||||
|
||||
# Копируем с сохранением прав
|
||||
cp -r "$TEST_DIR/$item" "$PROD_DIR/$(dirname "$item")/"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
success "Синхронизировано: $item"
|
||||
else
|
||||
warning "Ошибка синхронизации: $item"
|
||||
fi
|
||||
else
|
||||
warning "Файл/директория не найдена в тестовой среде: $item"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Обновление прав доступа
|
||||
fix_permissions() {
|
||||
log "Обновление прав доступа..."
|
||||
|
||||
# Устанавливаем правильные права для файлов
|
||||
find "$PROD_DIR" -type f -exec chmod 644 {} \;
|
||||
find "$PROD_DIR" -type d -exec chmod 755 {} \;
|
||||
|
||||
# Специальные права для исполняемых файлов
|
||||
find "$PROD_DIR" -name "*.sh" -exec chmod 755 {} \;
|
||||
find "$PROD_DIR" -name "*.php" -path "*/cron/*" -exec chmod 755 {} \;
|
||||
|
||||
# Права для кеша и временных файлов
|
||||
if [ -d "$PROD_DIR/test/templates_c" ]; then
|
||||
chmod -R 777 "$PROD_DIR/test/templates_c"
|
||||
fi
|
||||
|
||||
if [ -d "$PROD_DIR/cache" ]; then
|
||||
chmod -R 777 "$PROD_DIR/cache"
|
||||
fi
|
||||
|
||||
# Устанавливаем владельца
|
||||
chown -R fastuser:fastuser "$PROD_DIR"
|
||||
|
||||
success "Права доступа обновлены"
|
||||
}
|
||||
|
||||
# Проверка работоспособности
|
||||
health_check() {
|
||||
log "Проверка работоспособности после деплоя..."
|
||||
|
||||
# Проверяем доступность главной страницы
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://crm.clientright.ru)
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
success "Главная страница доступна (HTTP $HTTP_CODE)"
|
||||
else
|
||||
warning "Проблема с главной страницей (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
|
||||
# Проверяем наличие ключевых файлов
|
||||
KEY_FILES=(
|
||||
"index.php"
|
||||
"config.php"
|
||||
"crm_extensions/README.md"
|
||||
)
|
||||
|
||||
for file in "${KEY_FILES[@]}"; do
|
||||
if [ -f "$PROD_DIR/$file" ]; then
|
||||
success "Файл найден: $file"
|
||||
else
|
||||
warning "Файл не найден: $file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Откат изменений
|
||||
rollback() {
|
||||
log "Выполнение отката изменений..."
|
||||
|
||||
if [ -f "$BACKUP_DIR/latest_backup.txt" ]; then
|
||||
BACKUP_PATH=$(cat "$BACKUP_DIR/latest_backup.txt")
|
||||
|
||||
if [ -f "$BACKUP_PATH" ]; then
|
||||
log "Восстановление из резервной копии: $BACKUP_PATH"
|
||||
|
||||
# Останавливаем веб-сервер
|
||||
systemctl stop apache2
|
||||
|
||||
# Восстанавливаем файлы
|
||||
tar -xzf "$BACKUP_PATH" -C "$(dirname "$PROD_DIR")"
|
||||
|
||||
# Запускаем веб-сервер
|
||||
systemctl start apache2
|
||||
|
||||
success "Откат выполнен успешно"
|
||||
else
|
||||
error "Резервная копия не найдена: $BACKUP_PATH"
|
||||
fi
|
||||
else
|
||||
error "Файл с информацией о последней резервной копии не найден"
|
||||
fi
|
||||
}
|
||||
|
||||
# Основная функция
|
||||
main() {
|
||||
log "Начало деплоя CRM из тестовой среды в продакшн"
|
||||
log "Лог файл: $LOG_FILE"
|
||||
|
||||
case "${1:-deploy}" in
|
||||
"deploy")
|
||||
check_permissions
|
||||
create_backup
|
||||
sync_files
|
||||
fix_permissions
|
||||
health_check
|
||||
success "Деплой завершен успешно!"
|
||||
;;
|
||||
"rollback")
|
||||
rollback
|
||||
;;
|
||||
"backup")
|
||||
check_permissions
|
||||
create_backup
|
||||
;;
|
||||
"sync")
|
||||
check_permissions
|
||||
sync_files
|
||||
fix_permissions
|
||||
;;
|
||||
"health")
|
||||
health_check
|
||||
;;
|
||||
*)
|
||||
echo "Использование: $0 {deploy|rollback|backup|sync|health}"
|
||||
echo " deploy - Полный деплой (по умолчанию)"
|
||||
echo " rollback - Откат к последней резервной копии"
|
||||
echo " backup - Создание резервной копии"
|
||||
echo " sync - Синхронизация файлов"
|
||||
echo " health - Проверка работоспособности"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Запуск основной функции
|
||||
main "$@"
|
||||
|
||||
|
||||
167
crm_extensions/deploy_scripts/sync_crm_extensions.sh
Executable file
167
crm_extensions/deploy_scripts/sync_crm_extensions.sh
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Скрипт синхронизации только CRM Extensions между тестовой и боевой средой
|
||||
# Автор: Фёдор
|
||||
# Дата: 2025-09-26
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Пути
|
||||
TEST_DIR="/var/www/fastuser/data/www/crm-test.clientright.ru"
|
||||
PROD_DIR="/var/www/fastuser/data/www/crm.clientright.ru"
|
||||
EXTENSIONS_DIR="crm_extensions"
|
||||
|
||||
# Функция логирования
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
# Проверка существования директорий
|
||||
check_directories() {
|
||||
log "Проверка директорий..."
|
||||
|
||||
if [ ! -d "$TEST_DIR/$EXTENSIONS_DIR" ]; then
|
||||
error "CRM Extensions не найдены в тестовой среде: $TEST_DIR/$EXTENSIONS_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -d "$PROD_DIR" ]; then
|
||||
error "Продакшн директория не найдена: $PROD_DIR"
|
||||
fi
|
||||
|
||||
success "Директории проверены"
|
||||
}
|
||||
|
||||
# Создание резервной копии текущих Extensions
|
||||
backup_current() {
|
||||
log "Создание резервной копии текущих CRM Extensions..."
|
||||
|
||||
if [ -d "$PROD_DIR/$EXTENSIONS_DIR" ]; then
|
||||
BACKUP_NAME="crm_extensions_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||
BACKUP_PATH="/var/www/fastuser/data/backups/$BACKUP_NAME"
|
||||
|
||||
mkdir -p "$(dirname "$BACKUP_PATH")"
|
||||
|
||||
tar -czf "$BACKUP_PATH" -C "$PROD_DIR" "$EXTENSIONS_DIR"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
success "Резервная копия создана: $BACKUP_PATH"
|
||||
else
|
||||
error "Ошибка создания резервной копии"
|
||||
fi
|
||||
else
|
||||
warning "CRM Extensions не найдены в продакшн среде, пропускаем бэкап"
|
||||
fi
|
||||
}
|
||||
|
||||
# Синхронизация CRM Extensions
|
||||
sync_extensions() {
|
||||
log "Синхронизация CRM Extensions..."
|
||||
|
||||
# Удаляем старую версию
|
||||
if [ -d "$PROD_DIR/$EXTENSIONS_DIR" ]; then
|
||||
rm -rf "$PROD_DIR/$EXTENSIONS_DIR"
|
||||
log "Удалена старая версия CRM Extensions"
|
||||
fi
|
||||
|
||||
# Копируем новую версию
|
||||
cp -r "$TEST_DIR/$EXTENSIONS_DIR" "$PROD_DIR/"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
success "CRM Extensions скопированы"
|
||||
else
|
||||
error "Ошибка копирования CRM Extensions"
|
||||
fi
|
||||
|
||||
# Устанавливаем правильные права
|
||||
chown -R fastuser:fastuser "$PROD_DIR/$EXTENSIONS_DIR"
|
||||
find "$PROD_DIR/$EXTENSIONS_DIR" -type f -exec chmod 644 {} \;
|
||||
find "$PROD_DIR/$EXTENSIONS_DIR" -type d -exec chmod 755 {} \;
|
||||
find "$PROD_DIR/$EXTENSIONS_DIR" -name "*.sh" -exec chmod 755 {} \;
|
||||
|
||||
success "Права доступа установлены"
|
||||
}
|
||||
|
||||
# Проверка синхронизации
|
||||
verify_sync() {
|
||||
log "Проверка синхронизации..."
|
||||
|
||||
# Проверяем ключевые файлы
|
||||
KEY_FILES=(
|
||||
"crm_extensions/README.md"
|
||||
"crm_extensions/.env.example"
|
||||
"crm_extensions/nextcloud_api.php"
|
||||
)
|
||||
|
||||
for file in "${KEY_FILES[@]}"; do
|
||||
if [ -f "$PROD_DIR/$file" ]; then
|
||||
success "Файл найден: $file"
|
||||
else
|
||||
warning "Файл не найден: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Сравниваем размеры директорий
|
||||
TEST_SIZE=$(du -s "$TEST_DIR/$EXTENSIONS_DIR" | cut -f1)
|
||||
PROD_SIZE=$(du -s "$PROD_DIR/$EXTENSIONS_DIR" | cut -f1)
|
||||
|
||||
if [ "$TEST_SIZE" = "$PROD_SIZE" ]; then
|
||||
success "Размеры директорий совпадают: ${TEST_SIZE}KB"
|
||||
else
|
||||
warning "Размеры директорий отличаются: тест=${TEST_SIZE}KB, прод=${PROD_SIZE}KB"
|
||||
fi
|
||||
}
|
||||
|
||||
# Основная функция
|
||||
main() {
|
||||
log "Начало синхронизации CRM Extensions"
|
||||
|
||||
case "${1:-sync}" in
|
||||
"sync")
|
||||
check_directories
|
||||
backup_current
|
||||
sync_extensions
|
||||
verify_sync
|
||||
success "Синхронизация CRM Extensions завершена!"
|
||||
;;
|
||||
"backup")
|
||||
check_directories
|
||||
backup_current
|
||||
;;
|
||||
"verify")
|
||||
check_directories
|
||||
verify_sync
|
||||
;;
|
||||
*)
|
||||
echo "Использование: $0 {sync|backup|verify}"
|
||||
echo " sync - Синхронизация CRM Extensions (по умолчанию)"
|
||||
echo " backup - Создание резервной копии"
|
||||
echo " verify - Проверка синхронизации"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Запуск основной функции
|
||||
main "$@"
|
||||
|
||||
|
||||
0
crm_extensions/file_storage/n8n_comment_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/n8n_comment_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/n8n_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/n8n_migration.sh
Executable file → Normal file
2
crm_extensions/file_storage/n8n_migration_clean_fixed.sh
Executable file → Normal file
2
crm_extensions/file_storage/n8n_migration_clean_fixed.sh
Executable file → Normal file
@@ -35,3 +35,5 @@ echo "[n8n] Done at $(date '+%Y-%m-%d %H:%M:%S') with exit code $EXIT_CODE" >> "
|
||||
echo "$OUTPUT"
|
||||
|
||||
exit $EXIT_CODE
|
||||
|
||||
|
||||
|
||||
0
crm_extensions/file_storage/n8n_migration_json.sh
Executable file → Normal file
0
crm_extensions/file_storage/n8n_migration_json.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_auto_s3_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_auto_s3_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_batch_s3_metadata.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_batch_s3_metadata.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_s3_migration.sh
Executable file → Normal file
0
crm_extensions/file_storage/run_s3_migration.sh
Executable file → Normal file
116
crm_extensions/pdf_court_parser.py
Executable file
116
crm_extensions/pdf_court_parser.py
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Парсер судебных документов из PDF файлов
|
||||
Использование: python3 pdf_court_parser.py "путь_к_pdf_файлу"
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
from court_document_parser import CourtDocumentParser
|
||||
|
||||
|
||||
def extract_text_from_pdf(pdf_path):
|
||||
"""Извлекает текст из PDF файла"""
|
||||
try:
|
||||
# Пробуем использовать pdftotext (poppler-utils)
|
||||
result = subprocess.run(['pdftotext', '-layout', pdf_path, '-'],
|
||||
capture_output=True, text=True, check=True)
|
||||
return result.stdout
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
try:
|
||||
# Пробуем использовать pdfplumber
|
||||
import pdfplumber
|
||||
with pdfplumber.open(pdf_path) as pdf:
|
||||
text = ""
|
||||
for page in pdf.pages:
|
||||
text += page.extract_text() or ""
|
||||
return text
|
||||
except ImportError:
|
||||
try:
|
||||
# Пробуем использовать PyPDF2
|
||||
import PyPDF2
|
||||
with open(pdf_path, 'rb') as file:
|
||||
reader = PyPDF2.PdfReader(file)
|
||||
text = ""
|
||||
for page in reader.pages:
|
||||
text += page.extract_text()
|
||||
return text
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
"""Основная функция для парсинга PDF"""
|
||||
|
||||
# Создаем парсер
|
||||
document_parser = CourtDocumentParser()
|
||||
|
||||
try:
|
||||
# Получаем путь к PDF файлу из аргумента командной строки
|
||||
if len(sys.argv) < 2:
|
||||
error_result = {
|
||||
'error': 'Не указан путь к PDF файлу',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
pdf_path = sys.argv[1]
|
||||
|
||||
# Проверяем существование файла
|
||||
if not os.path.exists(pdf_path):
|
||||
error_result = {
|
||||
'error': f'Файл не найден: {pdf_path}',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
# Извлекаем текст из PDF
|
||||
text = extract_text_from_pdf(pdf_path)
|
||||
|
||||
if text is None:
|
||||
error_result = {
|
||||
'error': 'Не удалось извлечь текст из PDF. Установите poppler-utils, pdfplumber или PyPDF2',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
if not text.strip():
|
||||
error_result = {
|
||||
'error': 'PDF файл не содержит текста',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
# Создаем объект документа
|
||||
input_data = {"combinedText": text}
|
||||
|
||||
# Парсим документ
|
||||
results = document_parser.parse_documents([input_data])
|
||||
|
||||
# Добавляем информацию о файле
|
||||
results[0]['pdf_file'] = pdf_path
|
||||
results[0]['extracted_text_length'] = len(text)
|
||||
|
||||
# Выводим результаты в stdout
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
error_result = {
|
||||
'error': f'Ошибка обработки: {str(e)}',
|
||||
'status': 'error'
|
||||
}
|
||||
print(json.dumps(error_result, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
330
crm_extensions/simple_project_updater.php
Normal file
330
crm_extensions/simple_project_updater.php
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
/**
|
||||
* Простая версия скрипта для обновления проекта
|
||||
* Использует прямое подключение к MySQL
|
||||
*/
|
||||
|
||||
// Устанавливаем рабочую директорию
|
||||
chdir(__DIR__ . '/..');
|
||||
|
||||
// Функция для обновления проекта
|
||||
function updateProject() {
|
||||
try {
|
||||
// Получаем входные данные
|
||||
$input = '';
|
||||
|
||||
if (isset($argv[1])) {
|
||||
$input = $argv[1];
|
||||
} else {
|
||||
$input = stream_get_contents(STDIN);
|
||||
}
|
||||
|
||||
if (empty($input)) {
|
||||
throw new Exception('Не указаны входные данные');
|
||||
}
|
||||
|
||||
// Парсим JSON
|
||||
$data = json_decode($input, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Ошибка парсинга JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
// Проверяем структуру данных
|
||||
if (!is_array($data)) {
|
||||
throw new Exception('Неверная структура данных: ожидается массив');
|
||||
}
|
||||
|
||||
// Обрабатываем первый элемент массива
|
||||
$item = $data[0] ?? null;
|
||||
if (!$item || !isset($item['content']) || !is_array($item['content'])) {
|
||||
throw new Exception('Неверная структура данных: отсутствует content в первом элементе');
|
||||
}
|
||||
|
||||
$content = $item['content'];
|
||||
$file_url = $item['file'] ?? '';
|
||||
$file_name = $item['file_name'] ?? '';
|
||||
|
||||
// Подключаемся к базе данных
|
||||
$mysqli = new mysqli('localhost', 'ci20465_72new', 'EcY979Rn', 'ci20465_72new');
|
||||
|
||||
if ($mysqli->connect_error) {
|
||||
throw new Exception('Ошибка подключения к БД: ' . $mysqli->connect_error);
|
||||
}
|
||||
|
||||
$mysqli->set_charset('utf8');
|
||||
|
||||
// Поиск проекта
|
||||
$plaintiff_fio = $content['plaintiff_fio'] ?? '';
|
||||
$case_number = $content['case_number'] ?? '';
|
||||
$uid = $content['uid'] ?? '';
|
||||
|
||||
$project_id = null;
|
||||
|
||||
// Поиск по ФИО
|
||||
if ($plaintiff_fio) {
|
||||
// Извлекаем фамилию из полного ФИО
|
||||
$surname = explode(' ', $plaintiff_fio)[0];
|
||||
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE (p.projectname LIKE ? OR e.description LIKE ? OR p.projectname LIKE ? OR e.description LIKE ?)
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$search_term_full = "%$plaintiff_fio%";
|
||||
$search_term_surname = "%$surname%";
|
||||
$stmt->bind_param('ssss', $search_term_full, $search_term_full, $search_term_surname, $search_term_surname);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$project_id = $row['projectid'];
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
// Поиск по номеру дела
|
||||
if (!$project_id && $case_number) {
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE (p.project_no LIKE ? OR e.description LIKE ?)
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$search_term = "%$case_number%";
|
||||
$stmt->bind_param('ss', $search_term, $search_term);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$project_id = $row['projectid'];
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
// Поиск по УИД
|
||||
if (!$project_id && $uid) {
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE e.description LIKE ?
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$search_term = "%$uid%";
|
||||
$stmt->bind_param('s', $search_term);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$project_id = $row['projectid'];
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
if (!$project_id) {
|
||||
throw new Exception('Проект не найден по указанным данным');
|
||||
}
|
||||
|
||||
// Создаем документ в CRM
|
||||
$document_id = null;
|
||||
|
||||
if (!empty($file_url) && !empty($file_name)) {
|
||||
// Скачиваем файл
|
||||
$file_content = file_get_contents($file_url);
|
||||
if ($file_content === false) {
|
||||
throw new Exception('Не удалось скачать файл: ' . $file_url);
|
||||
}
|
||||
|
||||
// Формируем название документа
|
||||
$document_title = "СУДЕБНЫЙ ДОКУМЕНТ";
|
||||
if (!empty($content['document_title'])) {
|
||||
$document_title = $content['document_title'];
|
||||
}
|
||||
if (!empty($content['case_number'])) {
|
||||
$document_title .= " по делу " . $content['case_number'];
|
||||
}
|
||||
if (!empty($content['plaintiff_fio'])) {
|
||||
$document_title .= " " . $content['plaintiff_fio'];
|
||||
}
|
||||
$document_title .= " " . date('d.m.Y');
|
||||
|
||||
// Получаем следующий ID для документа
|
||||
$stmt = $mysqli->prepare("SELECT MAX(crmid) + 1 as next_id FROM vtiger_crmentity");
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
$document_id = $row['next_id'];
|
||||
$stmt->close();
|
||||
|
||||
// Обновляем vtiger_crmentity_seq чтобы избежать конфликтов ID
|
||||
$stmt = $mysqli->prepare("UPDATE vtiger_crmentity_seq SET id = ?");
|
||||
$stmt->bind_param('i', $document_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Создаем запись документа
|
||||
$stmt = $mysqli->prepare("
|
||||
INSERT INTO vtiger_crmentity
|
||||
(crmid, smcreatorid, smownerid, modifiedby, setype, description, createdtime, modifiedtime, viewedtime, status, version, presence, deleted, smgroupid, source, label)
|
||||
VALUES (?, 1, 8, 1, 'Documents', ?, NOW(), NOW(), NULL, NULL, 0, 1, 0, 0, 'CRM', ?)
|
||||
");
|
||||
$stmt->bind_param('iss', $document_id, $document_title, $document_title);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Создаем запись в vtiger_notes
|
||||
$stmt = $mysqli->prepare("
|
||||
INSERT INTO vtiger_notes
|
||||
(notesid, note_no, title, filename, notecontent, folderid, filetype, filelocationtype, filedownloadcount, filestatus, filesize, fileversion, tags, its4you_company, s3_bucket, s3_key, s3_etag, nc_path)
|
||||
VALUES (?, ?, ?, ?, ?, 3, 'application/pdf', 'E', 0, 1, ?, '', '', '', '', '', '', '')
|
||||
");
|
||||
$file_size = strlen($file_content);
|
||||
$note_no = "ДОК_" . $document_id;
|
||||
$stmt->bind_param('issssi', $document_id, $note_no, $document_title, $file_name, $document_title, $file_size);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Связываем документ с проектом
|
||||
$stmt = $mysqli->prepare("
|
||||
INSERT INTO vtiger_senotesrel (crmid, notesid) VALUES (?, ?)
|
||||
");
|
||||
$stmt->bind_param('ii', $project_id, $document_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Сохраняем файл в S3
|
||||
$s3_bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3_key = 'crm2/CRM_Active_Files/Documents/' . $document_id . '/' . $file_name;
|
||||
|
||||
// Генерируем ETag для S3
|
||||
$s3_etag = '"' . md5($file_content) . '"';
|
||||
|
||||
// Обновляем запись в vtiger_notes с S3 информацией
|
||||
$stmt = $mysqli->prepare("
|
||||
UPDATE vtiger_notes
|
||||
SET s3_bucket = ?, s3_key = ?, s3_etag = ?, nc_path = ?
|
||||
WHERE notesid = ?
|
||||
");
|
||||
$stmt->bind_param('ssssi', $s3_bucket, $s3_key, $s3_etag, $s3_key, $document_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Загружаем файл в S3
|
||||
$s3_url = "https://s3.twcstorage.ru/$s3_bucket/$s3_key";
|
||||
|
||||
// Используем curl для загрузки в S3
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $s3_url);
|
||||
curl_setopt($ch, CURLOPT_PUT, true);
|
||||
curl_setopt($ch, CURLOPT_INFILE, fopen('data://text/plain;base64,' . base64_encode($file_content), 'r'));
|
||||
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($file_content));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/pdf',
|
||||
'Content-Length: ' . strlen($file_content)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($http_code !== 200 && $http_code !== 201) {
|
||||
// Логируем ошибку, но не прерываем выполнение
|
||||
error_log("Ошибка загрузки в S3: HTTP $http_code, Response: $response");
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем статус проекта если нужно
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT projectstatus
|
||||
FROM vtiger_project
|
||||
WHERE projectid = ?
|
||||
");
|
||||
$stmt->bind_param('i', $project_id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
$current_status = $row['projectstatus'] ?? '';
|
||||
$stmt->close();
|
||||
|
||||
if ($current_status === 'представительство в суде 1й инстанции') {
|
||||
$new_status = 'получено определение суда';
|
||||
$stmt = $mysqli->prepare("
|
||||
UPDATE vtiger_project
|
||||
SET projectstatus = ?
|
||||
WHERE projectid = ?
|
||||
");
|
||||
$stmt->bind_param('si', $new_status, $project_id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
// Логируем действие
|
||||
$log_file = __DIR__ . '/logs/project_update.log';
|
||||
$log_dir = dirname($log_file);
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
$log_entry = date('Y-m-d H:i:s') . " - PROJECT_UPDATE_SUCCESS: " . json_encode([
|
||||
'project_id' => $project_id,
|
||||
'plaintiff_fio' => $plaintiff_fio,
|
||||
'case_number' => $case_number,
|
||||
'uid' => $uid
|
||||
], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
|
||||
|
||||
$mysqli->close();
|
||||
|
||||
// Формируем результат
|
||||
$result = [
|
||||
'success' => true,
|
||||
'project_id' => $project_id,
|
||||
'plaintiff_fio' => $plaintiff_fio,
|
||||
'case_number' => $case_number,
|
||||
'uid' => $uid,
|
||||
'document_id' => $document_id,
|
||||
'message' => 'Документ успешно добавлен к проекту'
|
||||
];
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error_result = [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Логируем ошибку
|
||||
$log_file = __DIR__ . '/logs/project_update.log';
|
||||
$log_dir = dirname($log_file);
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
$log_entry = date('Y-m-d H:i:s') . " - PROJECT_UPDATE_ERROR: " . json_encode($error_result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
|
||||
|
||||
echo json_encode($error_result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем скрипт
|
||||
if (php_sapi_name() === 'cli') {
|
||||
exit(updateProject());
|
||||
}
|
||||
?>
|
||||
458
crm_extensions/simple_project_updater_v2.php
Normal file
458
crm_extensions/simple_project_updater_v2.php
Normal file
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
// simple_project_updater_v2.php
|
||||
// Правильная версия: использует CRM API вместо прямых SQL запросов
|
||||
|
||||
// Устанавливаем рабочую директорию
|
||||
chdir(__DIR__ . '/..');
|
||||
|
||||
require_once 'config.inc.php';
|
||||
require_once 'include/utils/utils.php';
|
||||
require_once 'includes/Loader.php';
|
||||
vimport('includes.runtime.Globals');
|
||||
require_once 'include/database/PearDatabase.php';
|
||||
require_once 'modules/Users/Users.php';
|
||||
require_once 'include/Webservices/Utils.php';
|
||||
require_once 'include/Webservices/Create.php';
|
||||
require_once 'include/Webservices/Login.php';
|
||||
require_once 'include/Webservices/AuthToken.php';
|
||||
require_once 'include/Webservices/AddRelated.php';
|
||||
require_once 'data/CRMEntity.php';
|
||||
require_once 'modules/Vtiger/CRMEntity.php';
|
||||
require_once 'crm_extensions/file_storage/S3Client.php';
|
||||
|
||||
$adb = PearDatabase::getInstance();
|
||||
|
||||
// Логирование
|
||||
function log_message($level, $message) {
|
||||
$log_file = __DIR__ . '/logs/project_update.log';
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$log_entry = "{$timestamp} - {$level}: {$message}\n";
|
||||
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
// Функция для создания документа через внутренние функции CRM
|
||||
function createDocumentViaAPI($documentData) {
|
||||
global $adb, $current_user;
|
||||
|
||||
// Создаем пользователя для CRM
|
||||
if (!isset($current_user) || !$current_user) {
|
||||
$current_user = new Users();
|
||||
$current_user->retrieveCurrentUserInfoFromFile(8); // Фёдор Коробков
|
||||
}
|
||||
|
||||
// Создаем документ через прямые SQL запросы (как в simple_project_updater.php)
|
||||
log_message('DEBUG', "Создаем документ через прямые SQL запросы");
|
||||
|
||||
global $mysqli;
|
||||
|
||||
// Получаем следующий ID
|
||||
log_message('DEBUG', "Получаем следующий ID для документа");
|
||||
$result = $mysqli->query("SELECT MAX(crmid) as max_id FROM vtiger_crmentity");
|
||||
$row = $result->fetch_assoc();
|
||||
$next_id = ($row['max_id'] ?? 0) + 1;
|
||||
log_message('DEBUG', "Следующий ID: $next_id");
|
||||
|
||||
$created_time = date('Y-m-d H:i:s');
|
||||
|
||||
// Создаем запись в vtiger_crmentity
|
||||
log_message('DEBUG', "Создаем запись в vtiger_crmentity");
|
||||
$sql = "INSERT INTO vtiger_crmentity (crmid, smcreatorid, smownerid, modifiedby, setype, description, createdtime, modifiedtime, presence, deleted, label, source) VALUES ($next_id, 8, 8, 8, 'Documents', '" . $mysqli->real_escape_string($documentData['notecontent']) . "', '$created_time', '$created_time', 1, 0, '" . $mysqli->real_escape_string($documentData['notes_title']) . "', 'WEBSERVICE')";
|
||||
log_message('DEBUG', "SQL: $sql");
|
||||
$result = $mysqli->query($sql);
|
||||
if (!$result) {
|
||||
throw new Exception('Ошибка создания записи в vtiger_crmentity: ' . $mysqli->error);
|
||||
}
|
||||
log_message('DEBUG', "Запись в vtiger_crmentity создана");
|
||||
|
||||
// Создаем запись в vtiger_notes
|
||||
log_message('DEBUG', "Создаем запись в vtiger_notes");
|
||||
$note_no = 'ДОК_' . $next_id;
|
||||
// Формируем S3 URL для filename
|
||||
$s3_bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3_key = 'crm2/CRM_Active_Files/Documents/' . $next_id . '/' . $documentData['filename'];
|
||||
$s3_url = "https://s3.twcstorage.ru/$s3_bucket/$s3_key";
|
||||
|
||||
$sql = "INSERT INTO vtiger_notes (notesid, note_no, title, filename, notecontent, folderid, filetype, filelocationtype, filedownloadcount, filestatus, filesize, fileversion, tags, its4you_company, s3_bucket, s3_key, s3_etag, nc_path) VALUES ($next_id, '$note_no', '" . $mysqli->real_escape_string($documentData['notes_title']) . "', '" . $mysqli->real_escape_string($s3_url) . "', '" . $mysqli->real_escape_string($documentData['notecontent']) . "', 1, '" . $mysqli->real_escape_string($documentData['filetype']) . "', '" . $mysqli->real_escape_string($documentData['filelocationtype']) . "', 0, '" . $mysqli->real_escape_string($documentData['filestatus']) . "', " . intval($documentData['filesize']) . ", '" . $mysqli->real_escape_string($documentData['fileversion']) . "', '', '', '', '', '', '')";
|
||||
log_message('DEBUG', "SQL vtiger_notes: $sql");
|
||||
$result = $mysqli->query($sql);
|
||||
if (!$result) {
|
||||
throw new Exception('Ошибка создания записи в vtiger_notes: ' . $mysqli->error);
|
||||
}
|
||||
log_message('DEBUG', "Запись в vtiger_notes создана");
|
||||
|
||||
// Обновляем последовательность
|
||||
$mysqli->query("UPDATE vtiger_crmentity_seq SET id = $next_id");
|
||||
|
||||
log_message('DEBUG', "Документ создан с ID: $next_id");
|
||||
|
||||
return [
|
||||
'id' => '15x' . $next_id,
|
||||
'notes_title' => $documentData['notes_title'],
|
||||
'createdtime' => $created_time,
|
||||
'modifiedtime' => $created_time
|
||||
];
|
||||
}
|
||||
|
||||
// Функция для привязки документа к проекту через прямые SQL запросы
|
||||
function linkDocumentToProject($projectId, $documentId) {
|
||||
global $mysqli;
|
||||
|
||||
log_message('DEBUG', "Привязываем документ $documentId к проекту $projectId");
|
||||
|
||||
// Создаем связь в vtiger_senotesrel
|
||||
$stmt = $mysqli->prepare("INSERT INTO vtiger_senotesrel (crmid, notesid) VALUES (?, ?)");
|
||||
$stmt->bind_param('ii', $projectId, $documentId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
log_message('DEBUG', "Связь создана между проектом $projectId и документом $documentId");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Читаем JSON из stdin
|
||||
$input = file_get_contents('php://stdin');
|
||||
$data = json_decode($input, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Ошибка парсинга JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
// Проверяем структуру данных - может быть массив или объект
|
||||
if (is_array($data) && isset($data[0])) {
|
||||
// Если это массив, берем первый элемент
|
||||
$item = $data[0];
|
||||
} elseif (is_array($data) && isset($data['content'])) {
|
||||
// Если это объект, используем его напрямую
|
||||
$item = $data;
|
||||
} else {
|
||||
throw new Exception('Неверная структура данных: ожидается объект с полем content или массив объектов');
|
||||
}
|
||||
|
||||
// Проверяем наличие обязательных полей
|
||||
if (!$item || !isset($item['content']) || !is_array($item['content'])) {
|
||||
throw new Exception('Неверная структура данных: отсутствует content');
|
||||
}
|
||||
|
||||
$content = $item['content'];
|
||||
$file_url = trim($item['file'] ?? '');
|
||||
$file_name = $item['file_name'] ?? '';
|
||||
|
||||
if (empty($file_url) || empty($file_name)) {
|
||||
throw new Exception('Отсутствует URL файла или имя файла');
|
||||
}
|
||||
|
||||
// Инициализируем пользователя CRM
|
||||
$current_user = new Users();
|
||||
$current_user->retrieveCurrentUserInfoFromFile(8); // Фёдор Коробков
|
||||
log_message('INFO', "Инициализирован пользователь CRM: " . $current_user->user_name);
|
||||
|
||||
// Подключаемся к базе данных для поиска проекта
|
||||
$mysqli = new mysqli('localhost', 'ci20465_72new', 'EcY979Rn', 'ci20465_72new');
|
||||
if ($mysqli->connect_error) {
|
||||
throw new Exception('Не удалось подключиться к базе данных: ' . $mysqli->connect_error);
|
||||
}
|
||||
$mysqli->set_charset('utf8');
|
||||
|
||||
// Проверяем кодировку соединения
|
||||
$charset_result = $mysqli->query("SELECT @@character_set_connection, @@collation_connection");
|
||||
if ($charset_result) {
|
||||
$charset_row = $charset_result->fetch_row();
|
||||
log_message('DEBUG', "Кодировка БД: " . $charset_row[0] . ", collation: " . $charset_row[1]);
|
||||
}
|
||||
|
||||
$plaintiff_fio = $content['plaintiff_fio'] ?? '';
|
||||
$case_number = $content['case_number'] ?? '';
|
||||
$uid = $content['uid'] ?? '';
|
||||
|
||||
log_message('DEBUG', "Поиск проекта по данным: plaintiff_fio='$plaintiff_fio', case_number='$case_number', uid='$uid'");
|
||||
$project_id = null;
|
||||
|
||||
// Улучшенный поиск по комбинации критериев с нестрогим соответствием
|
||||
$search_criteria = [];
|
||||
$search_params = [];
|
||||
$param_types = '';
|
||||
|
||||
// ФИО с учетом мужского/женского рода
|
||||
if ($plaintiff_fio) {
|
||||
$surname = explode(' ', $plaintiff_fio)[0];
|
||||
$surname_male = $surname; // Соколов
|
||||
$surname_female = $surname . 'а'; // Соколова
|
||||
|
||||
$search_criteria[] = "(p.projectname LIKE ? OR e.description LIKE ? OR p.projectname LIKE ? OR e.description LIKE ?)";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($surname_male) . "%";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($surname_male) . "%";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($surname_female) . "%";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($surname_female) . "%";
|
||||
$param_types .= 'ssss';
|
||||
|
||||
log_message('DEBUG', "Поиск по ФИО: '$surname_male' и '$surname_female'");
|
||||
}
|
||||
|
||||
// Суд
|
||||
if (!empty($content['court'])) {
|
||||
$court = $content['court'];
|
||||
$search_criteria[] = "(e.description LIKE ? OR cf.cf_1499 LIKE ? OR cf.cf_2278 LIKE ?)";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
|
||||
$param_types .= 'sss';
|
||||
|
||||
log_message('DEBUG', "Поиск по суду: '$court'");
|
||||
}
|
||||
|
||||
// Номер дела
|
||||
if ($case_number) {
|
||||
$search_criteria[] = "(p.project_no = ? OR e.description LIKE ?)";
|
||||
$search_params[] = $case_number;
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($case_number) . "%";
|
||||
$param_types .= 'ss';
|
||||
|
||||
log_message('DEBUG', "Поиск по номеру дела: '$case_number'");
|
||||
}
|
||||
|
||||
// УИД
|
||||
if ($uid) {
|
||||
$search_criteria[] = "(e.description LIKE ?)";
|
||||
$search_params[] = "%" . $mysqli->real_escape_string($uid) . "%";
|
||||
$param_types .= 's';
|
||||
|
||||
log_message('DEBUG', "Поиск по УИД: '$uid'");
|
||||
}
|
||||
|
||||
// Выполняем поиск если есть критерии
|
||||
if (!empty($search_criteria)) {
|
||||
// Сначала пробуем строгий поиск (все критерии)
|
||||
$where_clause = implode(' AND ', $search_criteria);
|
||||
$sql = "
|
||||
SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
LEFT JOIN vtiger_projectcf cf ON cf.projectid = p.projectid
|
||||
WHERE $where_clause
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN p.projectname LIKE '%" . $mysqli->real_escape_string($surname ?? '') . "%' THEN 1
|
||||
WHEN e.description LIKE '%" . $mysqli->real_escape_string($surname ?? '') . "%' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
p.projectid ASC
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
log_message('DEBUG', "Строгий поиск SQL: $sql");
|
||||
log_message('DEBUG', "Параметры: " . json_encode($search_params));
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
if ($stmt) {
|
||||
if (!empty($search_params)) {
|
||||
$stmt->bind_param($param_types, ...$search_params);
|
||||
}
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$project_id = $row['projectid'];
|
||||
log_message('SUCCESS', "Найден проект (строгий поиск): ID=$project_id, название='{$row['projectname']}'");
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
log_message('ERROR', "Ошибка подготовки SQL: " . $mysqli->error);
|
||||
}
|
||||
|
||||
// Если строгий поиск не дал результатов, пробуем поиск только по ФИО
|
||||
if (!$project_id && !empty($surname)) {
|
||||
$sql = "
|
||||
SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
LEFT JOIN vtiger_projectcf cf ON cf.projectid = p.projectid
|
||||
WHERE (p.projectname LIKE ? OR e.description LIKE ? OR p.projectname LIKE ? OR e.description LIKE ?)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN p.projectname LIKE '%" . $mysqli->real_escape_string($surname) . "%' THEN 1
|
||||
WHEN e.description LIKE '%" . $mysqli->real_escape_string($surname) . "%' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
p.projectid ASC
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
log_message('DEBUG', "Поиск только по ФИО SQL: $sql");
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
if ($stmt) {
|
||||
$surname_male = "%" . $mysqli->real_escape_string($surname) . "%";
|
||||
$surname_female = "%" . $mysqli->real_escape_string($surname . 'а') . "%";
|
||||
log_message('DEBUG', "Параметры поиска по ФИО: '$surname_male', '$surname_female'");
|
||||
log_message('DEBUG', "Длина параметров: " . strlen($surname_male) . ", " . strlen($surname_female));
|
||||
log_message('DEBUG', "Кодировка параметров: " . mb_detect_encoding($surname_male) . ", " . mb_detect_encoding($surname_female));
|
||||
$stmt->bind_param('ssss', $surname_male, $surname_male, $surname_female, $surname_female);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
log_message('DEBUG', "Количество найденных записей: " . $result->num_rows);
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$project_id = $row['projectid'];
|
||||
log_message('SUCCESS', "Найден проект (поиск по ФИО): ID=$project_id, название='{$row['projectname']}'");
|
||||
} else {
|
||||
log_message('DEBUG', "Проект не найден по ФИО");
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
log_message('ERROR', "Ошибка подготовки SQL для поиска по ФИО: " . $mysqli->error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$project_id) {
|
||||
throw new Exception('Проект не найден по указанным данным');
|
||||
}
|
||||
|
||||
log_message('INFO', "Найден проект ID: $project_id");
|
||||
|
||||
// Формируем название документа
|
||||
$document_title = "СУДЕБНЫЙ ДОКУМЕНТ";
|
||||
if (!empty($content['document_title'])) {
|
||||
$document_title = $content['document_title'];
|
||||
}
|
||||
if (!empty($content['case_number'])) {
|
||||
$document_title .= " по делу " . $content['case_number'];
|
||||
}
|
||||
if (!empty($content['plaintiff_fio'])) {
|
||||
$document_title .= " " . $content['plaintiff_fio'];
|
||||
}
|
||||
$document_title .= " " . date('d.m.Y');
|
||||
|
||||
// Создаём документ через CRM API
|
||||
$documentData = [
|
||||
'notes_title' => $document_title,
|
||||
'filename' => $file_name,
|
||||
'assigned_user_id' => '19x8', // Фёдор Коробков
|
||||
'notecontent' => "Документ создан автоматически из судебного документа.\n\n" .
|
||||
"Номер дела: " . ($content['case_number'] ?? 'не указан') . "\n" .
|
||||
"Суд: " . ($content['court'] ?? 'не указан') . "\n" .
|
||||
"Истец: " . ($content['plaintiff_fio'] ?? 'не указан') . "\n" .
|
||||
"УИД: " . ($content['uid'] ?? 'не указан') . "\n" .
|
||||
"Дата создания: " . date('d.m.Y H:i:s'),
|
||||
'filetype' => 'application/pdf',
|
||||
'filesize' => '0', // Будет обновлено после загрузки
|
||||
'filelocationtype' => 'E', // External URL
|
||||
'fileversion' => '1.0',
|
||||
'filestatus' => '1', // Active
|
||||
'folderid' => '22x1', // Default папка
|
||||
];
|
||||
|
||||
log_message('INFO', "Создаём документ: $document_title");
|
||||
$documentResult = createDocumentViaAPI($documentData);
|
||||
|
||||
$documentWsId = $documentResult['id'];
|
||||
list(, $documentNumericId) = explode('x', $documentWsId, 2);
|
||||
|
||||
log_message('SUCCESS', "Документ создан: $documentWsId (numeric: $documentNumericId)");
|
||||
|
||||
// S3 метаданные уже установлены при создании записи
|
||||
log_message('SUCCESS', "S3 метаданные установлены для документа $documentNumericId");
|
||||
|
||||
// Загружаем файл в S3
|
||||
if (!empty($file_url) && !empty($file_name)) {
|
||||
log_message('DEBUG', "Начинаем загрузку файла в S3: $file_url");
|
||||
|
||||
// Скачиваем файл
|
||||
$file_content = file_get_contents($file_url);
|
||||
if ($file_content === false) {
|
||||
log_message('ERROR', "Не удалось скачать файл: $file_url");
|
||||
} else {
|
||||
log_message('DEBUG', "Файл скачан, размер: " . strlen($file_content) . " байт");
|
||||
|
||||
// Генерируем ETag для S3
|
||||
$s3_etag = '"' . md5($file_content) . '"';
|
||||
|
||||
// Формируем S3 ключ
|
||||
$s3_bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
||||
$s3_key = 'crm2/CRM_Active_Files/Documents/' . $documentNumericId . '/' . $file_name;
|
||||
|
||||
// Загружаем файл в S3 через S3Client
|
||||
try {
|
||||
log_message('DEBUG', "Загружаем конфигурацию S3");
|
||||
// Загружаем конфигурацию S3
|
||||
$config = require __DIR__ . '/file_storage/config.php';
|
||||
log_message('DEBUG', "Конфигурация S3 загружена");
|
||||
$s3Client = new S3Client($config['s3']);
|
||||
log_message('DEBUG', "S3Client создан");
|
||||
|
||||
// Создаем временный файл
|
||||
$temp_file = tempnam(sys_get_temp_dir(), 'crm_doc_');
|
||||
file_put_contents($temp_file, $file_content);
|
||||
|
||||
// Загружаем файл в S3
|
||||
log_message('DEBUG', "Загружаем файл в S3 с ключом: $s3_key");
|
||||
$result = $s3Client->uploadFile($temp_file, $s3_key, [
|
||||
'ContentType' => 'application/pdf'
|
||||
]);
|
||||
|
||||
// Удаляем временный файл
|
||||
unlink($temp_file);
|
||||
|
||||
log_message('SUCCESS', "Файл успешно загружен в S3");
|
||||
|
||||
// Обновляем ETag, размер файла и S3 метаданные в БД
|
||||
$stmt = $mysqli->prepare("
|
||||
UPDATE vtiger_notes
|
||||
SET s3_etag = ?, filesize = ?, s3_bucket = ?, s3_key = ?
|
||||
WHERE notesid = ?
|
||||
");
|
||||
$file_size = strlen($file_content);
|
||||
$stmt->bind_param('sissi', $s3_etag, $file_size, $s3_bucket, $s3_key, $documentNumericId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
log_message('SUCCESS', "S3 ETag и размер файла обновлены");
|
||||
|
||||
} catch (Exception $e) {
|
||||
log_message('ERROR', "Ошибка загрузки в S3: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Привязываем документ к проекту
|
||||
$linkSuccess = linkDocumentToProject($project_id, (int)$documentNumericId);
|
||||
|
||||
if ($linkSuccess) {
|
||||
log_message('SUCCESS', "Документ успешно привязан к проекту");
|
||||
} else {
|
||||
log_message('ERROR', "Ошибка привязки документа к проекту");
|
||||
}
|
||||
|
||||
$mysqli->close();
|
||||
|
||||
// Формируем результат
|
||||
$result = [
|
||||
'success' => true,
|
||||
'project_id' => $project_id,
|
||||
'plaintiff_fio' => explode(' ', $plaintiff_fio)[0],
|
||||
'case_number' => $case_number,
|
||||
'uid' => $uid,
|
||||
'document_id' => $documentNumericId,
|
||||
'document_ws_id' => $documentWsId,
|
||||
's3_url' => $s3_url,
|
||||
'message' => 'Документ успешно создан через CRM API и добавлен к проекту'
|
||||
];
|
||||
|
||||
log_message('SUCCESS', "Обработка завершена успешно: " . json_encode($result, JSON_UNESCAPED_UNICODE));
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error_message = $e->getMessage();
|
||||
log_message('ERROR', "Ошибка: $error_message");
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $error_message,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 1;
|
||||
}
|
||||
?>
|
||||
5
crm_extensions/test_input.json
Normal file
5
crm_extensions/test_input.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"combinedText": "29.09.2025 E-A.BOL 188.1 № М-5071/2025\n\nими И УИД № 50RS0052-01-2025-007323-70\n\nВложение edeb-2996-fd47\n- ОПРЕДЕЛЕНИЕ\n\n26 августа 2025 г. г.о. Щелково Московской\nобласти\n\nСудья Щелковского городского суда Московской области Пикулева Т.И.,\nизучив исковое заявление МОО «Клиентправ», действующего в интересах\nСоколова Александра Владимировича к ООО «СКИЛБОКС» о взыскании\nденежных средств, неустойки, процентов, штрафа, компенсации морального вреда,\n\nустановил:\n\nВ Щелковский городской суд Московской области поступило приведенное\nвыше исковое заявление МОО «Клиентправ».\n\nОпределением судьи Щелковского городского суда Московской области\nот 16 июля 2025 г. названное исковое заявление МОО «Клиентправ» было\nоставлено без движения до 21 августа 2025 г. (включительно) для исправления\nимеющихся недостатков поданного искового заявления.\n\nУсматривается, что в установленный в названном определении от 16 июля\n2025 г. срок, МОО «Клиентправ» недостатки искового заявления устранены не\nбыли.\n\nВ соответствии с п. 7 ч. | ст. 135 ГПК РФ судья возвращает исковое\nзаявление в случае, если не устранены обстоятельства, послужившие основаниями\nдля оставления искового заявления без движения, в срок, установленный в\nопределении суда.\n\nТаким образом, исковое заявление MOO «Клиентправ» подлежит\nвозвращению.\n\nНа основании изложенного, руководствуясь ст. ст. 135, 225 ГПК РФ,\n\nопределил:\n\nИсковое заявление МОО «Клиентправ», действующего в интересах\nСоколова Александра Владимировича к ООО «СКИЛБОКС» о взыскании\nденежных средств, неустойки, процентов, штрафа, компенсации морального вреда\n— возвратить со всеми приложенными к нему документами.\n\nРазъяснить, что возвращение заявления не является препятствием для\nповторного обращения заявителя в суд с заявлением о том же предмете и по тем\nже основаниям после устранения допущенного нарушения.\n\nОпределение может быть обжаловано в Московский областной суд в течение\nпятнадцати дней со дня вынесения определения через Щелковский городской\nсуд Московской области путем подачи частной жалобы.\n\nСудья ' Т.И. Пикулева\n\nДОКУМЕНТ ПОДПИСАН\nЭЛЕКТРОННОЙ ПОДПИСЬЮ\nСертификат сс217ае32е5 16752655060 Ic f6086¢\nВладелец Пикулева Татьяна Игоревна\nДолжность Судья Щелковского городского суда Московской облас\nПействителен с 29.1 1.2024 по 22.02.2026\n\nЩелковский городской суд\n\nМосковская o6n., г. Щелково,\nпл. Ленина, 5, 141100\n\n141100\n\nZQQVB5186WT6BLM\n\nПОЧТА\nРОССИИ\n\na a ов ПОЧТА РОССИИ СУДЕБНОЕ\n- cla M-5071/25 Пикулева\nTM.\n801084 12 06117 9\n\nКому: МОО Клиентправ\nКуда: г.Москва, ул. Тверская, д.22а, ст.1, оф.7\n\n1 25375 Получайте и отправляйте письма онлайн\nПодробности на сайте zakaznoe.pochta.ru\n\nТрек-номер: 80108412061179\nКод доступа: 33127398\n\nЭлектронный оригинал письма:\nhitns://zakaznoe.nochta.ni/access,"
|
||||
}
|
||||
]
|
||||
368
crm_extensions/update_project_from_document.php
Executable file
368
crm_extensions/update_project_from_document.php
Executable file
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
/**
|
||||
* Скрипт для обновления проекта CRM на основе данных из судебного документа
|
||||
*
|
||||
* Использование:
|
||||
* php update_project_from_document.php '{"content": {...}, "file": "...", "file_name": "..."}'
|
||||
*
|
||||
* Или через stdin:
|
||||
* echo '{"content": {...}}' | php update_project_from_document.php
|
||||
*/
|
||||
|
||||
// Устанавливаем рабочую директорию
|
||||
chdir(__DIR__ . '/..');
|
||||
|
||||
require_once 'config.inc.php';
|
||||
require_once 'include/utils/utils.php';
|
||||
require_once 'include/utils/CommonUtils.php';
|
||||
require_once 'modules/Vtiger/models/Record.php';
|
||||
|
||||
class ProjectUpdater {
|
||||
private $adb;
|
||||
private $current_user;
|
||||
|
||||
public function __construct() {
|
||||
global $adb;
|
||||
$this->adb = $adb;
|
||||
|
||||
// Получаем системного пользователя
|
||||
$this->current_user = Users::getActiveAdminUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск проекта по данным из документа
|
||||
*/
|
||||
public function findProject($content) {
|
||||
$plaintiff_fio = $content['plaintiff_fio'] ?? '';
|
||||
$case_number = $content['case_number'] ?? '';
|
||||
$uid = $content['uid'] ?? '';
|
||||
|
||||
// Поиск по ФИО истца
|
||||
if ($plaintiff_fio) {
|
||||
$sql = "SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE (p.projectname LIKE ? OR e.description LIKE ?)";
|
||||
|
||||
$params = ["%$plaintiff_fio%", "%$plaintiff_fio%"];
|
||||
$result = $this->adb->pquery($sql, $params);
|
||||
|
||||
if ($this->adb->num_rows($result) > 0) {
|
||||
$row = $this->adb->fetchByAssoc($result);
|
||||
return $row['projectid'];
|
||||
}
|
||||
}
|
||||
|
||||
// Поиск по номеру дела
|
||||
if ($case_number) {
|
||||
$sql = "SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE (p.project_no LIKE ? OR e.description LIKE ?)";
|
||||
|
||||
$params = ["%$case_number%", "%$case_number%"];
|
||||
$result = $this->adb->pquery($sql, $params);
|
||||
|
||||
if ($this->adb->num_rows($result) > 0) {
|
||||
$row = $this->adb->fetchByAssoc($result);
|
||||
return $row['projectid'];
|
||||
}
|
||||
}
|
||||
|
||||
// Поиск по УИД
|
||||
if ($uid) {
|
||||
$sql = "SELECT p.projectid, p.projectname, p.project_no, e.description
|
||||
FROM vtiger_project p
|
||||
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
|
||||
WHERE e.description LIKE ?";
|
||||
|
||||
$params = ["%$uid%"];
|
||||
$result = $this->adb->pquery($sql, $params);
|
||||
|
||||
if ($this->adb->num_rows($result) > 0) {
|
||||
$row = $this->adb->fetchByAssoc($result);
|
||||
return $row['projectid'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление проекта
|
||||
*/
|
||||
public function updateProject($project_id, $content) {
|
||||
try {
|
||||
// Получаем запись проекта
|
||||
$project = Vtiger_Record_Model::getInstanceById($project_id, 'Project');
|
||||
|
||||
// Обновляем описание с новыми данными
|
||||
$current_description = $project->get('description') ?: '';
|
||||
$new_info = $this->formatDocumentInfo($content);
|
||||
|
||||
// Добавляем информацию о документе в описание
|
||||
$updated_description = $current_description . "\n\n" . $new_info;
|
||||
|
||||
// Обновляем поля проекта
|
||||
$project->set('description', $updated_description);
|
||||
|
||||
// Обновляем статус если нужно
|
||||
$current_status = $project->get('projectstatus');
|
||||
if ($current_status === 'представительство в суде 1й инстанции') {
|
||||
$project->set('projectstatus', 'получено определение суда');
|
||||
}
|
||||
|
||||
// Сохраняем изменения
|
||||
$project->save();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'project_id' => $project_id,
|
||||
'message' => 'Проект успешно обновлен'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Ошибка обновления проекта: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка документа в CRM
|
||||
*/
|
||||
public function uploadDocument($project_id, $file_url, $file_name, $content) {
|
||||
try {
|
||||
// Скачиваем файл
|
||||
$file_content = file_get_contents($file_url);
|
||||
if ($file_content === false) {
|
||||
throw new Exception('Не удалось скачать файл');
|
||||
}
|
||||
|
||||
// Создаем временный файл
|
||||
$temp_file = tempnam(sys_get_temp_dir(), 'crm_doc_');
|
||||
file_put_contents($temp_file, $file_content);
|
||||
|
||||
// Создаем запись документа
|
||||
$document = Vtiger_Record_Model::getCleanInstance('Documents');
|
||||
|
||||
// Формируем название документа
|
||||
$document_title = $this->formatDocumentTitle($content);
|
||||
|
||||
$document->set('notes_title', $document_title);
|
||||
$document->set('filename', $file_name);
|
||||
$document->set('filetype', 'application/pdf');
|
||||
$document->set('filesize', strlen($file_content));
|
||||
$document->set('filelocationtype', 'E'); // External
|
||||
$document->set('folderid', 3); // Documents folder
|
||||
|
||||
// Сохраняем документ
|
||||
$document->save();
|
||||
|
||||
// Прикрепляем файл
|
||||
$document->uploadAndSaveFile($temp_file, $file_name);
|
||||
|
||||
// Связываем документ с проектом
|
||||
$this->linkDocumentToProject($document->getId(), $project_id);
|
||||
|
||||
// Удаляем временный файл
|
||||
unlink($temp_file);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'document_id' => $document->getId(),
|
||||
'message' => 'Документ успешно загружен'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Ошибка загрузки документа: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Связывание документа с проектом
|
||||
*/
|
||||
private function linkDocumentToProject($document_id, $project_id) {
|
||||
$sql = "INSERT INTO vtiger_senotesrel (crmid, notesid) VALUES (?, ?)";
|
||||
$this->adb->pquery($sql, [$project_id, $document_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование информации о документе
|
||||
*/
|
||||
private function formatDocumentInfo($content) {
|
||||
$info = "=== СУДЕБНЫЙ ДОКУМЕНТ ===\n";
|
||||
$info .= "Дата получения: " . date('d.m.Y H:i:s') . "\n";
|
||||
|
||||
if (!empty($content['case_number'])) {
|
||||
$info .= "Номер дела: " . $content['case_number'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['uid'])) {
|
||||
$info .= "УИД: " . $content['uid'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['court'])) {
|
||||
$info .= "Суд: " . $content['court'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['plaintiff_fio'])) {
|
||||
$info .= "Истец: " . $content['plaintiff_fio'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['defendant_fio'])) {
|
||||
$info .= "Ответчик: " . $content['defendant_fio'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['document_title'])) {
|
||||
$info .= "Тип документа: " . $content['document_title'] . "\n";
|
||||
}
|
||||
|
||||
if (!empty($content['description'])) {
|
||||
$info .= "Описание: " . $content['description'] . "\n";
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование названия документа
|
||||
*/
|
||||
private function formatDocumentTitle($content) {
|
||||
$title = "СУДЕБНЫЙ ДОКУМЕНТ";
|
||||
|
||||
if (!empty($content['document_title'])) {
|
||||
$title = $content['document_title'];
|
||||
}
|
||||
|
||||
if (!empty($content['case_number'])) {
|
||||
$title .= " по делу " . $content['case_number'];
|
||||
}
|
||||
|
||||
if (!empty($content['plaintiff_fio'])) {
|
||||
$title .= " " . $content['plaintiff_fio'];
|
||||
}
|
||||
|
||||
$title .= " " . date('d.m.Y');
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование действий
|
||||
*/
|
||||
private function logAction($action, $data) {
|
||||
$log_file = __DIR__ . '/logs/project_update.log';
|
||||
$log_dir = dirname($log_file);
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
$log_entry = date('Y-m-d H:i:s') . " - $action: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Основная функция
|
||||
*/
|
||||
function main() {
|
||||
try {
|
||||
// Получаем входные данные
|
||||
$input = '';
|
||||
|
||||
if (isset($argv[1])) {
|
||||
// Данные переданы как аргумент командной строки
|
||||
$input = $argv[1];
|
||||
} else {
|
||||
// Данные переданы через stdin
|
||||
$input = stream_get_contents(STDIN);
|
||||
}
|
||||
|
||||
if (empty($input)) {
|
||||
throw new Exception('Не указаны входные данные');
|
||||
}
|
||||
|
||||
// Парсим JSON
|
||||
$data = json_decode($input, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Ошибка парсинга JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
// Проверяем структуру данных
|
||||
if (!isset($data['content']) || !is_array($data['content'])) {
|
||||
throw new Exception('Неверная структура данных: отсутствует content');
|
||||
}
|
||||
|
||||
$content = $data['content'];
|
||||
$file_url = $data['file'] ?? '';
|
||||
$file_name = $data['file_name'] ?? '';
|
||||
|
||||
// Создаем экземпляр обновлятеля
|
||||
$updater = new ProjectUpdater();
|
||||
|
||||
// Ищем проект
|
||||
$project_id = $updater->findProject($content);
|
||||
|
||||
if (!$project_id) {
|
||||
throw new Exception('Проект не найден по указанным данным');
|
||||
}
|
||||
|
||||
// Обновляем проект
|
||||
$update_result = $updater->updateProject($project_id, $content);
|
||||
|
||||
if (!$update_result['success']) {
|
||||
throw new Exception($update_result['error']);
|
||||
}
|
||||
|
||||
// Загружаем документ если указан
|
||||
$upload_result = null;
|
||||
if (!empty($file_url) && !empty($file_name)) {
|
||||
$upload_result = $updater->uploadDocument($project_id, $file_url, $file_name, $content);
|
||||
}
|
||||
|
||||
// Формируем результат
|
||||
$result = [
|
||||
'success' => true,
|
||||
'project_id' => $project_id,
|
||||
'update_result' => $update_result,
|
||||
'upload_result' => $upload_result,
|
||||
'message' => 'Проект успешно обновлен'
|
||||
];
|
||||
|
||||
// Логируем действие
|
||||
$updater->logAction('PROJECT_UPDATE_SUCCESS', $result);
|
||||
|
||||
// Выводим результат
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error_result = [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Логируем ошибку
|
||||
if (isset($updater)) {
|
||||
$updater->logAction('PROJECT_UPDATE_ERROR', $error_result);
|
||||
}
|
||||
|
||||
echo json_encode($error_result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем скрипт
|
||||
if (php_sapi_name() === 'cli') {
|
||||
exit(main());
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user