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

GET /api/v1/reward-rules — Список правил вознаграждений застройщика

Постраничный список правил вознаграждений компании-застройщика с фильтрами по статусу и базе расчёта — экран «Правила вознаграждений» админки застройщика.

Назначение

Менеджер застройщика просматривает набор правил (черновики, действующие, архив), чтобы управлять таблицей решений комиссий. Owning-контекст — rewards, дом полей — domain/reward-rule.md. Feature group — rules.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areadeveloper-admin
Feature grouprules
Статусdraft
Документdocs/06-services/rewards/api/admin/list-reward-rules.md

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

ПроверкаЗначение
Actor typeUser (менеджер компании-застройщика)
Auth policy / Permissionsrewards.rules.view
Scope (RBAC)организация: компания Type = Developer
Record-levelне применяется (все правила tenant-а)
Tenant isolationTenantId = CompanyId застройщика из claims; tenant-интерсептор SDK фильтрует выборку
Auditnone (read, несенситивные данные класса Internal)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/reward-rules
Success status200
Idempotency headerне применяется (read)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
StatusqueryRewardRuleStatus?Нетenum Draft/Active/Archived; unknown-значение → ValidationError
CalculationBasequeryCommissionCalculationBase?Нетenum BasePrice/FinalPrice
ProjectIdqueryGuid?Нетфильтр по ЖК: правила, у которых Conditions.ProjectIds содержит ProjectId (правила-акции на конкретный ЖК). Правила-default (ProjectIds пуст) под фильтр не попадают (у них нет привязки к ЖК)
EffectiveAtqueryDateTimeOffset?Нетфильтр «действует на момент»: (EffectiveFrom is null or <= EffectiveAt) and (EffectiveTo is null or > EffectiveAt); применяется только совместно со Status = Active. Если задан без Status = Active — параметр игнорируется (период проверяется лишь для действующих правил), остальные фильтры действуют
SearchTextquerystring?Нет<= 256; ILIKE %text% по Nametrusted boundary: параметризованный запрос
Page / PageSizequeryintНетpaging-конвенция — README.md

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

Страница RewardRuleModel — модель элемента целиком по дому (README.md «Общие контрактные модели»), без дельты: список отдаёт полные правила, включая Conditions (условий немного, отдельная get-операция нужна для точечного refresh).

ПолеТипОбязательностьИсточникMaskingОписание
ItemsIReadOnlyList<RewardRuleModel>ДаprojectionСтраница правил.
Page / PageSizeintДаecho запросаПараметры страницы.
TotalCountlongДаCOUNT(*) по фильтруВсего правил под фильтром.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер компании-застройщика; scope — организация застройщика (TenantId); требуется право rewards.rules.view. Выборка ограничена tenant-ом застройщика.
  2. Проверка входа. Enum Status/CalculationBase, длина SearchText, диапазоны paging — иначе ValidationError.
  3. Выборка. Постранично отбираются правила tenant-а: по умолчанию неудалённые (для Status = Archived — наоборот, архивные), опционально с фильтрами по Status, CalculationBase, принадлежности ЖК ProjectId (вхождение в Conditions.ProjectIds), периоду EffectiveAt и поиском по Name (регистронезависимо, параметризованный запрос). Сортировка — Priority DESC, CreatedAt DESC (совпадает с порядком победы при подборе правила).
  4. Проекция. Каждая строка отдаётся как RewardRuleModel целиком, включая Conditions.
  5. Результат. Страница правил (Items, Page, PageSize, TotalCount) → 200. Freshness — source of truth, без кэша.

Диаграмма

Ошибки и failure modes

Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — README.md. Специфичных нет.

Acceptance-критерии

  • Happy path. Дано: у застройщика есть правила в разных статусах, актор с rewards.rules.view. Когда: GET /api/v1/reward-rules?page=1. Тогда: 200, страница неархивных правил RewardRuleModel, отсортированная Priority DESC, CreatedAt DESC; TotalCount — число под фильтром.
  • Фильтр по статусу. Дано: ?status=Active. Когда: GET. Тогда: только действующие правила; ?status=Archived — только архивные (иначе архив в выдачу не попадает).
  • Действует на момент. Дано: ?status=Active&effectiveAt=T. Когда: GET. Тогда: только активные правила, чей период включает T (EffectiveFrom <= T < EffectiveTo, с учётом null-границ).
  • EffectiveAt без Status=Active. Дано: ?effectiveAt=T без status=Active (или с status=Draft/Archived). Когда: GET. Тогда: EffectiveAt игнорируется (период применим только к действующим правилам); выдача формируется по остальным фильтрам.
  • Фильтр по ЖК. Дано: ?projectId=<ЖК-X>. Когда: GET. Тогда: только правила, у которых Conditions.ProjectIds содержит ЖК-X (акции/ставки на этот ЖК); правила-default без привязки к ЖК в выдачу не попадают.
  • Поиск по имени. Дано: ?searchText=парков. Когда: GET. Тогда: правила, чьё Name содержит подстроку без учёта регистра; специальные символы не ломают выборку (параметризованный запрос).
  • Пустой результат. Дано: под фильтр не подходит ни одно правило. Когда: GET. Тогда: 200, Items = [], TotalCount = 0.
  • Неизвестный enum. Дано: ?status=Foo. Когда: GET. Тогда: 400 ValidationError.
  • Чужой tenant не виден. Дано: у другого застройщика есть правила. Когда: GET. Тогда: они в выдаче не появляются (изоляция по tenant).

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

  • Новые optional фильтры/поля модели — additive; enum RewardRuleStatus расширяется только additive (клиент обязан игнорировать unknown при чтении).
  • Logs: TenantId, фильтры, TotalCount (суммы в списке не логируются); metrics: rewards_api_request_duration_seconds{route}; traces: OTel; dashboard rewards-overview; runbook — не применяется.

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

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

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