POST /api/developers/v1/ranking-rules/{ruleId}/enable — Включение правила ранжирования
Назначение
Переводит правило ранжирования Disabled → Active (ranking-rule.md,
stateDiagram): сохранённый Priority снова денормализуется в DeveloperPriority карточек в той же
транзакции (INV-4).
Симметричная операция
POST /api/developers/v1/ranking-rules/{ruleId}/disable (Active → Disabled; отдельный документ не
заводится, дельта описана здесь): тот же контракт без body-полей, карточки получают приоритет по каскаду
INV-5 без этого правила (объекты недвижимости возвращаются к проектному правилу или 0); no-op при
уже Disabled.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | catalog |
| API area | developers |
| Feature group | ranking |
| Статус | draft |
| Документ | docs/06-services/catalog/api/developers/enable-ranking-rule.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика — JWT identity) |
| Auth policy / Permissions | permission catalog.ranking.manage в scope организации/группы проектов/проекта |
| Scope (RBAC) | организация и ниже |
| Tenant isolation | TenantId = CompanyId (ADR-0052); чужое правило невидимо |
| Audit | actor-stamping UpdatedByUserId, Version++; переход статуса — доменное событие |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST (disable-дельта: POST …/disable) |
| Route | /api/developers/v1/ranking-rules/{ruleId}/enable |
| Success status | 200 |
| Idempotency header | не применяется (переход идемпотентен: повтор — no-op успех) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required | tenant isolation |
RuleId | route | Guid | Да | != Guid.Empty; существование — шаг 2 | — |
Модель ответа RankingRuleResult
Модель — дом create-ranking-rule.md. Дельта: Status — Active
(disable-дельта: Disabled); CardsRecomputed — затронутые карточки перехода.
Алгоритм
- Контекст и доступ. Актор — менеджер застройщика (JWT +
catalog.ranking.manage);TenantIdиз claims. - Проверка входа.
RuleId != Guid.Empty→ValidationError. Существование: правило (в рамках своего tenant) найдено; нет →CATALOG_RANKING_RULE_NOT_FOUND(404). Состояние:Disabled— выполняем переход;Active— идемпотентный no-op успех (CardsRecomputed = 0). Цель правила: карточка отсутствует илиDelisted→ переход разрешён, но пересчитывать нечего (CardsRecomputed = 0) — правило «доживёт» до восстановления цели (delete behavior — ranking-rule.md, «Связи»). - Что читаем. Каскадные данные как в create-ranking-rule.md шаг 3
(объекты недвижимости проекта + их активные правила объектов для
Project; карточка объекта недвижимости + активное проектное правило дляPropertyObject). - Правила и переходы. Enable:
Disabled → Active,Version++,UpdatedByUserId= актор; фиксируетсяRankingRuleChangedEvent. Каскад INV-5: enable — карточки получаютPriorityправила (объекты недвижимости с собственнымActive-правилом не трогаются); disable-дельта:Active → Disabled, карточки пересчитываются без правила (PropertyObject: объект → активное проектное правило?.Priority ?? 0;Project: карточка проектаDeveloperPriority = 0, объекты недвижимости без правил объектов →0). - Что меняется. Правило меняет статус; обновляются затронутые карточки (счётчик →
CardsRecomputed). Правка не должна затирать чужую (конкурентный доступ): если между чтением и записью правило было параллельно снято (soft-delete другим менеджером) или его версия изменилась (одновременныйenable/update-priority) — оптимистическая блокировка отклоняет проигравшего (CONCURRENCY_CONFLICT, 409); он перечитывает актуальное состояние и повторяет при необходимости. Победитель фиксирует переход; двойной параллельныйenableуже-Activeправила безопасен (второй — идемпотентный no-op,CardsRecomputed = 0). - События. Только доменное
RankingRuleChangedEvent; интеграционных нет. - Согласованность. Правило + карточки — в одном согласованном изменении (INV-4).
- Результат.
RankingRuleResult(200); отказ → ProblemDetails.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Проекции / поиск | обновление DeveloperPriority карточек (индекс ix_*_feed) |
Интеграционные события не публикуются; внешних вызовов и уведомлений нет.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Пустой RuleId | Ошибка валидации запроса | no retry |
UNAUTHORIZED | 401 | Нет/просрочен JWT | Требуется авторизация | no retry |
FORBIDDEN | 403 | Нет permission в scope | Доступ запрещён | no retry |
CATALOG_RANKING_RULE_NOT_FOUND | 404 | Правило не существует / чужой tenant / soft-deleted | Правило не найдено | no retry |
CONCURRENCY_CONFLICT | 409 | Гонка версий | Повторите операцию | retry |
Acceptance Criteria
- Happy path (enable, каскад Project). Дано
Disabled-правило проектаPriority = 100, объекты недвижимости проекта без собственныхActive-правил. Когда менеджер сcatalog.ranking.manageвызывает enable. Тогда правило →Active,Version++, карточка проекта и объекты недвижимости без собственного правила получаютDeveloperPriority = 100,CardsRecomputed— по числу затронутых карточек, ответ200. - Каскад не трогает объекты с собственным правилом. Дано
Disabled-правило проекта и объект недвижимости проекта с собственнымActive-правилом. Когда правило проекта включается. ТогдаDeveloperPriorityэтого объекта не меняется (правило объекта перекрывает проектное). - No-op (уже
Active). Дано правило ужеActive. Когда приходит enable. Тогда идемпотентный успех без мутаций,CardsRecomputed = 0, ответ200. - Disable-дельта. Дано
Active-правило. Когда менеджер вызывает.../disable. Тогда правило →Disabled, карточки пересчитываются по каскаду INV-5 без этого правила (объект — приоритет активного проектного правила или0; проект —0и его объекты без собственных правил —0),CardsRecomputed— по числу затронутых карточек, ответ200. - Цель отсутствует или
Delisted. ДаноDisabled-правило, чья цель удалена из каталога илиDelisted. Когда вызывается enable. Тогда переход вActiveвыполняется, но пересчитывать нечего —CardsRecomputed = 0; правило «доживёт» до восстановления цели. - Чужой tenant. Дано правило другого застройщика. Когда менеджер обращается по его
ruleId. ТогдаCATALOG_RANKING_RULE_NOT_FOUND(404) — существование чужого правила не раскрывается. - Конкурентная гонка. Дано правило, параллельно снятое (soft-delete) или изменённое другим
менеджером между чтением и записью. Когда завершается проигравший запрос. Тогда
CONCURRENCY_CONFLICT(409); актор перечитывает состояние и повторяет при необходимости. - Двойной параллельный enable. Дано два одновременных запроса enable уже-
Activeправила. Когда оба доходят. Тогда оба завершаются успешно: победитель фиксирует переход, второй получает идемпотентный no-op (CardsRecomputed = 0) — без конфликта.
Совместимость и наблюдаемость
- Новые optional поля — additive; удаление/переименование — breaking.
- Logs:
ranking-rule-status-changed(ruleId, tenantId, from → to, cardsRecomputed); metrics:catalog_developers_operations_total{operation="enable-ranking-rule"|"disable-ranking-rule"}; traces + correlation — сквозной trace черезtraceparent. Dashboard catalog-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): Catalog Developers API — реестр методов и общие правила области, DELETE /api/developers/v1/ranking-rules/{ruleId} — Удаление правила ранжирования, GET /api/developers/v1/ranking-rules — Список правил ранжирования застройщика, POST /api/developers/v1/ranking-rules — Создание правила ранжирования, PUT /api/developers/v1/ranking-rules/{ruleId}/priority — Изменение приоритета правила
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ranking-rule.md (stateDiagram, INV-4/5).
- Соседние операции: create-ranking-rule.md, update-ranking-rule-priority.md, delete-ranking-rule.md, list-ranking-rules.md.