Docs: sync Back#61 specialist_id + trial/402 (no auto-start). Fixes EventHub/EventHubSpec#14

This commit is contained in:
2026-07-27 21:24:09 +03:00
parent 546f888487
commit 644ded803c
3 changed files with 31 additions and 16 deletions
+21 -12
View File
@@ -78,14 +78,17 @@ EventHub — платформа для управления событиями
слоты освобождаются; слоты освобождаются;
- уже `confirmed`**оставляем**; - уже `confirmed`**оставляем**;
- owner: просмотр/редактирование своих событий и настроек, CRUD specialists — **разрешены**; - owner: просмотр/редактирование своих событий и настроек, CRUD specialists — **разрешены**;
- create нового commercial / upgrade type → `402`. - create нового commercial / upgrade type → `402` (нужна **уже active** подписка
или trial; auto-start trial при create **нет** — см. ниже и §2.9).
Периодический job (например раз в 60 с): помечает просроченные подписки `expired`, отменяет Периодический job (например раз в 60 с): помечает просроченные подписки `expired`, отменяет
pending владельца, **не** меняет `calendar.type`. Legacy `downgrade_user_calendars/1` pending владельца, **не** меняет `calendar.type`. Legacy `downgrade_user_calendars/1`
(type → personal) — удалить/не использовать. (type → personal) — удалить/не использовать.
Первое создание commercial без подписки: **auto-start trial** (один раз, `trial_used`); Create / upgrade `commercial` без **уже active** подписки или trial → **`402`**.
повтор без подписки → `402`. Планы/цены — §2.9. Trial стартует **только** явным `POST /v1/subscription` с `action=start_trial`
(один раз, `trial_used`); при create commercial auto-start trial **нет**.
Планы/цены — §2.9.
В ответах календаря (user API): `booking_open` (boolean) — производное от В ответах календаря (user API): `booking_open` (boolean) — производное от
`type=commercial` ∧ calendar `active` ∧ подписка владельца active. `type=commercial` ∧ calendar `active` ∧ подписка владельца active.
@@ -173,8 +176,9 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
допускается только как внутренний/тестовый путь или удаляется после миграции на invite. допускается только как внутренний/тестовый путь или удаляется после миграции на invite.
Продуктовый путь: invite → accept → specialist. Продуктовый путь: invite → accept → specialist.
- `event.specialist_id` опционален; если задан — только `active` specialist этого календаря - `event.specialist_id` опционален на `POST`/`PUT` события; если задан — персистится
(иначе `400`). и валидируется: только `active` specialist этого календаря (иначе `400`
`Invalid specialist_id for this calendar`).
- **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только - **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только
booking на событиях, где `event.specialist_id` = его `user_id`. booking на событиях, где `event.specialist_id` = его `user_id`.
- **Inbox к подтверждению:** `GET /v1/user/booking-requests` агрегирует **actionable** pending - **Inbox к подтверждению:** `GET /v1/user/booking-requests` агрегирует **actionable** pending
@@ -339,8 +343,9 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
- Цены (`logic_subscription:plan_price/1`, minor units): monthly **999**, quarterly **2499**, - Цены (`logic_subscription:plan_price/1`, minor units): monthly **999**, quarterly **2499**,
biannual **4499**, annual **7999**; trial — 0. Платёжный шлюз — заглушка (`process_payment` → ok). biannual **4499**, annual **7999**; trial — 0. Платёжный шлюз — заглушка (`process_payment` → ok).
- Статус подписки: `active`, `expired`, `cancelled`. - Статус подписки: `active`, `expired`, `cancelled`.
- Отслеживание использования пробного периода (`trial_used`); trial стартует автоматически при - Отслеживание использования пробного периода (`trial_used`); trial стартует **только**
первой попытке создать/апгрейднуть commercial (§2.1.2). явным `POST /v1/subscription` (`action=start_trial`), не при create/upgrade commercial
(§2.1.2). Create/upgrade commercial без уже active sub/trial → `402`.
- Истечение: периодический job → `expired`; commercial-календари **не** меняют type — - Истечение: периодический job → `expired`; commercial-календари **не** меняют type —
переходят в restricted; после `activate` / новой active подписки функционал восстанавливается переходят в restricted; после `activate` / новой active подписки функционал восстанавливается
(§2.1.2). Не использовать legacy downgrade type → personal. (§2.1.2). Не использовать legacy downgrade type → personal.
@@ -480,9 +485,11 @@ src/
В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart
**не** входит в контракт: поле URL. **не** входит в контракт: поле URL.
- `GET /v1/calendars` — список календарей. - `GET /v1/calendars` — список календарей.
- `POST /v1/calendars` — создать календарь (`commercial`подписка/trial; иначе `402`). - `POST /v1/calendars` — создать календарь (`commercial`нужна уже active sub/trial;
иначе `402`; auto-start trial нет).
- `GET /v1/calendars/:id` — календарь (`following`, `booking_open` для текущего контекста). - `GET /v1/calendars/:id` — календарь (`following`, `booking_open` для текущего контекста).
- `PUT /v1/calendars/:id` — обновить календарь (`personal→commercial`gate подписки). - `PUT /v1/calendars/:id` — обновить календарь (`personal→commercial`нужна уже
active sub/trial, иначе `402`).
- `DELETE /v1/calendars/:id` — удалить календарь. - `DELETE /v1/calendars/:id` — удалить календарь.
- `POST /v1/calendars/:id/follow` — отслеживать чужой календарь. - `POST /v1/calendars/:id/follow` — отслеживать чужой календарь.
- `DELETE /v1/calendars/:id/follow` — снять follow. - `DELETE /v1/calendars/:id/follow` — снять follow.
@@ -495,9 +502,10 @@ src/
- `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee. - `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee.
- `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link. - `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link.
- `GET /v1/calendars/:calendar_id/events` — события календаря. - `GET /v1/calendars/:calendar_id/events` — события календаря.
- `POST /v1/calendars/:calendar_id/events` — создать событие. - `POST /v1/calendars/:calendar_id/events` — создать событие (тело может включать
опциональный `specialist_id`; invalid → `400`).
- `GET /v1/events/:id` — событие. - `GET /v1/events/:id` — событие.
- `PUT /v1/events/:id` — обновить событие. - `PUT /v1/events/:id` — обновить событие (в т.ч. `specialist_id`; invalid → `400`).
- `DELETE /v1/events/:id` — удалить событие. - `DELETE /v1/events/:id` — удалить событие.
- `GET /v1/events/:id/occurrences` — вхождения повторяющегося события. - `GET /v1/events/:id/occurrences` — вхождения повторяющегося события.
- `DELETE /v1/events/:id/occurrences/:start_time` — отменить вхождение серии. - `DELETE /v1/events/:id/occurrences/:start_time` — отменить вхождение серии.
@@ -522,7 +530,8 @@ src/
- `POST /v1/tickets` — создать или обновить тикет (дедуп по `error_hash`): body `{ error_message, stacktrace?, context?, source?: frontend|manual }`; ответ 201 (или 429 при rate-limit новых). - `POST /v1/tickets` — создать или обновить тикет (дедуп по `error_hash`): body `{ error_message, stacktrace?, context?, source?: frontend|manual }`; ответ 201 (или 429 при rate-limit новых).
- `GET /v1/tickets/:id` — статус тикета. - `GET /v1/tickets/:id` — статус тикета.
- `GET /v1/subscription` — подписка пользователя. - `GET /v1/subscription` — подписка пользователя.
- `POST /v1/subscription`активировать подписку. - `POST /v1/subscription``action=start_trial` | `activate` (+ `plan`, опционально
`payment_info`). Trial — только через `start_trial`, не через create commercial.
- `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — HTML-календарь (владелец), включая архив. - `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — HTML-календарь (владелец), включая архив.
### WebSocket (порт 8081) ### WebSocket (порт 8081)
+6 -3
View File
@@ -142,7 +142,8 @@ cover (`.eh-discover-media--photo`); иначе mood wash + initials (`.eh-disco
- Просмотр коммерческого чужого календаря по id; при `booking_open=false` — баннер - Просмотр коммерческого чужого календаря по id; при `booking_open=false` — баннер
«Запись временно недоступна», кнопка записи скрыта «Запись временно недоступна», кнопка записи скрыта
- HTML month view владельца: `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — при переключении виджета на **прошедший месяц** (owner + вид «Месяц») автоматически подменяет клиентский grid серверным HTML (live/архив, задача #15). Текущий и будущие месяцы — React-виджет по `GET …/events`. - HTML month view владельца: `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — при переключении виджета на **прошедший месяц** (owner + вид «Месяц») автоматически подменяет клиентский grid серверным HTML (live/архив, задача #15). Текущий и будущие месяцы — React-виджет по `GET …/events`.
- Создание / апгрейд `commercial` без подписки`402``/subscription`; после оплаты — - Создание / апгрейд `commercial` без **уже active** sub/trial`402``/subscription`
(trial — явный `start_trial`, не auto при create); после оплаты —
возврат к созданию/редактированию возврат к созданию/редактированию
- Владелец commercial: специалисты **через invite** (не сырой `user_id`): - Владелец commercial: специалисты **через invite** (не сырой `user_id`):
typeahead `GET /v1/users/lookup` и/или email → `POST …/specialist-invites`; typeahead `GET /v1/users/lookup` и/или email → `POST …/specialist-invites`;
@@ -191,8 +192,10 @@ UI: форма отзыва скрыта без confirmed booking (event — boo
`GET /v1/subscription`, `POST /v1/subscription` (`start_trial` | `activate` + `plan` + опционально `payment_info`). `GET /v1/subscription`, `POST /v1/subscription` (`start_trial` | `activate` + `plan` + опционально `payment_info`).
Планы и цены (как Back `plan_price/1`, minor units → ₽): monthly **999**, quarterly **2499**, biannual **4499**, annual **7999**. Планы и цены (как Back `plan_price/1`, minor units → ₽): monthly **999**, quarterly **2499**, biannual **4499**, annual **7999**.
UI: карточки сравнения, локализованный «Бесплатно» без подписки, trial, демо-оплата (шлюз-заглушка). UI: карточки сравнения, локализованный «Бесплатно» без подписки, trial, демо-оплата (шлюз-заглушка).
Создание commercial без подписки`402``/subscription`. После renew commercial-календари Create/upgrade commercial без **уже active** sub/trial`402``/subscription`
владельца снова с `booking_open=true` без смены type (BackSpec §2.1.2 restricted → full). (сначала явный `start_trial` или `activate`; auto-start trial при create **нет**,
Back#61 / BackSpec §2.1.2, §2.9). После renew commercial-календари владельца снова
с `booking_open=true` без смены type (restricted → full).
### 5.10. Тикеты ### 5.10. Тикеты
- ErrorBoundary / необработанные ошибки → `POST /v1/tickets` с `source=frontend` - ErrorBoundary / необработанные ошибки → `POST /v1/tickets` с `source=frontend`
+4 -1
View File
@@ -73,7 +73,7 @@ PowerShell в Cursor **ломает экранирование** (кавычки
| Windows `npm` / `erl` / `rebar3` | npm и rebar **только в WSL** | | Windows `npm` / `erl` / `rebar3` | npm и rebar **только в WSL** |
| Push Front без локальной проверки | WSL: `npm run lint && npm run build && npm run test:e2e:ift`, потом push | | Push Front без локальной проверки | WSL: `npm run lint && npm run build && npm run test:e2e:ift`, потом push |
Канон для агентов: `EventHubBack/.cursor/rules/shell-wsl-not-powershell.mdc`. Канон для агентов: `EventHubBack/.cursor/rules/shell-wsl-not-powershell.mdc`, **`agent-pitfalls.mdc`** (типовые сбои shell/git/docker).
| Tool | Version | Path (WSL) | | Tool | Version | Path (WSL) |
|------|---------|------------| |------|---------|------------|
@@ -363,6 +363,7 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе)
- Одна задача — один логический объём работы. - Одна задача — один логический объём работы.
- Код фичи — в репозитории-владельце; `EventHubSpec` — для документации и процесса. - Код фичи — в репозитории-владельце; `EventHubSpec` — для документации и процесса.
- **Один агент — один репо** (не смешивать Front/Back/Spec/FrontAdmin в одной сессии): `.cursor/rules/one-repo-per-agent.mdc`. - **Один агент — один репо** (не смешивать Front/Back/Spec/FrontAdmin в одной сессии): `.cursor/rules/one-repo-per-agent.mdc`.
- **Типовые ошибки агентов** (PS, git clean, docker scale, scope): `EventHubBack/.cursor/rules/agent-pitfalls.mdc`; в промпт субагента включать shell + pitfalls.
- Метки во всех репозиториях держать одинаковыми (`sync-labels.ps1`). - Метки во всех репозиториях держать одинаковыми (`sync-labels.ps1`).
- Секреты и токены не коммитить; Gitea token — только в env / `~/.cursor/secrets/`. - Секреты и токены не коммитить; Gitea token — только в env / `~/.cursor/secrets/`.
- **Git commit/push — через WSL** (`~/.cursor/eventhub/git-commit.sh`, `git-push.sh`); **push — только после подтверждения пользователя** (см. §2.1). - **Git commit/push — через WSL** (`~/.cursor/eventhub/git-commit.sh`, `git-push.sh`); **push — только после подтверждения пользователя** (см. §2.1).
@@ -378,6 +379,8 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе)
| Дата | Изменение | | Дата | Изменение |
|------|-----------| |------|-----------|
| 2026-07-27 | Spec sync Back#61: `specialist_id` на POST event; commercial без active sub → 402; trial только `start_trial` |
| 2026-07-27 | `agent-pitfalls.mdc`: типовые сбои PS/git/docker/scope; промпт субагентам |
| 2026-07-27 | Spec sync: booking `expired` (Back#60) + Front#31 UX (desktop chrome, empty bookings, agenda copy, Discover covers, nickname, invites badge) | | 2026-07-27 | Spec sync: booking `expired` (Back#60) + Front#31 UX (desktop chrome, empty bookings, agenda copy, Discover covers, nickname, invites badge) |
| 2026-07-27 | Spec#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent | | 2026-07-27 | Spec#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent |
| 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback | | 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback |