Перейти к основному содержимому

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 areadevelopers
Feature groupranking
Статусdraft
Документdocs/06-services/catalog/api/developers/enable-ranking-rule.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (менеджер застройщика — JWT identity)
Auth policy / Permissionspermission catalog.ranking.manage в scope организации/группы проектов/проекта
Scope (RBAC)организация и ниже
Tenant isolationTenantId = CompanyId (ADR-0052); чужое правило невидимо
Auditactor-stamping UpdatedByUserId, Version++; переход статуса — доменное событие

HTTP-контракт

ПолеЗначение
MethodPOST (disable-дельта: POST …/disable)
Route/api/developers/v1/ranking-rules/{ruleId}/enable
Success status200
Idempotency headerне применяется (переход идемпотентен: повтор — no-op успех)
Correlationсквозной trace через traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequiredtenant isolation
RuleIdrouteGuidДа!= Guid.Empty; существование — шаг 2

Модель ответа RankingRuleResult

Модель — дом create-ranking-rule.md. Дельта: StatusActive (disable-дельта: Disabled); CardsRecomputed — затронутые карточки перехода.

Алгоритм

  1. Контекст и доступ. Актор — менеджер застройщика (JWT + catalog.ranking.manage); TenantId из claims.
  2. Проверка входа. RuleId != Guid.EmptyValidationError. Существование: правило (в рамках своего tenant) найдено; нет → CATALOG_RANKING_RULE_NOT_FOUND (404). Состояние: Disabled — выполняем переход; Active — идемпотентный no-op успех (CardsRecomputed = 0). Цель правила: карточка отсутствует или Delisted → переход разрешён, но пересчитывать нечего (CardsRecomputed = 0) — правило «доживёт» до восстановления цели (delete behavior — ranking-rule.md, «Связи»).
  3. Что читаем. Каскадные данные как в create-ranking-rule.md шаг 3 (объекты недвижимости проекта + их активные правила объектов для Project; карточка объекта недвижимости + активное проектное правило для PropertyObject).
  4. Правила и переходы. Enable: Disabled → Active, Version++, UpdatedByUserId = актор; фиксируется RankingRuleChangedEvent. Каскад INV-5: enable — карточки получают Priority правила (объекты недвижимости с собственным Active-правилом не трогаются); disable-дельта: Active → Disabled, карточки пересчитываются без правила (PropertyObject: объект → активное проектное правило?.Priority ?? 0; Project: карточка проекта DeveloperPriority = 0, объекты недвижимости без правил объектов → 0).
  5. Что меняется. Правило меняет статус; обновляются затронутые карточки (счётчик → CardsRecomputed). Правка не должна затирать чужую (конкурентный доступ): если между чтением и записью правило было параллельно снято (soft-delete другим менеджером) или его версия изменилась (одновременный enable/update-priority) — оптимистическая блокировка отклоняет проигравшего (CONCURRENCY_CONFLICT, 409); он перечитывает актуальное состояние и повторяет при необходимости. Победитель фиксирует переход; двойной параллельный enable уже-Active правила безопасен (второй — идемпотентный no-op, CardsRecomputed = 0).
  6. События. Только доменное RankingRuleChangedEvent; интеграционных нет.
  7. Согласованность. Правило + карточки — в одном согласованном изменении (INV-4).
  8. Результат. RankingRuleResult (200); отказ → ProblemDetails.

Диаграмма

Побочные эффекты

ТипДетали
Проекции / поискобновление DeveloperPriority карточек (индекс ix_*_feed)

Интеграционные события не публикуются; внешних вызовов и уведомлений нет.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Пустой RuleIdОшибка валидации запросаno retry
UNAUTHORIZED401Нет/просрочен JWTТребуется авторизацияno retry
FORBIDDEN403Нет permission в scopeДоступ запрещёнno retry
CATALOG_RANKING_RULE_NOT_FOUND404Правило не существует / чужой tenant / soft-deletedПравило не найденоno retry
CONCURRENCY_CONFLICT409Гонка версийПовторите операцию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.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы