Document commercial calendars: booking_open, specialists, restricted UX.

Refs EventHub/EventHubSpec#6
Refs EventHub/EventHubBack#54
This commit is contained in:
2026-07-22 16:12:54 +03:00
parent 8d0f4c3bb2
commit f3f199c791
2 changed files with 142 additions and 38 deletions
+116 -23
View File
@@ -1,5 +1,6 @@
# ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
Версия: 1.5 (актуальная реализация: выполнены задачи #12#17)
Версия: 1.5 (актуальная реализация: выполнены задачи #12#17; §2.1.2 commercial — контракт
к реализации)
## 1. ЦЕЛИ И НАЗНАЧЕНИЕ
EventHub — платформа для управления событиями с поддержкой календарей, записи участников
@@ -12,12 +13,10 @@ EventHub — платформа для управления событиями
### 2.1. Календари
- CRUD календаря (название, описание, теги, владелец)
- Расшаривание по ссылке (публичная/приватная)
- Приглашение пользователей с правами "запись" или "администрирование"
- Типы календарей: personal (бесплатный, без записи), commercial (платный, запись клиентов,
специалисты)
- Гибкое подтверждение заявок: auto (автоматически), manual (вручную), timeout (авто через N
секунд)
- Расшаривание по ссылке / приглашения с правами (`calendar_share`) — **фаза 2** (таблица есть;
user HTTP API и UI — Future, см. §2.1.3)
- Типы календарей: `personal` | `commercial` — семантика и коммерческий контур: **§2.1.2**
- Гибкое подтверждение заявок: `auto` | `manual` | `{timeout, N}` (секунды) — детали §2.1.2 / §2.3
- Теги календаря, рейтинг (средняя оценка, количество голосов)
- После успешной верификации email (`POST /v1/verify`, статус пользователя → `active`) система
идемпотентно создаёт дефолтный **personal**-календарь владельцу (`logic_calendar:ensure_default_calendar/1`):
@@ -32,10 +31,97 @@ EventHub — платформа для управления событиями
- `image_url` — изображение календаря
- `settings` — дополнительные настройки (map)
### 2.1.1. Специалисты календаря (задача #12)
Реализована отдельная таблица `calendar_specialist`, связывающая пользователя (специалиста) с
календарём. Специалист может иметь отображаемое имя (`name`) и список специализаций
(`specialization`). Статус специалиста: `active` | `inactive`.
### 2.1.1. Специалисты календаря
Таблица `calendar_specialist`: связь пользователя-специалиста с **commercial**-календарём.
Поля: `calendar_id`, `user_id`, `name` (отображаемое имя в этом календаре),
`specialization` (`[binary()]`), `status` (`active` | `inactive`).
Контракт CRUD, права confirm и связь с `event.specialist_id` — §2.1.2.
### 2.1.2. Коммерческие календари (продуктовый контракт)
Цель: владелец (бизнес) ведёт публичное расписание и принимает записи клиентов; участник находит
календарь, записывается, получает подтверждение, затем может оставить отзыв. Personal остаётся
бесплатным «дневником» без записи чужих.
#### Типы и видимость
| | `personal` | `commercial` |
|---|---|---|
| Подписка владельца | не требуется | нужна active (trial или paid), иначе **restricted** |
| Видимость чужим | только владелец (+ share — фаза 2) | публичный просмотр; в search/discover — только если **не** restricted |
| Booking чужим | **запрещён** (бэкенд `403`, код `personal_calendar`) | разрешён при active подписке владельца |
| Follow | нет (нет доступа) | да |
| Специалисты | не используются | CRUD владельца; `specialist_id` на событии |
| `confirmation` | не влияет на чужие booking | `auto` / `manual` / `{timeout,N}` |
- Смена `personal → commercial`: только при active подписке владельца; иначе `402`.
- Смена `commercial → personal`: разрешена владельцу; pending booking этого календаря → `cancelled`.
- Владелец **не** может создать booking на событие своего календаря.
#### Подписка и режим restricted (не downgrade type)
При истечении/отсутствии active подписки календари **остаются** `type=commercial` (данные,
специалисты, история сохраняются). Включается **restricted mode** — ограничение коммерческого
функционала; при новой активации подписки функционал **сразу восстанавливается** без смены type.
| Подписка владельца | Поведение |
|---|---|
| `active` и `expires_at > now` | полный commercial |
| иначе (`expired` / нет / `cancelled`) | restricted |
**Restricted:**
- из search / discovery tops — **исключить**;
- deep-link `GET /v1/calendars/:id` (и события) — **просмотр разрешён**; в JSON календаря
флаг `booking_open: false` (и при active — `true`); UI: «Запись временно недоступна»;
- новые booking → `403` (`subscription_inactive`);
- все `pending` booking календарей владельца → `cancelled` (причина `subscription_inactive`),
слоты освобождаются;
- уже `confirmed`**оставляем**;
- owner: просмотр/редактирование своих событий и настроек, CRUD specialists — **разрешены**;
- create нового commercial / upgrade type → `402`.
Периодический job (например раз в 60 с): помечает просроченные подписки `expired`, отменяет
pending владельца, **не** меняет `calendar.type`. Legacy `downgrade_user_calendars/1`
(type → personal) — удалить/не использовать.
Первое создание commercial без подписки: **auto-start trial** (один раз, `trial_used`);
повтор без подписки → `402`. Планы/цены — §2.9.
В ответах календаря (user API): `booking_open` (boolean) — производное от
`type=commercial` ∧ calendar `active` ∧ подписка владельца active.
#### Запись и capacity
- `POST /v1/events/:id/bookings` только если календарь события commercial, `booking_open=true`,
событие `active`, есть свободная вместимость.
- **Pending занимает capacity** наравне с confirmed (защита от overbook при auto/timeout).
- Capacity: число booking со статусом `pending` | `confirmed`; `cancelled` не считаются.
- Политика `confirmation` календаря при создании booking:
- `auto` → сразу `confirmed` + `confirmed_at`;
- `manual``pending`; confirm/decline — владелец или specialist (см. ниже);
- `{timeout, N}``pending`; через N секунд без решения: auto-confirm, если ещё есть
capacity, иначе `cancelled` (`timeout_full`).
- Участник: `DELETE /v1/bookings/:id` — отмена своей pending/confirmed.
- WS: `booking_update` участнику и владельцу (и specialist при confirm на «своём» событии).
#### Специалисты
- Специалист = существующий `user`, привязанный к commercial-календарю.
- API (владелец календаря):
- `GET/POST /v1/calendars/:id/specialists`
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id`
- `event.specialist_id` опционален; если задан — только `active` specialist этого календаря
(иначе `400`).
- **Confirm/decline:** владелец — любые booking календаря; `active` specialist — только booking
на событиях, где `event.specialist_id` = его `user_id`.
#### Фаза 2 (вне текущего контракта реализации)
- `calendar_share` (`read` | `write` | `admin`): не путать с follow и платной subscription.
- Реальный эквайринг, waitlist, оплата услуги клиентом (B2C).
### 2.1.3. Share / приглашения (фаза 2)
Таблица `calendar_share` (`calendar_id`, `user_id`, `rights`: `read` | `write` | `admin`)
существует в схеме. User HTTP API и клиентский UI — Future до отдельной задачи.
### 2.2. События (расширенная версия с повторяющимися событиями)
@@ -99,14 +185,15 @@ EventHub — платформа для управления событиями
(список active доступных календарей); в `GET /v1/calendars/:id` — поле `following` (boolean).
### 2.3. Запись участников и подтверждение
- Пользователь может отправить заявку на участие в событии.
- В зависимости от `confirmation` календаря заявка либо подтверждается автоматически, либо
ожидает ручного подтверждения владельцем, либо подтверждается по таймауту.
- Бронирование имеет статусы: `pending`, `confirmed`, `cancelled`.
- Пользователь может отменить свою запись.
- Владелец календаря может подтвердить или отклонить заявку.
- При подтверждении фиксируется время (`confirmed_at`).
- Вместимость события (`capacity`) ограничивает количество подтверждённых записей.
- Запись доступна только на событиях **commercial**-календаря с `booking_open=true` (§2.1.2).
- На personal → `403` (`personal_calendar`); при restricted commercial → `403` (`subscription_inactive`).
- В зависимости от `confirmation` календаря: auto / manual / timeout (§2.1.2).
- Статусы: `pending`, `confirmed`, `cancelled`.
- **Capacity:** лимит слотов считают `pending` + `confirmed` (pending резервирует место).
- Участник может отменить свою запись (`DELETE`).
- Confirm/decline: владелец календаря — любые заявки; active specialist — только на событиях
со своим `specialist_id` (§2.1.2).
- При подтверждении фиксируется `confirmed_at`.
### 2.4. Отзывы и рейтинги
- Пользователи могут оставлять отзывы (рейтинг 1–5 и комментарий) на события или календари.
@@ -181,7 +268,11 @@ EventHub — платформа для управления событиями
- Цены (`logic_subscription:plan_price/1`, minor units): monthly **999**, quarterly **2499**,
biannual **4499**, annual **7999**; trial — 0. Платёжный шлюз — заглушка (`process_payment` → ok).
- Статус подписки: `active`, `expired`, `cancelled`.
- Отслеживание использования пробного периода (`trial_used`).
- Отслеживание использования пробного периода (`trial_used`); trial стартует автоматически при
первой попытке создать/апгрейднуть commercial (§2.1.2).
- Истечение: периодический job → `expired`; commercial-календари **не** меняют type —
переходят в restricted; после `activate` / новой active подписки функционал восстанавливается
(§2.1.2). Не использовать legacy downgrade type → personal.
### 2.10. Административная панель
- Управление пользователями (просмотр, блокировка, изменение ролей).
@@ -306,12 +397,14 @@ src/
- `GET /v1/user/following` — календари, которые пользователь отслеживает (follow).
- `GET /v1/search` — поиск; пустой запрос (только auth + пагинация/`type`) — discovery tops.
- `GET /v1/calendars` — список календарей.
- `POST /v1/calendars` — создать календарь.
- `GET /v1/calendars/:id` — календарь (поле `following` для текущего пользователя).
- `PUT /v1/calendars/:id` — обновить календарь.
- `POST /v1/calendars` — создать календарь (`commercial` → подписка/trial; иначе `402`).
- `GET /v1/calendars/:id` — календарь (`following`, `booking_open` для текущего контекста).
- `PUT /v1/calendars/:id` — обновить календарь (`personal→commercial` → gate подписки).
- `DELETE /v1/calendars/:id` — удалить календарь.
- `POST /v1/calendars/:id/follow` — отслеживать чужой календарь.
- `DELETE /v1/calendars/:id/follow` — снять follow.
- `GET/POST /v1/calendars/:id/specialists` — список / добавить специалиста (владелец).
- `PUT/DELETE /v1/calendars/:id/specialists/:user_id` — обновить / убрать специалиста.
- `GET /v1/calendars/:calendar_id/events` — события календаря.
- `POST /v1/calendars/:calendar_id/events` — создать событие.
- `GET /v1/events/:id` — событие.