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).
Назначение
Руководитель/администратор агентства заводит кампанию повышенной доли агентов на период. Кампания
создаётся черновиком, редактируется до активации и не влияет на подтверждение начислений (Confirm —
domain/commission-accrual.md) до перевода в Active. Boost выражает
долю агента напрямую (BoostRule.Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice}) — удержание
агентства через boost не задаётся. Owning-контекст — rewards, feature group — agent-boost, операция
op58 реестра README.md.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | agency |
| Feature group | agent-boost |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agency/create-agent-boost-campaign.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (руководитель/администратор агентства) |
| Auth policy / Permissions | rewards.commission-policy.manage |
| Scope (RBAC) | организация: компания Type ∈ {Agency, Broker, Pin} |
| Record-level | не применяется (создаётся в своём tenant) |
| Tenant isolation | TenantId = CompanyId агентства из claims (не из body), ADR-0052 |
| Audit | create: CreatedByUserId = ActorUserId, Version = 1, доменное событие AgentBoostCampaignCreatedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/agency/agent-boost-campaigns |
| Success status | 201 (+ Location: /api/v1/agency/agent-boost-campaigns/{id}) |
| Idempotency header | не применяется (несколько черновиков допустимы; непересечение периодов стережётся при активации, INV-9) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required; компания Type ∈ {Agency, Broker, Pin} | Tenant isolation |
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
Name | body | string | Да | 1..256, не пусто после trim | — |
Description | body | string? | Нет | <= 2000 | основание/условия кампании |
BoostRule | body | AgencyRetentionRuleModel | Да | Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice}, 0 <= Value <= 100, CurrencyId = null (INV-8) | повышенная доля не логируется |
AgentUserIds | body | IReadOnlyList<Guid> | Да | 1..1000 элементов, уникальны (INV-11); существование/активность проверяются при активации | — |
EffectiveFrom | body | DateTimeOffset | Да | начало периода boost | — |
EffectiveTo | body | DateTimeOffset? | Нет | 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).
| Поле | Тип | Обязательность | Ограничения |
|---|
Kind | string (enum AgencyRetentionKind) | Да | только AgentPercentOfPool / AgentPercentOfPrice (INV-8) |
Value | decimal | Да | 0..100, scale 2 (повышенная доля агента) |
CurrencyId | short? | Нет | всегда null (для AgentPercentOfPrice база — конечная цена сделки, валюта берётся из начисления) |
Модель ответа AgentBoostCampaignModel
Полная проекция под-агрегата AgentBoostCampaignEntity; дом полей —
domain/agency-commission-policy.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | Id | — | Id созданной кампании; попадёт в снапшот начисления как AppliedBoostCampaignId. |
Name | string | Да | Name | — | Название кампании («Весенний boost 7%»). |
Description | string? | Нет | Description | — | Основание/условия. |
Status | string (enum AgentBoostCampaignStatus) | Да | Status | — | Всегда Draft. |
BoostRule | AgencyRetentionRuleModel | Да | BoostRule (jsonb) | ставка не логируется | Повышенная доля агента на период. |
AgentUserIds | IReadOnlyList<Guid> | Да | Targets.AgentUserIds | список агентов — только роли с rewards.commission-policy.{view,manage} | Набор агентов кампании. |
EffectiveFrom | DateTimeOffset | Да | EffectiveFrom | — | Начало периода. |
EffectiveTo | DateTimeOffset? | Нет | EffectiveTo | — | Конец периода (исключительно); null — бессрочно. |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Момент создания. |
UpdatedAt | DateTimeOffset | Да | UpdatedAt | — | Совпадает с CreatedAt. |
Version | long | Да | Version | — | 1 — для последующего ExpectedVersion. |
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании
Agency/Broker/Pin); scope — организация (TenantId из claims); требуется право
rewards.commission-policy.manage. Иначе — FORBIDDEN.
- Проверка входа.
- формат/диапазоны:
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 не проверяется — сверяется при
активации (несколько черновиков допустимы).
- Создание черновика. Заводится кампания:
Status = Draft, Version = 1, CreatedByUserId = актор;
документ набора агентов (Targets.AgentUserIds) и правило BoostRule сохраняются с ContractVersion = 1.
Инварианты INV-8/INV-11/INV-12 проверяются при сборке.
- Фиксация факта. Создание сопровождается доменным событием
AgentBoostCampaignCreatedEvent внутри
rewards. Интеграционных Kafka-событий нет — boost применяется только внутри rewards (при Confirm
начисления), внешним потребителям его изменения не нужны
(domain/agency-commission-policy.md, «Доменные и
интеграционные события»).
- Результат. Полная проекция созданной кампании (
Status = Draft, Version = 1) → HTTP 201 +
Location.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_AGENT_BOOST_RULE_INVALID | 400 | BoostRule.Kind ∉ {AgentPercentOfPool, AgentPercentOfPrice} или Value вне 0..100, либо задан CurrencyId (INV-8) | «Boost должен выражать долю агента в допустимом диапазоне» | no retry |
REWARDS_AGENT_BOOST_TARGETS_INVALID | 400 | AgentUserIds пуст / дубликат / длиннее 1000 (INV-11) | «Некорректный список агентов кампании» | no retry |
REWARDS_AGENT_BOOST_PERIOD_INVALID | 400 | EffectiveTo <= 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).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы