POST /api/v1/platform/referral-rate-rules — Создание ставки реферального вознаграждения (черновик)
Создание ставки реферального вознаграждения в статусе Draft: измерения специфичности — партнёрская ось
(PartnerUserId?) и объектная ось-цепочка (DeveloperCompanyId? ⊃ ProjectId? ⊃ LayoutGroupId?), процент
от конечной цены сделки и период действия —
domain/referral-rate-rule.md.
Назначение
Менеджер ПИН настраивает, какой процент от FinalPrice завершённой сделки получает партнёр, приведший
инвестора: персональные ставки, акции по застройщику, временно повышенные ставки, глобальный
страховочный уровень. Ставка создаётся черновиком и не участвует в резолюции до
активации. Owning-контекст — rewards. Feature group —
referral-rates.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | referral-rates |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/create-referral-rate-rule.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Менеджер ПИН) |
| Auth policy / Permissions | rewards.referral-rates.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — платформенная сущность (ADR-0052) |
| Audit | create: CreatedByUserId = ActorUserId, Version = 1, доменное событие ReferralRateRuleCreatedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/platform/referral-rate-rules |
| Success status | 201 (+ Location: /api/v1/platform/referral-rate-rules/{id}) |
| Idempotency header | Idempotency-Key (Guid) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
IdempotencyKey | header Idempotency-Key | Guid | Да | Required | — |
PartnerUserId | body | Guid? | Нет | null — любой партнёр; если задан — непустой Guid, логическая ссылка на пользователя-партнёра в identity (синхронно не проверяется — значение выбирается из реестра партнёров) | — |
DeveloperCompanyId | body | Guid? | Нет | null — любой застройщик; если задан — непустой Guid, логическая ссылка на компанию Type = Developer в identity (синхронно не проверяется) | — |
ProjectId | body | Guid? | Нет | null — любой проект застройщика; если задан — непустой Guid, логическая ссылка на проект каталога; требует заданного DeveloperCompanyId — иначе REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID (INV-RR-4) | — |
LayoutGroupId | body | Guid? | Нет | null — любая группа планировок проекта; если задан — непустой Guid, логическая ссылка на группу планировок каталога; требует заданного ProjectId (и, транзитивно, DeveloperCompanyId) — иначе REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID (INV-RR-4) | — |
Percent | body | decimal | Да | 0 < x <= 100, scale 2 (INV-RR-1) | — |
EffectiveFrom | body | DateTimeOffset | Да | Required | — |
EffectiveTo | body | DateTimeOffset? | Нет | > EffectiveFrom, если задан; null — бессрочно | — |
Comment | body | string? | Нет | <= 2000 | — |
Специфичность ставки (ReferralRateScope: 8 уровней от PartnerDeveloperProjectLayoutGroup до Global)
не передаётся — выводится из заполненности партнёрской (PartnerUserId) и объектной
(DeveloperCompanyId → ProjectId → LayoutGroupId) осей
(domain/referral-rate-rule.md).
Модель ответа CreateReferralRateRuleResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | ReferralRateRuleEntity.Id | — | Id созданной ставки. |
Scope | string (enum ReferralRateScope) | Да | computed из измерений | — | Выведенный уровень специфичности. |
Status | string (enum ReferralRateRuleStatus) | Да | Status | — | Всегда Draft. |
Version | long | Да | Version | — | 1 — для последующего ExpectedVersion. |
Предусловия и постусловия
- Предусловия. Актор — Менеджер ПИН с правом
rewards.referral-rates.manage (платформенный scope).
- Постусловия (успех). Появилась ровно одна новая ставка в статусе
Draft с Version = 1 и
зафиксированным автором; в резолюции ставки при создании реферальных начислений
(domain/referral-accrual.md) черновик не участвует.
- Постусловия (отказ). Ни одна ставка не создана; повтор с тем же
Idempotency-Key после успеха
возвращает исходный результат и второй ставки не создаёт.
Алгоритм
- Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право
rewards.referral-rates.manage. Повторный вызов с тем же IdempotencyKey возвращает ранее
сохранённый результат.
- Проверка входа.
Percent в (0; 100] (INV-RR-1) — иначе
REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE; вложенность объектных измерений (ProjectId требует
DeveloperCompanyId; LayoutGroupId требует ProjectId) (INV-RR-4) — иначе
REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID; EffectiveTo > EffectiveFrom, если задан — иначе
REWARDS_REFERRAL_RATE_PERIOD_INVALID; длина Comment — иначе ValidationError. Существование
партнёра/застройщика/проекта/группы планировок синхронно не проверяется (ответственность актора).
Непересечение периодов одной комбинации измерений (INV-RR-2) на черновике не проверяется —
черновики могут пересекаться, коллизия всплывёт при активации (REWARDS_REFERRAL_RATE_OVERLAP),
поэтому будущие акции готовятся заранее.
- Создание черновика. Формируется ставка в статусе
Draft: PartnerUserId, DeveloperCompanyId,
ProjectId, LayoutGroupId, Percent, EffectiveFrom/EffectiveTo, Comment — из тела;
фиксируются актор, CreatedAt, Version = 1. Ставка и запись идемпотентности сохраняются в одном
согласованном изменении.
- Результат.
Id, выведенный Scope, Status = Draft, Version = 1 → 201 + Location.
Интеграционные события не публикуются (ставка применяется только внутри rewards).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE | 400 | Percent вне (0; 100] (INV-RR-1) | «Процент вне допустимого диапазона» | no retry |
REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID | 400 | Задан потомок без родителя: ProjectId без DeveloperCompanyId либо LayoutGroupId без ProjectId (INV-RR-4) | «Проект требует застройщика, группа планировок — проекта» | no retry |
REWARDS_REFERRAL_RATE_PERIOD_INVALID | 400 | EffectiveTo <= EffectiveFrom | «Некорректный период действия» | no retry |
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — README.md.
Acceptance-критерии
- Happy path (персональная ставка по застройщику). Дано: Менеджер ПИН с
rewards.referral-rates.manage; PartnerUserId и DeveloperCompanyId заданы (ProjectId,
LayoutGroupId — null), Percent = 1.50, корректный период. Когда:
POST /api/v1/platform/referral-rate-rules. Тогда: 201 + Location, Scope = PartnerDeveloper,
Status = Draft, Version = 1; резолюция ставок черновик не применяет.
- Акция на группу планировок проекта. Дано:
DeveloperCompanyId, ProjectId, LayoutGroupId
заданы, PartnerUserId = null, Percent = 3.00, период акции. Когда: POST. Тогда: 201,
Scope = DeveloperProjectLayoutGroup — временно повышенная ставка для всех партнёров по группе.
- Персональная ставка по конкретному проекту. Дано:
PartnerUserId, DeveloperCompanyId,
ProjectId заданы, LayoutGroupId = null. Когда: POST. Тогда: 201,
Scope = PartnerDeveloperProject.
- Глобальная страховочная ставка. Дано: все измерения
null, EffectiveTo = null. Когда: POST.
Тогда: 201, Scope = Global — бессрочный страховочный уровень (заводится первым шагом внедрения).
- Процент вне диапазона. Дано:
Percent = 0 или > 100. Когда: POST. Тогда: 400
REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE, ставка не создана.
- Потомок без родителя. Дано:
ProjectId задан, DeveloperCompanyId = null (или LayoutGroupId
задан при ProjectId = null). Когда: POST. Тогда: 400
REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID, ставка не создана (INV-RR-4).
- Некорректный период. Дано:
EffectiveTo <= EffectiveFrom. Когда: POST. Тогда: 400
REWARDS_REFERRAL_RATE_PERIOD_INVALID.
- Пересечение на черновике допустимо. Дано: активная ставка той же комбинации измерений с
пересекающимся периодом. Когда:
POST. Тогда: 201 — непересечение (INV-RR-2) проверяется только при
активации.
- Идемпотентный повтор. Дано: ставка уже создана с ключом
K. Когда: повторный POST с тем же
Idempotency-Key = K. Тогда: тот же Id/Scope/Status/Version, второй ставки нет.
- Нет права. Дано: актор без
rewards.referral-rates.manage. Когда: POST. Тогда: 403
FORBIDDEN.
Совместимость и наблюдаемость
- Новые уровни специфичности — additive-расширение enum
ReferralRateScope (клиент игнорирует unknown
при чтении); измерения ставки расширяются только nullable-полями.
- Logs:
ActorUserId, RateRuleId, Scope, PartnerUserId, DeveloperCompanyId, ProjectId,
LayoutGroupId; metrics: rewards_referral_rate_create_total{result}; traces: OTel; dashboard
rewards-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы