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

POST /api/v1/agency/agent-boost-campaigns — Создание boost-кампании агентов (черновик)

Создание временной мотивационной кампании агентства (agent-boost) в статусе Draft: на ограниченный период доля набора агентов поднимается повышенным правилом (типовой кейс: 2–4% → 6–7%). Boost — отдельный агрегат поверх базовой комиссионной политики: постоянная конфигурация и её временные надстройки живут раздельно, кампанию можно завести и свернуть, не трогая иммутабельную активную политику. Черновик не участвует в резолюции удержания до активации; дом полей и инварианты INV-8..12 — domain/agency-commission-policy.md (под-агрегат AgentBoostCampaignEntity).

Назначение

Руководитель/администратор агентства заводит кампанию повышенной доли агентов на период. Кампания создаётся черновиком, редактируется до активации и не влияет на подтверждение начислений (Confirmdomain/commission-accrual.md) до перевода в Active. Boost выражает долю агента напрямую (BoostRule.Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice}) — удержание агентства через boost не задаётся. Owning-контекст — rewards, feature group — agent-boost, операция op58 реестра README.md.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areaagency
Feature groupagent-boost
Статусdraft
Документdocs/06-services/rewards/api/agency/create-agent-boost-campaign.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, доменное событие AgentBoostCampaignCreatedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agency/agent-boost-campaigns
Success status201 (+ Location: /api/v1/agency/agent-boost-campaigns/{id})
Idempotency headerне применяется (несколько черновиков допустимы; непересечение периодов стережётся при активации, INV-9)
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основание/условия кампании
BoostRulebodyAgencyRetentionRuleModelДаKind ∈ {AgentPercentOfPool, AgentPercentOfPrice}, 0 <= Value <= 100, CurrencyId = null (INV-8)повышенная доля не логируется
AgentUserIdsbodyIReadOnlyList<Guid>Да1..1000 элементов, уникальны (INV-11); существование/активность проверяются при активации
EffectiveFrombodyDateTimeOffsetДаначало периода boost
EffectiveTobodyDateTimeOffset?Нетnull = бессрочно; иначе > EffectiveFrom (INV-12)

Модель AgencyRetentionRuleModel (вход BoostRule)

Дом формы — list-agency-policies.md; дом семантики — domain/agency-commission-policy.md («Enum AgencyRetentionKind»). Для boost допустимы только формы прямой доли агента; Kind ∈ {Percent, FixedAmount} (удержание агентства) отклоняется (REWARDS_AGENT_BOOST_RULE_INVALID).

