Files
han-app/busines_tasks/notification-requirements.md
T

31 KiB
Raw Blame History

Бизнес-постановка: Уведомления (Notification Center)

Статус: бизнес-постановка согласована (v3: D1–D13 закрыты) → вход в проектирование arch / OpenAPI / модули
Продукт: HAN Chat (клиентское приложение + api-backend)
Источники: макет Figma (не канон — только визуализация элементов UI, где применимо; при расхождении приоритет у этого ТЗ), черновик требований, архитектура HAN_chat_specification
Связанный backlog: «Моделирование уведомлений»; смежно — кнопка «Позвонить оператору», кнопка «Войти»; отдельно — индикатор непрочитанных сообщений чата (не в scope этого ТЗ)


1. Цель

Дать клиенту единый канал сервисных и маркетинговых коммуникаций вне чата: срочные напоминания, статусы услуг, документы, оплаты, акции, а также подсказки в гостевой зоне (авторизация, установка PWA).

Чат остаётся каналом диалога с оператором; уведомления — канал «компания → клиент» с действиями (переход, оплата, загрузка/скачивание документов, переход в чат по акции). Непрочитанные ответы оператора в чате не моделируются как уведомления (см. §3.2).

Гостевые ads/promo — не персонализированная реклама на клиента; отдельное маркетинговое согласие не требуется. Функционал описывается в Пользовательском соглашении.


2. Два контура показа

Контур Кто видит Источник данных Frontend
G. Гостевые уведомления Только гость Таблица гостевых уведомлений в App DB → public API Единственный источник в гостевом режиме
P. Персональные уведомления Только авторизованный Таблица персональных (user_id NOT NULL) → JWT API Единственный источник в ЛК

Правила:

  1. До авторизации: только контур G (ответ public API as-is).
  2. После авторизации: только контур P. Гостевые не показываются и не переносятся в персональные.
  3. Смешивать G и P в одной таблице / через user_id IS NULLзапрещено.
  4. В v1 нет клиентских условий показа (ОС / PWA / прочие фильтры): фронт показывает список из API в рамках лимита и сортировки. Локальная генерация карточек на устройстве запрещена.
  5. У фронта всегда ровно один источник: public API или JWT API.

Дубли тематики ads/promo в G и P допустимы; после входа виден только P.


3. Границы релиза

3.1. В scope

  • Контуры G и P (§2); персональный жизненный цикл: lifecycle_status / visibility / is_read / close_reason + record_status (arch-05).
  • UI: карусель на главной, Центр уведомлений, деталка (где применимо).
  • Каталог типов; публичный id — UUID v7 (генерация в приложении).
  • Клиентские действия (ЛК): крестик, CTA, деталка/оплата, done/got it, документы (chat.attachments.*).
  • Internal API: Create, Cancel (без upsert / без update content) — §7.4.
  • Константы notification.* в app_settings; вложения — reuse chat.attachments.*.
  • Кнопки Чат + Оператор (tel:operator.call.phone).
  • Готовность модели к Web Push (UUID, события); реализация push — out of scope.

3.2. Вне scope

  • Тип message / непрочитанный чат (отдельная фича).
  • Push / Web Push / deep link из push.
  • Админ-UI для гостевых уведомлений (v1: insert/SQL).
  • Жёсткая привязка «тип ↔ upstream-сервис».
  • Профиль «Документы» / архив оплат — не заменяются уведомлениями.
  • Запись в sync_queue из application-кода: только триггер БД + новый task_type; куда в Bitrix — ТЗ bitrix-sync.
  • SMS/email поверх ЛК.
  • История чатов как UI-раздел — deprecated; backend API диалогов этим ТЗ не удаляется.
  • Архив lifecycle_status = N в UI v1 — нет.
  • Поле opened / impression — не моделируем (CTA → is_read).

4. Изменения UI

Место Было Стало
Под полем чата (см. текущий UI) Чат + Оператор (operator.call.phone)
Верхнее меню История чата Центр уведомлений (колокольчик; бейдж только в ЛК)
Главная Карусель (лимит/сортировка §6.1)
Центр ЛК — список; гость — auth-gate + «Авторизоваться»
Деталка Для типов с деталкой; гостевые ads/promoбез деталки

Визуал типов — по Figma, где применимо. Figma не канон поведения.


5. Бизнес-модель

5.1. Слои контента

  1. Краткий — баннер и строка списка: header, text, цена, CTA.
  2. Детальныйdetails.*, документы, кнопки. Только если у типа есть деталка.

5.2. Справочник типов (принято)

Слой Где Содержимое
Структурный App DB notification_types code, priority, countable, признак «есть деталка», record_status
Визуал Frontend (тема/константы), сверка с Figma Цвет, иконка, оформление
Тексты по умолчанию text_resources label, дефолтный CTA, empty states

В экземпляре не дублируются label/цвет/icon/countable/priority/action — только notification_type (FK/code).

5.3. Каталог типов

type label priority countable Action по CTA Контур
authorize Гостевой режим 2 нет Сценарий авторизации G
install_app Приложение 2 нет Адаптивная установка PWA (§5.7) G
urgent Срочно 1 да Детальный просмотр P
reminder Напоминание 2 да Детальный просмотр P
news Новость 2 да Детальный просмотр P
ads Предложение 2 да* G: чат после auth; P: чат + закрытие (§7.2) G и/или P
promo Акция 2 да* G: чат после auth; P: чат + закрытие (§7.2) G и/или P
docs_required Требуются документы 2 да Деталка (+ загрузка) P
docs_ready Документы готовы 2 да Деталка (+ скачивание) P
status_changed Статус 2 да Детальный просмотр P
payment_pending Оплата 2 да Переход по payment_url P

* Countable/read применяются только в контуре P. В G read-state нет.

Типы install_Android / install_IOS-HarmonyOS / messageотсутствуют.

priority: меньше = важнее. Seed: urgent = 1, остальные = 2.

CTA по умолчанию:

  • authorize — «Войти →»
  • install_app — «Установить →» (на UI инструкции текст может быть «Как установить →»)
  • urgent / reminder / news / status_changed — «Подробнее →»
  • ads / promo — «Узнать подробнее →»
  • docs_required — «Загрузить документы →»
  • docs_ready — «Скачать →»
  • payment_pending — «Оплатить →»

Инвариант: CTA у docs_required / docs_ready открывает карточку; upload/download — на карточке.
Инвариант: жёсткой привязки «сервис X → тип Y» нет.

5.4. Поля экземпляра (контур P)

Поле Обяз. Описание
id да UUID v7 (api-backend при Create)
user_id да UserIdentity.id NOT NULL
notification_type да Код из справочника
notification_datetime да UTC
header да Заголовок
text нет Подзаголовок

Опционально:

Поле Описание
date_expired lifecycle_status=N, close_reason=expired
price Текущая цена (₽), опционально. Может быть без old_price
old_price Старая цена (₽), опционально. Имеет смысл только вместе с price (зачёркнутая «было»)
payment_url Обязателен для payment_pending
details / details.* Деталка (если нужна)
details.deadline Срок на карточке
send_documents UI загрузки клиентом
documents[] Вложения компании в бакете han-chat-attachments (id объектов working S3)
chat_message_text Текст в чат при CTA (ads/promo P — обязателен)
button_done / button_gotit / button_gotit_text Кнопки деталки
external_id + source Идемпотентность Create (§8)

5.5. Состояние экземпляра (контур P)

Атрибут Значения Семантика
record_status A / D Soft-delete строки (arch-05)
lifecycle_status A / N N — нет в UI; в БД для аудита
visibility V / I I — нет на главной, есть в Центре (пока lifecycle_status=A)
is_read да / нет Для countable; бейдж = A ∧ countable ∧ ¬is_read
close_reason см. ниже При AN
close_reason Когда
user_done «Выполнено»; успех upload docs_required; Cancel после успешной оплаты
expired date_expired
cancelled Cancel (отзыв системой)

is_readvisibility: прочтение → бейдж; видимость → главная. Правила — §7.1.

Контур G: per-user lifecycle / visibility / is_read нет.

5.6. Константы и мнемоники

notification.* в app_settings. Вложения — ключи чата:

  • chat.attachments.allowed_extensions
  • chat.attachments.allowed_mime_types
  • chat.attachments.disallowed_extensions
  • chat.attachments.max_size_mb
  • chat.attachments.presigned_upload_ttl_seconds

Оператор: operator.call.phone.

Seed notification.*:

Ключ Смысл Default
notification.gotit.default_ttl_days Got it без date_expired → now+N дней 3
notification.docs_ready.default_ttl_days После скачивания документов, если date_expired пуст → now+N дней 3
notification.home.max_items Лимит карусели 15
notification.center.max_items Лимит Центра (ЛК) 15
notification.carousel.autoplay_enabled Y/N N
notification.carousel.autoplay_interval_ms Интервал 5000

Мнемоники UI (text_resources) — принято:

  • notification.center.empty.title / .text — пустой Центр (ЛК)
  • notification.guest_center.title / .text / .cta — гостевой Центр
  • notification.detail.not_found.title / .text — 404 деталки («На главную»)

API при чужом/N/отсутствии → 404; UI — нейтрально «Уведомление недоступно».

5.7. Гостевые уведомления (контур G)

  • Одна таблица гостевых уведомлений; record_status + актуальность A/N (без per-user state).
  • Public API отдаёт все активные (A); фронт не фильтрует по ОС/PWA в v1.
  • Наполнение v1 — insert/SQL; public API — read-only.
  • Типы в G: authorize, install_app, ads, promo (и при необходимости другие broadcast-типы позже).
  • Крестика нет. Деталки у ads/promo в G нет.

install_app — один тип, адаптивный CTA (принято, технически возможно):

  1. Пользователь нажал CTA.
  2. Если среда поддерживает установку PWA (beforeinstallprompt / эквивалент) — запускаем установку.
  3. Иначе — показываем инструкцию установки (локальный экран/модалка на фронте; отдельный тип в API не нужен).

Fallback (если адаптивный CTA окажется нереализуем на конкретной платформе сборки): две записи в G (install для Android-потока и для iOS/Harmony) — только как запасной план реализации, не целевая модель данных.

CTA прочих G:

  • authorize → сценарий авторизации.
  • ads / promo → как «популярные вопросы»: старт auth; после успеха в чат уходит chat_message_text кампании от лица клиента. Кампания для других гостей остаётся A.

5.8. Персональные (контур P) — кратко

Создаются Internal Create; полная модель §5.4–5.5. Поведение CTA — §7.


6. Поведение UI

6.1. Главная (карусель)

ЛК (P):

  • Выборка: record_status=A, lifecycle_status=A, visibility=V.
  • Лимит notification.home.max_items (15).
  • Сортировка: priority ↑, затем notification_datetime ↓.
  • Крестик → visibility=I (синхрон на все устройства).
  • Карусель: свайп; автопрокрутка по settings.
  • CTA → action типа + эффекты §7.1.

Гость (G):

  • Список = ответ public API; лимит 15; сортировка: priority ↑, затем datetime ↓ (поля сортировки — в таблице G / типе).
  • Крестика нет. Один источник — public API.

6.2. Центр

Гость: колокольчик без бейджа; экран auth-gate + «Авторизоваться» (мнемоники §5.6). Списка нет.

ЛК:

  • record_status=A, lifecycle_status=A (в т.ч. visibility=I), лимит 15, без архива N.
  • Точка на непрочитанных countable.
  • Сортировка: priority ↑ → непрочитанные выше → datetime ↓.
  • Бейдж = непрочитанные countable (lifecycle_status=A); sync через бэкенд.
  • Empty state — мнемоники §5.6.

6.3. Деталка (контур P; инструкция install_app — локальный UI)

  • SPA: /notification/{uuid}.
  • Чужой / N / deleted → 404 (не 403).
  • Upload: quarantine → Message Safety → working (han-chat-attachments) → триггер БДsync_queue.
  • Company documents[]: тот же бакет han-chat-attachments; preview/download + audit как в чате.
  • button_donelifecycle_status=N, close_reason=user_done.
  • button_gotitvisibility=I; при пустом date_expired — now + notification.gotit.default_ttl_days; is_read=true (got it = действие пользователя, см. §7.1).
  • Обе кнопки допустимы одновременно; исход по нажатой. Обе false → только back.

6.4. Завершение по типам (P)

Тип lifecycle_status=N
payment_pending Cancel после callback оплаты, close_reason=user_done
docs_required Успешный upload → user_done
Другой тип с send_documents Upload сам не закрывает
ads / promo CTA → N (§7.2)
status_changed «Выполнено»; с главной уходит уже по CTA (visibility=I)
docs_ready После скачивания — I + TTL/date_expired (§7.1); в N уходит по expire (или Cancel/done)
Прочие done / expired / cancelled

7. Матрицы поведения (контур P)

7.1. is_read и visibility — раздельно

Правило прочтения (единое):

Любое CTAis_read = true (идемпотентно).

CTA = первичное действие карточки: кнопка CTA на баннере, тап по строке Центра ведущий к action типа, «Оплатить», переход в деталку по CTA, отправка в чат по ads/promo.
Не CTA: системный back, крестик (крестик не ставит is_read).

Матрица visibility / lifecycle (избирательно):

Тип Событие is_read visibility lifecycle_status
любой Крестик на главной I
любой с button_gotit Got it true I (+ TTL если нужно)
любой с button_done «Выполнено» true N / user_done
urgent CTA → деталка true
reminder CTA → деталка true
status_changed CTA → деталка true I
news CTA → деталка true I
ads / promo CTA (чат) true I N (+ сообщение в чат)
docs_required CTA → деталка true
docs_required Успешный upload true N / user_done
docs_ready CTA → деталка true
docs_ready Скачивание документов true I — (+ TTL, см. ниже)
payment_pending CTA «Оплатить» true I — (далее Cancel→N при оплате)

Пояснения:

  • Прочерк «—» = поле этим событием не меняется.
  • Для ads/promo: одновременно is_read, visibility=I и lifecycle_status=N. В UI запись исчезает из-за N; I фиксирует намерение «убрать с главной» на случай гонок/аудита.
  • Открытие деталки из Центра по тапу строки = CTA → всегда is_read=true; visibility меняется только если тип/событие есть в матрице выше.
  • status_changed: CTA → деталка сразу уводит карточку с главной (visibility=I); в Центре остаётся, пока lifecycle_status=A.
  • docs_ready, скачивание документов: is_read=true, visibility=I; если date_expired пуст — выставить date_expired = now + notification.docs_ready.default_ttl_days (default 3). Если date_expired уже задан продюсером — не перезаписывать. Далее сработает общий expired-job → lifecycle_status=N, close_reason=expired.

7.2. ads / promo (P)

  1. CTA отправляет в чат chat_message_text (обязательное поле).
  2. is_read=true, visibility=I, lifecycle_status=N.
  3. Деталки у персональных ads/promo на MVP нет (симметрично G).

7.3. Валидация Internal Create (принято)

Тип Обязательно Не ожидается Примечания
urgent, reminder, news, status_changed header; details если нужна деталка payment_url button_* по необходимости
ads, promo header; chat_message_text payment_url price и old_price — разные опц. поля; price без old_price допустим; old_price без price — нет; деталки нет
docs_required header; send_documents=true payment_url
docs_ready header; documents[] ≥1 payment_url
payment_pending header; payment_url price не обязателен; details разрешены
любой user_id, notification_type, source, external_id неизвестный тип / user

Ошибка валидации → 400 (OpenAPI).

Типы контура G через Internal Create не создаются (только insert/public table).

7.4. Контракт продюсера

Операция Семантика
Create Создать новое уведомление. Ключ source+external_id: если уже есть активная (lifecycle_status=A) запись с той же парой → 409 Conflict (контент не обновляется).
Cancel lifecycle_status=N + close_reason (cancelled | user_done).
Смена контента Upsert / update content нет. Сценарий: Cancel старого → Create нового (новый external_id или та же пара после N — допустим Create, т.к. активной записи с ключом больше нет; политика ключа после N — в OpenAPI: разрешить Create с тем же ключом только если нет активной).

Кто закрывает:

Сценарий Кто reason
Оплата успешна Внешний → Cancel user_done
Upload docs_required api-backend user_done
CTA ads/promo api-backend по действию клиента (закрытие как user_done или отдельный reason — по OpenAPI; default user_done)
«Выполнено» Клиент user_done
Отзыв Внешний → Cancel cancelled
TTL Scheduler expired

8. Идентификация

  • Id P и G — UUID v7 (генерация приложением / seed-скриптом, не клиентом).
  • SPA: /notification/{uuid} (только P; гостевая инструкция install — локальный роут без обязательного id в API).
  • Deep link — с push (later).
  • Идемпотентность Create — §7.4 (без upsert).

9. API

9.1. Клиентские (JWT) — контур P

Список / get one / hide / mark-read·done·gotit / upload / counter — по §6–§7.
Владелец = user_id из JWT. Чужое/N → 404.

9.2. Public — контур G

GET списка активных гостевых уведомлений (без JWT).

9.3. Internal

  • Path: /internal/notifications/v1/...
  • Service token — как у прочих internal (arch-02).
  • Callers: сервисы приватной сети облака (в т.ч. другие ВМ). Интернет — нельзя.
  • Операции: Create, Cancel только.

9.4. Realtime (подписка)

Желательно в релизе, иначе polling.

