Files
BitrixDealsBot/docs/adr/006-guarded-deal-mutations.md

61 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-006: изменение сделки с проверкой актуального состояния
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Состояние сделки может измениться в Битрикс24 после формирования карточки в
Telegram, но до нажатия callback-кнопки. Используемый метод `crm.deal.update` не
предоставляет условный `UPDATE`, поэтому перед изменением требуется проверить,
что показанное пользователю состояние остаётся актуальным.
## Решение
### Назначение ответственного и изменение стадии
Callback-кнопка назначения содержит не только id сделки, но и `ASSIGNED_BY_ID`,
который был показан пользователю:
`deal:assign:<deal_id>:<expected_responsible_id>`. Перед изменением сервис
повторно загружает сделку и сравнивает фактического ответственного с ожидаемым.
Если карточка устарела, REST-обновление не выполняется.
Внутри одного процесса операции по паре `(member_id, deal_id)` последовательно
выполняются под `asyncio.Lock`. После `crm.deal.update` сервис повторно читает
сделку и убеждается, что ответственным стал `bitrix_user_id` привязанного
пользователя.
![Диаграмма последовательности взятия сделки в работу](assets/report/deal-assignment-sequence.png)
*Рисунок ADR-006/1. Диаграмма последовательности взятия сделки в работу*
При обновлении одновременно передаются `ASSIGNED_BY_ID` связанного пользователя,
рабочая `STAGE_ID` и параметр `REGISTER_HISTORY_EVENT=Y`. После REST-запроса
выполняется контрольное чтение сделки.
### Планирование звонка и просмотр истории
Действие «Позвонить позже» создаёт в Битрикс24 дело типа `todo` с крайним сроком
через один час. Владельцем является сделка (`ownerTypeId=2`), а ответственным —
связанный пользователь Битрикс24. Массив `pingOffsets=[0]` включает напоминание
в момент наступления срока.
История загружается методом `crm.stagehistory.list` с фильтром `OWNER_ID` и
сортировкой по убыванию идентификатора. В интерфейс выводятся первые пять
событий. Для каждого события идентификатор стадии преобразуется в название с
учётом `CATEGORY_ID`, после чего `DealFormatter` формирует защищённый HTML-текст
и клавиатуру возврата к карточке или списку.
![Диаграмма последовательности планирования звонка и просмотра истории](assets/report/reminder-history-sequence.png)
*Рисунок ADR-006/2. Диаграмма последовательности планирования звонка и просмотра
истории*
## Последствия
Локальный `asyncio.Lock` защищает только один процесс `bot`. При горизонтальном
масштабировании на несколько контейнеров потребуется распределённая блокировка
либо серверная условная операция. Повторная проверка REST-результата сохраняет
защиту от внешних изменений, но не делает два удалённых вызова одной транзакцией
Битрикс24.