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

POST /api/messenger/conferences/{conferenceId}/decline — Отклонить приглашение в конференцию

Приглашённый участник отказывается от адресного приглашения в конференцию. Его строка ростера (call-participant.md) переводится Invited → Declined (терминальная для этого приглашения; строка сохраняется в истории участия). Дом полей, статусы и инварианты — conference-session.md.

Назначение

Дать приглашённому (получившему ConferenceInvite в user:{UserId}) корректно отклонить звонок: зафиксировать Declined и снять «звенящий» экран, уведомив остальных, что он не придёт. Это единственный путь, которым строка ростера достигает состояния Declined. Медиа-комната и токен приглашённому не выдаются. Owning-сервис — messenger, feature group conferences, сценарий «групповой звонок по сделке».

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areaconferences
Feature groupconferences
Статусdraft
Документdocs/06-services/messenger/api/conferences/decline-conference.md

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

ПроверкаЗначение
Actor typeUserсам приглашённый (actor.UserId == строка ростера, State = Invited)
Auth policy / Permissionspermission messenger.conferences.use
Scope (RBAC)не применяется — доступ по членству в чате
Record-levelсобственная строка ростера актора в статусе Invited; членство в Deal-чате (INV-CHAT-4)
Tenant isolationне применяется (кросс-tenant); срез — по членству
Auditupdate ростера: Invited → Declined, метка LeftAt

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/conferences/{conferenceId}/decline
Success status200 (OK)
Idempotency headerне применяется — повтор ловит guard состояния (шаг 4): строка уже не InvitedMSG_CONFERENCE_INVALID_STATE
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ConferenceIdrouteGuidДа!= Empty; существующая конференция, у актора есть строка ростера Invited (шаги 3–4)
ActorUserIdidentity claimsGuidДаиз JWT; собственная строка ростера актора

Тела запроса нет. Отклонить можно только свою строку приглашения; чужую строку снимает модератор (remove-participant.md).

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

Лёгкий ack без дескриптора входа (Room не выдаётся — приглашение отклонено). Полную карточку с ростером при необходимости даёт get-conference.md.

ПолеТипОбязательностьИсточникMaskingОписание
ConferenceIdGuidДаConferenceSessionEntity.IdКонференция, приглашение в которую отклонено.
UserIdGuidДаCallParticipantEntity.UserIdАктор (приглашённый).
StateCallParticipantStateДа.StateDeclined на успехе.

Предусловия и постусловия

Предусловия. Актор авторизован и имеет messenger.conferences.use; активный участник Deal-чата (INV-CHAT-4). У актора есть строка ростера в статусе Invited (иначе — ошибки шага 4).

Постусловия. Строка ростера актора — State = Declined, LeftAt. Поднят ConferenceParticipantDeclinedEvent → в той же транзакции записано намерение доставки (persist-then-push): после коммита realtime-dispatch пушит MediaParticipant(Left) в chat:{ChatId} (участники видят, что приглашённый отказался). ActiveParticipantCount не меняется (приглашённый и не был активен). Медиа-комната/токен не выдавались. Kafka-события отклонение не публикует.

Алгоритм

  1. Контекст и доступ. Операцию выполняет приглашённый (User); ActorUserId — из JWT; требуется право messenger.conferences.use.
  2. Проверка входа. ConferenceId непустой; иначе ошибка валидации. Тела запроса нет.
  3. Загрузка сессии и строки. Конференция по ConferenceId; нет → MSG_CONFERENCE_NOT_FOUND. Актор — активный участник Deal-чата (INV-CHAT-4); иначе MSG_CHAT_ACCESS_DENIED. Строка ростера (ConferenceId, ActorUserId); нет → MSG_CONFERENCE_PARTICIPANT_NOT_FOUND.
  4. Переход. Требуется State == Invited; иначе MSG_CONFERENCE_INVALID_STATE (уже Joined/Left/Declined — отклонять нечего; вошедший выходит через leave-conference.md). При успехе — State = Declined, LeftAt; поднимается ConferenceParticipantDeclinedEvent.
  5. Присутствие. В том же согласованном изменении фиксируется намерение push MediaParticipant(Left) в chat:{ChatId} (persist-then-push) — участники убирают «ожидаем» у отказавшегося. Токен/комната не выдаются.
  6. Результат. Лёгкий ack ConferenceParticipantStateModel (State = Declined) → HTTP 200 OK.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone — отклонение Kafka не публикует
Realtime push (hub)MediaParticipant(Left)chat:{ChatId} (messenger-hub.md)
Cache / projections / searchростер (ix_call_participants_active не затрагивается — Invited/Declined не активны)
Notificationsнет

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ConferenceId = Empty«Проверьте параметры запроса»no retry
MSG_CONFERENCE_NOT_FOUND404конференции нет«Конференция не найдена»no retry
MSG_CHAT_ACCESS_DENIED403актор не участник Deal-чата«Нет доступа к конференции»no retry
MSG_CONFERENCE_PARTICIPANT_NOT_FOUND404у актора нет строки ростера«Приглашение не найдено»no retry
MSG_CONFERENCE_INVALID_STATE409строка не Invited (уже Joined/Left/Declined)«Приглашение уже недействительно»no retry (перечитать)
MSG_CONFERENCES_DISABLED503CONFERENCE_CALLS_ENABLED = false«Конференции временно недоступны»retry позже

Acceptance Criteria

  • Отклонение приглашения (happy). Дано: у актора строка ростера Invited в конференции. Когда POST /decline. Тогда 200, строка → Declined (LeftAt), State = Declined в ответе; остальные получают MediaParticipant(Left) в chat:{ChatId}; токен/комната не выдаются.
  • Уже вошёл. Дано: строка актора Joined. Когда POST /decline. Тогда 409 MSG_CONFERENCE_INVALID_STATE (вошедший выходит через leave-conference.md, не decline).
  • Нет строки. Дано: актора нет в ростере (не приглашался). Когда POST /decline. Тогда 404 MSG_CONFERENCE_PARTICIPANT_NOT_FOUND.
  • Повтор. Дано: строка актора уже Declined. Когда POST /decline. Тогда 409 MSG_CONFERENCE_INVALID_STATE (перечитать состояние).
  • Не участник чата. Дано: актор не активный участник Deal-чата. Когда POST /decline. Тогда 403 MSG_CHAT_ACCESS_DENIED.
  • Нет конференции. Дано: ConferenceId не существует. Тогда 404 MSG_CONFERENCE_NOT_FOUND.

Совместимость и наблюдаемость

  • Новые optional поля ответа — additive; смена маршрута — breaking. Kafka-контракта нет.
  • Logs: conference decline requested/declined/invalid-state (ConferenceId, ActorUserId); metrics: messenger_conference_decline_total{result}, latency p95; traces — сквозной trace (traceparent); dashboard pin-messenger.

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

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

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