PUT /api/v1/platform/referral-rate-rules/{rateRuleId} — Изменение ставки реферального вознаграждения
Полная правка ставки только в статусе Draft; для ставки в статусе Active допускается изменение
только EffectiveTo — досрочное завершение действия сокращением периода (INV-RR-3) —
domain/referral-rate-rule.md.
Назначение
Менеджер ПИН дорабатывает черновик ставки (измерения, процент, период) до ввода в действие либо
досрочно завершает действующую ставку (например, сворачивает акцию раньше срока). Owning-контекст —
rewards. Feature group — referral-rates.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | referral-rates |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/update-referral-rate-rule.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Менеджер ПИН) |
| Auth policy / Permissions | rewards.referral-rates.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — платформенная сущность (ADR-0052) |
| Audit | update: UpdatedByUserId = ActorUserId, Version + 1 |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT |
| Route | /api/v1/platform/referral-rate-rules/{rateRuleId} |
| Success status | 200 |
| Idempotency header | не применяется (естественная идемпотентность PUT + ExpectedVersion) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
RateRuleId | route {rateRuleId} | Guid | Да | Required, непустой | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version | optimistic concurrency |
PartnerUserId | body | Guid? | Нет | null — любой партнёр; логическая ссылка на пользователя-партнёра в identity (синхронно не проверяется) | — |
DeveloperCompanyId | body | Guid? | Нет | null — любой застройщик; логическая ссылка на компанию Type = Developer (синхронно не проверяется) | — |
ProjectId | body | Guid? | Нет | null — любой проект застройщика; логическая ссылка на проект каталога; требует заданного DeveloperCompanyId (INV-RR-4) — иначе REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID. Меняется только в Draft | — |
LayoutGroupId | body | Guid? | Нет | null — любая группа планировок проекта; логическая ссылка на группу планировок каталога; требует заданного ProjectId (INV-RR-4) — иначе REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID. Меняется только в Draft | — |
Percent | body | decimal | Да | 0 < x <= 100, scale 2 (INV-RR-1) | — |
EffectiveFrom | body | DateTimeOffset | Да | Required | — |
EffectiveTo | body | DateTimeOffset? | Нет | > EffectiveFrom, если задан; для Active — единственное изменяемое поле: только сокращение периода (новое значение >= UtcNow и раньше текущего EffectiveTo либо текущий EffectiveTo = null) — сокращение пересечений не создаёт (INV-RR-3) | — |
Comment | body | string? | Нет | <= 2000 | — |
PUT — полная замена: клиент присылает все поля (форма заполнена текущими значениями из
list-referral-rate-rules.md).
Модель ответа UpdateReferralRateRuleResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Rule | ReferralRateRuleModel | Да | обновлённый агрегат | — | Полная проекция после правки (модель — list-referral-rate-rules.md), в т.ч. новый Version и пересчитанный Scope. |
Предусловия и постусловия
- Предусловия. Актор — Менеджер ПИН с
rewards.referral-rates.manage; ставка существует в статусе
Draft (полная правка) или Active (только досрочное завершение); клиент передаёт актуальный
ExpectedVersion.
- Постусловия (успех,
Draft). Все поля черновика заменены значениями из тела, Scope пересчитан
из измерений; статус остаётся Draft, Version увеличен; резолюция ставок не затронута.
- Постусловия (успех,
Active). Изменён только EffectiveTo (период сокращён); Version увеличен;
с нового EffectiveTo ставка перестаёт применяться к новым начислениям — резолюция уходит на менее
специфичный уровень или следующую по периоду ставку. Уже созданные начисления не пересчитываются —
в них зафиксирован снапшот ставки (AppliedRateSnapshot).
- Постусловия (отказ). Ставка без изменений.
Алгоритм
- Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право
rewards.referral-rates.manage.
- Проверка входа и предусловий.
Percent (INV-RR-1) — иначе
REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE; для черновика — вложенность объектных измерений
(ProjectId требует DeveloperCompanyId; LayoutGroupId требует ProjectId, INV-RR-4) — иначе
REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID; согласованность периода — иначе
REWARDS_REFERRAL_RATE_PERIOD_INVALID; длина Comment — иначе ValidationError. Ставка существует
— иначе REWARDS_REFERRAL_RATE_NOT_FOUND. Статус ∈ {Draft, Active}; Archived не редактируется
→ REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE. Version совпадает с ExpectedVersion — иначе
REWARDS_CONCURRENCY_CONFLICT.
- Правка и переход.
- Черновик (
Status = Draft) — заменяются все поля из тела (полная замена PUT, включая
ProjectId/LayoutGroupId); Scope пересчитывается из измерений; непересечение периодов
(INV-RR-2) здесь не проверяется —
коллизия всплывёт при активации (activate-referral-rate-rule.md);
фиксируются актор и новая версия;
- действующая ставка (
Status = Active) — разрешено изменить только EffectiveTo и только
сокращением: новое значение >= UtcNow (нельзя завершить задним числом — резолюция уже созданных
начислений воспроизводима) и раньше текущего конца периода (либо текущий бессрочный). Расширение
периода или изменение любого другого поля → REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE (INV-RR-3);
некорректное новое значение EffectiveTo → REWARDS_REFERRAL_RATE_PERIOD_INVALID. Сокращение
периода пересечений не создаёт — повторная проверка INV-RR-2 не нужна.
- Результат. Полная проекция после правки (
ReferralRateRuleModel, включая новый Version) →
200. Интеграционные события не публикуются.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_REFERRAL_RATE_NOT_FOUND | 404 | Ставка не существует | «Ставка не найдена» | no retry |
REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE | 409 | Правка полей кроме EffectiveTo у Active; расширение периода Active; правка Archived (INV-RR-3) | «Действующую ставку можно только досрочно завершить — создайте новую» | no 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; для Active — новый EffectiveTo < UtcNow | «Некорректный период действия» | no retry |
REWARDS_CONCURRENCY_CONFLICT | 409 | ExpectedVersion устарел | «Ставка изменена другим пользователем» | retry после refresh |
Общие коды — README.md.
Acceptance-критерии
- Правка черновика. Дано: ставка в
Draft (Version = N). Когда: PUT с валидными полями и
ExpectedVersion = N. Тогда: 200, все поля заменены, Scope пересчитан, Status = Draft,
Version = N + 1.
- Углубление объектной оси черновика. Дано: черновик
Scope = PartnerDeveloper (ProjectId,
LayoutGroupId = null). Когда: PUT, где заданы ProjectId и LayoutGroupId (родители сохранены),
остальное валидно. Тогда: 200, Scope = PartnerDeveloperProjectLayoutGroup.
- Потомок без родителя в черновике. Дано: черновик. Когда:
PUT, где задан LayoutGroupId при
ProjectId = null (или ProjectId при DeveloperCompanyId = null). Тогда: 400
REWARDS_REFERRAL_RATE_SCOPE_HIERARCHY_INVALID (INV-RR-4), ставка не изменена.
- Досрочное завершение действующей. Дано: ставка
Active с EffectiveTo = T (или бессрочная).
Когда: PUT, где изменён только EffectiveTo на T' ∈ [UtcNow; T), остальные поля равны текущим.
Тогда: 200, EffectiveTo = T', Version + 1; новые начисления после T' эту ставку не применяют;
созданные начисления не пересчитываются (снапшот).
- Иммутабельность действующей. Дано: ставка
Active. Когда: PUT с изменённым Percent (или
измерениями, или EffectiveFrom). Тогда: 409 REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE, ставка не
изменена.
- Расширение периода действующей. Дано: ставка
Active с EffectiveTo = T. Когда: PUT с
EffectiveTo > T. Тогда: 409 REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE (только сокращение;
продление — новая ставка с последующим периодом).
- Завершение задним числом. Дано: ставка
Active. Когда: PUT с EffectiveTo < UtcNow. Тогда:
400 REWARDS_REFERRAL_RATE_PERIOD_INVALID.
- Правка архива. Дано: ставка
Archived. Когда: PUT. Тогда: 409
REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE.
- Конкурентная версия. Дано:
ExpectedVersion устарел. Когда: PUT. Тогда: 409
REWARDS_CONCURRENCY_CONFLICT; клиент перечитывает ставку и повторяет.
Совместимость и наблюдаемость
- Контракт PUT полный: добавление нового optional поля требует одновременного обновления формы (иначе
PUT затрёт значение).
- Logs:
ActorUserId, RateRuleId, ветка (draft-edit/close-period); metrics:
rewards_referral_rate_update_total{result,branch}; traces: OTel; dashboard rewards-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы