POST /api/agent/crm/fixations/{fixationId}/cancel — Отозвать фиксацию
Агент-инициатор отзывает свою заявку на фиксацию до решения застройщика. Переход заявки Pending → Cancelled
(INV-FIX-4). Дом полей заявки, статусы и инварианты — в fixation-request.md.
Назначение
Дать агентской стороне откатить незавершённую заявку: агент передумал, ошибся застройщиком/клиентом или клиент
отказался — пока менеджер застройщика не принял решение. Отзыв возможен только из Pending и только актором
агентской стороны (инициатор, руководитель его агентства или Менеджер ПИН). После отзыва по той же паре
агент↔клиент↔застройщик можно подать новую заявку. В отличие от решения застройщика (approve/reject) —
kafka-событий нет (см. «Побочные эффекты»).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent |
| Feature group | fixation |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/cancel-fixation.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — агент-инициатор, руководитель агентства или Менеджер ПИН |
| Auth policy / Permissions | crm.fixations.manage (агентская сторона); Менеджер ПИН — platform-scope |
| Scope (RBAC) | организация (агентство): record-level по OriginAgencyCompanyId |
| Record-level | OriginAgencyCompanyId = actor.CompanyId; рядовой агент дополнительно — агент-инициатор (OriginAgentUserId = actor.UserId); руководитель агентства — любая заявка своего OriginAgencyCompanyId; Менеджер ПИН — обход record-level |
| Tenant isolation | Заявка TenantId = DeveloperCompanyId (владелец решения — застройщик, ADR-0052). Операция агентской стороны выполняется по явному предикату OriginAgencyCompanyId, а не по авто-tenant-фильтру |
| Audit | update: переход Pending → Cancelled, поля CancelledAt, UpdatedByUserId, Version |
Заявка чужого агентства не раскрывается: и отсутствие записи, и отсутствие прав на неё возвращают одинаковый
404 CRM_FIXATION_NOT_FOUND. Менеджер ПИН — единственный актор, обходящий record-level по
OriginAgencyCompanyId.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/agent/crm/fixations/{fixationId}/cancel |
| Success status | 204 (No Content) |
| Idempotency header | не применяется — повтор ловится проверкой состояния: вторая отмена уже Cancelled заявки → CRM_FIXATION_INVALID_STATE |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
FixationId | route | Guid | Да | != Guid.Empty; существующая заявка своего агентства | — |
ActorUserId | identity claims | Guid | Да | из JWT | — |
ActorAgencyCompanyId | identity claims | Guid | Да | AgencyCompanyId актора — сверяется с OriginAgencyCompanyId заявки (record-level предикат) | — |
IsAgencyHead | identity claims | bool | Да | флаг роли «руководитель агентства» (доступ на всех агентов своего AgencyCompanyId) | — |
IsPinManager | identity claims | bool | Да | platform-scope Менеджера ПИН (обход record-level) | — |
Тело запроса пустое. Версия строки клиентом не передаётся — гонка решается сервером оптимистически.
Модель ответа
Тела нет: HTTP 204 No Content. Актуальное состояние заявки агент перечитывает методом получения фиксации
(GET .../fixations/{id}).
Алгоритм
- Контекст и доступ. Операцию выполняет агент-инициатор / руководитель агентства / Менеджер ПИН (permission
crm.fixations.manage). - Проверка входа и предусловий.
- формат:
FixationId != Guid.Empty— иначе ошибка валидации; - заявка существует и принадлежит агентству актора (record-level предикат) — иначе
CRM_FIXATION_NOT_FOUND(404, существование заявок чужого агентства не раскрывается); - допустимость состояния:
Status = Pending— иначеCRM_FIXATION_INVALID_STATE(409); - право актора на отзыв (проверяется ниже) — иначе
CRM_FIXATION_FORBIDDEN.
- формат:
- Что читаем. Заявку (по явному предикату
OriginAgencyCompanyId, у актора нет tenant застройщика). Лид/клиент/ сделка не загружаются — отзыв их не затрагивает. - Бизнес-правила и переходы. Право актора: Менеджер ПИН, руководитель агентства при совпавшем
OriginAgencyCompanyIdили агент-инициатор (OriginAgentUserId == actor); иначеCRM_FIXATION_FORBIDDEN(403, INV-FIX-4). Затем переходPending → Cancelled(guardStatus == Pending, иначе доменный отказ →CRM_FIXATION_INVALID_STATE):CancelledAt = now,UpdatedByUserId = actor. Поля решения застройщика (DecidedByUserId/DecidedAt/Uniqueness) не меняются. - Что меняется. Заявка переводится в
Cancelled. - События. Интеграционных (kafka) событий нет. Отзыв никого вне
crmне затрагивает: сделка не создавалась, право на комиссию не возникало, инвестор фиксаций не видит — в отличие от решения застройщика (Pending → FixedUnique/FixedNonUnique→ fixation-approved.md;Pending → Rejected→ fixation-rejected.md). Публикуется только доменноеFixationDecided(in-process) для внутренней аудит-ленты/метрик. - Результат. HTTP
204 No Content. Если застройщик одобрил заявку параллельно — конкурентный конфликт →CRM_FIXATION_CONFLICT(409); агент перечитывает заявку (ужеFixedUnique/FixedNonUnique).
Диаграмма
Предусловия и постусловия
Предусловия. Актор — агент-инициатор / руководитель агентства / Менеджер ПИН с crm.fixations.manage.
Заявка существует, принадлежит агентству актора, Status = Pending (решение застройщика ещё не принято).
Постусловие (успех). Status = Pending → Cancelled, CancelledAt = now, UpdatedByUserId = actor; поля
решения застройщика (DecidedByUserId/DecidedAt/Uniqueness) не изменены. Проекция фиксаций получает
IsActive = false — заявка выпадает из активных выборок и из дубль-детекта новых заявок. По той же тройке
(агент, инвестор, застройщик) снова можно подать заявку. Интеграционных Kafka-событий нет; поднято доменное
FixationDecided (in-process, для аудит-ленты/метрик).
Постусловие (отказ). Заявка не изменена.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none (в отличие от approve/reject — kafka-контракта отмены нет) |
| Внешние вызовы | none |
| Проекции / поиск | проекция фиксаций (FixationRequestSearchDocument) обновляется (Status = Cancelled → IsActive = false) — заявка выпадает из активных выборок и дубль-детекта новых заявок; ФИО/телефон snapshot иммутабельны |
| Уведомления | none (актор — сам инициатор; менеджер застройщика решения не принимал) |
Ошибки и failure modes
Отозвать можно только заявку в Pending (INV-FIX-4) — повторный или поздний отзыв всегда 409 CRM_FIXATION_INVALID_STATE. Гонка с решением застройщика (approve/reject) параллельно с отзывом даёт 409 CRM_FIXATION_CONFLICT (retry после перечитки состояния).
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | FixationId = Guid.Empty | «Некорректный идентификатор заявки» | no retry |
CRM_FIXATION_NOT_FOUND | 404 | заявки нет или она чужого агентства (record-level скрывает существование) | «Заявка на фиксацию не найдена» | no retry |
CRM_FIXATION_FORBIDDEN | 403 | актор не инициатор и не руководитель агентства/Менеджер ПИН (INV-FIX-4) | «Недостаточно прав для отзыва заявки» | no retry |
CRM_FIXATION_INVALID_STATE | 409 | Status != Pending (уже решена застройщиком или отменена) | «Заявку нельзя отозвать в текущем статусе» | no retry |
CRM_FIXATION_CONFLICT | 409 | гонка: застройщик одобрил/отклонил параллельно | «Заявка изменена, обновите данные» | retry (перечитать состояние) |
Совместимость и наблюдаемость
- Контракт минимальный (route + пустой ответ); breaking — только смена маршрута/семантики статуса. Новые
причины отказа добавляются как additive-коды
CRM_*без breaking change. - Logs:
fixation-cancel requested/cancelled/conflict(FixationId,OriginAgencyCompanyId,ActorUserId; ФИО/ телефон в логи не пишутся); metrics:crm_fixation_cancel_total{result}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/.
Acceptance Criteria
- Happy path. Given
Pending-заявка своего агентства; Whencancel; Then204,Status = Cancelled, заявка выпала из активных выборок. - Уже решена/отменена. Given
Status ∈ {FixedUnique, FixedNonUnique, Rejected, Cancelled}; Whencancel; Then409 CRM_FIXATION_INVALID_STATE. - Гонка с решением застройщика. Given застройщик одобряет заявку параллельно; When
cancel; Then проигравший —409 CRM_FIXATION_CONFLICT, при перечитке заявкаFixedUnique/FixedNonUnique. - Чужое агентство. Given заявка другого агентства; When
cancel; Then404 CRM_FIXATION_NOT_FOUND(существование не раскрывается). - Не инициатор. Given актор своего агентства, но не инициатор и не руководитель/ПИН; When
cancel; Then403 CRM_FIXATION_FORBIDDEN. - Повторная подача. Given заявка отменена; When новая фиксация по той же тройке; Then допустима (
201).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): POST /api/agent/crm/bookings/{bookingId}/cancel — Отменить бронь, POST /api/developer/crm/fixations/{fixationId}/reject — Отклонить фиксацию, PUT /api/agent/crm/deals/{dealId}/step — Переместить сделку по этапу (канбан)
- Use Cases (1): UC-FIX-003. Застройщик отклоняет фиксацию
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, статусы, INV-FIX-4, метод
Cancel): fixation-request.md; лид-основание — lead.md. - Контраст (решение застройщика с kafka-событиями): fixation-approved.md, fixation-rejected.md.
- Пример command-эндпоинта area agent: create-booking-request.md.