Перейти к основному содержимому

POST /api/agent/crm/fixations/{fixationId}/cancel — Отозвать фиксацию

Агент-инициатор отзывает свою заявку на фиксацию до решения застройщика. Переход заявки Pending → Cancelled (INV-FIX-4). Дом полей заявки, статусы и инварианты — в fixation-request.md.

Назначение

Дать агентской стороне откатить незавершённую заявку: агент передумал, ошибся застройщиком/клиентом или клиент отказался — пока менеджер застройщика не принял решение. Отзыв возможен только из Pending и только актором агентской стороны (инициатор, руководитель его агентства или Менеджер ПИН). После отзыва по той же паре агент↔клиент↔застройщик можно подать новую заявку. В отличие от решения застройщика (approve/reject) — kafka-событий нет (см. «Побочные эффекты»).

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent
Feature groupfixation
Статусdraft
Документdocs/06-services/crm/api/agent/cancel-fixation.md

Актор и доступ

ПроверкаЗначение
Actor typeUser — агент-инициатор, руководитель агентства или Менеджер ПИН
Auth policy / Permissionscrm.fixations.manage (агентская сторона); Менеджер ПИН — platform-scope
Scope (RBAC)организация (агентство): record-level по OriginAgencyCompanyId
Record-levelOriginAgencyCompanyId = actor.CompanyId; рядовой агент дополнительно — агент-инициатор (OriginAgentUserId = actor.UserId); руководитель агентства — любая заявка своего OriginAgencyCompanyId; Менеджер ПИН — обход record-level
Tenant isolationЗаявка TenantId = DeveloperCompanyId (владелец решения — застройщик, ADR-0052). Операция агентской стороны выполняется по явному предикату OriginAgencyCompanyId, а не по авто-tenant-фильтру
Auditupdate: переход Pending → Cancelled, поля CancelledAt, UpdatedByUserId, Version
Доступ

Заявка чужого агентства не раскрывается: и отсутствие записи, и отсутствие прав на неё возвращают одинаковый 404 CRM_FIXATION_NOT_FOUND. Менеджер ПИН — единственный актор, обходящий record-level по OriginAgencyCompanyId.

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/agent/crm/fixations/{fixationId}/cancel
Success status204 (No Content)
Idempotency headerне применяется — повтор ловится проверкой состояния: вторая отмена уже Cancelled заявки → CRM_FIXATION_INVALID_STATE
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
FixationIdrouteGuidДа!= Guid.Empty; существующая заявка своего агентства
ActorUserIdidentity claimsGuidДаиз JWT
ActorAgencyCompanyIdidentity claimsGuidДаAgencyCompanyId актора — сверяется с OriginAgencyCompanyId заявки (record-level предикат)
IsAgencyHeadidentity claimsboolДафлаг роли «руководитель агентства» (доступ на всех агентов своего AgencyCompanyId)
IsPinManageridentity claimsboolДаplatform-scope Менеджера ПИН (обход record-level)

Тело запроса пустое. Версия строки клиентом не передаётся — гонка решается сервером оптимистически.

Модель ответа

Тела нет: HTTP 204 No Content. Актуальное состояние заявки агент перечитывает методом получения фиксации (GET .../fixations/{id}).

Алгоритм

  1. Контекст и доступ. Операцию выполняет агент-инициатор / руководитель агентства / Менеджер ПИН (permission crm.fixations.manage).
  2. Проверка входа и предусловий.
    • формат: FixationId != Guid.Empty — иначе ошибка валидации;
    • заявка существует и принадлежит агентству актора (record-level предикат) — иначе CRM_FIXATION_NOT_FOUND (404, существование заявок чужого агентства не раскрывается);
    • допустимость состояния: Status = Pending — иначе CRM_FIXATION_INVALID_STATE (409);
    • право актора на отзыв (проверяется ниже) — иначе CRM_FIXATION_FORBIDDEN.
  3. Что читаем. Заявку (по явному предикату OriginAgencyCompanyId, у актора нет tenant застройщика). Лид/клиент/ сделка не загружаются — отзыв их не затрагивает.
  4. Бизнес-правила и переходы. Право актора: Менеджер ПИН, руководитель агентства при совпавшем OriginAgencyCompanyId или агент-инициатор (OriginAgentUserId == actor); иначе CRM_FIXATION_FORBIDDEN (403, INV-FIX-4). Затем переход Pending → Cancelled (guard Status == Pending, иначе доменный отказ → CRM_FIXATION_INVALID_STATE): CancelledAt = now, UpdatedByUserId = actor. Поля решения застройщика (DecidedByUserId/DecidedAt/Uniqueness) не меняются.
  5. Что меняется. Заявка переводится в Cancelled.
  6. События. Интеграционных (kafka) событий нет. Отзыв никого вне crm не затрагивает: сделка не создавалась, право на комиссию не возникало, инвестор фиксаций не видит — в отличие от решения застройщика (Pending → FixedUnique/FixedNonUniquefixation-approved.md; Pending → Rejectedfixation-rejected.md). Публикуется только доменное FixationDecided (in-process) для внутренней аудит-ленты/метрик.
  7. Результат. 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 = CancelledIsActive = 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_FAILED400FixationId = Guid.Empty«Некорректный идентификатор заявки»no retry
CRM_FIXATION_NOT_FOUND404заявки нет или она чужого агентства (record-level скрывает существование)«Заявка на фиксацию не найдена»no retry
CRM_FIXATION_FORBIDDEN403актор не инициатор и не руководитель агентства/Менеджер ПИН (INV-FIX-4)«Недостаточно прав для отзыва заявки»no retry
CRM_FIXATION_INVALID_STATE409Status != Pending (уже решена застройщиком или отменена)«Заявку нельзя отозвать в текущем статусе»no retry
CRM_FIXATION_CONFLICT409гонка: застройщик одобрил/отклонил параллельно«Заявка изменена, обновите данные»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; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path. Given Pending-заявка своего агентства; When cancel; Then 204, Status = Cancelled, заявка выпала из активных выборок.
  • Уже решена/отменена. Given Status ∈ {FixedUnique, FixedNonUnique, Rejected, Cancelled}; When cancel; Then 409 CRM_FIXATION_INVALID_STATE.
  • Гонка с решением застройщика. Given застройщик одобряет заявку параллельно; When cancel; Then проигравший — 409 CRM_FIXATION_CONFLICT, при перечитке заявка FixedUnique/FixedNonUnique.
  • Чужое агентство. Given заявка другого агентства; When cancel; Then 404 CRM_FIXATION_NOT_FOUND (существование не раскрывается).
  • Не инициатор. Given актор своего агентства, но не инициатор и не руководитель/ПИН; When cancel; Then 403 CRM_FIXATION_FORBIDDEN.
  • Повторная подача. Given заявка отменена; When новая фиксация по той же тройке; Then допустима (201).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы