UC-NTF-001. Пользователь настраивает уведомления
Назначение
Описывает сценарий: пользователь (инвестор/агент/менеджер) настраивает свои уведомления — включает/выключает
виды по каналам, задаёт тихие часы, локаль, GlobalMute. Связан с контекстом notifications.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-NTF-001 |
| Контекст | notifications (см. docs/06-services/notifications/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Инвестор / Агент / МенеджерПИН (любой авторизованный пользователь) |
| Permissions / scope | notifications.settings.manage (own action) |
| Tenant | TenantId = CompanyId или null для инвестора (ADR-0052) |
| Record-level | владелец = UserId (актор) |
Предусловие
- Пользователь авторизован (JWT
identity). - Каталог
NotificationKind/Channelи дефолтная политика заданы (SEED).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button (экран «Уведомления» в профиле) |
| Триггерящий элемент | страница /profile → раздел уведомлений (front-end) |
Основной Поток
- Пользователь открывает экран «Уведомления»; фронт запрашивает текущие настройки.
- Фронт вызывает
GET /api/public/notifications/settings(get-my-notification-settings) — получает эффективную модель (дефолты + явные переопределения, тихие часы, локаль). - Пользователь меняет переключатели (
Kind × Channel), задаёт тихие часы (22:00–08:00,Europe/Moscow), выбирает локаль; фронт формирует запрос. - Фронт вызывает
PUT /api/public/notifications/settings(update-my-notification-settings) с моделью настроек. - Система проверяет вход: уникальность пар
(Kind, Channel), валидный IANA-timezone тихих часов, поддерживаемая локаль; при нарушении —VALIDATION_FAILED. - Настройки пользователя
UserNotificationSettingsEntityсоздаются или обновляются (набор предпочтенийNotificationPreferenceEntityзаменяется целиком);Critical-виды остаются включёнными — opt-out по ним игнорируется (INV-UNS-4); кэш резолва настроек инвалидируется (событиеSettingsChanged). - Изменения сохраняются в одном согласованном изменении; в ответ возвращается эффективная модель настроек.
- Фронт обновляет UI (перезапрос настроек); переключатели отражают сохранённое состояние.
Альтернативные Потоки
A1. Первая настройка (записи нет)
- На шаге 3 записи
UserNotificationSettingsEntityещё нет —GETвернул чистые дефолты (IsDefault = true). PUTсоздаёт запись настроек для пользователя (владелец =UserId, tenant поADR-0052).
Возвращается к основному потоку на шаг 7.
A2. Конкурентная правка (две вкладки)
- На шаге 7 обнаружена конкурентная правка (версия записи устарела) →
NTF_SETTINGS_CONFLICT(409). - Фронт перечитывает настройки и предлагает повторить (побеждает первая зафиксированная правка; проигравший видит конфликт и переприменяет свой набор поверх актуального).
A3. Включение GlobalMute
- Пользователь включает
GlobalMute. Запись сохраняется (INV-UNS-3): при последующих уведомлениях создаются только каналы дляUrgency = Critical(безопасность/деньги/дедлайны), остальные подавляются. - In-app лента при этом продолжает наполняться (подавление касается interruptive-каналов доставки, не самой записи уведомления) — детали резолва каналов см. notification.md.
A4. Попытка отключить Critical-вид
- Пользователь снимает переключатель у
Critical-вида (напр.SecurityNewSignIn,BookingPaid,BookingExpiringSoon).PUTпринимается (валидна запись предпочтения), но при резолве каналов opt-out поCriticalигнорируется (INV-UNS-4) — вид всё равно доставляется; пользователь может лишь сменить набор каналов, но не отключить вид полностью.
A5. Тихие часы через полночь
- Пользователь задаёт окно
22:00–08:00(Start > End→ интервал через полночь), timezoneEurope/Moscow. - Запись валидна; при последующих уведомлениях interruptive-каналы в этом окне деферятся (
ScheduledAt= открытие окна, INV-UNS-5/INV-NTF-4), не-interruptive (InApp/Email) — доставляются сразу.
Постусловие
UserNotificationSettingsEntityпользователя отражает выбор; резолв каналов при последующих уведомлениях использует новые настройки (INV-UNS-3/4/5).- Появилась/обновилась запись настроек + owned
Preferences; при первом сохранении — владелец =UserId, tenant по ADR-0052. - Опубликовано доменное
SettingsChanged; кэш резолва настроек инвалидирован. Kafka-события не публикуются (внутренние настройки, ADR-0056). - Уже созданные (in-flight) уведомления, для которых каналы были резолвлены до сохранения, новые настройки не пересматривают — правила применяются к уведомлениям, создаваемым после инвалидации кэша.
Инварианты и валидации
- INV-UNS-1 — одна запись на
UserId; правит только владелец (UserId = actor), иначеNTF_SETTINGS_FORBIDDEN(здесь own-action, посторонний доступ исключён). - INV-UNS-2 — пары
(Kind, Channel)вPreferencesуникальны; дубль →VALIDATION_FAILED(NTF_PREFERENCE_DUPLICATEна уровне записи предпочтения). - INV-UNS-3 — резолв канала = явное
IsEnabled, иначе дефолт политикиKind;GlobalMuteоставляет толькоUrgency = Critical. - INV-UNS-4 —
Critical-вид нельзя отключить (opt-out игнорируется), можно лишь сменить каналы. - INV-UNS-5 — тихие часы применяются только к interruptive-каналам; пустое
QuietHours→ без дефера. - Валидация входа:
(Kind, Channel)уникальны;QuietHours.Start/End— валидное локальное время,TimeZoneId— существующая IANA-зона (окно может пересекать полночь);Locale— поддерживаемый BCP-47. Нарушение любого —VALIDATION_FAILED(400), запись не меняется.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
UNAUTHENTICATED | 401 | нет/невалидный JWT | редирект на вход |
VALIDATION_FAILED | 400 | дубли (Kind, Channel) / невалидный timezone/локаль | подсветить поля |
NTF_SETTINGS_CONFLICT | 409 | конкурентная правка | перечитать и повторить |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
SettingsChanged | после сохранения настроек | in-process invalidate кэша резолва настроек |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
| — | — | не публикуется (внутренние настройки, ADR-0056) | — |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Выключенный (Kind, Channel) не порождает уведомление этого канала | интеграционный: opt-out + событие → нет NotificationEntity этого канала |
| AC2 | Critical-вид (напр. SecurityNewSignIn) нельзя отключить | попытка opt-out → уведомление всё равно создаётся |
| AC3 | Тихие часы деферят interruptive-канал (не Critical) | событие в окне → ScheduledAt = открытие окна |
| AC4 | Первая настройка создаёт запись; повтор — идемпотентен по содержимому | PUT дважды → одна запись, одинаковый результат |
| AC5 | GlobalMute подавляет всё, кроме Critical | включить mute + событие не-Critical → нет уведомлений interruptive-каналов; Critical — доставляется |
| AC6 | Дубль (Kind, Channel) отклоняется | PUT с двумя одинаковыми парами → VALIDATION_FAILED, запись не меняется (INV-UNS-2) |
| AC7 | Конкурентная правка — первая побеждает | параллельные PUT двух вкладок → одна успех, вторая NTF_SETTINGS_CONFLICT |
| AC8 | Невалидный timezone/локаль отклоняется | PUT с несуществующей IANA-зоной/неподдерживаемой локалью → VALIDATION_FAILED |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: get-my-notification-settings, update-my-notification-settings.
- Domain entity: user-notification-settings.md, notification.md, channel.md.
- Frontend page/feature:
/profile(раздел уведомлений); контекст — README.