diff --git a/EventHubBackSpec.md b/EventHubBackSpec.md index 5571458..7750414 100644 --- a/EventHubBackSpec.md +++ b/EventHubBackSpec.md @@ -78,14 +78,17 @@ EventHub — платформа для управления событиями слоты освобождаются; - уже `confirmed` — **оставляем**; - owner: просмотр/редактирование своих событий и настроек, CRUD specialists — **разрешены**; -- create нового commercial / upgrade type → `402`. +- create нового commercial / upgrade type → `402` (нужна **уже active** подписка + или trial; auto-start trial при create **нет** — см. ниже и §2.9). Периодический job (например раз в 60 с): помечает просроченные подписки `expired`, отменяет pending владельца, **не** меняет `calendar.type`. Legacy `downgrade_user_calendars/1` (type → personal) — удалить/не использовать. -Первое создание commercial без подписки: **auto-start trial** (один раз, `trial_used`); -повтор без подписки → `402`. Планы/цены — §2.9. +Create / upgrade `commercial` без **уже active** подписки или trial → **`402`**. +Trial стартует **только** явным `POST /v1/subscription` с `action=start_trial` +(один раз, `trial_used`); при create commercial auto-start trial **нет**. +Планы/цены — §2.9. В ответах календаря (user API): `booking_open` (boolean) — производное от `type=commercial` ∧ calendar `active` ∧ подписка владельца active. @@ -173,8 +176,9 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ допускается только как внутренний/тестовый путь или удаляется после миграции на invite. Продуктовый путь: invite → accept → specialist. -- `event.specialist_id` опционален; если задан — только `active` specialist этого календаря - (иначе `400`). +- `event.specialist_id` опционален на `POST`/`PUT` события; если задан — персистится + и валидируется: только `active` specialist этого календаря (иначе `400` + `Invalid specialist_id for this calendar`). - **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только booking на событиях, где `event.specialist_id` = его `user_id`. - **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**, biannual **4499**, annual **7999**; trial — 0. Платёжный шлюз — заглушка (`process_payment` → ok). - Статус подписки: `active`, `expired`, `cancelled`. -- Отслеживание использования пробного периода (`trial_used`); trial стартует автоматически при - первой попытке создать/апгрейднуть commercial (§2.1.2). +- Отслеживание использования пробного периода (`trial_used`); trial стартует **только** + явным `POST /v1/subscription` (`action=start_trial`), не при create/upgrade commercial + (§2.1.2). Create/upgrade commercial без уже active sub/trial → `402`. - Истечение: периодический job → `expired`; commercial-календари **не** меняют type — переходят в restricted; после `activate` / новой active подписки функционал восстанавливается (§2.1.2). Не использовать legacy downgrade type → personal. @@ -480,9 +485,11 @@ src/ В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart **не** входит в контракт: поле URL. - `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` для текущего контекста). -- `PUT /v1/calendars/:id` — обновить календарь (`personal→commercial` → gate подписки). +- `PUT /v1/calendars/:id` — обновить календарь (`personal→commercial` → нужна уже + active sub/trial, иначе `402`). - `DELETE /v1/calendars/:id` — удалить календарь. - `POST /v1/calendars/:id/follow` — отслеживать чужой календарь. - `DELETE /v1/calendars/:id/follow` — снять follow. @@ -495,9 +502,10 @@ src/ - `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee. - `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link. - `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` — событие. -- `PUT /v1/events/:id` — обновить событие. +- `PUT /v1/events/:id` — обновить событие (в т.ч. `specialist_id`; invalid → `400`). - `DELETE /v1/events/:id` — удалить событие. - `GET /v1/events/:id/occurrences` — вхождения повторяющегося события. - `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 новых). - `GET /v1/tickets/:id` — статус тикета. - `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-календарь (владелец), включая архив. ### WebSocket (порт 8081) diff --git a/EventHubFrontSpec.md b/EventHubFrontSpec.md index f0a662c..99958c8 100644 --- a/EventHubFrontSpec.md +++ b/EventHubFrontSpec.md @@ -142,7 +142,8 @@ cover (`.eh-discover-media--photo`); иначе mood wash + initials (`.eh-disco - Просмотр коммерческого чужого календаря по id; при `booking_open=false` — баннер «Запись временно недоступна», кнопка записи скрыта - 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`): 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`). Планы и цены (как Back `plan_price/1`, minor units → ₽): monthly **999**, quarterly **2499**, biannual **4499**, annual **7999**. UI: карточки сравнения, локализованный «Бесплатно» без подписки, trial, демо-оплата (шлюз-заглушка). -Создание commercial без подписки → `402` → `/subscription`. После renew commercial-календари -владельца снова с `booking_open=true` без смены type (BackSpec §2.1.2 restricted → full). +Create/upgrade commercial без **уже active** sub/trial → `402` → `/subscription` +(сначала явный `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. Тикеты - ErrorBoundary / необработанные ошибки → `POST /v1/tickets` с `source=frontend` diff --git a/WORKFLOW.md b/WORKFLOW.md index de87dd3..023343c 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -73,7 +73,7 @@ PowerShell в Cursor **ломает экранирование** (кавычки | Windows `npm` / `erl` / `rebar3` | npm и rebar **только в WSL** | | 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) | |------|---------|------------| @@ -363,6 +363,7 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе) - Одна задача — один логический объём работы. - Код фичи — в репозитории-владельце; `EventHubSpec` — для документации и процесса. - **Один агент — один репо** (не смешивать 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`). - Секреты и токены не коммитить; Gitea token — только в env / `~/.cursor/secrets/`. - **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#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent | | 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback |