Юридический реестр договоров
Учёт договоров, связанных документов и контролируемых сроков — рабочее место юриста. Третий блок АРМ бухгалтера и юриста.
Реализовано: модуль
internal/modules/contracts, миграции0111(схема) и0112(RBAC).
Зачем
Тип документа contract в модуле документов — это «болванка» без юридических атрибутов (стороны, срок действия, статус, связи). Реестр договоров достраивает этот контур: единый список договоров с контрагентами, сроками действия, статусами, деревом связанных документов и контролем дедлайнов.
Реестр самодостаточен: договор опционально ссылается на сгенерированный документ (document_id), но не требует его — юрист может вести карточку договора независимо.
Сущности
Договор (Contract)
| Поле | Описание |
|---|---|
number | Юридический номер договора |
counterparty_type / counterparty_id | Сторона: company или contact |
subject | Предмет договора |
amount | Сумма (опц., точные деньги) |
signed_at | Дата подписания |
effective_from / effective_to | Срок действия |
auto_prolong | Признак автопролонгации |
state | Статус (см. ниже) |
responsible_user_id | Ответственный сотрудник |
document_id | Необязательная связь со сгенерированным документом |
notes | Заметки |
Статусы и переходы:
draft ──▶ signed ──▶ active ──▶ expired
│ │ │ │
└──────────┴──────────┴──────────┴──▶ terminated
expired ──▶ active (пролонгация)terminated — терминальный. Недопустимые переходы возвращают 409/ошибку перехода.
Связь (ContractLink)
Связывает договор с документом: amendment (допсоглашение), annex (приложение), claim (претензия), act, invoice. Формирует дерево связанных документов договора.
Пара «документ + вид связи» у договора уникальна: повторная привязка отвечает 409 contracts_link_already_exists («Этот документ уже привязан к договору с такой ролью»); интерфейс помечает уже привязанные документы и не отправляет повтор. Несуществующий документ, договор или ответственный — contracts_document_not_found / contracts_contract_not_found / contracts_responsible_not_found, а не текст драйвера БД.
В ответе связи — сводка документа, чтобы карточка договора не запрашивала каждый документ отдельно: document_type, document_number, document_date, document_amount, project_id (LEFT JOIN документа; для удалённого документа поля пустые). В интерфейсе связь привязывается из карточки договора: «Привязать документ» → проект контрагента → документ проекта → вид связи.
Срок (ContractDeadline)
Контролируемая дата: expiry (окончание), payment (платёж), milestone (этап), custom. Поле notify_days_before задаёт, за сколько дней предупреждать; done помечает закрытый срок.
API
| Метод | Путь | Право | Описание |
|---|---|---|---|
GET | /api/v1/contracts | legal.view | Реестр (фильтры: counterparty_type, counterparty_id, state, responsible_user_id); у каждого договора — deadlines (одним запросом на страницу), чтобы колонка «Сроки» не была пустой |
POST | /api/v1/contracts | legal.manage | Создать договор |
GET | /api/v1/contracts/:id | legal.view | Карточка + связи + сроки |
PATCH | /api/v1/contracts/:id | legal.manage | Обновить поля договора |
PATCH | /api/v1/contracts/:id/state | legal.manage | Сменить статус |
POST | /api/v1/contracts/:id/links | legal.manage | Привязать документ |
POST | /api/v1/contracts/:id/deadlines | legal.manage | Добавить срок |
GET | /api/v1/contracts/deadlines/due | legal.view | Сроки в горизонте ?horizon_days= (по умолчанию 30), включая просроченные |
PATCH | /api/v1/contracts/deadlines/:id/done | legal.manage | Закрыть срок |
GET | /api/v1/contracts/legal-card/:type/:id | legal.view | Юр. карточка контрагента (договоры + счётчики по статусам) |
Правка (PATCH /contracts/:id)
Отсутствующее в теле поле не меняется. null (у дат также "") в amount, signed_at, effective_from, effective_to, responsible_user_id, document_id — явная очистка поля (SET NULL). Диапазон «Действует с / по» проверяется по итоговым значениям: переданная граница сравнивается с сохранённой второй, очищенная не участвует.
Пример создания
POST /api/v1/contracts
{
"number": "Д-2026-001",
"counterparty_type": "company",
"counterparty_id": 7,
"subject": "Поставка оборудования",
"amount": 1200000.0,
"signed_at": "2026-06-01",
"effective_from": "2026-06-01",
"effective_to": "2026-12-31",
"auto_prolong": false,
"responsible_user_id": 3
}Сроки и напоминания
Эндпоинт GET /api/v1/contracts/deadlines/due?horizon_days=14 отдаёт незакрытые сроки, наступающие в пределах горизонта (и просроченные), с контекстом договора. Сроки договоров в статусах «Истёк» и «Расторгнут» не отдаются — контролируются только черновики, подписанные и действующие договоры — это основа для виджета напоминаний на фронте и для крон-задачи.
Фоновый воркер с рассылкой через уведомления и автоматический сдвиг
effective_toприauto_prolong— запланированный следующий шаг; сейчас источник данных (due) уже доступен.
Юридическая карточка контрагента
GET /api/v1/contracts/legal-card/:type/:id сводит все договоры контрагента и счётчики по статусам (state_counts, active_count). Фронт объединяет её с взаиморасчётами из кассы/платежей (/counterparties/:type/:id/balance) и реквизитами из CRM для единой карточки.
Доступ (RBAC)
Роль lawyer и права legal.view / legal.manage (seeded в migrations/0112). admin обходит проверку, director видит данные через system.global_read. Подробнее — rbac_matrix.md.