Entity / Aggregate: AgencyCommissionPolicyEntity
Дом полей агрегата «Комиссионная политика агентства» контекста rewards. Проекции (модели ответов API,
snapshot применения в начислении) ссылаются сюда и описывают только дельту.
Назначение
AgencyCommissionPolicyEntity — настройка руководителя агентства, определяющая, как комиссия по
завершённой сделке распределяется между агентом-исполнителем и самим агентством (матрица «Агенство»
5.2: «установка размера комиссии — в % или фиксированной суммой — которая удерживается из вознаграждения
агента в пользу агентства»; 5.3: «автоматический расчёт: сколько заработал агент, сколько удержано
агентством, сколько выплачено»).
Политика применяется в момент подтверждения начисления застройщиком (Confirm в
commission-accrual.md, INV-4/INV-5/INV-10) и делит пул агентства —
CommissionAmount − PlatformFeeAmount (удержание ПИН снимается до политики по снапшоту условий
платформы, ADR-0066) — между строками Agency (удержание агентства) и Agent (остаток пула). Строка
Platform трёхстрочного распределения формируется вне политики и от её настроек не зависит. По документу
правил выбирается удержание для конкретного агента (персональный оверрайд или правило по умолчанию).
У агентства одновременно одна активная политика (INV-1); смена политики не пересчитывает уже
подтверждённые начисления — в начислении хранится AppliedPolicyId. Точечная правка долей конкретного
начисления — per-deal корректировка распределения (INV-11 начисления), не через политику.
Политика поддерживает две ортогональные возможности поверх базовой retention-модели:
- Прямое выражение доли агента. Правило распределения (
AgencyRetentionRule) задаёт либо удержание
агентства (Percent/FixedAmount — агенту достаётся остаток пула), либо долю агента напрямую
(AgentPercentOfPool/AgentPercentOfPrice — агентство удерживает остаток). Обе формы делят один и тот
же пул; результат всегда agencyShare + agentShare = пул.
- Time-bounded agent-boost. Временная мотивационная кампания
(
AgentBoostCampaignEntity) поднимает
долю набора агентов на период (типовой кейс: 2–4% → 6–7%). Boost — отдельный агрегат той же специфичности
с периодом EffectiveFrom/EffectiveTo?; резолюция удержания на asOf = CompletedAt применяет активный
boost вместо базового правила (ADR-0068).
Классификация
| Поле | Значение |
|---|
| Контекст | rewards |
| Kind | AggregateRoot |
| Source of truth | rewards |
| Таблица | rewards.agency_commission_policies |
| Tenant isolation | TenantId = CompanyId агентства (Type ∈ {Agency, Broker, Pin}, ADR-0052) |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|
Id | Guid | Да | PK | app | Уникальный идентификатор политики. |
TenantId | Guid | Да | index; partial-unique ux_agency_policies_tenant_active (tenant_id) where status = 'Active' and deleted_at is null — единственность активной политики (INV-1) | identity (актор) | Агентство-владелец политики. |
Name | string | Да | varchar(256), min 1 | app | Название политики (например «Стандарт 30/70»). |
Description | string? | Нет | varchar(2000) | app | Комментарий руководителя. |
Status | AgencyCommissionPolicyStatus | Да | хранится как text, varchar(32), index (tenant_id, status) | app | Draft / Active / Archived (stateDiagram ниже). |
EffectiveFrom | DateTimeOffset? | Нет | заполняется при Activate | app | Момент активации; информационный (распределение всегда считает текущая активная политика). |
Rules | AgencyDistributionRulesDocument (jsonb) | Да | jsonb, typed, ContractVersion | app | Правила распределения (см. JSONB ниже). |
CreatedAt | DateTimeOffset | Да | index | app | Момент создания (аудит). |
CreatedByUserId | Guid? | Нет | — | app | Руководитель агентства. |
UpdatedAt | DateTimeOffset | Да | — | app | Момент изменения. |
UpdatedByUserId | Guid? | Нет | — | app | Последний изменивший. |
DeletedAt | DateTimeOffset? | Нет | partial-filter на индексах | app | Soft-delete; вместе с Status = Archived. |
DeletedByUserId | Guid? | Нет | — | app | Кто архивировал. |
Version | long | Да | >= 1 | app | Доменная версия (инкремент при мутации). |
Enum AgencyCommissionPolicyStatus
| Значение | Описание |
|---|
Draft | Черновик: редактируется, в расчёте распределения не участвует. |
Active | Действующая политика — единственная на агентство; применяется ко всем подтверждениям начислений. Правила иммутабельны (INV-4). |
Archived | Архив (soft-delete); ссылки AppliedPolicyId из начислений живы. |
Enum AgencyRetentionKind (вид правила распределения пула)
Правило задаёт либо удержание агентства (Percent/FixedAmount — историческая retention-модель,
агенту достаётся остаток), либо долю агента напрямую (AgentPercentOfPool/AgentPercentOfPrice —
агентство удерживает остаток). Обе формы делят один и тот же пул агентства
(CommissionAmount − PlatformFeeAmount); выбор Kind — вопрос удобства выражения, результат всегда
agencyShare + agentShare = пул. Значения Percent/FixedAmount полностью совместимы с прежней моделью.
| Значение | Выражает | Описание |
|---|
Percent | удержание агентства | Агентство удерживает Value процентов от пула агентства начисления (CommissionAmount − PlatformFeeAmount); агенту — остаток. |
FixedAmount | удержание агентства | Агентство удерживает фиксированную сумму Value (в валюте CurrencyId); если сумма удержания больше пула — удержание срезается до пула, доля агента = 0 (commission-accrual.md, INV-5). |
AgentPercentOfPool | доля агента | Агент получает Value процентов от пула агентства; агентство удерживает остаток (пул − доля агента). Прямая форма записи доли агента (в т.ч. для agent-boost). |
AgentPercentOfPrice | доля агента | Агент получает Value процентов от конечной цены сделки (FinalPrice, из снимка сделки в начислении); если результат превышает пул — доля агента срезается до пула (агентство удерживает 0). Позволяет мотивировать агента в терминах цены объекта. |
Value objects
Отдельных value objects нет — денежные значения политики живут внутри типизированного jsonb
(AgencyRetentionRule), потому что это конфигурация правил, а не транзакционные суммы (транзакционные
Money — в начислении).
Модели JSONB (versioned)
AgencyDistributionRulesDocument (jsonb, колонка rules, ContractVersion = 1)
| Поле | Тип | Обязательность | Ограничения | Since version |
|---|
ContractVersion | int | Да | = 1 | v1 |
DefaultRule | AgencyRetentionRule | Да | правило по умолчанию для всех агентов агентства | v1 |
AgentOverrides | IReadOnlyList<AgentRetentionOverride> | Нет (пусто = нет персональных условий) | ≤ 1000 элементов; AgentUserId уникален в списке | v1 |
Вложенная модель AgencyRetentionRule
| Поле | Тип | Обязательность | Ограничения | Since version |
|---|
Kind | AgencyRetentionKind (enum-string) | Да | Percent / FixedAmount / AgentPercentOfPool / AgentPercentOfPrice | v1 |
Value | decimal | Да | Percent/AgentPercentOfPool/AgentPercentOfPrice: >= 0, <= 100, scale 2; FixedAmount: >= 0, <= 100000000, scale 2 | v1 |
CurrencyId | short? | Да при Kind = FixedAmount, null при остальных Kind (для AgentPercentOfPrice база — цена сделки, валюта берётся из начисления) | ссылка на валюту справочника (дефолт RUB; RUB-only — ADR-0048); валидируется при сохранении/активации через internal resolve справочника (../../handbook/api/internal/resolve-references.md) | v1 |
Вложенная модель AgentRetentionOverride
| Поле | Тип | Обязательность | Ограничения | Since version |
|---|
AgentUserId | Guid | Да | пользователь компании TenantId (проверка при активации через identity-реплику пользователей агентства) | v1 |
Rule | AgencyRetentionRule | Да | те же ограничения | v1 |
Правила версии: новые поля — additive (nullable/default); смена семантики — ContractVersion + 1 +
migration-reader. Секреты/PII в jsonb запрещены (UserId — не PII, ФИО не хранится).
Алгоритм применения политики (расчёт распределения при подтверждении начисления)
Вход: AgentUserId, AgencyPoolAmount : Money — пул агентства = CommissionAmount − PlatformFeeAmount (commission-accrual.md, INV-10), DealFinalPrice : Money —
конечная цена сделки (нужна только для Kind = AgentPercentOfPrice; берётся из снимка сделки в начислении),
asOf = CompletedAt — момент завершения сделки. Политика делит пул, а не gross-комиссию: удержание ПИН
(строка Platform) снято до применения политики и от её настроек не зависит. Применяется при подтверждении
начисления (Confirm). round — банковское округление до scale 2.
- Если у агентства нет активной политики в момент
Confirm — fallback INV-6: agencyShare = 0, весь
пул — агенту, AppliedPolicyId = null, AppliedBoostCampaignId = null; шаги 2–5 не выполняются
(строка Platform создаётся независимо от политики). Boost поверх отсутствующей политики не
применяется — агент и так получает весь пул.
- Базовое правило. Найти в
Rules.AgentOverrides элемент с AgentUserId — если найден, взять его
Rule, иначе DefaultRule. Оверрайд применяется по AgentUserId независимо от того, состоит ли
агент в агентстве на момент подтверждения: политика иммутабельна в Active (INV-4), состав
пользователей сверялся при Activate (INV-3); последующий выход агента распределение не меняет.
- Активный boost (time-bounded). Отобрать
AgentBoostCampaign со Status = Active, чей период
покрывает asOf (EffectiveFrom <= asOf и (EffectiveTo is null или EffectiveTo > asOf)) и в чьём
наборе AgentUserIds есть AgentUserId. Такой кампании не больше одной (INV-9 — периоды boost одного
агента не пересекаются), поэтому резолюция детерминирована. Если найдена — effectiveRule = campaign.BoostRule, AppliedBoostCampaignId = campaign.Id; иначе effectiveRule = базовое правило из
шага 2, AppliedBoostCampaignId = null. Boost всегда перекрывает базовое правило (его назначение —
временно поднять долю агента).
- Расчёт по
effectiveRule.Kind:
Percent → agencyShare = round(AgencyPoolAmount.Value × Value / 100); agentShare = пул − agencyShare. Граничные значения: Value = 0 → удержание 0, весь пул агенту; Value = 100 →
удержание = весь пул, доля агента 0.
FixedAmount → проверить CurrencyId == AgencyPoolAmount.CurrencyId (REWARDS_CURRENCY_MISMATCH);
agencyShare = min(Value, AgencyPoolAmount.Value) — если фиксированное удержание превышает пул, оно
срезается до пула (доля агента 0, INV-5 commission-accrual.md); agentShare = пул − agencyShare.
AgentPercentOfPool → agentShare = round(AgencyPoolAmount.Value × Value / 100); agencyShare = пул − agentShare. Граничные значения: Value = 0 → доля агента 0, весь пул агентству; Value = 100 →
агенту весь пул (удержание 0).
AgentPercentOfPrice → проверить DealFinalPrice.CurrencyId == AgencyPoolAmount.CurrencyId
(REWARDS_CURRENCY_MISMATCH); agentShare = min(round(DealFinalPrice.Value × Value / 100), AgencyPoolAmount.Value) — доля агента не может превысить пул (при срезке agencyShare = 0);
agencyShare = пул − agentShare.
- Одна из долей вычислена округлением, вторая — остаток без отдельного округления, поэтому
agencyShare + agentShare = AgencyPoolAmount.Value точно; вместе со строкой Platform:
PlatformFeeAmount + agencyShare + agentShare = CommissionAmount (INV-5). Вернуть пары
(Agency, agencyShare, AppliedRetentionKind = effectiveRule.Kind, AppliedBoostCampaignId?) и
(Agent, agentShare). Снапшот применённого правила (политика — AppliedPolicyId; boost —
AppliedBoostCampaignId + период EffectiveFrom/EffectiveTo + Kind/Value) фиксируется в
начислении и не пересчитывается при последующей смене политики или архивации кампании (ADR-0068 п.4).
Под-агрегат AgentBoostCampaignEntity (time-bounded agent-boost)
Agent-boost — временная мотивационная кампания агентства: на ограниченный период доля агентов из
заданного набора поднимается (типовой кейс: 2–4% → 6–7%). Boost вынесен в отдельный агрегат, а не в
документ политики, потому что активная политика иммутабельна и единственна (INV-1/INV-4): постоянная
конфигурация и её временные надстройки живут раздельно, кампанию можно завести и свернуть, не трогая
базовую политику (ADR-0068 п.3 — акции и boost как отдельные time-bounded записи, без промо-флагов в
основной записи). Резолюция удержания при Confirm начисления учитывает активный на asOf = CompletedAt
boost и применяет его правило вместо базового (см. «Алгоритм применения политики», шаг 3). Кампания не
пересчитывает уже подтверждённые начисления: применённый boost зафиксирован снапшотом AppliedBoostCampaignId
в начислении.
Классификация
| Поле | Значение |
|---|
| Контекст | rewards |
| Kind | AggregateRoot |
| Source of truth | rewards |
| Таблица | rewards.agent_boost_campaigns |
| Tenant isolation | TenantId = CompanyId агентства (Type ∈ {Agency, Broker, Pin}, ADR-0052) |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|
Id | Guid | Да | PK | app | Идентификатор кампании; попадает в снапшот начисления как AppliedBoostCampaignId. |
TenantId | Guid | Да | index (tenant_id, status) | identity (актор) | Агентство-владелец кампании. |
Name | string | Да | varchar(256), min 1 | app | Название кампании (например «Весенний boost 7%»). |
Description | string? | Нет | varchar(2000) | app | Основание/условия кампании. |
Targets | AgentBoostTargetsDocument (jsonb) | Да | jsonb, typed, ContractVersion; набор агентов кампании | app | Агенты, на которых распространяется boost (см. JSONB ниже). |
BoostRule | AgencyRetentionRule (jsonb) | Да | jsonb, typed; Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice} (boost выражает долю агента напрямую); те же ограничения Value | app | Повышенная доля агента на период кампании. |
EffectiveFrom | DateTimeOffset | Да | check effective_to is null or effective_to > effective_from | app | Начало периода boost. |
EffectiveTo | DateTimeOffset? | Нет | — | app | Конец периода (исключительно); null — бессрочно. |
Status | AgentBoostCampaignStatus | Да | text, varchar(32), index (tenant_id, status) | app | Draft / Active / Archived (stateDiagram ниже). |
CreatedAt | DateTimeOffset | Да | index | app | Момент создания (аудит). |
CreatedByUserId | Guid? | Нет | — | app | Руководитель агентства. |
UpdatedAt | DateTimeOffset | Да | — | app | Момент изменения. |
UpdatedByUserId | Guid? | Нет | — | app | Последний изменивший. |
DeletedAt | DateTimeOffset? | Нет | partial-filter на индексах | app | Soft-delete (вместе с Status = Archived). |
Version | long | Да | >= 1 | app | Доменная версия (инкремент при мутации). |
Enum AgentBoostCampaignStatus
| Значение | Описание |
|---|
Draft | Черновик: редактируется, в резолюции boost не участвует. |
Active | Действует: участвует в резолюции с учётом периода EffectiveFrom/EffectiveTo. Иммутабелен, кроме сокращения EffectiveTo (INV-10). |
Archived | Архив (soft-delete): выведен из резолюции навсегда; снапшоты AppliedBoostCampaignId в начислениях живы. |
Модель JSONB AgentBoostTargetsDocument (колонка targets, ContractVersion = 1)
| Поле | Тип | Обязательность | Ограничения | Since version |
|---|
ContractVersion | int | Да | = 1 | v1 |
AgentUserIds | IReadOnlyList<Guid> | Да | 1..1000 элементов, уникальны; каждый — активный пользователь компании TenantId (сверка при Activate через identity-реплику пользователей агентства) | v1 |
BoostRule хранит Kind/Value в тех же ограничениях, что AgencyRetentionRule; для
AgentPercentOfPrice CurrencyId не задаётся — база берётся из цены сделки. Разные кампании могут
пересекаться по набору агентов только если их периоды не пересекаются для общих агентов (INV-9).
Инварианты и переходы состояний boost
| Инвариант | Условие | Где проверяется | Ошибка |
|---|
| INV-8 BoostRuleKind | BoostRule.Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice} и 0 <= Value <= 100 (boost выражает долю агента; удержание агентства через boost не задаётся) | проверка документа | REWARDS_AGENT_BOOST_RULE_INVALID |
| INV-9 NoBoostOverlapPerAgent | Для каждого агента периоды [EffectiveFrom, EffectiveTo|∞) активных кампаний, содержащих этого агента, не пересекаются — на любой asOf к агенту применима не более одной кампании (детерминизм резолюции) | Activate: активные кампании, пересекающиеся по агентам, читаются под блокировкой, пересечение периодов отклоняется | REWARDS_AGENCY_POLICY_OVERLAP |
| INV-10 BoostImmutableWhenActive | В Active меняется только EffectiveTo — досрочное завершение (только сокращение периода, не раньше текущего момента; сокращение пересечений не создаёт); остальные поля и набор агентов — только в Draft | guard по Status в операции изменения | REWARDS_AGENT_BOOST_ACTIVE_IMMUTABLE |
| INV-11 BoostTargetsValid | AgentUserIds непуст, ≤ 1000, без повторов, каждый — активный пользователь TenantId | проверка документа + Activate (сверка по identity) | REWARDS_AGENT_BOOST_TARGETS_INVALID |
| INV-12 BoostPeriodValid | EffectiveTo is null или EffectiveTo > EffectiveFrom | проверка входа + db check | REWARDS_AGENT_BOOST_PERIOD_INVALID |
Коды ошибок boost
| Код | HTTP | Условие |
|---|
REWARDS_AGENT_BOOST_RULE_INVALID | 400 | BoostRule не выражает долю агента или Value вне диапазона (INV-8). |
REWARDS_AGENT_BOOST_TARGETS_INVALID | 400 | Пустой/дублирующий набор агентов или неизвестный пользователь агентства (INV-11). |
REWARDS_AGENT_BOOST_PERIOD_INVALID | 400 | EffectiveTo <= EffectiveFrom (INV-12). |
REWARDS_AGENCY_POLICY_OVERLAP | 409 | Пересечение периодов активных кампаний по общему агенту при активации (INV-9). |
REWARDS_AGENT_BOOST_ACTIVE_IMMUTABLE | 409 | Попытка изменить Active-кампанию сверх сокращения EffectiveTo (INV-10). |
REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION | 409 | Недопустимый переход статуса: активация не из Draft либо архивирование уже Archived-кампании. |
REWARDS_AGENT_BOOST_NOT_FOUND | 404 | Кампания не найдена (операции управления). |
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|
CompanyEntity (агентство, identity) | reference (логическая, без FK) | TenantId | tenant-контекст актора | restrict |
CommissionAccrualEntity (политика) | reference (обратная) | commission_accruals.applied_policy_id → agency_commission_policies.id | Filter | restrict — политика с применениями только архивируется |
CommissionAccrualEntity (boost) | reference (обратная) | commission_accruals.applied_boost_campaign_id → agent_boost_campaigns.id | Filter | restrict — кампания с применениями только архивируется |
AgentBoostCampaignEntity (под-агрегат) | reference (в рамках агентства) | TenantId (то же агентство) | Filter по tenant | restrict |
Пользователи агентства (identity) | external reference | Rules.AgentOverrides[].AgentUserId, Targets.AgentUserIds[] | валидация при Activate (запрос списка пользователей компании) | — |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|
| INV-1 SingleActivePolicy | На агентство — не более одной Active политики; Activate новой атомарно переводит предыдущую в Archived | partial-unique index + команда Activate (текущая активная политика читается под блокировкой) | REWARDS_POLICY_ACTIVE_EXISTS (гонка — конфликт индекса → retry) |
| INV-2 ValidRuleValues | Percent/AgentPercentOfPool/AgentPercentOfPrice: 0 <= Value <= 100; FixedAmount: Value >= 0 и CurrencyId задан (резолвится в справочнике); для не-FixedAmount Kind CurrencyId = null | проверка документа + internal resolve | REWARDS_POLICY_RULE_INVALID |
| INV-3 UniqueOverrides | AgentUserId уникален в AgentOverrides; каждый — активный пользователь компании TenantId | проверка документа + Activate (сверка по identity) | REWARDS_POLICY_OVERRIDE_INVALID |
| INV-4 ImmutableWhenActive | В Active документ Rules и Name не меняются — правка идёт через новый Draft + Activate (сохраняет воспроизводимость применённого распределения) | guard по Status в операции изменения | REWARDS_POLICY_ACTIVE_IMMUTABLE |
| INV-5 ArchiveConsistency | Status = Archived ⇔ DeletedAt != null | операция архивирования | REWARDS_POLICY_INVALID_ARCHIVE_STATE |
| INV-6 NoPolicyFallback | Если у агентства нет активной политики в момент Confirm начисления — удержание агентства = 0, весь пул агентства (CommissionAmount − PlatformFeeAmount) — агенту (Agency-строка с Amount = 0, AppliedPolicyId = null); удержание ПИН (строка Platform) применяется независимо от наличия политики | подтверждение начисления (Confirm) | нет ошибки — задокументированный fallback |
| INV-7 TenantOwnership | Актор — руководитель/администратор компании TenantId с permission rewards.commission-policy.manage | изоляция по tenant + permission-policy | 403 |
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|
AgencyPolicyCreatedEvent | доменное | Успешный Create | Id, TenantId |
AgencyPolicyActivatedEvent | доменное | Draft → Active | Id, PreviousPolicyId? |
AgencyPolicyArchivedEvent | доменное | * → Archived | Id, actor |
AgentBoostCampaignCreatedEvent | доменное | Успешный Create кампании | Id, TenantId |
AgentBoostCampaignActivatedEvent | доменное | Draft → Active | Id, EffectiveFrom/EffectiveTo, число агентов |
AgentBoostCampaignArchivedEvent | доменное | * → Archived | Id, actor |
Интеграционные Kafka-события не публикуются — политика и boost применяются только внутри контекста
rewards (при Confirm начисления); внешним потребителям их изменения не нужны. Наблюдаемость и
инвалидация кэша резолвера ставок обеспечиваются общим топиком слоёв конфигурации
pin.rewards.commission-rules.v1 (created/updated/archived записи слоёв, ADR-0068 п.6) — если он будет
включён для агентского слоя, изменения политики и кампаний boost публикуются через additive-процедуру
событийного реестра.
Индексы, хранение, безопасность
| Аспект | Значение |
|---|
| Индексы | Политика: PK; ux_agency_policies_tenant_active unique btree (tenant_id) where status = 'Active' and deleted_at is null; ix_agency_policies_tenant_status btree (tenant_id, status) where deleted_at is null; ix_agency_policies_created_at btree. Boost: PK; ix_agent_boost_campaigns_tenant_status btree (tenant_id, status) where deleted_at is null; ix_agent_boost_campaigns_created_at btree. Непересечение периодов boost по агенту (INV-9) — app-level при Activate (кампаний единицы, ADR-0056) |
| Партиционирование / retention | none — единицы записей на агентство (политики и кампании boost); retention бессрочный (soft-delete) |
| Concurrency | оптимистическая блокировка (правка не должна затирать чужую) + блокировка текущей активной политики при её смене (INV-1) и активных кампаний, пересекающихся по агентам, при Activate boost (INV-9) |
| Permissions / PII | Управление политикой и кампаниями boost — rewards.commission-policy.manage (scope = организация-агентство: роль «Руководитель»/«Администратор агентства»); чтение — rewards.commission-policy.view. Агенту своя ставка удержания и действующий boost видны в проекции «моя комиссия» (только собственный оверрайд/дефолт и относящиеся к нему кампании), полный документ политики и списки агентов кампаний агенту не отдаются. PII нет |
| Audit | Version + actor-stamping; история версий политики = цепочка архивированных записей (Draft→Active→Archived), применённая версия зафиксирована AppliedPolicyId в начислении; применённый boost зафиксирован AppliedBoostCampaignId + период в снапшоте начисления и не пересчитывается ретроспективно (ADR-0068 п.4) |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (4): Entity / Aggregate: CommissionAccrualEntity, Entity / Aggregate: PayoutOrderEntity, Entity / Aggregate: PlatformCommissionTermsEntity, Entity / Aggregate: RewardRuleEntity
- API (16): GET /api/v1/agency/agent-boost-campaigns — Список boost-кампаний агентов, GET /api/v1/agency/agents/{agentUserId}/commission-summary — Сводка по конкретному агенту, GET /api/v1/agency/commission-policies — Список комиссионных политик агентства, GET /api/v1/agency/commission-summary — Сводка комиссий агентства, GET /api/v1/my/commission-summary — Моя сводка комиссий (агент), POST /api/v1/agency/agent-boost-campaigns — Создание boost-кампании агентов (черновик), POST /api/v1/agency/agent-boost-campaigns/{campaignId}/activate — Активация boost-кампании, POST /api/v1/agency/agent-boost-campaigns/{campaignId}/archive — Архивирование boost-кампании, POST /api/v1/agency/commission-accruals/{accrualId}/adjust-distribution — Per-deal корректировка долей внутри пула, POST /api/v1/agency/commission-policies — Создание комиссионной политики агентства (черновик), POST /api/v1/agency/commission-policies/{policyId}/activate — Активация политики агентства, POST /api/v1/agency/commission-policies/{policyId}/archive — Архивирование политики (+4)
- Use Cases (4): UC-RWD-004. Застройщик подтверждает начисление комиссии, UC-RWD-005. Агентство настраивает распределение и формирует платёжное поручение, UC-RWD-015. Настройка индивидуальных условий вознаграждения по области и сроку (акции и boost), US-реестр: Владелец агентства (US-OWN-AG)
- Саги (1): Saga «Commission Settlement» (финансовый контур вознаграждений)
- adr (3): ADR-0066 — удержание ПИН и реферальные ставки, ADR-0068 — Scoped и time-bounded конфигурация комиссионных и реферальных ставок, ADR-0069: Co-broking — расщепление комиссии между несколькими агентами (дизайн, v-next)
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
- index.md (1): Rewards Service
Связанные документы
- commission-accrual.md — точка применения (
Confirm, INV-4/5/10: пул =
CommissionAmount − PlatformFeeAmount), трёхстрочное распределение Platform/Agency/Agent,
fallback без политики и per-deal корректировка долей (INV-11); хранит снапшот применённого правила
(AppliedPolicyId, AppliedBoostCampaignId? + период) и DealFinalPrice для AgentPercentOfPrice.
- platform-commission-terms.md — удержание ПИН, формирующее пул.
- payout-order.md — выплата агентских долей.
- referral-rate-rule.md — эталон паттерна scoped+time-bounded (период,
«специфичное побеждает», непересечение периодов внутри специфичности,
asOf = CompletedAt).
- Роли агентства:
../../identity/domain/role.md; пользователи: ../../identity/domain/user.md.
- ADR-0052 (tenancy), ADR-0056 (simplicity-first — jsonb-конфигурация вместо отдельных таблиц правил),
ADR-0066 (пул вместо gross, строка
Platform),
ADR-0068
(единый паттерн scoped+time-bounded: agent-boost как time-bounded запись, снапшот области/периода в
начисление, непересечение периодов внутри специфичности).
- Шаблон: entity-template.md; правила:
cross-cutting-document-requirements.md.