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

PUT /api/developers/v1/ranking-rules/{ruleId}/priority — Изменение приоритета правила

Назначение

Менеджер застройщика меняет значение Priority существующего правила ранжирования (ranking-rule.md). Если правило Active, новый приоритет в той же транзакции денормализуется в DeveloperPriority затронутых карточек (INV-4); для Disabled-правила меняется только сохранённое значение (применится при Enable).

Metadata

ПолеЗначение
Сервис/контекстcatalog
API areadevelopers
Feature groupranking
Статусdraft
Документdocs/06-services/catalog/api/developers/update-ranking-rule-priority.md

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

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

HTTP-контракт

ПолеЗначение
MethodPUT
Route/api/developers/v1/ranking-rules/{ruleId}/priority
Success status200
Idempotency headerне применяется (PUT абсолютного значения идемпотентен по смыслу)
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequiredtenant isolation
RuleIdrouteGuidДа!= Guid.Empty; существование — шаг 2
PrioritybodyintДа0..1000 (INV-3)
ExpectedVersionbodylong?Нет>= 1; optimistic-check против Version правила

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

Модель — дом create-ranking-rule.md (та же структура: Id, TargetKind, TargetId, Priority, Status, CardsRecomputed, Version). Дельта: Version — инкремент; CardsRecomputed = 0 для Disabled-правила.

Алгоритм

  1. Контекст и доступ. Актор — менеджер застройщика (JWT + catalog.ranking.manage); TenantId из claims.
  2. Проверка входа. Priority 0..1000 (INV-3), RuleId != Guid.EmptyValidationError. Существование: правило (в рамках своего tenant) найдено; нет → CATALOG_RANKING_RULE_NOT_FOUND (404). Состояние: Active и Disabled допустимы (soft-deleted отфильтрован). Optimistic-check: заданный ExpectedVersion не совпал с версией правила → CONCURRENCY_CONFLICT (409). No-op: значение приоритета не изменилось → идемпотентный успех без мутаций (CardsRecomputed = 0).
  3. Что читаем. При Status == Active — данные каскада как в create-ranking-rule.md шаг 3: TargetKind=Project → объекты недвижимости проекта + их активные правила объектов; TargetKind=PropertyObject → карточка объекта недвижимости. При Disabled — ничего.
  4. Правила и переходы. Меняется Priority (guard INV-3), Version++, UpdatedByUserId = актор; фиксируется RankingRuleChangedEvent. Для Active-правила — каскад INV-5 (правило объекта недвижимости ?? проектное ?? 0): маппинг идентичен create — карточка проекта DeveloperPriority = Priority + объекты недвижимости без собственного Active-правила, либо один объект недвижимости → Priority.
  5. Что меняется. Правило обновляется; для Active — обновляются затронутые карточки (счётчик → CardsRecomputed). Правка не должна затирать чужую (конкурентный доступ).
  6. События. Только доменное RankingRuleChangedEvent (тот же коммит); интеграционных нет.
  7. Согласованность. Правило + карточки — в одном согласованном изменении (INV-4).
  8. Результат. RankingRuleResult (200); отказ → ProblemDetails.

Диаграмма

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

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

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

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Priority вне 0..1000Ошибка валидации запросаno retry
UNAUTHORIZED401Нет/просрочен JWTТребуется авторизацияno retry
FORBIDDEN403Нет permission в scopeДоступ запрещёнno retry
CATALOG_RANKING_RULE_NOT_FOUND404Правило не существует / чужой tenant / soft-deletedПравило не найденоno retry
CONCURRENCY_CONFLICT409ExpectedVersion не совпал / гонка версийДанные изменены параллельноretry после перечитывания

Acceptance Criteria

  • Happy path (Active-правило, каскад). Дано активное правило проекта Priority = 100 и три опубликованных объекта недвижимости проекта без собственных правил. Когда менеджер с catalog.ranking.manage ставит Priority = 300. Тогда правило получает Priority = 300, Version инкрементируется, DeveloperPriority карточки проекта и трёх объектов недвижимости = 300, CardsRecomputed = 4, ответ 200; сортировка «Рекомендованные» отражает новый вес.
  • Happy path (Disabled-правило). Дано Disabled-правило. Когда меняется Priority. Тогда сохранённое значение обновлено, каскада в карточки нет, CardsRecomputed = 0, витринная сортировка не меняется до Enable.
  • No-op (значение не изменилось). Дано правило Priority = 200. Когда приходит Priority = 200. Тогда мутаций нет, CardsRecomputed = 0, ответ 200 (идемпотентно по смыслу PUT).
  • Конкурентная правка (optimistic-check). Дано две параллельные правки с одинаковым ExpectedVersion. Когда обе доходят. Тогда первая применяется, вторая получает CONCURRENCY_CONFLICT (409) и должна перечитать актуальную версию правила.
  • Гонка с консюмером каскада. Дано параллельный пересчёт затронутой карточки консюмером chessboard-sync. Когда правка каскадит DeveloperPriority. Тогда конфликт версий карточки разрешается повтором (проигравший перечитывает), итоговый DeveloperPriority согласован.
  • Чужой tenant. Дано правило другого застройщика. Когда менеджер обращается по его ruleId. Тогда CATALOG_RANKING_RULE_NOT_FOUND (404) — существование чужого правила не раскрывается.
  • Валидация диапазона. Дано Priority = 1500. Когда приходит запрос. Тогда ValidationError (400), правило не изменено.

Совместимость и наблюдаемость

  • Новые optional поля — additive; удаление/переименование — breaking.
  • Logs: ranking-rule-priority-updated (ruleId, tenantId, oldPriority → newPriority, cardsRecomputed); metrics: catalog_developers_operations_total{operation="update-ranking-rule-priority"}, duration; traces + correlation — сквозной trace через traceparent. Dashboard catalog-overview.

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

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

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