POST /api/v1/platform/commission-terms — Создание условий платформенной комиссии (черновик)
Создание записи условий удержания ПИН в статусе Draft: измерения специфичности — ось агентства
(AgencyCompanyId?) и объектная ось-цепочка (DeveloperCompanyId? ⊃ ProjectId? ⊃ LayoutGroupId?),
размер удержания в процентных пунктах ставки застройщика (0.25–1.50 п.п.) и период действия —
domain/platform-commission-terms.md.
Назначение
Менеджер ПИН заводит условия монетизации платформы: сколько пунктов ставки застройщика ПИН удерживает
в свою пользу — по сделкам конкретного агентства или всех агентств, по конкретному застройщику / проекту /
группе планировок или всем объектам (глобальный дефолт). Период даёт выразить временные акции ПИН
(понижение удержания на срок) обычной записью с ограниченным EffectiveTo. Условия создаются черновиком и
не влияют на расчёт удержания до активации. Owning-контекст — rewards.
Feature group — platform-terms.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | platform-terms |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/create-platform-terms.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Менеджер ПИН) |
| Auth policy / Permissions | rewards.platform-terms.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — платформенная сущность (ADR-0052); tenant-фильтрация не применяется |
| Audit | create: CreatedByUserId = ActorUserId, Version = 1, доменное событие PlatformTermsCreatedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/platform/commission-terms |
| Success status | 201 (+ Location: /api/v1/platform/commission-terms/{id}) |
| Idempotency header | Idempotency-Key (Guid) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
IdempotencyKey | header Idempotency-Key | Guid | Да | Required | — |
AgencyCompanyId | body | Guid? | Нет | null — по всем агентствам; если задан — непустой Guid, логическая ссылка на компанию Type ∈ {Agency, Broker} в identity (синхронно не проверяется — значение выбирается из реестра организаций) | — |
DeveloperCompanyId | body | Guid? | Нет | null — по всем застройщикам; если задан — непустой Guid, логическая ссылка на компанию Type = Developer в identity (синхронно не проверяется) | — |
ProjectId | body | Guid? | Нет | null — по всем проектам; если задан — требует заданного DeveloperCompanyId (INV-PT-5), логическая ссылка на проект каталога (синхронно не проверяется) | — |
LayoutGroupId | body | Guid? | Нет | null — по всем группам планировок; если задан — требует заданного ProjectId (INV-PT-5), логическая ссылка на группу планировок каталога (синхронно не проверяется) | — |
RetentionPercentagePoints | body | decimal | Да | 0.25 <= x <= 1.50, scale 2 (INV-PT-1) | — |
EffectiveFrom | body | DateTimeOffset | Да | Required | — |
EffectiveTo | body | DateTimeOffset? | Нет | > EffectiveFrom, если задан; null — бессрочно | — |
Comment | body | string? | Нет | <= 2000 | — |
Специфичность условий (PlatformTermsScope: AgencyLayoutGroup / LayoutGroup / AgencyProject /
Project / AgencyDeveloper / Developer / Agency / Global) не передаётся — выводится из
заполненности измерений (domain/platform-commission-terms.md).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | PlatformCommissionTermsEntity.Id | — | Id созданных условий. |
Scope | string (enum PlatformTermsScope) | Да | computed из измерений | — | Выведенный уровень специфичности. |
Status | string (enum PlatformCommissionTermsStatus) | Да | Status | — | Всегда Draft. |
Version | long | Да | Version | — | 1 — для последующего ExpectedVersion. |
Предусловия и постусловия
- Предусловия. Актор — Менеджер ПИН с правом
rewards.platform-terms.manage (платформенный scope).
- Постусловия (успех). Появилась ровно одна новая запись условий в статусе
Draft с Version = 1 и
зафиксированным автором; в резолюции удержания (domain/platform-commission-terms.md,
«Резолюция условий») черновик не участвует; действующие условия и созданные начисления не затронуты.
- Постусловия (отказ). Ни одна запись не создана; повтор с тем же
Idempotency-Key после успеха
возвращает исходный результат и второй записи не создаёт.
Алгоритм
- Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право
rewards.platform-terms.manage. Повторный вызов с тем же IdempotencyKey возвращает ранее сохранённый
результат.
- Проверка входа.
RetentionPercentagePoints в диапазоне 0.25–1.50 (INV-PT-1) — иначе
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE; ось объекта заполнена сверху вниз (ProjectId требует
DeveloperCompanyId, LayoutGroupId требует ProjectId, INV-PT-5) — иначе
REWARDS_PLATFORM_TERMS_SCOPE_INVALID; EffectiveTo > EffectiveFrom, если задан — иначе
REWARDS_PLATFORM_TERMS_PERIOD_INVALID; длина Comment — иначе ValidationError. Существование и тип
компаний / проекта / группы синхронно не проверяются (ответственность актора). Непересечение периодов
одной специфичности (INV-PT-2) на черновике не проверяется — черновики-дубли и пересечения
допустимы, коллизия всплывёт при активации (REWARDS_PLATFORM_TERMS_OVERLAP), поэтому будущие акции
готовятся заранее.
- Создание черновика. Формируется запись в статусе
Draft: измерения (AgencyCompanyId,
DeveloperCompanyId, ProjectId, LayoutGroupId), RetentionPercentagePoints,
EffectiveFrom/EffectiveTo, Comment — из тела; фиксируются актор, CreatedAt, Version = 1.
Запись и запись идемпотентности сохраняются в одном согласованном изменении.
- Результат.
Id, выведенный Scope, Status = Draft, Version = 1 → 201 + Location.
Интеграционные события не публикуются (условия применяются только внутри rewards).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE | 400 | RetentionPercentagePoints вне [0.25; 1.50] (INV-PT-1) | «Удержание вне допустимого диапазона 0.25–1.50 п.п.» | no retry |
REWARDS_PLATFORM_TERMS_SCOPE_INVALID | 400 | Ось объекта заполнена не сверху вниз (INV-PT-5) | «Проект требует застройщика, группа планировок — проекта» | no retry |
REWARDS_PLATFORM_TERMS_PERIOD_INVALID | 400 | EffectiveTo <= EffectiveFrom | «Некорректный период действия» | no retry |
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — README.md.
Acceptance-критерии
- Happy path (условия по проекту для агентства). Дано: Менеджер ПИН с
rewards.platform-terms.manage;
AgencyCompanyId, DeveloperCompanyId, ProjectId заданы, LayoutGroupId = null,
RetentionPercentagePoints = 1.00, корректный период. Когда: POST /api/v1/platform/commission-terms. Тогда: 201 + Location, Scope = AgencyProject,
Status = Draft, Version = 1; запись видна в list-platform-terms.md;
резолюция удержания черновик не применяет.
- Happy path (глобальный дефолт, бессрочно). Дано: все измерения
null,
RetentionPercentagePoints = 0.50, EffectiveTo = null. Когда: POST. Тогда: 201,
Scope = Global — бессрочный страховочный уровень.
- Удержание вне диапазона. Дано:
RetentionPercentagePoints = 0.20 (или 1.60). Когда: POST.
Тогда: 400 REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE, запись не создана.
- Ось объекта не сверху вниз. Дано:
ProjectId задан, DeveloperCompanyId = null (или
LayoutGroupId без ProjectId). Когда: POST. Тогда: 400 REWARDS_PLATFORM_TERMS_SCOPE_INVALID.
- Некорректный период. Дано:
EffectiveTo <= EffectiveFrom. Когда: POST. Тогда: 400
REWARDS_PLATFORM_TERMS_PERIOD_INVALID.
- Пересечение на черновике допустимо. Дано: активные условия той же комбинации измерений с
пересекающимся периодом. Когда:
POST. Тогда: 201 — непересечение (INV-PT-2) проверяется только при
активации.
- Идемпотентный повтор. Дано: условия уже созданы с ключом
K. Когда: повторный POST с тем же
Idempotency-Key = K. Тогда: тот же Id/Scope/Status/Version, второй записи нет.
- Нет права. Дано: актор без
rewards.platform-terms.manage. Когда: POST. Тогда: 403 FORBIDDEN.
Совместимость и наблюдаемость
- Новые уровни специфичности — additive-расширение enum
PlatformTermsScope (клиент игнорирует unknown
при чтении); измерения расширяются только nullable-полями.
- Logs:
ActorUserId, TermsId, Scope, AgencyCompanyId, DeveloperCompanyId, ProjectId,
LayoutGroupId; metrics: rewards_platform_terms_create_total{result}; traces: OTel; dashboard
rewards-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы