POST /api/v1/agency/commission-accruals/{accrualId}/adjust-distribution — Per-deal корректировка долей внутри пула
Точечная правка распределения одного начисления: руководитель агентства перераспределяет пул
агентства (CommissionAmount − PlatformFeeAmount) между строками Agency и Agent без смены политики
(INV-11 domain/commission-accrual.md). Строка Platform (удержание
ПИН) неприкосновенна — сумма и валюта начисления не меняются, меняется только деление пула.
Назначение
Руководитель/администратор агентства корректирует долю конкретного агента по конкретной сделке
(индивидуальная договорённость, бонус за сложную сделку, исправление ошибки политики) — не трогая политику
агентства и другие начисления. Каждая корректировка оставляет обязательный аудит-след с причиной
(CommissionAccrualAdjustmentEntity, Kind = Distribution). Feature group — agency-commission.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agency |
| Feature group | agency-commission |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agency/adjust-accrual-distribution.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (руководитель/администратор агентства) |
| Auth policy / Permissions | rewards.commission-policy.manage (распоряжение пулом агентства — то же право, что и управление политикой) |
| Scope (RBAC) | организация: компания Type ∈ {Agency, Broker, Pin} |
| Record-level | не применяется (доступ на уровне tenant: начисление своего агентства) |
| Tenant isolation | In-tenant: TenantId из claims; чужое начисление невидимо → 404 |
| Audit | update: UpdatedByUserId = ActorUserId, Version + 1; обязательная append-only строка корректировки CommissionAccrualAdjustmentEntity (Kind = Distribution, OldAmount/NewAmount — доля агента до/после, Reason, ActorUserId) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/agency/commission-accruals/{accrualId}/adjust-distribution |
| Success status | 200 |
| Idempotency header | не применяется (повтор с теми же суммами — no-op по значениям, но создаёт вторую аудит-строку; защита от гонок — ExpectedVersion) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ActorUserId | identity claims (sub) | Guid | Да | Required | actor-stamping аудита |
AccrualId | route {accrualId} | Guid | Да | Required, непустой | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version начисления (optimistic concurrency) | — |
AgentAmount | body | MoneyModel | Да | Value >= 0, scale 2; CurrencyId = валюте начисления (INV-6) | Sensitive — не логируется |
AgencyAmount | body | MoneyModel | Да | Value >= 0, scale 2; CurrencyId = валюте начисления (INV-6); AgentAmount.Value + AgencyAmount.Value = пул агентства | Sensitive — не логируется |
Reason | body | string | Да | 1..1000, не пусто после trim — обязательная причина корректировки (INV-11) | — |
Модель ответа CommissionAccrualModel
Полная проекция начисления с обновлённым распределением — дом модели: README.md
(«Общие контрактные модели»); Distributions[] содержит три строки (Platform — без изменений,
Agency/Agent — с новыми суммами), Version — новый.
Предусловия и постусловия
- Предусловия. Начисление существует в tenant-е агентства и находится в статусе
ConfirmedилиMoneyReceived(распределение уже построено — три строки INV-4/9);Agent-строка не включена в неотменённое платёжное поручение (PayoutOrderItemId is null). Актор — руководитель/администратор сrewards.commission-policy.manageи актуальнымVersion. - Постусловия (успех). Суммы строк
AgencyиAgentзаменены на переданные; строкаPlatform,CommissionAmount,PlatformFeeAmountи статус не изменились; Σ трёх строк по-прежнему равнаCommissionAmount(INV-5); создана аудит-строкаKind = Distributionс причиной;Version + 1. - Постусловия (отказ). Распределение, версия и аудит-история начисления не изменились.
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель/администратор агентства; scope — организация
(
TenantIdиз claims); требуется правоrewards.commission-policy.manage. - Проверка входа.
AccrualIdнепустой,ExpectedVersion >= 1;AgentAmount/AgencyAmount—Value >= 0, scale 2;Reasonнепустой после trim,<= 1000— иначеREWARDS_ACCRUAL_ADJUSTMENT_REASON_REQUIRED. Начисление существует в tenant-е агентства — иначеREWARDS_ACCRUAL_NOT_FOUND.CurrencyIdобеих сумм совпадает с валютой начисления (INV-6) — иначеREWARDS_CURRENCY_MISMATCH. - Guard корректировки (INV-11). Статус начисления ∈ {
Confirmed,MoneyReceived} иAgent-строка свободна (PayoutOrderItemId is null) — иначеREWARDS_ACCRUAL_ADJUSTMENT_FORBIDDEN.Versionсовпадает сExpectedVersion— иначеREWARDS_CONCURRENCY_CONFLICT. Начисление читается под блокировкой на запись (гонка со сборкой поручения — create-payout-order.md, INV-1 поручения). - Сверка пула.
AgentAmount.Value + AgencyAmount.Value= пул агентства (CommissionAmount.Value − PlatformFeeAmount.Value, INV-10) — иначеREWARDS_ACCRUAL_DISTRIBUTION_SUM_INVALID. СтрокаPlatformв перераспределении не участвует и не меняется ни при каких входных данных (INV-11). - Применение. В одном согласованном изменении: строка
AgentполучаетAgentAmount, строкаAgency—AgencyAmount(еёAppliedRetentionKindсохраняется как исторический след применённой политики); создаётся аудит-строкаCommissionAccrualAdjustmentEntity(Kind = Distribution,OldAmount= прежняя доля агента,NewAmount = AgentAmount,Reason,ActorUserId);Version + 1. Статус начисления не меняется — событие статуса не публикуется. Если начисление вMoneyReceived(счета получателей вwalletуже кредитованы), в том же изменении публикуется компенсирующее событиеpin.rewards.commission-distribution-adjusted.v1с дельтой долейAgency/Agent—walletпереносит разницу между счетами получателей (см. «Побочные эффекты»). ВConfirmedкошелёк ещё не кредитован — событие не публикуется. - Результат. Полная проекция начисления с обновлённым распределением и новой версией →
200.
Диаграмма
Побочные эффекты
Событие статуса не публикуется (статус не меняется). Компенсация кошелька зависит от статуса начисления на момент корректировки:
Confirmed(кошелёк ещё не кредитован — деньги не получены). Никаких интеграционных событий: распределение существует только как строки начисления, балансыwalletего не отражают. Рекомендованный порядок — править доли именно здесь, до регистрации акта.MoneyReceived(счета получателейwalletуже кредитованы по прежнему распределению на момент регистрации акта, domain/platform-receipt.md). В том же изменении публикуется компенсирующее событиеpin.rewards.commission-distribution-adjusted.v1(../../events/commission-distribution-adjusted.md, key =AccrualId) с долямиAgency/Agentдо и после правки.walletавто-проводкойAdjustmentпереносит дельту между счетамиAgentCommissionиAgencyCommission(../../../wallet/consumers/rewards-commission-distribution-adjusted.md, ADR-0065) — балансы выравниваются под новое деление пула без ручного вмешательства оператора ПИН. СтрокаPlatformи Σ балансов получателей не меняются. Частота таких корректировок учитывается метрикойrewards_distribution_adjust_after_receipt_total.
Ошибки и failure modes
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — дом README.md «Общие
ошибки». Дельта операции:
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
REWARDS_ACCRUAL_NOT_FOUND | 404 | Начисление не существует или принадлежит чужому tenant | «Начисление не найдено» | no retry |
REWARDS_ACCRUAL_ADJUSTMENT_REASON_REQUIRED | 400 | Reason пустой/отсутствует (INV-11) | «Укажите причину корректировки» | no retry |
REWARDS_ACCRUAL_ADJUSTMENT_FORBIDDEN | 409 | Статус вне {Confirmed, MoneyReceived} или Agent-строка уже в неотменённом поручении (INV-11) | «Корректировка распределения недоступна в текущем состоянии начисления» | no retry |
REWARDS_ACCRUAL_DISTRIBUTION_SUM_INVALID | 409 | AgentAmount + AgencyAmount ≠ пулу агентства (INV-5/10) | «Сумма долей не равна пулу агентства» | no retry (пересчитать по актуальному пулу) |
REWARDS_CURRENCY_MISMATCH | 400 | CurrencyId сумм не совпадает с валютой начисления (INV-6) | «Валюта долей не совпадает с валютой начисления» | no retry |
REWARDS_CONCURRENCY_CONFLICT | 409 | ExpectedVersion не совпал (параллельная сборка поручения / другая корректировка) | «Начисление изменено — обновите данные» | retry после refresh |
Acceptance criteria
- AC-1 (перераспределение пула). Given начисление
Confirmedс распределениемPlatform = 10 000,Agency = 27 000,Agent = 63 000(пул 90 000); When руководитель передаётAgentAmount = 70 000,AgencyAmount = 20 000,Reason; Then200: строкиAgency/Agentзаменены,PlatformиCommissionAmountне изменились, Σ трёх строк =CommissionAmount(INV-5); создана аудит-строкаDistribution(OldAmount = 63 000,NewAmount = 70 000);Version + 1. - AC-2 (Platform неприкосновенна). Given любые входные суммы; When корректировка; Then
строка
Platformне меняется никогда — перераспределяется только пул; вход, гдеAgentAmount + AgencyAmount≠ пул, отклоняетсяREWARDS_ACCRUAL_DISTRIBUTION_SUM_INVALID(409). - AC-3 (граничные доли). Given
AgentAmount = 0,AgencyAmount= пул (или наоборот); When корректировка; Then200— нулевая доля допустима (строка остаётся сAmount = 0,>= 0по дому). - AC-4 (без причины). Given пустой
Reason; When корректировка; ThenREWARDS_ACCRUAL_ADJUSTMENT_REASON_REQUIRED(400), распределение не изменено. - AC-5 (строка в поручении). Given
Agent-строка начисления уже включена в неотменённое платёжное поручение; When корректировка; ThenREWARDS_ACCRUAL_ADJUSTMENT_FORBIDDEN(409) — сначала исключить строку из черновика поручения (update-payout-order-items.md) либо дождаться терминации поручения. - AC-6 (недопустимый статус). Given начисление в
PendingConfirmation(строк ещё нет) /Paid/Cancelled; When корректировка; ThenREWARDS_ACCRUAL_ADJUSTMENT_FORBIDDEN(409). - AC-7 (гонка со сборкой поручения). Given параллельно создаётся поручение, захватывающее
Agent-строку; When обе операции; Then обе мутируют начисление под блокировкой — проходит одна; проигравшая корректировка получаетREWARDS_CONCURRENCY_CONFLICTилиREWARDS_ACCRUAL_ADJUSTMENT_FORBIDDEN(строка уже занята) и перечитывает состояние. - AC-8 (корректировка после получения денег). Given начисление
MoneyReceived, строка свободна, счета получателей вwalletкредитованы по прежнему распределению; When корректировка (AgentAmountуменьшен на δ,AgencyAmountувеличен на δ); Then200, инкрементrewards_distribution_adjust_after_receipt_total, публикуетсяpin.rewards.commission-distribution-adjusted.v1(OldAgentAmount/NewAgentAmount,OldAgencyAmount/NewAgencyAmount);walletавто-проводкойAdjustmentпереносит δ со счёта агента на счёт агентства — балансы выравниваются без ручного вмешательства (см. «Побочные эффекты»). - AC-9 (корректировка до получения денег — без события). Given начисление
Confirmed; When корректировка; Then200, компенсирующее событие не публикуется (кошелёк ещё не кредитован).
Совместимость и наблюдаемость
- Контракт additive-only;
CommissionAccrualModel— дом README.md. - Logs:
AccrualId,TenantId,Status,result(суммы не логируются — Sensitive); metrics:rewards_accrual_adjust_distribution_total{result},rewards_distribution_adjust_after_receipt_total; traces: OTel, correlation черезtraceparent; dashboardrewards-overview. - НФТ (ADR-0056): мутация одного агрегата; целевая латентность p95 < 300 мс; частота — единичные корректировки (back-office руководителя агентства).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: CommissionAccrualEntity
- API (5): POST /api/v1/agency/commission-adjustment-requests/{requestId}/resolve — Решение по заявке агента на корректировку доли, POST /api/v1/agency/payout-orders — Создание платёжного поручения, POST /api/v1/developer/commission-accruals/{accrualId}/confirm — Подтверждение начисления застройщиком, POST /api/v1/my/commission-accruals/{accrualId}/adjustment-requests — Заявка агента на корректировку своей доли, Rewards API — реестр операций и общие контрактные модели
- Use Cases (3): UC-RWD-011. Корректировки начисления комиссии, UC-RWD-013. Агент предлагает корректировку своей доли, руководитель одобряет, US-реестр: Владелец агентства (US-OWN-AG)
- События (1): Event Contract: pin.rewards.commission-distribution-adjusted (CommissionDistributionAdjustedKafkaEvent)
- Консюмеры (1): Consumer: Rewards Commission Distribution Adjusted
- registries (1): Реестр API-методов PIN
- index.md (1): Rewards Service
Связанные документы
- Сущность и guard-ы: domain/commission-accrual.md (INV-5/6/9/10/11,
CommissionAccrualAdjustmentEntity,Kind = Distribution). - Пул агентства и строка
Platform: domain/platform-commission-terms.md, domain/agency-commission-policy.md. - Смежные операции: confirm-commission-accrual.md (построение трёх строк + корректировка gross застройщиком), create-payout-order.md, update-payout-order-items.md.
- Компенсация кошелька: ../../events/commission-distribution-adjusted.md
(событие дельты долей при
MoneyReceived), ../../../wallet/consumers/rewards-commission-distribution-adjusted.md (авто-проводкаAdjustment); контур — ../../sagas/commission-settlement-saga.md. - Общие модели/ошибки: README.md; ADR-0066 (три строки распределения), ADR-0067 (корректировки, гейт денег), ADR-0065 (кошелёк), ADR-0052 (tenancy).