Первая постановка на уведомления
This commit is contained in:
@@ -0,0 +1,493 @@
|
||||
# Бизнес-постановка: Уведомления (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` | см. ниже | При `A` → `N` |
|
||||
|
||||
| `close_reason` | Когда |
|
||||
|---|---|
|
||||
| `user_done` | «Выполнено»; успех upload `docs_required`; Cancel после успешной оплаты |
|
||||
| `expired` | `date_expired` |
|
||||
| `cancelled` | Cancel (отзыв системой) |
|
||||
|
||||
**`is_read` ≠ `visibility`:** прочтение → бейдж; видимость → главная. Правила — §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_done` → `lifecycle_status=N`, `close_reason=user_done`.
|
||||
- `button_gotit` → `visibility=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` — раздельно
|
||||
|
||||
**Правило прочтения (единое):**
|
||||
|
||||
> Любое **CTA** → `is_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_status` ≠ `lifecycle_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. |
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user