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

UC-RWD-010. Акт поступления средств — гейт получения денег

Назначение

Описывает сценарий подтверждения фактического получения комиссионных денег от застройщика: финансовый оператор ПИН заводит «Акт поступления средств» (RC-{yyyy}-{seq}) по пакетному платежу застройщика, подбирает покрываемые подтверждённые начисления, сверяет сходимость (Σ позиций = сумме поступления) и регистрирует акт. Регистрация — единый гейт всех денежных следствий (ADR-0067): начисления атомарно переходят Confirmed → MoneyReceived, реферальные начисления — Pending → Approved, кошельки получателей кредитуются, доли открываются платёжным поручениям. Связан с контекстом rewards.

Metadata

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

Актор

ПолеЗначение
РольФинансовый оператор ПИН
Permissions / scoperewards.receipts.manage — платформенный scope; право независимо от прав арбитража сделок CRM (ADR-0067 п.5)
Tenantвне tenant-модели — платформенная сущность (ADR-0052); позиции акта могут ссылаться на начисления разных агентств одного застройщика
Record-levelне применяется

Предусловие

  • Существуют начисления Status = Confirmed застройщика-плательщика (UC-RWD-004), не включённые в другой не-отменённый акт (INV-RC-3).
  • Застройщик перечислил комиссию (пакетный платёж — одна платёжка за несколько сделок); у оператора — платёжный документ с суммой и реквизитом.
  • Частичные оплаты — вне scope v1: акт формируется только на полностью покрытые начисления (../domain/platform-receipt.md).

Триггер

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

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

  1. Оператор сверяет платёж застройщика с реестром Confirmed-начислений (../api/platform/list-platform-receipts.md — акты; кандидаты в позиции — свободные Confirmed застройщика) и нажимает «Создать акт».
  2. Фронт вызывает POST /api/v1/platform/receipts с телом { developerCompanyId, amount (сумма фактического поступления), paymentReference? (реквизит платёжки), comment?, accrualIds? (явный состав, 1..500) | periodFrom?/periodTo? (автоподбор по DealCompletedAt) } (../api/platform/create-platform-receipt.md).
  3. Система создаёт черновик: подбор позиций — каждое начисление Confirmed, того же застройщика (INV-RC-1), не в другом не-отменённом акте (INV-RC-3); на позицию фиксируется Amount = CommissionAmount начисления на момент включения; номер RC-{yyyy}-{seq}; пустой состав — REWARDS_RECEIPT_EMPTY. Ответ — акт Draft с предпросмотром сходимости: ItemsTotal (Σ позиций) против Amount; расхождение в Draft допустимо.
  4. Оператор выравнивает расхождение (если есть) до регистрации: правит состав, корректирует gross начисления (UC-RWD-011) или исправляет сумму акта (A1); затем нажимает «Зарегистрировать».
  5. Фронт вызывает POST /api/v1/platform/receipts/{receiptId}/register { expectedVersion } (../api/platform/register-platform-receipt.md).
  6. Система перепроверяет состав по актуальному состоянию (позиции могли устареть): каждое начисление — Confirmed того же застройщика (нарушение — A3), сумма позиции равна текущему CommissionAmount (расхождение — A2), ItemsCount >= 1; сходимость Σ позиций = Amount акта (INV-RC-2, иначе — A1).
  7. Атомарный каскад (INV-RC-4) — в одном согласованном изменении, «либо всё, либо ничего»:
    • акт → Registered (RegisteredAt, актор) — терминальный, неизменяемый, не откатывается;
    • каждое начисление-позиция → Confirmed → MoneyReceived (MoneyReceivedAt = RegisteredAt, PlatformReceiptId);
    • каждая Pending-рефералка, чей родитель вошёл в акт, → Approved (INV-RA-3, UC-RWD-009);
    • публикуются события: commission-money-received — по одному на начисление (payload включает Distributions[] Platform/Agency/Agent), commission-accrual-status-changed (→ MoneyReceived) — по каждому, статусные события рефералок (→ Approved) — по каждой.
  8. Кредиты кошелька (асинхронно, окно — секунды). wallet по событиям кредитует счета: доля агента → AgentCommission, удержание агентства → AgencyCommission, строка PlatformPlatformRevenue, апрувнутая рефералка → ReferralCommission партнёра; идемпотентность — (OperationKind, SourceId) (ADR-0065, ../../wallet/README.md).
  9. С этого момента Agent-строки распределений доступны платёжным поручениям агентств (UC-RWD-005), Approved-рефералки — реферальным поручениям (UC-RWD-009); notifications уведомляет агентов/агентства («деньги получены — доля доступна к выплате») и партнёров.

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

A1. Сумма не сходится (INV-RC-2)

  1. При регистрации Σ позиций ≠ Amount акта → REWARDS_RECEIPT_SUM_MISMATCH (409), акт остаётся Draft, каскад не выполняется. Оператор лечит расхождение: исключает непокрытые позиции (они остаются Confirmed до следующего акта), корректирует gross начисления (UC-RWD-011) или исправляет сумму акта по платёжке. Возвращается к шагу 5.

A2. Позиция устарела (gross скорректирован после включения)

  1. Сумма позиции разошлась с актуальным CommissionAmount (корректировка ПИН-админа между включением и регистрацией) → REWARDS_RECEIPT_ITEM_STALE (409). Оператор пересобирает состав по актуальным суммам. Возвращается к шагу 4.

A3. Начисление-позиция стало недействительным

  1. Начисление отменено из Confirmed (расторжение сделки) до регистрации → REWARDS_RECEIPT_ITEM_INVALID (409). Оператор исключает позицию и корректирует сумму акта. Возвращается к шагу 4.

A4. Гонка двух актов за одно начисление

  1. Начисление уже в составе другого не-отменённого акта (Draft/Registered) — уникальный индекс по начислению (INV-RC-3): победитель включает позицию, проигравший получает REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED (409) и пересобирает состав. Двойное подтверждение получения денег исключено структурно.

A5. Отмена черновика

  1. Draft-акт отменяется (Draft → Cancelled): позиции физически удаляются — начисления освобождаются и доступны другому акту. Registered-акт не отменяется (REWARDS_RECEIPT_REGISTERED_IMMUTABLE). Завершается.

A6. Идемпотентный повтор регистрации

  1. Повторный register уже Registered-акта (сетевой ретрай клиента) → no-op: 200 с текущей проекцией, каскад и события не повторяются — двойного MoneyReceived/Approved не бывает. Потребители событий дополнительно дедуплицируют по id начисления + переходу.

A7. Ошибочно зарегистрированный акт

  1. Registered-акт не разворачивается (INV-RC-4) — каскад уже разошёлся по кошелькам и апрувам. Исправление — компенсирующее ручное начисление / корректировка следующим периодом (../sagas/commission-settlement-saga.md, «Компенсации»); двухшаговость Draft → Registered с предпросмотром Σ снижает риск. Завершается.

Постусловие

  • Акт в Registered (терминальный, неизменяемый финансовый документ; сам себе аудит-след); Σ позиций = сумме фактического поступления.
  • Все начисления-позиции — в MoneyReceived (MoneyReceivedAt, PlatformReceiptId); их Agent-строки доступны поручениям; все Pending-рефералки родителей — в Approved.
  • События опубликованы атомарно с каскадом; кошельки получателей кредитованы асинхронно (окно — секунды; в окне статусы в rewards уже новые, баланс — ещё прежний, это лаг, не ошибка).
  • Агентства и застройщики акты не видят — им виден эффект: Status = MoneyReceived, MoneyReceivedAt/PlatformReceiptId своих начислений.

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

КодHTTPУсловиеПоведение клиента
403 FORBIDDEN403Нет rewards.receipts.manageСкрыть работу с актами
REWARDS_RECEIPT_NOT_FOUND404Акт не существуетВернуться к реестру
REWARDS_RECEIPT_EMPTY409Подбор позиций пуст (нет свободных Confirmed застройщика)Проверить фильтры/период
REWARDS_RECEIPT_ITEM_INVALID409Позиция не Confirmed / чужого застройщика (INV-RC-1, A3)Исключить позицию, поправить сумму
REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED409Начисление уже в другом не-отменённом акте (INV-RC-3, A4)Пересобрать состав
REWARDS_RECEIPT_ITEM_STALE409Сумма позиции устарела после корректировки gross (A2)Пересобрать состав
REWARDS_RECEIPT_SUM_MISMATCH409Σ позиций ≠ сумме акта при регистрации (INV-RC-2, A1)Выровнять суммы до регистрации
REWARDS_RECEIPT_REGISTERED_IMMUTABLE409Правка/отмена Registered-акта (INV-RC-4, A5/A7)Компенсация отдельным начислением
REWARDS_RECEIPT_COMPOSITION_LOCKED409Правка состава вне Draft (INV-RC-4)Перечитать акт
REWARDS_RECEIPT_INVALID_STATUS_TRANSITION409Регистрация из Cancelled и другие недопустимые переходыПеречитать акт
REWARDS_CURRENCY_MISMATCH400Валюта позиции/акта не совпадаетПроверить данные
REWARDS_CONCURRENCY_CONFLICT409Параллельные register/cancel/правка составаПеречитать и повторить

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

СобытиеКогда поднимаетсяSubscribers
PlatformReceiptCreatedEventСоздание черновика (шаг 3)in-proc аудит
PlatformReceiptRegisteredEventDraft → Registered (шаг 7)in-proc аудит

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
CommissionMoneyReceivedKafkaEvent.v1pin.rewards.commission-money-received.v1Регистрация акта — по одному на каждое начисление-позицию (key = id начисления; payload: PlatformReceiptId, MoneyReceivedAt, Distributions[] {RecipientType, RecipientUserId?, Amount})wallet (кредиты счетов по строкам, идемпотентность (CommissionCredit, AccrualId)), notifications
CommissionAccrualStatusChangedKafkaEvent.v1pin.rewards.commission-accrual-status-changed.v1Confirmed → MoneyReceived — по каждому начислению актаnotifications; self-consume AgentPerformance (переход — no-op для метрик, ../consumers/agent-performance-projection.md)
ReferralAccrualStatusChangedKafkaEvent.v1pin.rewards.referral-accruals.v1Pending → Approved — по каждой апрувнутой рефералкеnotifications; wallet (кредит ReferralCommission)

Acceptance Criteria

#КритерийПроверка
AC1Регистрация атомарна: акт Registered + все позиции MoneyReceived + рефералки Approved + все события — одно согласованное изменение; при отказе любого шага акт целиком остаётся Draftинтеграционный тест каскада + инъекция сбоя
AC2Σ позиций ≠ сумме акта блокирует регистрацию (INV-RC-2, A1)тест расхождения → 409, статусы не изменены
AC3Начисление — максимум в одном не-отменённом акте (INV-RC-3, A4), включая гонкупараллельное включение в два акта → один победитель
AC4Корректировка gross после включения ловится при регистрации (ITEM_STALE, A2)adjust между включением и register → 409
AC5Повторный register — идемпотентный no-op: без второго каскада и событий (A6)двойной вызов → одна публикация каждого события
AC6Registered-акт неизменяем и не отменяется (INV-RC-4, A7)попытки правки/отмены → 409
AC7Отмена черновика освобождает начисления для другого акта (A5)cancel → повторное включение в новый акт успешно
AC8Кредиты кошелька идемпотентны: повторная доставка события не двоит балансreplay commission-money-received → один кредит на строку
AC9После регистрации Agent-строки доступны поручению, до — нет (гейт ADR-0067)автосборка поручения до/после акта
AC10Возраст старейшего Confirmed без акта мониторится (rewards_accrual_awaiting_receipt_max_age_minutes)метрика растёт до регистрации, сбрасывается после

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

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

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