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

Entity / Aggregate: PlatformCommissionTermsEntity

Дом полей агрегата «Условия платформенной комиссии (удержание ПИН)» контекста rewards. Проекции (модели ответов platform-API, снапшот применения в начислении) ссылаются сюда и описывают только дельту.

Назначение

PlatformCommissionTermsEntity — настройка Менеджера ПИН, определяющая долю платформы в комиссии по завершённой сделке: сколько процентных пунктов ставки застройщика ПИН удерживает в свою пользу (ADR-0066). Модель монетизации — только пункты ставки (не процент от суммы комиссии): при ставке застройщика 10% и удержании 1.00 п.п. платформа получает 1% от цены сделки, пул агентской стороны — 9%.

Условия задаются на пересечении измерений специфичности (агентство AgencyCompanyId? и ось объекта сделки DeveloperCompanyId?ProjectId?LayoutGroupId?; все null = глобальный дефолт) и периода действия (EffectiveFrom/EffectiveTo?) — единый scoped+time-bounded паттерн (ADR-0068). Период даёт выразить временные акции ПИН — понижение удержания на срок промо — обычной записью с ограниченным EffectiveTo, без отдельных промо-флагов. Условия применяются при создании начисления (commission-accrual.md) consumer-ом завершения сделки (../consumers/crm-deal-completed.md): резолвится наиболее специфичная активная запись на момент asOf = CompletedAt сделки, её снапшот (область + период + процент) снимается в начисление — последующая смена условий существующие начисления не пересчитывает. Политика агентства (agency-commission-policy.md) делит пул (gross − удержание ПИН), а не gross.

Классификация

ПолеЗначение
Контекстrewards
KindAggregateRoot
Source of truthrewards
Таблицаrewards.platform_commission_terms
Tenant isolationвне tenant-модели — платформенная сущность (владелец процесса — ПИН, ADR-0052); управление — платформенный scope

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappУникальный идентификатор условий; на него ссылается AppliedPlatformTermsId начисления.
AgencyCompanyIdGuid?Нетindex ix_platform_terms_agency (agency_company_id) where not null; логическая ссылка на identity.companies.id (Type ∈ {Agency, Broker}), без FKappИзмерение специфичности: агентство, для которого действуют условия; null — по всем агентствам.
DeveloperCompanyIdGuid?Нетindex ix_platform_terms_developer (developer_company_id) where not null; логическая ссылка на identity.companies.id (Type = Developer), без FKappИзмерение специфичности: застройщик объекта сделки; null — по всем застройщикам.
ProjectIdGuid?Нетindex ix_platform_terms_project (project_id) where not null; логическая ссылка на проект каталога (catalog/chessboard), без FKappИзмерение специфичности: проект объекта сделки; null — по всем проектам застройщика.
LayoutGroupIdGuid?Нетindex ix_platform_terms_layout_group (layout_group_id) where not null; логическая ссылка на группу планировок (catalog/chessboard), без FKappИзмерение специфичности (самое узкое по оси объекта): группа планировок объекта сделки; null — по всем группам проекта.
RetentionPercentagePointsdecimalДаnumeric(4,2), check retention_percentage_points >= 0.25 AND retention_percentage_points <= 1.50appУдержание ПИН в процентных пунктах ставки застройщика (INV-PT-1).
EffectiveFromDateTimeOffsetДаcheck effective_to is null or effective_to > effective_fromappНачало периода действия.
EffectiveToDateTimeOffset?НетappКонец периода действия (исключительно); null — бессрочно.
Commentstring?Нетvarchar(2000)appКомментарий Менеджера ПИН (основание, договорённость, условия акции).
StatusPlatformCommissionTermsStatusДахранится как text, varchar(32), index ix_platform_terms_status (status)appDraft / Active / Archived (stateDiagram ниже).
CreatedAtDateTimeOffsetДаindexappМомент создания (аудит).
CreatedByUserIdGuid?НетappМенеджер ПИН, создавший условия.
UpdatedAtDateTimeOffsetДаappМомент изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
VersionlongДа>= 1appДоменная версия (инкремент при мутации).

Enum PlatformCommissionTermsStatus

ЗначениеОписание
DraftЧерновик: редактируется, в резолюции удержания не участвует.
ActiveДействующие условия: участвуют в резолюции (с учётом периода EffectiveFrom/EffectiveTo и измерений специфичности). Иммутабельны, кроме EffectiveTo (INV-PT-3).
ArchivedАрхив: выведены из резолюции навсегда; ссылки AppliedPlatformTermsId из начислений живы, снятый снапшот остаётся источником истины для исторических начислений.

Enum PlatformTermsScope (специфичность; не хранится — выводится из заполненности измерений)

Специфичность условий определяется заполненностью двух осей: оси объекта сделки (DeveloperCompanyIdProjectIdLayoutGroupId, от широкого к узкому) и измерения агентства (AgencyCompanyId). При резолюции сравниваются лексикографически: сначала глубина по оси объекта (LayoutGroup > Project > Developer > нет), при равной глубине — наличие агентства (задано уточняет запись «по всем агентствам»). Итоговый порядок «наиболее специфичное побеждает» — LayoutGroup > Project > Developer > Agency > Global, с агентским уточнением на каждом уровне объекта:

ЗначениеAgencyCompanyIdDeveloperCompanyIdProjectIdLayoutGroupIdРангОписание
AgencyLayoutGroupзаданзаданзаданзадан8Удержание по группе планировок для конкретного агентства — максимальная специфичность.
LayoutGroupnullзаданзаданзадан7Удержание по группе планировок для всех агентств.
AgencyProjectзаданзаданзаданnull6Удержание по проекту для конкретного агентства.
Projectnullзаданзаданnull5Удержание по проекту для всех агентств.
AgencyDeveloperзаданзаданnullnull4Удержание по застройщику для конкретного агентства.
Developernullзаданnullnull3Удержание по застройщику для всех агентств.
Agencyзаданnullnullnull2Удержание для конкретного агентства по всем застройщикам.
Globalnullnullnullnull1Глобальный дефолт — страховочный уровень.

Ось объекта заполняется сверху вниз: ProjectId без DeveloperCompanyId или LayoutGroupId без ProjectId/DeveloperCompanyId недопустимы (INV-PT-5).

Резолюция условий и расчёт удержания

Выполняется при создании начисления по завершённой сделке (../consumers/crm-deal-completed.md). Вход: AgencyCompanyId сделки и координаты объекта (DeveloperCompanyId, ProjectId, LayoutGroupId — из снимка объекта в событии завершения, ADR-0068), применённое правило застройщика (CalculationBase, CommissionPercentreward-rule.md), суммы цен, момент asOf = CompletedAt сделки.

  1. Кандидаты. Отобрать условия: Status = Active, период покрывает asOf (EffectiveFrom <= asOf и (EffectiveTo is null или EffectiveTo > asOf)), измерения совместимы со сделкой — каждое nullable-измерение либо null (шире), либо равно значению сделки (AgencyCompanyId is null или = агентству; DeveloperCompanyId is null или = застройщику; ProjectId is null или = проекту; LayoutGroupId is null или = группе планировок). Записей единицы — выборка целиком (ADR-0056).
  2. Наиболее специфичная комбинация. Выбрать кандидата с максимальным рангом специфичности PlatformTermsScope (AgencyLayoutGroup → … → Global; таблица выше). Внутри одной комбинации измерений кандидат ровно один — периоды активных условий одной специфичности не пересекаются (INV-PT-2), поэтому резолюция детерминирована.
  3. Fallback 0 п.п. Если подошедших условий нет — начисление создаётся без удержания: PlatformRetentionPercentagePoints = 0, PlatformFeeAmount = 0, AppliedPlatformTermsId = null, строка распределения Platform при подтверждении строится с нулевой суммой. Инкрементируется warn-метрика rewards_platform_terms_missing_total + алерт (платформа теряет доход, пока условия не заведены; глобальная бессрочная запись — страховочный уровень).
  4. Guard конфигурации (INV-PT-4). Если RetentionPercentagePoints >= CommissionPercent применённого правила (удержание съедает ставку целиком, пул ≤ 0) — начисление не создаётся, событие уходит в DLQ с кодом REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE (ручной разбор Менеджером ПИН: правится либо правило застройщика, либо условия; после исправления событие переигрывается из DLQ).
  5. Расчёт. PlatformFeeAmount = база × RetentionPercentagePoints / 100 (banker's rounding, 2 знака); база — та же, что у применённого правила (CalculationBase: базовая или конечная цена). Пул агентской стороны = CommissionAmountPlatformFeeAmount; пул делит политика агентства (agency-commission-policy.md).

В начисление снимается снапшот: PlatformRetentionPercentagePoints, PlatformFeeAmount, AppliedPlatformTermsId, а также применённая область (заполненные измерения → Scope) и период (EffectiveFrom/EffectiveTo) — дом полей: commission-accrual.md. Снапшот делает начисление объяснимым постфактум («удержание 0.50 п.п. применено — акция проекта X, период 01.04–30.06»). При подтверждении начисления строится строка распределения Platform (RecipientUserId = null) — ровно одна, наравне со строками Agency и Agent; строка Platform неприкосновенна для агентства (per-deal корректировка распределения перераспределяет только пул, gross и удержание не пересчитываются).

Value objects

VO не используются: условия хранят только пункты ставки (decimal), денежные суммы появляются в начислении (PlatformFeeAmountcommission-accrual.md).

Модели JSONB

JSONB-модели не используются.

Связи

СвязьТипКлючDelete behavior
CompanyEntity (агентство, identity)reference (логическая, без FK)AgencyCompanyIdrestrict
CompanyEntity (застройщик, identity)reference (логическая, без FK)DeveloperCompanyIdrestrict
Проект каталога (catalog/chessboard)reference (логическая, без FK)ProjectIdrestrict
Группа планировок (catalog/chessboard)reference (логическая, без FK)LayoutGroupIdrestrict
CommissionAccrualEntityreference (обратная)commission_accruals.applied_platform_terms_id → platform_commission_terms.idrestrict — условия с применениями только архивируются

Инварианты и переходы состояний

ИнвариантУсловиеГде проверяетсяОшибка
INV-PT-1 RetentionRange0.25 <= RetentionPercentagePoints <= 1.50 (numeric(4,2))проверка входа + db checkREWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE
INV-PT-2 NoOverlapWithinSpecificityПериоды [EffectiveFrom, EffectiveTo|∞) активных условий с одинаковым набором заполненных измерений (AgencyCompanyId, DeveloperCompanyId, ProjectId, LayoutGroupId), включая null-значения, не пересекаются — на любой момент внутри одной специфичности активна не более чем одна запись, резолюция детерминированаоперация Activate, app-level: активные условия той же комбинации измерений читаются под блокировкой, пересечение периодов отклоняетсяREWARDS_PLATFORM_TERMS_OVERLAP
INV-PT-3 ImmutableWhenActiveВ Active меняется только EffectiveTo — досрочное завершение, только сокращение периода (не раньше текущего момента; сокращение пересечений не создаёт); измерения и RetentionPercentagePoints — только в Draft (сохраняет воспроизводимость снапшотов начислений)guard по Status в операции измененияREWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE
INV-PT-4 RetentionBelowRulePercentRetentionPercentagePoints < CommissionPercent применённого правила застройщика — пул агентской стороны строго положителенсоздание начисления (consumer завершения сделки, шаг 4 резолюции)REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE → DLQ, ручной разбор
INV-PT-5 ObjectAxisTopDownОсь объекта заполняется сверху вниз: ProjectId требует DeveloperCompanyId, LayoutGroupId требует ProjectId (иначе комбинация измерений некорректна)проверка входа при Create/ActivateREWARDS_PLATFORM_TERMS_SCOPE_INVALID

Коды ошибок

КодHTTPУсловие
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE400Пункты удержания вне диапазона 0.25–1.50 (INV-PT-1).
REWARDS_PLATFORM_TERMS_PERIOD_INVALID400EffectiveTo <= EffectiveFrom.
REWARDS_PLATFORM_TERMS_SCOPE_INVALID400Ось объекта заполнена не сверху вниз (INV-PT-5).
REWARDS_PLATFORM_TERMS_OVERLAP409Пересечение периодов активных условий одной специфичности при активации (INV-PT-2).
REWARDS_PLATFORM_TERMS_ACTIVE_IMMUTABLE409Попытка изменить Active-условия сверх сокращения EffectiveTo (INV-PT-3).
REWARDS_PLATFORM_TERMS_NOT_FOUND404Условия не найдены (операции platform-API).
REWARDS_PLATFORM_TERMS_INVALID_STATUS_TRANSITION409Недопустимый переход статуса (например, Activate архивной записи).
REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE— (DLQ)Удержание ≥ ставки применённого правила при создании начисления (INV-PT-4); событие в DLQ, начисление не создано.

Доменные и интеграционные события

СобытиеТипКогдаSnapshot/поля
PlatformTermsCreatedEventдоменноеУспешный CreateId, AgencyCompanyId?, DeveloperCompanyId?, ProjectId?, LayoutGroupId?
PlatformTermsActivatedEventдоменноеDraft → ActiveId, Scope, RetentionPercentagePoints, EffectiveFrom/EffectiveTo
PlatformTermsArchivedEventдоменное* → ArchivedId, actor

Интеграционные Kafka-события не публикуются — условия применяются только внутри контекста rewards (при создании начисления); внешним потребителям их изменения не нужны.

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK; ix_platform_terms_status btree; ix_platform_terms_agency btree (agency_company_id) where not null; ix_platform_terms_developer btree (developer_company_id) where not null; ix_platform_terms_project btree (project_id) where not null; ix_platform_terms_layout_group btree (layout_group_id) where not null; ix_platform_terms_created_at btree. Непересечение периодов (INV-PT-2) — app-level при Activate (записей единицы, ADR-0056)
Партиционирование / retentionnone — единицы записей на комбинацию измерений + глобальный дефолт; retention бессрочный (asOf-резолюция по историческим периодам, исторические снапшоты ссылаются)
Concurrencyоптимистическая блокировка (правка не должна затирать чужую) + блокировка активных условий той же комбинации измерений при Activate (INV-PT-2)
Permissions / PIIУправление — rewards.platform-terms.manage, чтение реестра — rewards.platform-terms.view (платформенный scope, Менеджер ПИН). Агентству сами условия не отдаются — применённое удержание видно в снапшоте начисления (PlatformRetentionPercentagePoints, PlatformFeeAmount). PII нет
AuditVersion + actor-stamping; история условий = записи со своими периодами EffectiveFrom/EffectiveTo и специфичностью, применённая версия зафиксирована AppliedPlatformTermsId (+ снапшот области/периода) в начислении

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

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

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

  • commission-accrual.md — точка применения (снапшот-поля PlatformRetentionPercentagePoints/PlatformFeeAmount/AppliedPlatformTermsId, строка распределения Platform).
  • agency-commission-policy.md — политика агентства делит пул (gross − удержание ПИН).
  • reward-rule.md — ставка застройщика (CommissionPercent, CalculationBase) — источник guard-а INV-PT-4.
  • Consumer-точка резолюции: ../consumers/crm-deal-completed.md.
  • Валюта денежных полей начисления: CurrencyId short + CurrencySnapshot../../handbook/domain/snapshots.md.
  • ADR-0068 (единый scoped+time-bounded паттерн: ось Developer → Project → LayoutGroup, период, непересечение, снапшот области/периода; замещает пункт ADR-0066 «per-developer override вне scope v1»), referral-rate-rule.md (эталон периодной резолюции того же паттерна).
  • ADR-0066 (модель удержания, три строки распределения), ADR-0067 (гейт MoneyReceived), ADR-0052 (tenancy — платформенная сущность), ADR-0048 (денежная арифметика, RUB-only).