ПолеТипОбязательностьОграничения
Kindstring (enum AgencyRetentionKind)Датолько AgentPercentOfPool / AgentPercentOfPrice (INV-8)
ValuedecimalДа0..100, scale 2 (повышенная доля агента)
CurrencyIdshort?Нетвсегда null (для AgentPercentOfPrice база — конечная цена сделки, валюта берётся из начисления)

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

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаIdId созданной кампании; попадёт в снапшот начисления как AppliedBoostCampaignId.
NamestringДаNameНазвание кампании («Весенний boost 7%»).
Descriptionstring?НетDescriptionОснование/условия.
Statusstring (enum AgentBoostCampaignStatus)ДаStatusВсегда Draft.
BoostRuleAgencyRetentionRuleModelДаBoostRule (jsonb)ставка не логируетсяПовышенная доля агента на период.
AgentUserIdsIReadOnlyList<Guid>ДаTargets.AgentUserIdsсписок агентов — только роли с rewards.commission-policy.{view,manage}Набор агентов кампании.
EffectiveFromDateTimeOffsetДаEffectiveFromНачало периода.
EffectiveToDateTimeOffset?НетEffectiveToКонец периода (исключительно); null — бессрочно.
CreatedAtDateTimeOffsetДаCreatedAtМомент создания.
UpdatedAtDateTimeOffsetДаUpdatedAtСовпадает с CreatedAt.
VersionlongДаVersion1 — для последующего ExpectedVersion.

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании Agency/Broker/Pin); scope — организация (TenantId из claims); требуется право rewards.commission-policy.manage. Иначе — FORBIDDEN.
  2. Проверка входа.
    • формат/диапазоны: Name 1..256, Description <= 2000;
    • правило boost: BoostRule.Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice}, 0 <= Value <= 100, CurrencyId не задан (INV-8) — иначе REWARDS_AGENT_BOOST_RULE_INVALID;
    • набор агентов: AgentUserIds непуст, <= 1000, без повторов (INV-11) — иначе REWARDS_AGENT_BOOST_TARGETS_INVALID; существование/активность пользователей здесь не проверяется — отложено до активации (INV-11), чтобы черновик можно было готовить до синхронизации состава агентства;
    • период: EffectiveTo is null или EffectiveTo > EffectiveFrom (INV-12) — иначе REWARDS_AGENT_BOOST_PERIOD_INVALID;
    • непересечение периодов по общим агентам (INV-9) при создании Draft не проверяется — сверяется при активации (несколько черновиков допустимы).
  3. Создание черновика. Заводится кампания: Status = Draft, Version = 1, CreatedByUserId = актор; документ набора агентов (Targets.AgentUserIds) и правило BoostRule сохраняются с ContractVersion = 1. Инварианты INV-8/INV-11/INV-12 проверяются при сборке.
  4. Фиксация факта. Создание сопровождается доменным событием AgentBoostCampaignCreatedEvent внутри rewards. Интеграционных Kafka-событий нет — boost применяется только внутри rewards (при Confirm начисления), внешним потребителям его изменения не нужны (domain/agency-commission-policy.md, «Доменные и интеграционные события»).
  5. Результат. Полная проекция созданной кампании (Status = Draft, Version = 1) → HTTP 201 + Location.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
REWARDS_AGENT_BOOST_RULE_INVALID400BoostRule.Kind ∉ {AgentPercentOfPool, AgentPercentOfPrice} или Value вне 0..100, либо задан CurrencyId (INV-8)«Boost должен выражать долю агента в допустимом диапазоне»no retry
REWARDS_AGENT_BOOST_TARGETS_INVALID400AgentUserIds пуст / дубликат / длиннее 1000 (INV-11)«Некорректный список агентов кампании»no retry
REWARDS_AGENT_BOOST_PERIOD_INVALID400EffectiveTo <= EffectiveFrom (INV-12)«Некорректный период кампании»no retry

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

Acceptance criteria

  • AC-1 (создание черновика). Given валидные Name (1..256), BoostRule.Kind = AgentPercentOfPool Value = 7, AgentUserIds (1..1000, уникальны), EffectiveFrom < EffectiveTo; When руководитель агентства создаёт кампанию; Then заводится кампания Draft, Version = 1, набор агентов и правило сохранены с ContractVersion = 1; ответ 201 + Location. Постусловие: кампания в резолюции удержания не участвует до активации (activate-agent-boost-campaign.md), интеграционных событий нет.
  • AC-2 (бессрочная кампания). Given EffectiveTo = null; When создание; Then успех (201) — boost действует с EffectiveFrom бессрочно (до архивации/сокращения периода).
  • AC-3 (набор на будущих сотрудников). Given AgentUserIds, которых пока нет в реплике состава агентства; When создание черновика; Then успех (201) — существование/активность проверяются только при активации (INV-11), чтобы черновик можно было готовить до синхронизации состава.
  • AC-4 (недопустимая форма правила). Given BoostRule.Kind = Percent (или FixedAmount) либо Value = 120 (INV-8); When создание; Then REWARDS_AGENT_BOOST_RULE_INVALID (400).
  • AC-5 (некорректный набор агентов). Given пустой AgentUserIds, дубликат или список длиннее 1000 (INV-11); When создание; Then REWARDS_AGENT_BOOST_TARGETS_INVALID (400).
  • AC-6 (некорректный период). Given EffectiveTo <= EffectiveFrom (INV-12); When создание; Then REWARDS_AGENT_BOOST_PERIOD_INVALID (400).

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

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

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

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

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