Entity / Aggregate: PlatformReceiptEntity
Дом полей агрегата «Акт поступления средств» контекста rewards (+ дочерняя сущность
PlatformReceiptItemEntity). Проекции (реестр актов platform-API, payload событий получения денег)
ссылаются сюда и описывают только дельту.
Назначение
PlatformReceiptEntity — «Акт поступления средств»: документ, которым финансовый оператор ПИН
подтверждает фактическое получение комиссионных денег от застройщика
(ADR-0067). Застройщик перечисляет
комиссию пакетно — одним платежом за несколько сделок; акт связывает пришедшую сумму с конкретным набором
подтверждённых начислений (commission-accrual.md) и контролирует их сходимость
(Σ позиций = сумме акта).
Регистрация акта — единый гейт всех денежных следствий: позиции атомарно переходят
Confirmed → MoneyReceived, связанные реферальные начисления
(referral-accrual.md) — Pending → Approved, публикуются события получения денег
(кредиты кошелька, ADR-0065). До регистрации акта никакие выплаты (агентские и реферальные) не
допускаются. Частичные оплаты — вне scope v1: если поступило меньше, акт формируется только на
полностью покрытые начисления; частичное покрытие одного начисления не моделируется.
Классификация
| Поле | Значение |
|---|
| Контекст | rewards |
| Kind | AggregateRoot (+ дочерняя Entity PlatformReceiptItemEntity внутри границы агрегата) |
| Source of truth | rewards |
| Таблицы | rewards.platform_receipts, rewards.platform_receipt_items |
| Tenant isolation | вне tenant-модели — платформенная сущность (владелец процесса — ПИН, ADR-0052); позиции могут ссылаться на начисления разных агентств одного застройщика |
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|
Id | Guid | Да | PK | app | Уникальный идентификатор акта. |
Number | string | Да | varchar(32), unique ux_platform_receipts_number; формат RC-{yyyy}-{seq}, seq — платформенный PostgreSQL sequence per year (генерация при создании) | app | Человекочитаемый номер акта («RC-2026-0042») — сверка с бухгалтерией. |
DeveloperCompanyId | Guid | Да | index ix_platform_receipts_developer (developer_company_id, status); логическая ссылка на identity.companies.id (Type = Developer), без FK | app | Застройщик-плательщик — акт формируется по одному застройщику (INV-RC-1). |
Amount | Money (owned) | Да | см. VO; check amount_value > 0 | app | Сумма фактического поступления (из платёжного документа застройщика); при регистрации обязана сойтись с Σ позиций (INV-RC-2). |
ItemsCount | int | Да | >= 0; денормализация для списков | вычисление | Число позиций акта. |
PaymentReference | string? | Нет | varchar(256) | app | Реквизит платёжного документа застройщика (номер/дата платёжки) — след для сверки. |
Comment | string? | Нет | varchar(2000) | app | Комментарий оператора ПИН. |
Status | PlatformReceiptStatus | Да | хранится как text, varchar(32), index (status) | app | Draft / Registered / Cancelled (stateDiagram ниже). |
RegisteredAt | DateTimeOffset? | Нет | заполняется при Register | app | Момент регистрации (= MoneyReceivedAt позиций). |
RegisteredByUserId | Guid? | Нет | — | app | Финансовый оператор ПИН, зарегистрировавший акт. |
CancelledAt | DateTimeOffset? | Нет | заполняется при Cancel | app | Момент отмены черновика. |
CancelledByUserId | Guid? | Нет | — | app | Кто отменил. |
CreatedAt | DateTimeOffset | Да | index | app | Момент создания (аудит). |
CreatedByUserId | Guid? | Нет | — | app | Создавший оператор. |
UpdatedAt | DateTimeOffset | Да | — | app | Момент изменения. |
UpdatedByUserId | Guid? | Нет | — | app | Последний изменивший. |
Version | long | Да | >= 1 | app | Доменная версия (инкремент при мутации). |
| Значение | Описание |
|---|
Draft | Черновик: состав позиций редактируется; предпросмотр Σ позиций против Amount. |
Registered | Зарегистрирован — финансовый документ: неизменяем, не откатывается (INV-RC-4); позиции переведены в MoneyReceived, рефералки апрувнуты, события опубликованы. Терминальный. |
Cancelled | Черновик отменён: позиции освобождены — начисления доступны другому акту. Терминальный. |
Позиция акта — одно подтверждённое комиссионное начисление, покрытое поступлением. Kind: Entity
внутри границы агрегата PlatformReceiptEntity; мутации только через корень. Позиции отменённого акта
физически удаляются при Cancel (черновиковый состав — рабочие данные, не финансовый след), поэтому
уникальный индекс по начислению структурно гарантирует эксклюзивность (INV-RC-3).
| Поле | Тип | Обязательность | Ограничения | Источник | Описание |
|---|
Id | Guid | Да | PK | app | Идентификатор позиции. |
ReceiptId | Guid | Да | FK fk_platform_receipt_items_receipt → platform_receipts.id (cascade), index | app | Родительский акт. |
CommissionAccrualId | Guid | Да | FK fk_platform_receipt_items_accrual → rewards.commission_accruals.id (restrict); unique ux_platform_receipt_items_accrual (commission_accrual_id) — начисление максимум в одном не-отменённом акте (INV-RC-3) | app | Покрываемое начисление (commission-accrual.md); на момент включения — Confirmed того же застройщика (INV-RC-1). |
AgencyCompanyId | Guid | Да | index | начисление (TenantId) | Агентство начисления — денормализация для отображения состава (позиции разных агентств одного застройщика). |
Amount | Money (owned) | Да | check amount_value > 0 | начисление (CommissionAmount) | Сумма позиции — CommissionAmount начисления на момент включения; при регистрации сверяется с актуальной (коды ошибок ниже). |
CreatedAt | DateTimeOffset | Да | — | app | Момент включения позиции. |
Value objects
| Поле | Тип | Обязательность | Ограничения | Колонка (пример для Amount акта) |
|---|
Value | decimal | Да | numeric(18,2), >= 0 | amount_value |
CurrencyId | short | Да | ссылка на валюту справочника (../../handbook/domain/snapshots.md); RUB-only (ADR-0048) | amount_currency_id |
Валюта акта и всех позиций едина и совпадает с валютой покрываемых начислений
(REWARDS_CURRENCY_MISMATCH).
Модели JSONB
JSONB-модели не используются.
Связи
| Связь | Тип | Ключ | Delete behavior |
|---|
PlatformReceiptItemEntity | owned (внутри агрегата) | ReceiptId | cascade (позиции Cancel удаляются — начисления освобождаются) |
CommissionAccrualEntity | reference (со стороны позиции) | CommissionAccrualId; обратная ссылка commission_accruals.platform_receipt_id заполняется при Register | restrict |
ReferralAccrualEntity | косвенная (через родительское начисление) | referral_accruals.commission_accrual_id | — (каскад авто-апрува при Register) |
CompanyEntity (застройщик, identity) | reference (логическая, без FK) | DeveloperCompanyId | restrict |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|
| INV-RC-1 OnlyConfirmedSameDeveloper | Позиция ссылается на начисление в статусе Confirmed с DeveloperCompanyId = застройщику акта | включение позиции + повторная проверка при Register | REWARDS_RECEIPT_ITEM_INVALID |
| INV-RC-2 SumConsistency | Σ Item.Amount.Value = Amount.Value акта — сходимость поступления с покрытием; ItemsCount = count(Items), ItemsCount >= 1 при регистрации | операция Register (пересчёт по актуальным суммам начислений) | REWARDS_RECEIPT_SUM_MISMATCH |
| INV-RC-3 ExclusiveMembership | Начисление состоит максимум в одном не-отменённом акте (Draft или Registered) — двойное подтверждение получения исключено структурно (unique index; Cancel удаляет позиции) | unique ux_platform_receipt_items_accrual при включении позиции | REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED (гонка — конфликт индекса) |
| INV-RC-4 RegisteredImmutableAtomicCascade | Registered — терминальный и неизменяемый (состав/поля не правятся, отката нет — ошибки лечатся компенсирующими начислениями/корректировками следующим периодом); регистрация выполняется атомарно: все позиции Confirmed → MoneyReceived (+ MoneyReceivedAt, PlatformReceiptId начисления), связанные рефералки в Pending → Approved, публикация событий получения денег — либо всё, либо ничего. Изменение состава и полей — только в Draft | операция Register (одно согласованное изменение) + guard по Status | REWARDS_RECEIPT_REGISTERED_IMMUTABLE / REWARDS_RECEIPT_COMPOSITION_LOCKED |
Коды ошибок
| Код | HTTP | Условие |
|---|
REWARDS_RECEIPT_NOT_FOUND | 404 | Акт не найден (операции platform-API). |
REWARDS_RECEIPT_ITEM_INVALID | 409 | Позиция ссылается на начисление не в Confirmed / чужого застройщика (INV-RC-1). |
REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED | 409 | Начисление уже в составе другого не-отменённого акта (INV-RC-3). |
REWARDS_RECEIPT_SUM_MISMATCH | 409 | Σ позиций ≠ сумме акта при регистрации (INV-RC-2). |
REWARDS_RECEIPT_ITEM_STALE | 409 | Сумма позиции разошлась с актуальным CommissionAmount начисления (gross скорректирован после включения) — оператор пересобирает состав. |
REWARDS_RECEIPT_REGISTERED_IMMUTABLE | 409 | Попытка изменить/отменить Registered-акт (INV-RC-4). |
REWARDS_RECEIPT_COMPOSITION_LOCKED | 409 | Изменение состава вне Draft (INV-RC-4). |
REWARDS_RECEIPT_INVALID_STATUS_TRANSITION | 409 | Недопустимый переход статуса. |
REWARDS_CURRENCY_MISMATCH | 400 | Валюта позиции/акта не совпадает. |
Регистрация (атомарный каскад)
- Перепроверить состав по актуальному состоянию начислений: каждое —
Confirmed, того же застройщика
(INV-RC-1), сумма позиции равна текущему CommissionAmount (расхождение — gross скорректирован после
включения → REWARDS_RECEIPT_ITEM_STALE, оператор пересобирает состав); ItemsCount >= 1.
- Сверить сходимость: Σ позиций =
Amount акта, иначе REWARDS_RECEIPT_SUM_MISMATCH (расхождение
фактического платежа лечится до регистрации: корректировкой gross начисления или исключением
непокрытых позиций — они остаются в Confirmed до следующего акта).
- В одном согласованном изменении: акт →
Registered (RegisteredAt, актор); каждая позиция —
начисление Confirmed → MoneyReceived (+ MoneyReceivedAt = RegisteredAt,
PlatformReceiptId = Id акта); реферальные начисления в Pending, чьё родительское начисление вошло
в акт, — Pending → Approved (referral-accrual.md, INV-RA-3).
- Атомарно с изменением публикуются события:
pin.rewards.commission-money-received.v1 — по одному на
каждое начисление акта (key = id начисления, payload включает строки распределения
Platform/Agency/Agent); commission-accrual-status-changed (→ MoneyReceived) — по каждому
начислению; referral-accruals status-changed (→ Approved) — по каждой апрувнутой рефералке.
- После регистрации:
Agent-строки распределений доступны платёжным поручениям агентств,
Approved-рефералки — реферальным поручениям (payout-order.md); кошелёк кредитует
счета получателей по событиям (ADR-0065).
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|
PlatformReceiptCreatedEvent | доменное | Успешный Create | Id, Number, DeveloperCompanyId, Amount |
PlatformReceiptRegisteredEvent | доменное | Draft → Registered | Id, Number, состав позиций |
CommissionMoneyReceivedKafkaEvent | интеграционное (ContractVersion = 1, topic pin.rewards.commission-money-received.v1, key = id начисления) | Register — по каждому начислению акта | id начисления, TenantId, DealId, DeveloperCompanyId, PlatformReceiptId, MoneyReceivedAt, CommissionAmount, Distributions[] (RecipientType Platform/Agency/Agent, RecipientUserId?, Amount: Value + CurrencyId + CurrencySnapshot), Version. Контракт — ../events/commission-money-received.md. Потребители: wallet (кредиты счетов по строкам), notifications («деньги получены» — агенту/агентству) |
Смена статусов начислений и рефералок дополнительно публикует их собственные события
(../events/commission-accrual-status-changed.md,
топик pin.rewards.referral-accruals.v1 — referral-accrual.md). Все публикации
атомарны с регистрацией.
Конкурентность и восстановление
- Гонка включения начисления в два акта. Разрешается unique
ux_platform_receipt_items_accrual
(INV-RC-3): победитель включает позицию, проигравший получает
REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED и пересобирает состав.
- Гонка регистрации и корректировки gross. Корректировка gross ПИН-админом допустима в
Confirmed
(ADR-0067 п.4) — в т.ч. пока
начисление в Draft-акте; перепроверка сумм на шаге 1 регистрации ловит расхождение
(REWARDS_RECEIPT_ITEM_STALE); обе операции мутируют начисление под оптимистической блокировкой.
После MoneyReceived gross неизменяем.
- Гонка регистрации и отмены начисления. Начисление, отменённое из
Confirmed (отмена сделки),
ловится перепроверкой статуса на шаге 1 (REWARDS_RECEIPT_ITEM_INVALID) — оператор исключает позицию
и корректирует сумму акта.
- Уникальность номера.
Number — из платформенного per-year sequence; при гонке уникальный индекс
ux_platform_receipts_number страхует.
- Ошибочно зарегистрированный акт. Не откатывается (INV-RC-4) — каскад уже разошёлся по кошелькам и
апрувам; исправление — компенсирующее начисление / корректировка следующим периодом (двухшаговость
Draft → Registered с предпросмотром состава и Σ снижает риск).
- Оптимистическая блокировка переходов. Параллельные
Register/Cancel/правка состава над одним
актом разрешаются по Version: проходит один, проигравший перечитывает статус, повтор отклоняется
guard-ом (REWARDS_RECEIPT_INVALID_STATUS_TRANSITION).
Индексы, хранение, безопасность
| Аспект | Значение |
|---|
| Индексы | PK; ux_platform_receipts_number unique btree; ix_platform_receipts_developer btree (developer_company_id, status); ix_platform_receipts_created_at btree; items: PK, ux_platform_receipt_items_accrual unique btree (commission_accrual_id), ix_platform_receipt_items_receipt btree, ix_platform_receipt_items_agency btree |
| Партиционирование / retention | none — акты создаются по платежам застройщиков (единицы в неделю на застройщика); retention бессрочный (финансовый документ) |
| Concurrency | оптимистическая блокировка на переходах; включение позиций — под блокировкой начислений (INV-RC-1/3) |
| Permissions / PII | Создание/состав/регистрация/отмена и чтение реестра — rewards.receipts.manage (платформенный scope, финансовый оператор ПИН — право независимо от прав арбитража сделок CRM, ADR-0067 п.5). Агентства и застройщики акты не видят — им виден эффект: статус MoneyReceived и PlatformReceiptId/MoneyReceivedAt своих начислений. Суммы — Sensitive-класс, в логи не пишутся. PII нет |
| Audit | Version + actor-stamping (RegisteredBy/CancelledBy) + доменные события; Registered-акт неизменяем — сам себе аудит-след |
Наблюдаемость
- Метрика возраста старейшего
Confirmed-начисления без акта —
rewards_accrual_awaiting_receipt_max_age_minutes (gauge) + алерт: оператор ПИН — узкое место, без
регистрации акта выплаты заблокированы (ADR-0067, риски).
- Счётчики регистраций/отмен актов; лог регистрации —
Id, Number, DeveloperCompanyId,
ItemsCount, id начислений (без сумм).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Entity / Aggregate: CommissionAccrualEntity, Entity / Aggregate: LedgerTransactionEntity (+ LedgerEntryEntity), Entity / Aggregate: ReferralAccrualEntity
- API (10): GET /api/v1/my/referral-accruals — Мои реферальные начисления (партнёр), GET /api/v1/platform/receipts — Реестр актов поступления средств, POST /api/v1/agency/commission-accruals/{accrualId}/adjust-distribution — Per-deal корректировка долей внутри пула, POST /api/v1/agency/payout-orders — Создание платёжного поручения, POST /api/v1/agency/payout-orders/{orderId}/mark-paid — Ручная отметка о выплате, POST /api/v1/developer/commission-accruals/{accrualId}/confirm — Подтверждение начисления застройщиком, POST /api/v1/platform/commission-accruals/{accrualId}/adjust — Корректировка суммы начисления (gross), POST /api/v1/platform/receipts — Создание акта поступления средств (черновик), POST /api/v1/platform/receipts/{receiptId}/register — Регистрация акта поступления средств, Rewards API — реестр операций и общие контрактные модели
- Use Cases (6): UC-RWD-009. Жизненный цикл реферального начисления (связь → начисление → гейт → выплата), UC-RWD-010. Акт поступления средств — гейт получения денег, UC-RWD-011. Корректировки начисления комиссии, UC-WLT-001. Получение денег кредитует балансы получателей, US-реестр: Реферальный партнёр (US-PRT), US-реестр: Финансовый блок ПИН (US-FIN)
- События (4): Event Contract: pin.rewards.commission-accrual-status-changed (CommissionAccrualStatusChangedKafkaEvent), Event Contract: pin.rewards.commission-money-received (CommissionMoneyReceivedKafkaEvent), Event Contract: pin.rewards.referral-accrual-created (ReferralAccrualCreatedKafkaEvent), Event Contract: pin.rewards.referral-accrual-status-changed (ReferralAccrualStatusChangedKafkaEvent)
- Консюмеры (3): Consumer: CRM Deal Cancelled, Consumer: Rewards Commission Money Received, Consumer: Rewards Referral Accrual Status Changed
- Саги (1): Saga «Commission Settlement» (финансовый контур вознаграждений)
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
- index.md (2): Rewards Service, Wallet Service
Связанные документы
- commission-accrual.md — позиции-начисления: статус
MoneyReceived, поля
MoneyReceivedAt/PlatformReceiptId, строки распределения.
- referral-accrual.md — каскад авто-апрува (
Pending → Approved, INV-RA-3).
- payout-order.md — выплаты после гейта (
Agent-строки из MoneyReceived,
рефералки из Approved).
- platform-commission-terms.md — строка
Platform в Distributions
события получения денег.
- Контракт события: ../events/commission-money-received.md;
статусные события —
../events/commission-accrual-status-changed.md,
../events/referral-accrual-status-changed.md.
- Snapshot-модели валюты: ../../handbook/domain/snapshots.md.
- ADR-0067 (гейт, батчевость,
неизменяемость), ADR-0066
(три строки распределения), ADR-0065 (кредиты кошелька), ADR-0052 (tenancy — платформенный процесс),
ADR-0048 (RUB-only), ADR-0056 (simplicity-first — акт вместо отдельного биллинг-сервиса).