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

POST /api/v1/reward-rules — Создание правила вознаграждения (черновик)

Создание нового правила вознаграждения застройщика в статусе Draft: база расчёта (базовая/конечная цена), процент, приоритет, период действия (EffectiveFrom/EffectiveTo — временные промо-ставки), условия-предикаты (тип объекта / ProjectIds (ЖК) / LayoutGroupIds / LayoutIds / комнаты / диапазон площади).

Назначение

Менеджер застройщика настраивает правило комиссии агентов. Правило создаётся черновиком и не влияет на расчёты до активации. Owning-контекст — rewards, дом полей и инварианты INV-1..9 — domain/reward-rule.md. Feature group — rules.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (менеджер компании-застройщика)
Auth policy / Permissionsrewards.rules.manage
Scope (RBAC)организация: компания Type = Developer
Record-levelне применяется
Tenant isolationTenantId = CompanyId из claims (не из body) — правило создаётся только в своей компании
Auditcreate: CreatedByUserId = ActorUserId, Version = 1, доменное событие RewardRuleCreatedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/reward-rules
Success status201 (+ Location: /api/v1/reward-rules/{id})
Idempotency headerIdempotency-Key (Guid)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ActorUserIdidentity claims (sub)GuidДаRequired
IdempotencyKeyheader Idempotency-KeyGuidДаRequired
NamebodystringДа1..256
Descriptionbodystring?Нет<= 2000
CalculationBasebodyCommissionCalculationBaseДаenum BasePrice/FinalPrice
CommissionPercentbodydecimalДа> 0, <= 100, scale 2 (INV-1)
PrioritybodyintДа0..100000; уникальность среди активных проверяется при активации (INV-3), в Draft дубли допустимы
EffectiveFrombodyDateTimeOffset?Нет
EffectiveTobodyDateTimeOffset?Нет> EffectiveFrom, если обе заданы (INV-2)
ConditionsbodyRewardRuleConditionsModelДа (все поля могут быть пустыми = правило-default)по таблице модели (README.md): уникальность и лимиты списков, RoomsRange.Min <= RoomsRange.Max, AreaRangeM2.Min <= AreaRangeM2.Max (INV-2); существование ProjectIds/LayoutGroupIds/LayoutIds в реплике справочника не проверяется здесь — отложено до активации (INV-4), чтобы черновик можно было готовить до синка chessboard

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаRewardRuleEntity.IdId созданного правила.
Statusstring (enum RewardRuleStatus)ДаStatusВсегда Draft.
VersionlongДаVersion1 — для последующего ExpectedVersion.

Предусловия и постусловия

  • Предусловия. Актор авторизован; его компания — застройщик (Type = Developer); есть право rewards.rules.manage. Синхронизация реплики планировок не требуется (черновик готовится заранее).
  • Постусловия (успех). В tenant-е застройщика появилось ровно одно новое правило в статусе Draft с Version = 1 и зафиксированным автором; правило не участвует в подборе комиссии и не видно catalog (интеграционное событие не публикуется до активации); приоритет-дубли в Draft допустимы.
  • Постусловия (отказ). Ни одно правило не создано; повтор с тем же Idempotency-Key после успеха возвращает исходный результат и второго правила не создаёт.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер компании-застройщика (тип компании Developer, иначе FORBIDDEN); scope — организация застройщика (TenantId); требуется право rewards.rules.manage. Повторный вызов с тем же IdempotencyKey возвращает ранее сохранённый результат.
  2. Проверка входа. Проценты (INV-1), приоритет, согласованность диапазонов Rooms/Area/ Effective (INV-2), лимиты и уникальность элементов списков условий — по таблице входных данных; нарушение → REWARDS_RULE_PERCENT_OUT_OF_RANGE / REWARDS_RULE_RANGE_INVALID / ValidationError. Существование ProjectIds/LayoutGroupIds/LayoutIds здесь не проверяется — отложено до активации (INV-4); непересечение периодов правил одной специфичности (INV-9) — тоже (черновик в подборе не участвует), проверяется при активации.
  3. Создание черновика. Формируется правило в статусе Draft: TenantId из claims; Name, Description, CalculationBase, CommissionPercent, Priority, EffectiveFrom/To, Conditions — из тела; фиксируются актор, CreatedAt, Version = 1.
  4. Согласованность. Правило и запись идемпотентности сохраняются в одном согласованном изменении. Интеграционного события нет: RewardRuleChangedKafkaEvent публикуется только при активации, деактивации, архивации или смене EffectiveTo (черновик не виден catalog).
  5. Результат. Id, Status = Draft, Version = 1 (для последующего ExpectedVersion) → 201
    • Location.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
REWARDS_RULE_PERCENT_OUT_OF_RANGE400CommissionPercent вне (0;100] (INV-1)«Процент вне допустимого диапазона»no retry
REWARDS_RULE_RANGE_INVALID400Несогласованные диапазоны комнат/площади/периода (INV-2)«Некорректный диапазон условий»no retry

Общие коды — README.md.

Acceptance-критерии

  • Happy path. Дано: менеджер застройщика с rewards.rules.manage, валидное тело (CommissionPercent(0;100], согласованные диапазоны). Когда: POST /api/v1/reward-rules. Тогда: 201 + Location, Status = Draft, Version = 1; правило видно в list-reward-rules.md со Status = Draft; catalog о черновике не уведомляется.
  • Процент вне диапазона. Дано: CommissionPercent = 0 или > 100. Когда: POST. Тогда: 400 REWARDS_RULE_PERCENT_OUT_OF_RANGE, правило не создано.
  • Несогласованный диапазон. Дано: RoomsRange.Min > RoomsRange.Max (или EffectiveTo <= EffectiveFrom). Когда: POST. Тогда: 400 REWARDS_RULE_RANGE_INVALID.
  • Промо-период на черновике. Дано: Conditions.ProjectIds = [ЖК-X], CommissionPercent = 12, EffectiveFrom/EffectiveTo на границы недели акции. Когда: POST. Тогда: 201, Status = Draft (более специфичная промо-ставка на ЖК готовится черновиком; конфликт периодов с другими правилами той же специфичности всплывёт лишь при активации — INV-9).
  • Ссылки каталога не проверяются на черновике. Дано: Conditions.ProjectIds/LayoutGroupIds/ LayoutIds содержат ещё не синхронизированные из chessboard id. Когда: POST. Тогда: 201 (существование ссылок откладывается до активации — INV-4).
  • Идемпотентный повтор. Дано: правило уже создано с ключом K. Когда: повторный POST с тем же Idempotency-Key = K. Тогда: тот же Id/Status/Version, второго правила нет.
  • Не застройщик / нет права. Дано: компания актора не Developer либо нет rewards.rules.manage. Когда: POST. Тогда: 403 FORBIDDEN.

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

  • Новые optional измерения Conditions — additive (та же семантика «null = any», как в jsonb-доме).
  • Logs: TenantId, ActorUserId, RuleId, Priority (процент и условия не логируются — конкурентная информация застройщика); metrics: rewards_rule_create_total{result}; traces: OTel; dashboard rewards-overview; runbook — не применяется.

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

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

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