После connect на WS /api/v1/realtime клиент выполняет явный subscribe на уведомления (например subscribe: { notifications: true }), отдельно от dialog_ids.
События: notification.created / notification.updated / notification.closed.
Reconnect → reconciliation GET списком. Гостю WS для G не требуется.

9.5. Готовность к Web Push

Стабильный UUID v7; события жизненного цикла; header/text для текста push; path /notification/{uuid}. Реализация push — вне scope.


10. Смежные сервисы

Компонент Изменение
Frontend Один источник (G или P); карусель; Центр; адаптивный install_app; Чат/Оператор
api-backend Модель G+P; public/JWT/internal; scheduler expired; WS subscribe notifications; upload
App DB Две таблицы; types+priority; триггер → sync_queue; seed
nginx Internal не в интернет; rate limit; SPA
Redis/WS События notification.* по подписке
message-safety Как chat attachments
bitrix-sync Новый task_type
S3 Бакет вложений уведомлений (клиент и компания): han-chat-attachments
settings notification.*, chat.attachments.*, operator.call.phone, мнемоники

Порядок: (1) модель + API + UI → (2) Internal Create/Cancel + seed G → (3) документы + триггер → (4) WS subscribe.


11. Критерии приёмки

  1. Колокольчик; ЛК — бейдж непрочитанных, sync is_read/visibility между устройствами; гость — без бейджа, Центр = «Авторизоваться».
  2. ЛК главная: лимит 15, sort priority→datetime, только A+V; крестик → I; N не в UI.
  3. Гость: только public API, без клиентских фильтров ОС/PWA; один тип install_app с адаптивным CTA.
  4. Типы каталога отличимы; message нет; после логина только P.
  5. Деталка/кнопки по флагам; docs_* CTA → карточка; ads/promo P: чат + is_read+I+N.
  6. Internal только Create/Cancel; дубликат активного source+external_id → 409; смена контента = Cancel+Create.
  7. Expired → N, UI исчезает.
  8. Upload → quarantine → working → триггер → sync_queue; company docs в han-chat-attachments.
  9. Оператор — только operator.call.phone.
  10. Константы из app_settings; UUID v7; 404 на чужое/N.
  11. Валидация Create по §7.3.
  12. WS: события после subscribe на notifications.
  13. Push не обязателен; модель push-ready.
  14. Архива N нет; поля opened нет.

12. Решения D1–D13 (закрыты)

# Решение
D1 Два контура: G (всё через public API) и P (JWT). Без локальных карточек и без условий показа в v1. install_app — один тип, адаптивный CTA (установка / инструкция). Fallback — два баннера в данных, не целевая модель.
D2 Схема справочника §5.2 принята.
D3 Мнемоники empty/404 и поведение 404 приняты (§5.6).
D4 ads/promo CTA: is_read + visibility=I + lifecycle_status=N + чат.
D5 Любое CTA → is_read=true. visibility — только по матрице §7.1.
D6 Валидация §7.3: chat_message_text обязателен для ads/promo; price у payment необязателен; details у payment разрешены.
D7 /internal/notifications/v1/... + service token.
D8 WS: явная подписка на notifications.
D9 Бакет company/client файлов уведомлений: han-chat-attachments.
D10 opened / impression удалены; достаточно is_read от CTA.
D11 Upsert нет; механика Cancel + Create; конфликт активного ключа → 409.
D12 IDOR: единый 404.
D13 Одновременные done+gotit допустимы без доп. правил продюсеру.

13. История решений (Q)

# Решение
Q1 Непрочитанный чат / message — вне ТЗ.
Q2 N в БД, не в UI; архива v1 нет.
Q3 payment_pending: CTA «Оплатить» → is_read+I; деталка не обязательна.
Q4 Гостевые — отдельная таблица; insert v1.
Q5 Документы через S3; клиент quarantine→working.
Q6 sync_queue только триггером БД.
Q7 Лимит 15; sort priority→datetime.
Q8 Подсказки установки/входа — в контуре G через API (не локальный FE).
Q9 UUID v7; /notification/{uuid}.
Q10 message исключён.
Q11 Владелец = user_id.
Q12 record_statuslifecycle_status.
Q13 operator.call.phone.
Q14 Got it = I (+TTL), не обязательно N.
Q15 Два контура G/P (вместо A/B/C).
Q16 Гостевой Центр = auth-gate.
Q17 Reuse chat.attachments.*.
Q18 Поле opened снято (D10).
Q19 Модель push-ready; push вне scope.