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

POST /api/v1/agency/commission-policies — Создание комиссионной политики агентства (черновик)

Создание комиссионной политики агентства в статусе Draft: правило распределения пула по умолчанию и персональные оверрайды по агентам (матрица «Агенство» 5.2 — «установка размера комиссии в % или фиксированной суммой, удерживаемой из вознаграждения агента в пользу агентства»). Правило задаётся в одной из четырёх форм (AgencyRetentionKind): удержание агентства (Percent/FixedAmount — агенту остаётся остаток пула) или прямая доля агента (AgentPercentOfPool/AgentPercentOfPrice — агентство удерживает остаток). Черновик не участвует в распределении до активации; дом полей и инварианты INV-1..7 — domain/agency-commission-policy.md.

Назначение

Руководитель/администратор агентства заводит настройку распределения комиссии между агентом-исполнителем и агентством. Политика создаётся черновиком, редактируется до активации и не влияет на подтверждение начислений (Confirmdomain/commission-accrual.md). Owning-контекст — rewards, feature group — agency-policy, операция op14 реестра README.md.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areaagency
Feature groupagency-policy
Статусdraft
Документdocs/06-services/rewards/api/agency/create-agency-policy.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (руководитель/администратор агентства)
Auth policy / Permissionsrewards.commission-policy.manage
Scope (RBAC)организация: компания Type ∈ {Agency, Broker, Pin}
Record-levelне применяется (создаётся в своём tenant)
Tenant isolationTenantId = CompanyId агентства из claims (не из body), ADR-0052
Auditcreate: CreatedByUserId = ActorUserId, Version = 1, доменное событие AgencyPolicyCreatedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agency/commission-policies
Success status201 (+ Location: /api/v1/agency/commission-policies/{id})
Idempotency headerне применяется (несколько черновиков допустимы; INV-1 стережёт только Active)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequired; компания Type ∈ {Agency, Broker, Pin}Tenant isolation
ActorUserIdidentity claims (sub)GuidДаRequired
NamebodystringДа1..256, не пусто после trim
Descriptionbodystring?Нет<= 2000
RulesbodyAgencyDistributionRulesModelДапо модели ниже (INV-2, INV-3)ставки удержания не логируются

Модель AgencyDistributionRulesModel (проекция jsonb AgencyDistributionRulesDocument)

Дом полей — domain/agency-commission-policy.md («Модели JSONB»). ContractVersion наружу не принимается — сервер проставит = 1.

ПолеТипОбязательностьОграничения
DefaultRuleAgencyRetentionRuleModelДаправило по умолчанию для всех агентов агентства
AgentOverridesIReadOnlyList<AgentRetentionOverrideModel>Нет (пусто = нет персональных условий)<= 1000 элементов; AgentUserId уникален в списке (INV-3)

Модель AgencyRetentionRuleModel

ПолеТипОбязательностьОграничения
Kindstring (enum AgencyRetentionKind)ДаPercent / FixedAmount (удержание агентства) / AgentPercentOfPool / AgentPercentOfPrice (прямая доля агента)
ValuedecimalДаPercent/AgentPercentOfPool/AgentPercentOfPrice: 0..100, scale 2; FixedAmount: 0..100000000, scale 2 (INV-2)
CurrencyIdshort?Да при Kind = FixedAmount, null при Percent/AgentPercentOfPool/AgentPercentOfPrice (для AgentPercentOfPrice база — конечная цена сделки, валюта берётся из начисления)ссылка на валюту справочника (дефолт RUB; RUB-only — ADR-0048); резолвится через internal resolve справочника при сохранении (../../../handbook/api/internal/resolve-references.md)

Модель AgentRetentionOverrideModel

ПолеТипОбязательностьОграничения
AgentUserIdGuidДапользователь компании TenantId; существование/активность не проверяется здесь — отложено до Activate (INV-3)
RuleAgencyRetentionRuleModelДате же ограничения значений

Модель ответа AgencyCommissionPolicyModel

Полная проекция AgencyCommissionPolicyEntity; дом полей — domain/agency-commission-policy.md.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаIdId созданной политики.
NamestringДаNameНазвание политики.
Descriptionstring?НетDescriptionКомментарий руководителя.
Statusstring (enum AgencyCommissionPolicyStatus)ДаStatusВсегда Draft.
EffectiveFromDateTimeOffset?НетEffectiveFromnull до активации.
RulesAgencyDistributionRulesModelДаRules (jsonb)ставки не логируютсяПравила распределения.
CreatedAtDateTimeOffsetДаCreatedAtМомент создания.
UpdatedAtDateTimeOffsetДаUpdatedAtСовпадает с CreatedAt.
VersionlongДаVersion1 — для последующего ExpectedVersion.

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании Agency/Broker/Pin); scope — организация (TenantId из claims); требуется право rewards.commission-policy.manage. Иначе — FORBIDDEN (INV-7).
  2. Проверка входа.
    • формат/диапазоны: Name 1..256, Description <= 2000; значения DefaultRule и каждого AgentOverrides[].Rule (INV-2: Percent/AgentPercentOfPool/AgentPercentOfPrice0..100 и CurrencyId = null; FixedAmount>= 0 с заданным CurrencyId, резолвящимся в справочнике валют) — иначе ошибка валидации / REWARDS_POLICY_RULE_INVALID; лимит AgentOverrides <= 1000 и уникальность AgentUserId (INV-3) — иначе REWARDS_POLICY_OVERRIDE_INVALID;
    • существование пользователей-оверрайдов (AgentOverrides[].AgentUserId) здесь не проверяется — отложено до активации (INV-3), чтобы черновик можно было готовить до синхронизации состава агентства;
    • единственность активной политики (INV-1) при создании Draft не проверяется (несколько черновиков допустимы).
  3. Создание черновика. Заводится политика: Status = Draft, EffectiveFrom = null, Version = 1, CreatedByUserId = актор; документ правил Rules (DefaultRule + AgentOverrides) сохраняется с ContractVersion = 1. Инварианты INV-2/INV-3 проверяются при сборке.
  4. Фиксация факта. Создание сопровождается доменным событием AgencyPolicyCreatedEvent внутри rewards. Интеграционных Kafka-событий нет — политика применяется только внутри rewards (при Confirm начисления), внешним потребителям её изменения не нужны (domain/agency-commission-policy.md, «Доменные и интеграционные события»).
  5. Результат. Полная проекция созданной политики (Status = Draft, Version = 1) → HTTP 201 + Location.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
REWARDS_POLICY_RULE_INVALID400Значение правила вне диапазона (процентные Kind вне 0..100) / CurrencyId не задан или не резолвится в справочнике при FixedAmount / CurrencyId задан при процентном Kind (INV-2)«Некорректные параметры распределения»no retry
REWARDS_POLICY_OVERRIDE_INVALID400Дубликат AgentUserId в AgentOverrides или превышен лимит 1000 (INV-3)«Некорректный список персональных условий»no retry

Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) — README.md.

Acceptance criteria

  • AC-1 (создание черновика). Given валидные Name (1..256) и Rules.DefaultRule в допустимых границах (INV-2); When руководитель агентства создаёт политику; Then заводится политика Draft, Version = 1, EffectiveFrom = null, Rules сохранён с ContractVersion = 1; ответ 201 + Location. Постусловие: политика в распределении не участвует до активации (activate-agency-policy.md), интеграционных событий нет.
  • AC-2 (несколько черновиков). Given у агентства уже есть черновики/активная политика; When создаётся ещё один черновик; Then он создаётся успешно — INV-1 стережёт единственность только для Active.
  • AC-3 (оверрайд на будущего сотрудника). Given AgentOverrides[].AgentUserId, которого пока нет в реплике состава агентства; When создание черновика; Then успех (201) — существование/активность проверяются только при активации (INV-3), чтобы черновик можно было готовить до синхронизации состава.
  • AC-4 (прямая доля агента). Given DefaultRule.Kind = AgentPercentOfPool (или AgentPercentOfPrice), Value = 70, CurrencyId = null; When руководитель создаёт политику; Then заводится черновик Draft, Version = 1, правило сохранено в форме прямой доли агента (агентство удержит остаток пула при применении).
  • AC-5 (некорректное правило распределения). Given DefaultRule или любой AgentOverrides[].Rule с Value вне диапазона (процентный Kind вне 0..100 / FixedAmount < 0), либо FixedAmount без CurrencyId, либо процентный Kind с непустым CurrencyId (INV-2); When создание; Then REWARDS_POLICY_RULE_INVALID (400).
  • AC-6 (некорректный список оверрайдов). Given дубликат AgentUserId в AgentOverrides или список длиннее 1000 (INV-3); When создание; Then REWARDS_POLICY_OVERRIDE_INVALID (400).

Совместимость и наблюдаемость

  • Новые optional поля правил — additive (та же семантика jsonb-дома); удаление/переименование/смена типа или семантики — breaking (ContractVersion + 1 + migration-reader, domain).
  • Logs: TenantId, ActorUserId, PolicyId, счётчик AgentOverrides (ставки/суммы не логируются — sensitive); metrics: rewards_policy_create_total{result}; traces: OTel, correlation через traceparent; dashboard rewards-overview; runbook — не применяется.
  • НФТ (ADR-0056): создание одного агрегата с jsonb-документом правил (до 1000 оверрайдов); целевая латентность p95 < 300 мс; частота — редкая (заведение/пересборка политики агентства, back-office).

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

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

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