PUT /api/v1/platform/commission-terms/{termsId} — Изменение условий платформенной комиссии
Полная правка условий удержания ПИН только в статусе Draft; для условий в статусе Active
допускается изменение только EffectiveTo — досрочное завершение действия сокращением периода
(INV-PT-3). Смена измерений и RetentionPercentagePoints действующих условий — через новый черновик +
активацию (сохраняет воспроизводимость снапшотов в начислениях) —
domain/platform-commission-terms.md.
Назначение
Менеджер ПИН дорабатывает черновик условий (измерения, пункты удержания, период, комментарий) до ввода в
действие либо досрочно завершает действующие условия (например, сворачивает акцию раньше срока).
Owning-контекст — rewards. Feature group — platform-terms.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | platform |
| Feature group | platform-terms |
| Статус | draft |
| Документ | docs/06-services/rewards/api/platform/update-platform-terms.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Менеджер ПИН) |
| Auth policy / Permissions | rewards.platform-terms.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | вне tenant-модели — платформенная сущность (ADR-0052) |
| Audit | update: UpdatedByUserId = ActorUserId, Version + 1 |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT |
| Route | /api/v1/platform/commission-terms/{termsId} |
| Success status | 200 |
| Idempotency header | не применяется (естественная идемпотентность PUT + ExpectedVersion) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
TermsId | route {termsId} | Guid | Да | Required, непустой | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version | optimistic concurrency |
AgencyCompanyId | body | Guid? | Нет | null — по всем агентствам; логическая ссылка на компанию Type ∈ {Agency, Broker} (синхронно не проверяется) | — |
DeveloperCompanyId | body | Guid? | Нет | null — по всем застройщикам; логическая ссылка на компанию Type = Developer (синхронно не проверяется) | — |
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, если задан; для Active — единственное изменяемое поле: только сокращение периода (новое значение >= UtcNow и раньше текущего EffectiveTo либо текущий EffectiveTo = null) — сокращение пересечений не создаёт (INV-PT-3) | — |
Comment | body | string? | Нет | <= 2000 | — |
PUT — полная замена: клиент присылает все поля (форма заполнена текущими значениями из
list-platform-terms.md).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Terms | PlatformCommissionTermsModel | Да | обновлённый агрегат | — | Полная проекция после правки (модель — list-platform-terms.md), в т.ч. новый Version и пересчитанный Scope. |
Предусловия и постусловия
- Предусловия. Актор — Менеджер ПИН с
rewards.platform-terms.manage; условия существуют в статусе
Draft (полная правка) или Active (только досрочное завершение); клиент передаёт актуальный
ExpectedVersion.
- Постусловия (успех,
Draft). Все поля черновика заменены значениями из тела, Scope пересчитан из
измерений; статус остаётся Draft, Version увеличен, зафиксирован актор; резолюция удержания и
созданные начисления не затронуты.
- Постусловия (успех,
Active). Изменён только EffectiveTo (период сокращён); Version увеличен;
с нового EffectiveTo условия перестают применяться к новым начислениям — резолюция уходит на менее
специфичный уровень / следующие по периоду условия / fallback 0 п.п. Уже созданные начисления не
пересчитываются — в них зафиксирован снапшот (PlatformRetentionPercentagePoints,
AppliedPlatformTermsId — domain/commission-accrual.md).
- Постусловия (отказ). Условия без изменений, версия не меняется.
Алгоритм
- Контекст и доступ. Операцию выполняет Менеджер ПИН в платформенном scope; требуется право
rewards.platform-terms.manage.
- Проверка входа и предусловий.
RetentionPercentagePoints в диапазоне 0.25–1.50 (INV-PT-1) — иначе
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE; ось объекта заполнена сверху вниз (INV-PT-5) — иначе
REWARDS_PLATFORM_TERMS_SCOPE_INVALID; согласованность периода — иначе
REWARDS_PLATFORM_TERMS_PERIOD_INVALID; длина Comment — иначе ValidationError. Условия существуют
— иначе REWARDS_PLATFORM_TERMS_NOT_FOUND. Статус ∈ {Draft, Active}; Archived не редактируется →
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE. Version совпадает с ExpectedVersion — иначе
REWARDS_CONCURRENCY_CONFLICT (правка не должна затирать чужую).
- Правка и переход.
- Черновик (
Status = Draft) — заменяются все поля из тела (полная замена PUT); Scope
пересчитывается из измерений; смена измерений (в т.ч. агентство ↔ по всем агентствам, углубление/
обобщение оси объекта) допустима; непересечение периодов (INV-PT-2) здесь не проверяется —
коллизия всплывёт при активации (activate-platform-terms.md);
- действующие условия (
Status = Active) — разрешено изменить только EffectiveTo и только
сокращением: новое значение >= UtcNow (нельзя завершить задним числом — резолюция уже созданных
начислений воспроизводима) и раньше текущего конца периода (либо текущий бессрочный). Изменение
измерений, RetentionPercentagePoints, EffectiveFrom или расширение периода →
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE (INV-PT-3); некорректное новое значение EffectiveTo →
REWARDS_PLATFORM_TERMS_PERIOD_INVALID. Сокращение периода пересечений не создаёт — повторная
проверка INV-PT-2 не нужна;
- фиксируются актор и новая версия.
- Результат. Полная проекция после правки (
PlatformCommissionTermsModel, включая новый Version)
→ 200. Интеграционные события не публикуются.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_PLATFORM_TERMS_NOT_FOUND | 404 | Условия не существуют | «Условия не найдены» | no retry |
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE | 409 | Правка полей кроме EffectiveTo у Active; расширение периода Active; правка Archived (INV-PT-3) | «Действующие условия можно только досрочно завершить — создайте новый черновик и активируйте его» | no 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; для 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, актор зафиксирован.
- Углубление оси объекта в черновике. Дано: черновик уровня
Developer (ProjectId = null). Когда:
PUT с заданным ProjectId (при заданном DeveloperCompanyId). Тогда: 200, Scope = Project;
коллизия периодов всплывёт только при активации.
- Досрочное завершение действующих. Дано: условия
Active с EffectiveTo = T (или бессрочные).
Когда: PUT, где изменён только EffectiveTo на T' ∈ [UtcNow; T), остальные поля равны текущим.
Тогда: 200, EffectiveTo = T', Version + 1; новые начисления после T' эти условия не применяют;
созданные начисления не пересчитываются (снапшот).
- Иммутабельность действующих. Дано: условия
Active. Когда: PUT с изменённым
RetentionPercentagePoints (или измерениями, или EffectiveFrom). Тогда: 409
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE, условия не изменены.
- Расширение периода действующих. Дано: условия
Active с EffectiveTo = T. Когда: PUT с
EffectiveTo > T. Тогда: 409 REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE (только сокращение).
- Завершение задним числом. Дано: условия
Active. Когда: PUT с EffectiveTo < UtcNow. Тогда:
400 REWARDS_PLATFORM_TERMS_PERIOD_INVALID.
- Ось объекта не сверху вниз. Дано: черновик,
PUT с ProjectId без DeveloperCompanyId. Когда:
PUT. Тогда: 400 REWARDS_PLATFORM_TERMS_SCOPE_INVALID.
- Удержание вне диапазона. Дано:
RetentionPercentagePoints = 2.00. Когда: PUT. Тогда: 400
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE.
- Правка архива. Дано: условия в
Archived. Когда: PUT. Тогда: 409
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE.
- Конкурентная версия. Дано:
ExpectedVersion не совпадает с текущим Version. Когда: PUT. Тогда:
409 REWARDS_CONCURRENCY_CONFLICT; клиент перечитывает запись и повторяет.
Совместимость и наблюдаемость
- Контракт PUT полный: добавление нового optional поля требует одновременного обновления формы (иначе PUT
затрёт значение).
- Logs:
ActorUserId, TermsId, ветка (draft-edit/close-period); metrics:
rewards_platform_terms_update_total{result,branch}; traces: OTel; dashboard rewards-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы