Подписывайтесь:

Блог AST-SoftPro

System design юридической CRM: обращения, договоры, судебные и претензионные дела

16.09.2026 22 мин чтения
System design юридической CRM: обращения, договоры, судебные и претензионные дела

Введение

Проектирование CRM для юридической компании — задача, которая на первый взгляд похожа на обычную бизнес-систему, а на практике несёт в себе ряд специфических требований: строгий аудит всех действий, работа с документами длительного хранения, сложные статусные модели дел и необходимость масштабирования на десятки филиалов. В этой статье разберём ключевые вопросы system design для юридической CRM с расчётом нагрузок на 30 филиалов и 100 сотрудников, а также предложим конкретный стек на Python.

Специфика юридической CRM: что отличает от обычной CRM

Обычная CRM хранит контакты, сделки и задачи. Юридическая CRM работает с принципиально другим набором сущностей и требованиями:

Обращения (intake). Клиент обращается с вопросом — по телефону, через сайт, лично в филиале. Обращение должно быть зарегистрировано за минуты, а не часы. Каждое обращение привязывается к конкретному филиалу и сотруднику. При этом обращения часто конвертируются в договоры или остаются «холодными» — система должна гибко отражать оба сценария.

Договоры. Договор — это не просто документ, а жизненный цикл: черновик → согласование → подписание → исполнение → закрытие/продление. Каждый переход между статусами фиксируется, прикладываются версии документов, отслеживаются обязательства сторон и сроки.

Судебные дела. Судебное дело имеет жёсткую привязку к процессуальным стадиям: подача иска, принятие к производству, предварительное заседание, судебные заседания, решение, апелляция, исполнение. Каждое событие (заседание, ходатайство, определение) фиксируется с датой и приложением документов.

Претензионные дела. Претензия — это досудебный этап: отправка претензии, срок ответа контрагента, результат (принята/отклонена/без ответа). Если претензия не увенчалась успехом, она конвертируется в судебное дело с переносом истории.

⚠️ Ключевое отличие: в юридической CRM каждое действие должно быть аудируемым. Кто изменил статус дела, когда, что было до и после — это не «лог действий», а юридически значимая информация, которая может потребоваться через годы.

Расчёт нагрузки: 30 филиалов, 100 сотрудников

Базовые допущения

Параметр Значение Обоснование
Филиалов 30 По условию
Сотрудников (юристы + администраторы) 100 ~3–4 на филиал
Пиковая нагрузка (9:00–12:00) 80% сотрудников онлайн Утренний пик работы с документами
Среднее количество действий на сотрудника в час 15–25 операций Заполнение полей, поиск, переходы между экранами
Пиковый RPS (операций/сек) ~30–40 80 * 20 / 3600 ≈ 0.44 опс на одного; с учётом API-вызовов (1 операция = 2–5 запросов к БД) → ~35 RPS на БД
Пиковый RPS на API ~150–200 Учёт нескольких запросов на одно действие пользователя

Оценка объёма данных

Сущность За год (оценка) Объём записи
Обращения 3 000–6 000 ~5 KB (текст, метаданные)
Договоры 1 500–3 000 ~2 KB + вложения (документы отдельно)
Судебные дела 300–800 ~3 KB + события (~5–15 на дело)
Претензионные дела 400–900 ~2 KB + события
Пользователи/филиалы 100 / 30 Минуемо
Итого за год ~6 000–12 000 записей ~50–150 MB без вложений

Вложения (сканы, PDF) хранятся отдельно — в объектном хранилище или на файловом сервере. В БД хранятся только метаданные и пути.

Вывод по нагрузке

Для 30 филиалов и 100 сотрудников нагрузка не является высоконагруженной в классическом понимании (тысячи RPS). Это скорее среднезагруженная система с критичностью к:

  • Низкой латентности (< 200 мс на запрос) — юристы не ждут

  • Доступности (99.5%+ uptime) — простой CRM = простой работы всего офиса

  • Аудиту — каждое изменение фиксируется

  • Масштабируемости по филиалам — добавление 10 новых филиалов не должно требовать переписывания архитектуры

💡 Практический совет: при такой нагрузке модульный монолит с PostgreSQL и одним Redis более чем достаточен. Микросервисы на этом этапе избыточны и усложняют разработку без реальной выгоды по производительности.

Архитектурные решения

Модульный монолит vs микросервисы

Для описанного масштаба (100 пользователей, ~40 RPS на БД) модульный монолит — оптимальный выбор:

  • Один деплой, одна кодовая база, один цикл CI/CD

  • Модули (intake, contracts, litigation, claims) разделены по пакетам с чёткими границами

  • При росте до 100+ филиалов и 500+ сотрудников модуль «судебные дела» можно вынести в отдельный сервис без переписывания остальных

⚠️ Типичная ошибка: начинать с микросервисов «на будущее». Для 100 пользователей это означает 3–5 отдельных деплоев, межсервисную коммуникацию, распределённые транзакции — всё это усложняет поддержку без реальной нагрузки.

Модель данных: реляционная + event log

Юридическая CRM требует двух слоёв данных:

  1. Текущее состояние — обычные таблицы PostgreSQL: clients, intakes, contracts, cases, claims. Используются для быстрого чтения и поиска.

  2. Журнал событий — append-only таблица events с колонками (id, entity_type, entity_id, event_type, actor_id, timestamp, payload JSONB). Каждый переход статуса, изменение поля, добавление документа фиксируется здесь.

Такой подход не является полным CQRS/event sourcing (это было бы избыточно для данного масштаба), но даёт:

  • Полную аудит-трейл без триггеров

  • Возможность восстановления состояния на любую точку времени

  • Гибкость при изменении бизнес-логики (старые события остаются валидными)

CREATE TABLE events (
    id BIGSERIAL PRIMARY KEY,
    entity_type VARCHAR(32) NOT NULL,  -- 'intake', 'contract', 'case', 'claim'
    entity_id BIGINT NOT NULL,
    event_type VARCHAR(64) NOT NULL,   -- 'created', 'status_changed', 'document_added'
    actor_id INTEGER REFERENCES users(id),
    occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    payload JSONB NOT NULL DEFAULT '{}'
);

CREATE INDEX idx_events_entity ON events (entity_type, entity_id, occurred_at DESC);

Статусные модели и workflow

Каждая сущность имеет конечный автомат статусов. Для судебного дела:

submitted → accepted → preliminary_hearing → hearing → decision
    ↓           ↓              ↓                ↓         ↓
  rejected   dismissed      postponed     postponed   appealed
                                                      ↓
                                              enforcement / closed

Реализация: таблица case_statuses с колонками (status, allowed_transitions JSONB). Переходы валидируются на уровне сервиса — нельзя перейти из hearing в closed, минуя decision.

💡 Совет: храните допустимые переходы в БД, а не хардкодом. Это позволяет администраторам настраивать workflow без деплоя при изменении процессуального законодательства.

Мультифилиальность и права доступа

30 филиалов — это не мультиарендность (multi-tenancy) в классическом смысле, но требует разграничения данных:

  • Сотрудник видит дела своего филиала по умолчанию

  • Руководитель филиала видит все дела филиала

  • Штаб/адвокат может видеть дела нескольких или всех филиалов (настраивается через роли)

Реализация: колонка branch_id на основных сущностях + таблица user_branch_grants (user_id, branch_id, access_level). В запросах — фильтр по разрешённым филиалам. Для 30 филиалов это не создаёт проблем производительности; индексы по branch_id решают задачу.

Хранение документов

Документы (сканы исков, договоры в PDF, фото) не хранятся в БД:

  • Объектное хранилище (MinIO/S3-совместимое) или файловый сервер

  • В БД — таблица documents (id, entity_type, entity_id, file_key, mime_type, size_bytes, uploaded_by, uploaded_at)

  • Версионирование: при замене документа создаётся новая запись со ссылкой на предыдущую

Для 30 филиалов с оценкой ~5–10 GB вложений в год — MinIO на одном сервере более чем достаточно. При росте до 100+ филиалов переходим на S3-совместимое облако или кластер MinIO.

Рекомендуемый стек на Python

Сводная таблица

Компонент Выбор Обоснование
Web-фреймворк FastAPI Async, автогенерация OpenAPI, Pydantic-валидация. Для ~200 RPS — с запасом в 10–20 раз
ORM / DB layer SQLAlchemy 2.0 (async) + asyncpg Зрелый ORM с поддержкой async; asyncpg — самый быстрый драйвер PostgreSQL для Python (примерно в 7 раз быстрее psycopg по bytea)
База данных PostgreSQL 16 JSONB для payload событий, полнотекстовый поиск (tsvector), частичные индексы. Для ~100 MB/год — один сервер достаточно
Кэш / сессии Redis 7 Сессии, кэш частых запросов (списки дел по филиалу), rate limiting. Hit rate цель: 80%+
Очередь задач Celery + Redis Отправка уведомлений, генерация PDF, синхронизация с внешними системами. Celery — зрелый выбор с экосистемой (flower для мониторинга)
Файловое хранилище MinIO (S3-совместимое) Версионирование, доступ по presigned URL, масштабируемость
Аутентификация JWT + refresh token Stateless, подходит для SPA-фронтенда. Refresh — 7 дней, access — 15 минут
Логирование / аудит structlog → JSON → файл/ELK Структурированные логи; события из events таблицы дублируются в лог для мониторинга
Мониторинг Prometheus + Grafana Метрики: RPS, P95/P99 latency, размер очереди Celery, hit rate Redis
Фронтенд React + TypeScript (или Vue 3) SPA с роутингом по модулям. Для 100 пользователей — один фронтенд-бандл достаточно

Почему FastAPI, а не Django?

Django — отличный выбор для CRUD-приложений с админкой «из коробки». Но для юридической CRM:

  • Нужен async (одновременные запросы к БД и файловому хранилищу)

  • Нужна точная контроль над сериализацией (Pydantic vs Django serializers — Pydantic быстрее и типобезопаснее)

  • Нужно OpenAPI-документирование для интеграций с внешними системами (судебные порталы, электронная подпись)

  • Django Ninja закрывает часть этих потребностей, но FastAPI + SQLAlchemy 2.0 async — более чистый стек без «Django-магии»

При этом Django остаётся валидным выбором, если команда сильнее в Django-экосистеме (admin, auth, forms).

Почему Celery, а не Dramatiq/RQ?

Для юридической CRM фоновые задачи:

  • Отправка email/SMS уведомлений о смене статуса

  • Генерация PDF-документов (договоры, претензии)

  • Периодическая синхронизация с внешними API (судебные реестры)

  • Расчёт SLA и напоминания о сроках

Celery даёт: приоритеты очередей, rate limiting на задачу, flower для мониторинга, celery beat для планирования. Dramatiq проще, но экосистема уже. RQ — слишком прост для 30 филиалов с разными очередями задач.

Ключевые вопросы производительности

Проблема N+1 и выборка данных

Типичный экран «Список судебных дел филиала» запрашивает:

  • Список дел (50 записей)

  • По каждому делу: последний статус, следующее заседание, ответственный юрист

Наивная реализация = 1 + 50*3 = 151 запрос. Оптимизация: SELECT с JOIN и подзапросами, либо joinedload в SQLAlchemy. Пример для SQLAlchemy 2.0:

stmt = (
    select(Case)
    .where(Case.branch_id == branch_id, Case.status != 'closed')
    .options(joinedload(Case.current_status), joinedload(Case.assignee))
    .order_by(Case.next_hearing_date)
    .limit(50)
)

Один запрос вместо 151. Разница: ~2 мс vs ~300 мс на экране.

Индексация для юридической CRM

Критичные индексы:

  • cases (branch_id, status) — основной фильтр «дела моего филиала в работе»

  • events (entity_type, entity_id, occurred_at DESC) — аудит по сущности

  • intakes (branch_id, created_at DESC) — лента обращений

  • contracts (party_name GIN trgm) — полнотекстовый поиск по контрагенту

Кэширование

Что кэшируем TTL Почему
Список дел филиала (список, без деталей) 30 сек Часто открывают «ленту», данные меняются редко
Справочники (статусы, типы дел, роли) 1 час Меняются администратором раз в квартал
Результаты полнотекстового поиска Не кэшируем Слишком много уникальных запросов

⚠️ Кэш не должен скрывать проблемы. Если P95 latency > 200 мс — сначала оптимизируйте запросы, а уже потом добавляйте кэш. Кэш как «костыль» для медленных запросов создаёт проблемы с консистентностью.

Безопасность и соответствие требованиям

Юридическая CRM хранит персональные данные клиентов (ФИО, паспортные данные, реквизиты). Требования:

  • Шифрование при передаче: TLS 1.3 на всех соединениях

  • Шифрование в покое: PostgreSQL — pgcrypto для чувствительных полей; MinIO — server-side encryption

  • Маскирование в логах: ФИО, паспортные данные не пишутся в логи

  • Разграничение доступа: RBAC с минимальными привилегиями. Юрист видит только дела своего филиала (по умолчанию)

  • Аудит: таблица events + логи доступа (кто когда открыл дело). Хранение — минимум 5 лет (требование законодательства о персональных данных)

  • Резервное копирование: PostgreSQL PITR (восстановление на определённую точку времени), MinIO — репликация. RPO ≤ 1 час, RTO ≤ 4 часа

⚠️ Для продакшн-решений с персональными данными рекомендуется привлечение специалистов по безопасности. Поверхностная реализация аутентификации или разграничения доступа может привести к утечке данных клиентов — это не только юридическая ответственность, но и потеря репутации.

Масштабирование: путь от 30 до 100+ филиалов

Этап Филиалы / Сотрудники Архитектура Что меняется
Старт 30 / 100 Монолит, 1 PostgreSQL, 1 Redis, 1 MinIO, 2–4 воркера FastAPI —
Рост 60–80 / 250 + read replica PostgreSQL (для отчётов), + отдельный Celery worker для тяжёлых задач Read replica разгружает основной сервер при генерации отчётов
Масштаб 100+ / 500+ Вынос модуля «судебные дела» в отдельный сервис (если нужно независимое масштабирование), шардирование по branch_id (PostgreSQL Citus или на уровне приложения) Шардирование — только если одна БД не справляется с P99 < 200 мс

💡 Практический опыт: большинство юридических компаний останавливаются на этапе «Рост». Шардирование для CRM на 100 филиалов — редкость. Скорее всего, потребуются read replica и оптимизация запросов, но не горизонтальное шардирование БД.

Типичные ошибки при проектировании

Ошибка Последствие Как избежать
Хранение документов в БД (BLOB) Раздувание БД, медленные бэкапы, невозможность версионирования Объектное хранилище + метаданные в БД
Отсутствие event log Невозможно восстановить «что было до изменения» при спорной ситуации Append-only таблица events с первого дня
Жёсткий хардкод статусов Изменение процессуального законодательства = деплой Таблица статусов + допустимые переходы в БД
Один воркер FastAPI на всё При генерации PDF (5–10 сек) блокируются обычные запросы Отдельная очередь Celery для тяжёлых задач; async I/O не блокирует event loop
Отсутствие rate limiting Один «зависший» клиентский скрипт может перегрузить API Redis-based rate limit: 60 req/min на пользователя
Мультиарендность через схему (schema per tenant) Сложность миграций, проблемы с connection pool Для 30 филиалов — одна схема + branch_id фильтр. Schema-per-tenant — только при 100+ независимых аренд

Заключение

Юридическая CRM на 30 филиалов и 100 сотрудников — это среднезагруженная система с критичностью к аудиту, латентности и доступности, а не к пропускной способности. Модульный монолит на FastAPI + SQLAlchemy 2.0 async + PostgreSQL 16 + Redis + Celery + MinIO закрывает все требования с запасом в 5–10 раз по производительности.

Ключевые решения:

  • Журнал событий (append-only) для полного аудита — не CQRS, но достаточный аудит-трейл

  • Статусные автоматы в БД — гибкость при изменении законодательства

  • Документы вне БД — MinIO/S3 + метаданные

  • Read replica при росте до 60+ филиалов для отчётности

Важно понимать: выбор стека — лишь половина задачи. Вторая половина — корректная реализация с учётом edge-кейсов (конкурентные изменения статуса, частичные сбои сети между филиалами), безопасности (разграничение доступа, маскирование PII) и производительности (проблема N+1, индексация). Команды, которые доверяют критичные задачи опытным разработчикам с пониманием системного дизайна, экономят время и деньги на исправлении ошибок, которые могли быть предотвращены на этапе проектирования.

Источники