Перейти к основному содержимому

UC-RWD-008. Платформа настраивает ставки реферального вознаграждения

Назначение

Описывает сценарий управления ставками реферального вознаграждения: Менеджер ПИН задаёт процент вознаграждения партнёра от конечной цены сделки приведённого им инвестора — на одном из четырёх уровней специфичности (партнёр × застройщик / партнёр / застройщик / глобально, ADR-0066), с периодом действия (в т.ч. временно повышенные ставки на акцию), — активирует ставку и получает её автоматическое применение: при создании реферального начисления резолвится наиболее специфичная действующая ставка на момент завершения сделки. Связан с контекстом rewards.

Metadata

ПолеЗначение
IDUC-RWD-008
Контекстrewards (см. docs/06-services/rewards/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольМенеджер ПИН
Permissions / scoperewards.referral-rates.manage (управление и чтение реестра); scope — платформа
Tenantвне tenant-модели — платформенная сущность (владелец денег реферальной программы — ПИН, ADR-0052)
Record-levelне применяется

Предусловие

  • Актор аутентифицирован, права платформенного scope.
  • Для персональных ставок: партнёр существует в identity; для ставок по застройщику: компания Type = Developer существует.
  • Глобальная бессрочная ставка — страховочный уровень резолюции — заводится первым шагом внедрения (без неё сделки без специфичных ставок реферальных начислений не порождают, A5).

Триггер

ПолеЗначение
ИсточникUI button
Триггерящий элементАдмин-панель ПИН → «Финансы» → «Реферальные ставки» → «Создать» / «Активировать» / «Завершить досрочно» / «Архивировать»

Основной Поток

Фаза 1. Создание и активация ставки

  1. Менеджер ПИН открывает реестр ставок (GET /api/v1/platform/referral-rate-rules, фильтры по партнёру/застройщику/статусу/специфичности) и нажимает «Создать».
  2. Фронт вызывает POST /api/v1/platform/referral-rate-rules с телом { partnerUserId?: Guid, developerCompanyId?: Guid, percent: decimal (0 < x <= 100, scale 2), effectiveFrom: DateTimeOffset, effectiveTo?: DateTimeOffset (> effectiveFrom; null = бессрочно), comment?: string (<= 2000) } (../api/platform/create-referral-rate-rule.md). Специфичность (ReferralRateScope) не передаётся — выводится из заполненности измерений: PartnerDeveloper / Partner / Developer / Global.
  3. Система создаёт ставку-черновик: проверка диапазона процента (INV-RR-1, иначе REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE) и периода (effectiveTo > effectiveFrom, иначе REWARDS_REFERRAL_RATE_PERIOD_INVALID); запись в Status = Draft — в резолюции не участвует. Результат — { ruleId, scope, status = Draft }.
  4. Менеджер нажимает «Активировать»; фронт вызывает POST /api/v1/platform/referral-rate-rules/{ruleId}/activate { expectedVersion } (../api/platform/activate-referral-rate-rule.md).
  5. Система активирует ставку (INV-RR-2): периоды активных ставок той же пары значений (PartnerUserId, DeveloperCompanyId, включая null) не пересекаются — пересечение отклоняется REWARDS_REFERRAL_RATE_OVERLAP (гарантия детерминированности резолюции: внутри уровня на любой момент действует не более одной ставки). Ставка переходит Draft → Active. Результат — { ruleId, status = Active }.

Фаза 2. Резолюция при создании реферального начисления (автоматически, system)

  1. crm завершает сделку приведённого инвестора → реферальная ветка consumer-а (../consumers/crm-deal-completed.md) резолвит ставку на момент asOf = CompletedAt: кандидаты — Active-ставки, период покрывает asOf, измерения совместимы со сделкой (PartnerUserId пуст или равен рефереру связи; DeveloperCompanyId пуст или равен застройщику сделки).
  2. Из кандидатов выбирается наиболее специфичный непустой уровень: PartnerDeveloperPartnerDeveloperGlobal («специфичное побеждает»); внутри уровня кандидат ровно один (INV-RR-2). Алгоритм — ../domain/referral-rate-rule.md.
  3. В реферальное начисление снимается снапшот применённой ставки (AppliedRateSnapshot: RateRuleId, Scope, Percent, период, версия ставки) и считается Amount = FinalPrice × Percent / 100 (banker's, 2 знака, INV-RA-2) — последующая смена ставок существующие начисления не пересчитывает (UC-RWD-009).

Альтернативные Потоки

A1. Правка черновика

  1. PUT /api/v1/platform/referral-rate-rules/{ruleId} (те же поля + expectedVersion) — только в Draft (../api/platform/update-referral-rate-rule.md). Возвращается к шагу 4.

A2. Пересечение периодов при активации

  1. На шаге 5 найдена активная ставка той же пары значений с пересекающимся периодом → REWARDS_REFERRAL_RATE_OVERLAP (409). Менеджер либо правит период черновика (A1), либо досрочно завершает действующую ставку (A3) и повторяет активацию.

A3. Временная акция и досрочное завершение

  1. Повышенная ставка на период акции — обычная ставка с ограниченным EffectiveFrom/EffectiveTo (например, Developer-уровня): на время акции резолюция выбирает её, после EffectiveTo — снова базовую (страховочную Global или персональную).
  2. Досрочное завершение действующей ставки — единственная разрешённая мутация в Active (INV-RR-3): сокращение EffectiveTo (не раньше текущего момента; сокращение пересечений не создаёт); остальные поля иммутабельны — REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE. Начисления, созданные в период действия, не пересчитываются (снапшот).

A4. Архивирование ставки

  1. POST /api/v1/platform/referral-rate-rules/{ruleId}/archive { expectedVersion } (../api/platform/archive-referral-rate-rule.md) — ставка выводится из резолюции навсегда; снапшоты в созданных начислениях живы. Уровень остаётся резолюции нижележащим специфичностям (вплоть до Global). Завершается.

A5. Ставка не найдена при начислении

  1. На момент CompletedAt ни один уровень не дал кандидата → реферальное начисление не создаётся; warn-метрика rewards_referral_accrual_no_rate_total. Комиссионная ветка сделки не затронута.
  2. Менеджер ПИН заводит недостающую ставку (страховочную Global — бессрочно) и при необходимости инициирует replay событий завершения сделок — начисления создадутся по ставкам, действующим на asOf = CompletedAt (ретроактивное покрытие требует периода, включающего прошлое). Завершается.

A6. Гонка двух активаций

  1. Две параллельные активации ставок одной пары значений с пересекающимися периодами → активные ставки той же специфичности читаются под блокировкой: побеждает первая, проигравшая получает REWARDS_REFERRAL_RATE_OVERLAP (409) и пересматривает период.

Постусловие

  • Внутри каждой пары значений (PartnerUserId, DeveloperCompanyId) периоды активных ставок не пересекаются (INV-RR-2) — резолюция детерминирована на любой момент времени.
  • Новые реферальные начисления по сделкам с CompletedAt в периоде действия получают снапшот ставки (AppliedRateSnapshot) и сумму по ней; созданные ранее начисления не пересчитаны.
  • Партнёру ставка отдельно не отдаётся — применённый процент виден в снапшоте его начисления (rewards.referral.view-own, UC-RWD-009).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
403 FORBIDDEN403Нет rewards.referral-rates.manageСкрыть управление ставками
REWARDS_REFERRAL_RATE_PERCENT_OUT_OF_RANGE400Процент вне (0; 100] (INV-RR-1)Подсветить поле процента
REWARDS_REFERRAL_RATE_PERIOD_INVALID400effectiveTo <= effectiveFromПодсветить период
REWARDS_REFERRAL_RATE_OVERLAP409Пересечение периодов активных ставок одной специфичности (INV-RR-2, A2/A6)Поправить период либо завершить действующую ставку
REWARDS_REFERRAL_RATE_ACTIVE_IMMUTABLE409Правка Active сверх сокращения EffectiveTo (INV-RR-3, A3)Создать новый черновик
REWARDS_REFERRAL_RATE_INVALID_STATUS_TRANSITION409Активация не из Draft / повторная активацияОбновить карточку ставки
REWARDS_REFERRAL_RATE_NOT_FOUND404Ставка не существуетВернуться к реестру
REWARDS_CONCURRENCY_CONFLICT409expectedVersion не совпалПеречитать и повторить

Доменные События

СобытиеКогда поднимаетсяSubscribers
ReferralRateRuleCreatedEventУспешное создание черновика (шаг 3)in-proc аудит
ReferralRateRuleActivatedEventDraft → Active (шаг 5)in-proc аудит
ReferralRateRuleArchivedEvent* → Archived (A4)in-proc аудит

Kafka-события ставки не публикуют — применяются только внутри rewards при создании реферального начисления (../domain/referral-rate-rule.md).

Acceptance Criteria

#КритерийПроверка
AC1Специфичное побеждает: при активных ставках всех четырёх уровней применяется PartnerDeveloperсделка партнёра по застройщику при 4 ставках → снапшот Scope = PartnerDeveloper
AC2Уровень без кандидата пропускается: без персональных ставок применяется Developer, затем Globalпоэтапное отключение уровней → резолюция спускается ниже
AC3Пересечение периодов одной специфичности отклоняется при активации, включая гонку (INV-RR-2, A2/A6)два периода с наложением → 409; параллельные активации → одна успешна
AC4Резолюция идёт на CompletedAt: сделка в период акции получает акционную ставку, после — базовуюдве сделки до/после EffectiveTo акции → разные снапшоты
AC5Смена/архив ставки не пересчитывает созданные начисления (снапшот)архивировать ставку → перечитать начисление: AppliedPercent прежний
AC6В Active меняется только EffectiveTo и только сокращением (INV-RR-3, A3)попытка сменить процент → 409; сокращение периода → 200
AC7Нет применимой ставки → начисление не создаётся, метрика rewards_referral_accrual_no_rate_total (A5)сделка без покрытия ставками → нет записи + метрика
AC8Границы процента (0; 100] соблюдаются (INV-RR-1)0 / 100.01 → 400; 100 → 200

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные Документы