Добавлены скрипты для парсинга судебных документов и обновления проектов в 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:
Fedor
2025-09-30 19:54:37 +03:00
parent 68b5067b0e
commit dabcd43a00
24 changed files with 2637 additions and 0 deletions

View File

@@ -85,3 +85,5 @@
---
**Последнее обновление:** 24 сентября 2025

View File

@@ -120,3 +120,5 @@ curl -I "https://s3.twcstorage.ru/your_bucket/"
---
**💡 Совет:** Всегда делайте backup перед изменениями!

View 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)

View 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
- ✅ Надежное извлечение текста

View 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 хранилищу

View 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
- Более надежная обработка ошибок

View 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())

View 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())

View 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. Все права защищены.

View 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 "$@"

View 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
View File

0
crm_extensions/file_storage/n8n_migration.sh Executable file → Normal file
View File

View 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
View File

0
crm_extensions/file_storage/run_auto_s3_migration.sh Executable file → Normal file
View File

0
crm_extensions/file_storage/run_batch_s3_metadata.sh Executable file → Normal file
View File

0
crm_extensions/file_storage/run_s3_migration.sh Executable file → Normal file
View File

View 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())

View 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());
}
?>

View 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;
}
?>

View 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,"
}
]

View 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());
}
?>