UC-RWD-014. ПИН корректирует размер реферального вознаграждения партнёра
Назначение
Описывает first-class корректировку суммы уже начисленного реферального вознаграждения оператором ПИН —
вместо отмены и пересоздания начисления. Оператор выравнивает сумму Amount реферального начисления с
реальностью (договорённость по партнёрской программе, пересчёт базы, ошибка данных сделки), указывая новую
сумму и обязательную причину. Исходная автосчитанная сумма (Amount = FinalPrice × Percent / 100) фиксируется
в OriginalAmount при первой корректировке; каждая корректировка оставляет append-only аудит-строку
(ReferralAccrualAdjustmentEntity: кто, когда, старое → новое, почему). Гейт: корректировка возможна только
пока начисление свободно и не выплачено; после Approved (кошелёк партнёра уже кредитован) дельта
компенсируется корректирующей проводкой wallet; после Paid/Cancelled сумма неизменяема. Это не отмена
начисления (UC-RWD-009 A5/A6) и не правка ставки на будущее (UC-RWD-008). Связан с контекстом rewards.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-RWD-014 |
| Контекст | rewards (см. docs/06-services/rewards/) |
| Тип | альтернативный (корректирующая операция реферального контура) |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Менеджер ПИН (корректирует); Партнёр — получатель вознаграждения (видит изменение, не инициирует) |
| Permissions / scope | Любое из rewards.referral-rates.manage / rewards.accruals.adjust; scope — платформа |
| Tenant | Вне tenant-модели — реферальные начисления платформенные (получатель-партнёр может не иметь компании; процесс ведёт ПИН, ADR-0052) |
| Record-level | Не применяется для оператора (платформенный реестр); партнёр видит только свои начисления (PartnerUserId), корректировку суммы не выполняет |
Предусловие
- Реферальное начисление существует (создано по завершённой сделке приведённого инвестора, UC-RWD-009).
- Статус ∈ {
Pending,Approved} и начисление свободно (PayoutOrderItemId is null) — изPaid/Cancelledили из включённого в неотменённое реферальное поручение сумма неизменяема. - У актора — актуальная
Versionначисления (optimistic concurrency) и одно из требуемых прав. - Причина корректировки сформулирована — без непустой
Reasonкорректировка отклоняется.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | Админ-панель ПИН → «Рефералы» → реестр реферальных начислений → карточка → «Скорректировать сумму» |
Основной Поток
- Менеджер ПИН в реестре реферальных начислений (../api/platform/list-referral-accruals.md) открывает начисление и обнаруживает расхождение суммы с фактической договорённостью. Нажимает «Скорректировать сумму».
- Фронт вызывает
POST /api/v1/platform/referral-accruals/{accrualId}/adjustс телом{ newAmount: MoneyModel (Value > 0, scale 2, CurrencyId = валюте начисления), reason: string (1..1000, непусто после trim), expectedVersion: long }(../api/platform/adjust-referral-accrual.md). - Система проверяет доступ и вход: одно из
rewards.referral-rates.manage/rewards.accruals.adjustв платформенном scope; начисление найдено (иначе —REWARDS_REFERRAL_ACCRUAL_NOT_FOUND);newAmount.Value > 0иReasonнепуст (иначе —REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_REASON_REQUIRED);CurrencyId= валюте начисления (INV-RA-2, иначе —REWARDS_CURRENCY_MISMATCH);expectedVersion=Version(иначе —REWARDS_CONCURRENCY_CONFLICT). Начисление читается под блокировкой. - Guard корректировки (INV-RA-6): статус ∈ {
Pending,Approved} и начисление свободно (PayoutOrderItemId is null) — все нарушения →REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_FORBIDDEN. - Система применяет корректировку в одном согласованном изменении: при первой корректировке
OriginalAmount := Amount(фиксируется однократно и далее не перезаписывается);Amount := newAmount; создаётся append-only аудит-строкаReferralAccrualAdjustmentEntity(OldAmount= сумма до корректировки,NewAmount,Reason,ActorUserId);Version + 1. Статус иAppliedRate(снапшот ставки) не меняются — скорректированная сумма может расходиться с формулойBaseAmount × Percent, в этом и назначение ручной корректировки. - Компенсация кошелька зависит от статуса на момент корректировки:
Pending. Кошелёк ещё не кредитован (кредит происходит только наApproved) — интеграционные события не публикуются; при последующем авто-апруве (регистрация акта, UC-RWD-010) кошелёк кредитуется уже новой суммой.Approved. Кошелёк партнёра уже кредитован исходной суммой — публикуетсяReferralAccrualAmountAdjustedKafkaEvent(топикpin.rewards.referral-accruals.v1, key =AccrualId, дельтаNewAmount − OldAmount) →walletприменяет корректирующую проводкуCorrectionна счётReferralCommissionпартнёра (ADR-0065, асинхронно — окно секунды);notificationsуведомляет партнёра об изменении суммы.
- Система возвращает проекцию начисления (
Amount = newAmount, заполненныйOriginalAmount, новаяVersion); партнёр в ЛК видит обновлённую сумму, оператор — новую запись в истории корректировок.
Альтернативные Потоки
A1. Корректировка в Pending (до получения денег)
- Начисление ещё
Pending(родительская комиссия не прошла гейт денег): корректировка меняетAmountиOriginalAmount, добавляет аудит-строку, но кошелёк не затрагивается (кредита ещё не было) — событий нет. При последующем авто-апруве регистрацией акта кошелёк кредитуется уже скорректированной суммой. Завершается.
A2. Повторная корректировка
- Начисление уже корректировалось (
OriginalAmountзаполнен): следующая корректировка не перезаписываетOriginalAmount,OldAmountновой аудит-строки = сумме после предыдущей корректировки. Любое число последовательных корректировок допустимо в рамках guard-ов. Завершается.
A3. Начисление занято реферальным поручением
- Начисление
Approvedвключено в неотменённое реферальное поручение (PayoutOrderItemId != null) →REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_FORBIDDEN(409). Сначала освободить начисление (Reject/Cancelпоручения, UC-RWD-009), затем корректировать. Возвращается к основному потоку.
A4. Корректировка после выплаты (Paid) или отмены (Cancelled)
- Начисление в терминальном статусе
Paid/Cancelled→REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_FORBIDDEN(409): после выплаты финансовая история неизменяема, ошибка лечится вне статусной машины (регламент компенсации — возврат/доплата вне платформы). Завершается.
A5. Гонка с авто-апрувом или включением в поручение
- Корректировка
Approved-начисления параллельно захвату его реферальным поручением: блокировка и сверка версии пропускают одну операцию; проигравшийadjustполучаетREWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_FORBIDDEN(начисление уже занято) либоREWARDS_CONCURRENCY_CONFLICT. Перечитать состояние и повторить. - Корректировка
Pending-начисления параллельно авто-апруву (регистрация акта переводитPending → Approved): сериализуется по версии; выигравшая операция определяет, кредитуется кошелёк исходной или скорректированной суммой (при проигрышеadjustповторяется уже изApproved→ компенсацияCorrection).
Постусловие
Amountначисления равен внесённой корректировке;OriginalAmountхранит исходную автосчитанную сумму (расчёт объясним постфактум). На каждую применённую корректировку — ровно одна append-only аудит-строка (старое → новое, обязательная причина, актор); история не редактируется и не удаляется.- Кошелёк партнёра согласован с суммой: при корректировке из
ApprovedбалансReferralCommissionвыровнен проводкойCorrection(асинхронно, окно секунды); при корректировке изPendingкредит произойдёт уже новой суммой при авто-апруве. - Статус и снапшот ставки (
AppliedRate) не изменены — корректируется только сумма конкретного вознаграждения.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
403 FORBIDDEN | 403 | Нет rewards.referral-rates.manage и rewards.accruals.adjust | Скрыть действие |
REWARDS_REFERRAL_ACCRUAL_NOT_FOUND | 404 | Начисление не существует | Вернуться к реестру |
REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_REASON_REQUIRED | 400 | Reason пуст/whitespace (INV-RA-6) | Подсветить поле причины |
REWARDS_REFERRAL_ACCRUAL_ADJUSTMENT_FORBIDDEN | 409 | Статус вне {Pending, Approved}; начисление в неотменённом поручении (INV-RA-6, A3/A4) | Показать порядок разблокировки |
REWARDS_CURRENCY_MISMATCH | 400 | newAmount.CurrencyId ≠ валюте начисления (INV-RA-2) | Исправить вход |
REWARDS_CONCURRENCY_CONFLICT | 409 | expectedVersion ≠ Version (параллельная мутация, A5) | Перечитать и повторить |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
ReferralAccrualAmountAdjustedEvent | Применённая корректировка суммы (шаг 5) | in-proc аудит |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
ReferralAccrualAmountAdjustedKafkaEvent.v1 | pin.rewards.referral-accruals.v1 (key = AccrualId) | Только при корректировке из Approved (шаг 6) — OldAmount/NewAmount с CurrencyId + CurrencySnapshot | wallet — Correction на дельту кредита ReferralCommission (ADR-0065); notifications — партнёру об изменении суммы |
Из Pending события не публикуются (кошелёк ещё не кредитован).
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Корректировка в Pending: OriginalAmount зафиксирован, Amount заменён, аудит-строка создана, событий нет; при авто-апруве кошелёк кредитуется новой суммой | тест 50 000 → 45 000 в Pending |
| AC2 | Корректировка в Approved публикует ReferralAccrualAmountAdjustedKafkaEvent; wallet применяет Correction на дельту | тест 50 000 → 45 000 в Approved → Correction −5 000 |
| AC3 | Повторная корректировка не перезаписывает OriginalAmount; каждая оставляет свою аудит-строку (OldAmount = значение после предыдущей) | две последовательные корректировки → 2 строки аудита |
| AC4 | После Paid/Cancelled сумма неизменяема (A4) | корректировка терминального начисления → 409 |
| AC5 | Начисление в неотменённом поручении не корректируется (A3) | занять поручением → adjust → 409 |
| AC6 | Без причины любая корректировка отклонена (400), состояние не изменено | пустая/пробельная Reason |
| AC7 | Валюта корректировки ≠ валюте начисления → 400 (INV-RA-2) | смена CurrencyId во входе |
| AC8 | Гонка с захватом поручением/авто-апрувом разрешается блокировкой и версией; проигравший получает 409 | параллельные adjust + захват/апрув |
| AC9 | История корректировок append-only, видна в реестре ПИН, упорядочена по времени | чтение проекции начисления |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: ../api/platform/adjust-referral-accrual.md; реестр и модель — ../api/platform/list-referral-accruals.md; ЛК партнёра — ../api/partner/list-my-referral-accruals.md.
- Domain entity: ../domain/referral-accrual.md
(
ReferralAccrualAdjustmentEntity, INV-RA-2/6, stateDiagram); ставка на будущее — ../domain/referral-rate-rule.md. - События: ../events/referral-accrual-status-changed.md;
компенсация кошелька — ../../wallet/README.md (проводка
Correction). - Связанные UC: UC-RWD-009 (жизненный цикл реферального начисления, отмена — не корректировка суммы), UC-RWD-008 (ставки реферального вознаграждения), UC-RWD-010 (гейт денег и авто-апрув), UC-RWD-011 (образец gross-корректировки комиссионного начисления).
- ADR: 0066
(реферальные ставки и отдельная сущность), 0067
п.4 (first-class корректировки, аудит), ADR-0065 (компенсация кошелька
Correction), ADR-0052 (tenancy), ADR-0048 (денежная арифметика).