Rewards Service
Назначение
rewards владеет финансовым контуром вознаграждений платформы PIN: правилами вознаграждений
застройщиков (процент от базовой или конечной цены с условиями-предикатами), условиями платформенной
комиссии (удержание ПИН в процентных пунктах ставки, ADR-0066), начислениями комиссий по завершённым
сделкам CRM (статусы «ожидает подтверждения / подтверждено / деньги получены / выплачено», распределение
тремя строками Platform/Agency/Agent), комиссионной политикой агентства (деление пула на
агента и агентство), реферальными ставками и реферальными начислениями партнёрам (ADR-0066), актами
поступления средств — гейтом всех выплат (ADR-0067), first-class корректировками сумм и долей,
платёжными поручениями (агентскими и реферальными) с историей выплат (исполнение платежей — реализация
отложена, решение №9) и Excel-выгрузками агентства (ADR-0041). Также отдаёт catalog расчёт «сколько
заработает агент на объекте недвижимости» (gross + net после удержания ПИН) и сводки комиссий для ЛК
агента/агентства; балансы получателей ведёт смежный контекст wallet по событиям rewards (ADR-0065).
Metadata
| Поле | Значение |
|---|---|
| Статус | draft |
| Owning team | PIN Platform (backend) |
| Domain | Каталог и деньги (market, см. service-boundaries.md) |
| Data classification | Sensitive (суммы комиссий/выплат) + Internal (правила) |
| Runtime criticality | medium (деньги считаются асинхронно; недоступность не блокирует сделки) |
Ownership
| Artifact | Owner/Rule |
|---|---|
| PostgreSQL schema | rewards |
| API areas | developer-admin (правила, подтверждение начислений ± корректировка), agency (политика, поручения, сводки, per-deal корректировка долей, Excel-выгрузки), agent (мои комиссии + личная user-centric статистика), partner (мои реферальные начисления), platform (условия платформы, реферальные ставки, акты поступления, gross-корректировки, реферальные поручения — Менеджер/оператор ПИН), internal (расчёт комиссии для catalog). Реестр операций — api/README.md |
| Owned entities | RewardRuleEntity, CommissionAccrualEntity (+CommissionDistributionEntity, +CommissionAccrualAdjustmentEntity), AgencyCommissionPolicyEntity, PayoutOrderEntity (+PayoutOrderItemEntity), PlatformCommissionTermsEntity, ReferralRateRuleEntity, ReferralAccrualEntity (+ReferralAccrualAdjustmentEntity), PlatformReceiptEntity (+PlatformReceiptItemEntity), AgentPerformanceEntity (user-centric read-проекция, ADR-0061 п.5); реплика связей rewards.referral_bond_replicas (consumers/referral-bond.md) |
| Produced events | pin.rewards.commission-rules.v1, pin.rewards.reward-rule-changed.v1, pin.rewards.commission-accrual-created.v1, pin.rewards.commission-accrual-status-changed.v1, pin.rewards.commission-money-received.v1, pin.rewards.referral-accruals.v1, pin.rewards.payout-order-status-changed.v1 |
| Consumed events | pin.crm.deal-completed.v1, pin.crm.deal-cancelled.v1, pin.referral.bonds.v1 (реплика реферальных связей), pin.crm.fixation-approved.v1 (счётчик фиксаций проекции), pin.identity.agent-membership-changed.v1 (состояние членства проекции), pin.chessboard.layout-upserted.v1 + pin.chessboard.layout-group-upserted.v1 (реплика справочника планировок); self-consume pin.rewards.commission-accrual-created.v1 / -status-changed.v1 для наполнения проекции AgentPerformance |
| Search indexes | none |
| Analytics facts | none (агрегации сводок — SQL по PostgreSQL, ADR-0056) |
Ответственность И Границы
- Что сервис делает: CRUD и активация правил вознаграждений застройщика; детерминированный подбор правила
для объекта недвижимости; условия платформенной комиссии (удержание ПИН 0.25–1.50 п.п., per-agency +
глобальный дефолт, asOf-резолюция и снапшот в начисление — ADR-0066); создание начислений по
deal-completed(комиссионного и реферального — двумя ветками одного consumer-а); подтверждение начислений застройщиком (± корректировка суммы) с распределением тремя строкамиPlatform/Agency/Agent(пул агентства делит политика); реферальные ставки (4 уровня специфичности, сроки действия) и реферальные начисления партнёрам; акты поступления средств — гейтMoneyReceivedвсех выплат (ADR-0067); first-class корректировки gross и долей с обязательной причиной и append-only аудитом; формирование/утверждение/отметка выплаты платёжных поручений двух видов (AgencyCommission/ReferralReward); сводки «получим/получили × подтверждено/ожидает/деньги получены» для ЛК агента и агентства; Excel-выгрузки агентства (два отчёта, async job + signed URL, ADR-0041); внутренний расчёт комиссии по объекту недвижимости дляcatalog(gross + удержание ПИН + пул); личная user-centric статистика агента — перестраиваемая проекцияAgentPerformance(агрегация поAgentUserIdсквозь все компании: одобренные фиксации, закрытые/сорванные сделки, начислено/выплачено, достижения; сохраняется при смене агентства, ADR-0061 п.5). - Что сервис не делает: не проводит платежи (исполнение отложено — ручная отметка
MarkPaid); не хранит банковские реквизиты (дом —profile); не ведёт балансы получателей — кошельки и ledger живут в контекстеwalletи наполняются событиямиrewards(ADR-0065); не владеет фактом реферальной связи «кто кого привёл» (дом —referral; вrewards— реплика и деньги); не владеет сделками (crm), объектами недвижимости/планировками (chessboard), пользователями/компаниями/членством (identity); не пересчитывает исторические снапшотыAgencyCompanyId/AgentUserIdсделок/лидов/фиксаций (иммутабельны, дом —crm, ADR-0061 п.4); agency-centric статистика воронки агентства (счётчики сделок в границах текущего агентства) — домcrm(get-statistics.md); здесь денежные сводки и user-centric проекция агента. - Source of truth: правила вознаграждений, условия платформы, реферальные ставки, начисления
(комиссионные и реферальные) с распределениями и корректировками, акты поступления, политики агентств,
платёжные поручения. Проекция
AgentPerformance— производна (не source of truth), перестраивается реплеем событий. - Реплики/projections: справочник планировок
Layout/LayoutGroup(id + принадлежность застройщику, консюмер consumers/chessboard-dimensions.md) — только для валидации условий правил; реферальные связиrewards.referral_bond_replicas(консюмер consumers/referral-bond.md) — основание реферальных начислений.
Зависимости
| Зависимость | Тип | Зачем Нужна | Failure mode |
|---|---|---|---|
PostgreSQL (schema rewards) | db | Все агрегаты, outbox | Недоступность = отказ API/consumer-ов; Kafka-события накапливаются, обработка догоняет |
| Kafka | queue | Consume crm/chessboard; produce события начислений/поручений | Lag = задержка начислений; идемпотентность по DealId защищает от дублей |
crm | service (events) | Источник завершённых/отменённых сделок с ценами и участниками; одобренные фиксации (pin.crm.fixation-approved.v1) для счётчика проекции AgentPerformance | Нет событий → нет новых начислений/счётчиков; ручное начисление доступно |
referral | service (events) | Факт реферальной связи «кто кого привёл» (pin.referral.bonds.v1) → реплика связей — основание реферальных начислений | Лаг синка → сделка обработана без рефералки; исправление — replay после догона реплики |
wallet | consumer наших событий | Балансы получателей: кредиты по commission-money-received/referral-accruals (Approved), дебеты по payout-order-status-changed (Paid) (ADR-0065) | Лаг consumer-а → баланс отстаёт от статусов rewards (окно секунды); данные rewards не затронуты |
chessboard | service (events) | Справочник Layout/LayoutGroup для валидации условий правил | Устаревшая реплика → активация правила с новой планировкой отклонится до прихода синка |
identity | service (JWT/permissions + events) | Акторы, tenant, permissions, состав пользователей агентства (валидация оверрайдов политики); события членства агента pin.identity.agent-membership-changed.v1 — текущее состояние членства (в агентстве/независимый) в проекции AgentPerformance (ADR-0061 п.5) | Нет авторизации → API недоступно; лаг membership-событий → устаревшее состояние членства в проекции (метрики не затронуты) |
catalog | consumer нашего API/событий | Показ комиссии агенту в витрине | Недоступность rewards → каталог скрывает блок комиссии (graceful) |
notifications | consumer наших событий | Уведомления о начислении/подтверждении/выплате | Потеря уведомлений не влияет на данные |
Проекты
src/services/rewards/
Pin.Rewards.Api/
Pin.Rewards.Application/
Pin.Rewards.Domain/
Pin.Rewards.Infrastructure/
Pin.Rewards.Background/
Pin.Rewards.Migrator/
Pin.Rewards.Contracts/
Основные Сущности
| Entity | Назначение | Source Of Truth | Документ |
|---|---|---|---|
RewardRuleEntity | Правило вознаграждения застройщика: база (базовая/конечная цена), процент, условия-предикаты (тип объекта/Layout/LayoutGroup/комнаты/площадь), приоритет, период | rewards | domain/reward-rule.md |
PlatformCommissionTermsEntity | Условия платформенной комиссии: удержание ПИН 0.25–1.50 п.п. ставки застройщика, per-agency + глобальный дефолт, asOf-резолюция по интервалам действия (ADR-0066) | rewards | domain/platform-commission-terms.md |
CommissionAccrualEntity (+ CommissionDistributionEntity, CommissionAccrualAdjustmentEntity) | Начисление комиссии по сделке: суммы, снапшоты правила и условий платформы, статусы PendingConfirmation → Confirmed → MoneyReceived → Paid | Cancelled, распределение тремя строками Platform/Agency/Agent, append-only аудит корректировок | rewards | domain/commission-accrual.md |
AgencyCommissionPolicyEntity | Комиссионная политика агентства: удержание в % или фиксированной суммой от пула (gross − удержание ПИН), персональные оверрайды по агентам | rewards | domain/agency-commission-policy.md |
ReferralRateRuleEntity | Ставка реферального вознаграждения: 4 уровня специфичности (партнёр×застройщик/партнёр/застройщик/глобально), период действия, непересечение внутри уровня (ADR-0066) | rewards | domain/referral-rate-rule.md |
ReferralAccrualEntity (+ ReferralAccrualAdjustmentEntity) | Реферальное начисление партнёру: Amount = FinalPrice × %, снапшот ставки, статусы Pending → Approved → Paid | Cancelled; Approved — автоматически по гейту денег родителя; сумма корректируема first-class путём до выплаты (append-only аудит) | rewards | domain/referral-accrual.md |
PlatformReceiptEntity (+ PlatformReceiptItemEntity) | Акт поступления средств RC-{yyyy}-{seq}: пакетный платёж застройщика ↔ покрытые начисления, Σ позиций = сумме акта; регистрация — атомарный гейт MoneyReceived (ADR-0067) | rewards | domain/platform-receipt.md |
PayoutOrderEntity (+ PayoutOrderItemEntity) | Платёжное поручение двух видов: AgencyCommission (доли агентов из MoneyReceived-начислений) / ReferralReward (Approved-рефералки, tenant — ПИН); статусы (ожидается/выплачено/отклонено), история выплат; исполнение отложено | rewards | domain/payout-order.md |
AgentPerformanceEntity | User-centric read-проекция личной статистики агента (ключ AgentUserId, вне tenant): пожизненные фиксации/сделки/комиссии + состояние членства + достижения; перестраиваема реплеем событий (ADR-0061 п.5) | — (проекция) | domain/agent-performance.md |
ER-обзор
API
Полный реестр операций (56) и общие контрактные модели — api/README.md. Карта областей:
- Developer-admin (
rewards.rules.manage,rewards.accruals.confirm/manage): правила вознаграждений (create/update/activate/deactivate/archive/list/test), очередь начислений, подтверждение (api/admin/confirm-commission-accrual.md — ± корректировка суммы), отказ, ручное начисление; - Agency (
rewards.commission-policy.manage,rewards.payouts.manage/approve,rewards.commission.view-all): политика распределения (create/update/activate/archive/list), начисления и сводки «получим/получили × подтверждено/ожидает/деньги получены», per-deal корректировка долей (api/agency/adjust-accrual-distribution.md), платёжные порученияAgencyCommission(create/update-items/approve/mark-paid/reject/cancel/list), Excel-выгрузки (api/agency/create-rewards-export.md + api/agency/get-rewards-export-job.md, ADR-0041); - Agent (
rewards.commission.view-own,rewards.payouts.view-own,rewards.performance.view-own): мои начисления, моя сводка, мои выплаты, личная user-centric статистика (ADR-0061 п.5); - Partner (
rewards.referral.view-own): мои реферальные начисления (api/partner/list-my-referral-accruals.md, record-level); - Platform (Менеджер/оператор ПИН, платформенный scope): условия платформы
(
rewards.platform-terms.view/manage), реферальные ставки (rewards.referral-rates.manage), акты поступления средств (rewards.receipts.manage— api/platform/register-platform-receipt.md), gross-корректировки (rewards.accruals.adjust— api/platform/adjust-commission-accrual.md), реестр/отмена реферальных начислений (rewards.accruals.manage) и корректировка их суммы (rewards.referral-rates.manage/rewards.accruals.adjust— api/platform/adjust-referral-accrual.md), реферальные порученияReferralReward(rewards.payouts.manage/approve); - Internal (для
catalog, s2s): api/internal/calculate-property-object-commission.md — применимое правило + gross-комиссия + удержание ПИН + пул агентства (net для витрины); - Callbacks: not applicable.
Events И Consumers
| Type | Name | Topic | Назначение | Link |
|---|---|---|---|---|
| Consumed | DealCompletedEvent | pin.crm.deal-completed.v1 | Две ветки: комиссионное начисление PendingConfirmation (правило + снапшот условий платформы) и реферальное начисление Pending (активная связь + ставка) | consumers/crm-deal-completed.md; контракт — ../crm/events/deal-completed.md |
| Consumed | DealCancelledEvent | pin.crm.deal-cancelled.v1 | Отмена начислений (CancelReason = DealCancelled) до гейта денег; после MoneyReceived — warning + ручная компенсация | consumers/crm-deal-cancelled.md |
| Consumed | ReferralBondCreatedKafkaEvent / ReferralBondClosedKafkaEvent | pin.referral.bonds.v1 | Реплика реферальных связей: создание/закрытие; закрытие прекращает новые начисления (созданные остаются) | consumers/referral-bond.md; контракты — ../referral/events/bond-created.md, ../referral/events/bond-closed.md |
| Consumed | FixationApprovedEvent | pin.crm.fixation-approved.v1 | Счётчик FixationsApprovedCount проекции AgentPerformance (user-centric) | consumers/crm-fixation-approved.md; контракт — ../crm/events/fixation-approved.md |
| Consumed | AgentMembershipChangedKafkaEvent | pin.identity.agent-membership-changed.v1 | Текущее состояние членства (в агентстве/независимый) в проекции AgentPerformance; метрики не трогает (ADR-0061 п.5) | consumers/agent-membership-changed.md; контракт — ../identity/events/agent-membership-changed.md |
| Consumed (self) | CommissionAccrualCreatedKafkaEvent / CommissionAccrualStatusChangedKafkaEvent | pin.rewards.commission-accrual-created.v1 / -status-changed.v1 | Наполнение сделочных/денежных счётчиков проекции AgentPerformance (перестраиваемо реплеем; MoneyReceived — no-op) | consumers/agent-performance-projection.md |
| Consumed | LayoutUpsertedKafkaEvent / LayoutGroupUpsertedKafkaEvent | pin.chessboard.layout-upserted.v1 / pin.chessboard.layout-group-upserted.v1 | Реплика справочника Layout/LayoutGroup для валидации условий правил | consumers/chessboard-dimensions.md |
| Produced | CommissionRulesChangedKafkaEvent | pin.rewards.commission-rules.v1 | catalog: per-developer снапшот набора активных правил (key = DeveloperCompanyId) → замена набора + батч-пересчёт диапазона комиссии (основной канал, ADR-0068) | domain/reward-rule.md → events/commission-rules.md |
| Produced | RewardRuleChangedKafkaEvent | pin.rewards.reward-rule-changed.v1 | catalog: per-rule дельта того же изменения (key = Id) | domain/reward-rule.md → events/reward-rule-changed.md |
| Produced | CommissionAccrualCreatedKafkaEvent | pin.rewards.commission-accrual-created.v1 | notifications (уведомление агенту/агентству), crm (таймлайн сделки) | events/commission-accrual-created.md |
| Produced | CommissionAccrualStatusChangedKafkaEvent | pin.rewards.commission-accrual-status-changed.v1 | notifications: подтверждение/получение денег/выплата/отмена | events/commission-accrual-status-changed.md |
| Produced | CommissionMoneyReceivedKafkaEvent | pin.rewards.commission-money-received.v1 | wallet — кредиты счетов по Distributions[] (Platform/Agency/Agent, ADR-0065); notifications | events/commission-money-received.md |
| Produced (условно) | CommissionDistributionAdjustedKafkaEvent | pin.rewards.commission-distribution-adjusted.v1 | wallet — авто-Adjustment, перенос дельты долей Agency↔Agent при корректировке распределения в MoneyReceived (ADR-0065) | events/commission-distribution-adjusted.md |
| Produced | ReferralAccrualCreatedKafkaEvent / ReferralAccrualStatusChangedKafkaEvent | pin.rewards.referral-accruals.v1 (общий топик, key = id начисления) | notifications (партнёру); wallet — кредит ReferralCommission по Approved, Correction по отмене после Approved | events/referral-accrual-created.md, events/referral-accrual-status-changed.md |
| Produced | PayoutOrderStatusChangedKafkaEvent | pin.rewards.payout-order-status-changed.v1 | notifications: статусы выплат получателям и владельцу поручения; wallet — дебет счетов по Paid (Items[]) | events/payout-order-status-changed.md |
Все интеграционные события несут версию контракта (ContractVersion) и публикуются через транзакционный
outbox — атомарно с изменением агрегата, в одном согласованном изменении.
Сквозные Потоки (интеграционные последовательности)
Жизненный цикл денег проходит через несколько сервисов; ниже — кто кому что шлёт и в каком порядке (sync — синхронный вызов/ответ пользователю, async — событие через шину; окна eventual-consistency — секунды). Сквозная хореография с компенсациями — sagas/commission-settlement-saga.md; детали отказов и кодов — в документах операций/консюмеров.
- Начисления по завершённой сделке (async).
crmзавершает сделку → публикуетpin.crm.deal-completed.v1→ консюмерrewardsдвумя ветками одного коммита: подбирает правило застройщика, считает grossCommissionAmount, резолвит условия платформы наCompletedAtи снимает снапшоты (правило +PlatformRetentionPercentagePoints/PlatformFeeAmount), создаёт начислениеPendingConfirmation(идемпотентность поDealId, INV-1); при активной реферальной связи инвестора и применимой ставке — реферальное начислениеPending(INV-RA-1) → публикуетpin.rewards.commission-accrual-created.v1иpin.rewards.referral-accruals.v1→ потребители:notifications,crm(таймлайн сделки), self-consumerewards(проекцияAgentPerformance). Правило не найдено — комиссионного начисления нет (warning +rewards_accrual_no_rule_total, ручное начисление доступно); удержание ≥ ставки правила — событие в DLQ (INV-PT-4). - Подтверждение застройщиком (sync + async). Застройщик подтверждает начисление (cross-tenant по
DeveloperCompanyId, ADR-0052), опционально скорректировав сумму с причиной (INV-11) → строится распределение тремя строками:Platform(снапшот условий),Agency/Agent(деление пула по активной политике или fallback) → статусConfirmed→ публикуетсяpin.rewards.commission-accrual-status-changed.v1→notifications. Выплаты ещё недоступны — гейт денег не пройден. Отказ застройщика ведёт начисление вCancelled(DeveloperRejected) тем же событием статуса. - Гейт получения денег (sync + async, ADR-0067). Финансовый оператор ПИН заводит акт поступления
средств по пакетному платежу застройщика и регистрирует его → атомарный каскад: начисления-позиции
Confirmed → MoneyReceived, рефералки родителейPending → Approved, публикуютсяpin.rewards.commission-money-received.v1(сDistributions[]) и статусные события →walletкредитует счета получателей (AgentCommission/AgencyCommission/PlatformRevenue/ReferralCommission, ADR-0065),notificationsуведомляет стороны. До регистрации акта никакие выплаты недоступны. - Выплата (sync + async). Агентство формирует и утверждает поручение
AgencyCommissionпо свободнымAgent-долям начислений вMoneyReceived(оператор ПИН — поручениеReferralRewardпоApproved-рефералкам) → отметкаMarkPaid(исполнение платежа вне системы, решение №5) → строки получаютPaidAt, начисления каскадно переходят вPaid(INV-7) → публикуетсяpin.rewards.payout-order-status-changed.v1(Items[]) →walletдебетует счета получателей,notificationsуведомляет. - Отмена/расторжение сделки (async).
crmпубликуетpin.crm.deal-cancelled.v1→ консюмерrewardsотменяет начисления (CancelReason = DealCancelled), пока гейт денег не пройден; изMoneyReceived/Paidотмена запрещена — warning + алерт, компенсация отдельным ручным корректирующим начислением (финансовая история иммутабельна);Approved-рефералка отменяется с компенсацией кошелька (Correction). - Изменение правила → каталог (async). Активация/деактивация/архивирование/смена
EffectiveToправила → per-developer снапшот набораpin.rewards.commission-rules.v1(key =DeveloperCompanyId, основной канал, ADR-0068) + per-rule дельтаpin.rewards.reward-rule-changed.v1(key =Id) →catalogатомарно заменяет денормализованный набор правил застройщика и пересчитывает диапазон комиссии в витрине (окно секунды). Внутренний расчётrewards(api/internal/calculate-property-object-commission.md) отражает новое правило сразу после коммита — в окне рассинхронизации витрина может показывать прежнюю комиссию. - Реферальная связь (async).
referralпубликуетpin.referral.bonds.v1→ консюмерrewardsведёт реплику связей; закрытие связи прекращает новые начисления, созданные остаются (фрод — ручная отменаBondRevoked). - Смена агентства агента (async).
identityпубликуетpin.identity.agent-membership-changed.v1→ консюмерrewardsобновляет текущее состояние членства в проекцииAgentPerformance; денежные и сделочные метрики проекции не пересчитываются (историческая привязка иммутабельна, ADR-0061 п.4).
Runtime И Эксплуатация
| Область | Требование |
|---|---|
| Health checks | /health/live, /health/ready (PostgreSQL + Kafka), /health/startup — стандарт Pin.Kernel |
| SLO/SLA | API p95 < 300 мс; лаг consumer-а deal-completed < 1 мин; расчёт комиссии для catalog p95 < 100 мс |
| Dashboard | Grafana rewards-overview (создаётся при внедрении) |
| Alerts | lag consumer-group > 5 мин; рост rewards_accrual_no_rule_total (сделки без правила); rewards_platform_terms_missing_total (fallback 0 п.п. — платформа теряет доход); rewards_accrual_awaiting_receipt_max_age_minutes выше порога (оператор ПИН — узкое место гейта, ADR-0067); DLQ > 0 (в т.ч. REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE); ошибки outbox |
| Runbooks | runbooks/rewards-accrual-replay.md (по конвенции — повторная обработка deal-completed) |
| Feature flags | none |
Безопасность
- Основные actor types: менеджер застройщика (правила, подтверждение ± корректировка), руководитель/администратор агентства (политика, поручения, сводки, корректировка долей, выгрузки), агент (свои комиссии), реферальный партнёр (свои начисления), Менеджер ПИН (условия платформы, ставки, gross-корректировки), финансовый оператор ПИН (акты поступления, реферальные поручения), system-actor consumer-ов.
- Permission model:
rewards.rules.{view,manage},rewards.accruals.{confirm,manage,adjust},rewards.commission-policy.{view,manage},rewards.payouts.{view-own,manage,approve},rewards.commission.{view-own,view-all,view-property-object},rewards.platform-terms.{view,manage},rewards.referral-rates.manage,rewards.receipts.manage,rewards.referral.view-own,rewards.performance.view-own(личная user-centric статистика агента) — каталог permissions: ../identity/domain/permission.md. - Sensitive data classes: суммы комиссий/выплат/удержаний (не логируются, доступ по ролям);
InvestorUserIdмаскируется в проекциях агентства и не отдаётся партнёру; условия платформы и ставки видны только платформенному scope (агентству/партнёру — применённые снапшоты); банковские реквизиты не хранятся; выгрузки — auditExportSensitive(ADR-0041). - Audit requirements: версионирование (
Version) + actor-stamping на всех агрегатах; финансовые переходы — события статусов; корректировки сумм и долей — append-only строкиCommissionAccrualAdjustmentEntityс обязательной причиной;Registered-акт неизменяем; начисления, акты и поручения не удаляются (отмена — статус). - Tenant isolation rule (ADR-0052): правила — tenant застройщика; начисления/политики/агентские
поручения — tenant агентства с явным
DeveloperCompanyIdдля cross-tenant доступа застройщика (подтверждение) через отдельный permission-фильтрованный query-path; условия платформы, ставки, акты, реферальные начисления — платформенные сущности вне tenant-модели (реферальные поручения — tenant компании ПИН); агент — record-level поAgentUserId, партнёр — поPartnerUserId.
Принятые Решения
| # | Вопрос | Решение |
|---|---|---|
| 1 | Конфликт правил при пересечении условий | Пересечение допустимо; выигрывает наибольший Priority, уникальный среди активных правил застройщика (partial-unique index) — детерминированно без проверки непересечения предикатов |
| 2 | Tenant начисления (двусторонняя сущность) | TenantId = агентство (владелец процесса получения/распределения/выплаты); застройщик — явный DeveloperCompanyId + cross-tenant permission (ADR-0052) |
| 3 | Сделка завершена, правило не найдено | Начисление не создаётся: warning + метрика rewards_accrual_no_rule_total; доступно ручное начисление застройщиком |
| 4 | Воспроизводимость расчёта | Snapshot правила (typed jsonb AppliedRuleSnapshotDocument) и AppliedPolicyId фиксируются в начислении; правила/политики иммутабельны в Active |
| 5 | Исполнение платежей | Отложено (решение №9 ТЗ): полная структура поручений + ручной MarkPaid; реквизиты — из profile при будущей интеграции |
| 6 | Хранилища | PostgreSQL-only, без Redis/ClickHouse (ADR-0056); сводки ЛК — SQL-агрегации по индексам (tenant_id, agent_user_id, status, deal_completed_at) |
| 7 | Дом личной статистики агента (ADR-0061 п.5) | rewards — здесь агрегируются деньги/сделки/фиксации. User-centric проекция AgentPerformance (ключ AgentUserId, вне tenant, платформенный scope агента), перестраиваемая реплеем; членство — из событий identity. Agency-centric отчёт воронки остаётся в crm (get-statistics, якорь AgencyCompanyId) |
| 8 | Иммутабельность исторической привязки (ADR-0061 п.4) | rewards не пересчитывает и не back-fill-ит снапшоты AgencyCompanyId/AgentUserId сделок/фиксаций при смене агентом агентства: основание комиссии — компания-владелец на момент события; проекция считает по AgentUserId, не переписывая историю crm |
| 9 | Модель удержания ПИН (ADR-0066) | Только процентные пункты ставки застройщика (0.25–1.50 п.п.), не процент от суммы комиссии; условия per-agency + глобальный дефолт, asOf-резолюция по CompletedAt, снапшот в начисление; третья строка распределения Platform, политика агентства делит пул (gross − удержание) |
| 10 | Реферальные ставки и начисления (ADR-0066) | Ставки — 4 уровня специфичности (PartnerDeveloper > Partner > Developer > Global) со сроками действия и непересечением внутри уровня; реферальное начисление — отдельная сущность (не kind-дискриминатор): другой получатель, свой жизненный цикл, база — FinalPrice; владелец денег — rewards, referral — только факт связи |
| 11 | Гейт получения денег (ADR-0067) | Статус MoneyReceived — гейт всех выплат (агентских и реферальных); переход — только регистрацией батчевого «Акта поступления средств» (RC-{yyyy}-{seq}, Σ позиций = сумме акта, атомарный каскад + авто-Approve рефералок); Registered-акт неизменяем, откатов нет — только компенсирующие начисления |
| 12 | Корректировки — first-class (ADR-0067 п.4) | Три вида: застройщик при подтверждении / ПИН-админ gross до гейта денег / агентство — доли пула per-deal; каждая — с обязательной причиной и append-only аудит-строкой; строка Platform неприкосновенна; после MoneyReceived gross неизменяем |
| 13 | Excel-выгрузки агентства (ADR-0041) | Два отчёта (по сотрудникам-сделкам / сводный за период) асинхронной задачей: лимиты (период ≤ 1 года, 100k строк, 2 активные задачи), XLSX в приватном хранилище (retention 7 дней), скачивание по signed URL (TTL 1 час), audit ExportSensitive |
Известные Ограничения И Assumptions
- Событие
pin.crm.deal-completed.v1обязано нести:DealId,DeveloperCompanyId,AgencyCompanyId,AgentUserId?,InvestorUserId,PropertyObjectId, атрибуты объекта недвижимости (PropertyType,LayoutId?,LayoutGroupId?,Rooms?,AreaTotalM2),BasePriceиFinalPrice(Amount+CurrencyId+CurrencySnapshot— ../handbook/domain/snapshots.md),CompletedAt— контракт ../crm/events/deal-completed.md. - Сделка без агента (инвестор напрямую) комиссионного начисления не порождает (consumer пропускает по
отсутствию
AgentUserId); реферальная ветка при этом выполняется. - Валюта платформы — RUB (ADR-0048);
CurrencyId— типизированная ссылка на справочник валют, не мультивалютность (guardREWARDS_CURRENCY_MISMATCH). - Пересчёт подтверждённых начислений при смене политики агентства, условий платформы или реферальных ставок не выполняется (применённые значения зафиксированы снапшотами).
- Fixed-удержание больше пула срезается до пула (доля агента = 0) — задокументированный edge-case, не ошибка.
- Частичные оплаты акта поступления — вне scope v1: акт формируется только на полностью покрытые начисления; частичное покрытие одного начисления не моделируется (ADR-0067).
- Исполнение платежей — вне scope (решение №9 ТЗ): полная структура поручений + ручной
MarkPaid; интеграция с банком/эквайрингом отложена, реквизиты при её включении — изprofile. - Обход guard-а самореферала через вторую учётку — осознанное ограничение v1 (INV-RA-4); инструмент
разбора — admin-коррекции связей в
referral.
Связанные Документы
- technical-assignment.md (строка контекста
rewards), service-boundaries.md, glossary.md («Правило вознаграждения», «Комиссионная политика агентства», «Условия платформы», «Акт поступления средств», «Реферальное начисление»). - Домен: domain/reward-rule.md, domain/commission-accrual.md, domain/platform-commission-terms.md, domain/agency-commission-policy.md, domain/referral-rate-rule.md, domain/referral-accrual.md, domain/platform-receipt.md, domain/payout-order.md, domain/agent-performance.md (user-centric проекция агента, ADR-0061 п.5).
- Сквозной процесс: sagas/commission-settlement-saga.md; отчёты — reports/agency-rewards-by-employee-per-deal.md, reports/agency-rewards-period-summary.md, reports/referral-rewards-by-partner.md, reports/referral-rewards-period-summary.md.
- Смежные контексты:
../crm/(сделки, фиксации, agency-centric статистика), ../wallet/README.md (балансы и ledger по событиямrewards, ADR-0065),../referral/(факт связи «кто кого привёл»),../chessboard/(планировки),../catalog/(показ комиссии),../profile/(реквизиты),../identity/(permissions, события членства агента — ../identity/events/agent-membership-changed.md), ../handbook/domain/snapshots.md (CurrencyId + CurrencySnapshot в событиях с деньгами). - ADR-0052 (tenancy), ADR-0056 (simplicity-first), ADR-0061 (независимый агент, user-centric метрики), ADR-0065 (кошелёк), ADR-0066 (удержание ПИН, реферальные ставки), ADR-0067 (гейт получения денег, корректировки), ADR-0041 (выгрузки), ADR-0048 (RUB-only).
- Шаблоны: service-readme-template.md, entity-template.md; правила: cross-cutting-document-requirements.md, algorithm-documentation-rules.md.