UC-RWD-005. Агентство настраивает распределение и формирует платёжное поручение
Назначение
Описывает сценарий комиссионной политики агентства (матрица «Агенство» 5.2–5.5): руководитель агентства
настраивает правила распределения комиссии (удержание агентства в % или фиксированной суммой, с
персональными оверрайдами по агентам; политика делит пул — комиссию за вычетом удержания ПИН,
ADR-0066), затем формирует платёжное поручение — собирает свободные доли агентов по начислениям, деньги
по которым уже получены от застройщика (Status = MoneyReceived, гейт ADR-0067 пройден —
UC-RWD-010), утверждает поручение и после фактической выплаты (вне платформы — исполнение платежей
отложено, решение №9 ТЗ) отмечает его выплаченным; по событию выплаты кошельки агентов дебетуются
(ADR-0065). Связан с контекстом rewards.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-RWD-005 |
| Контекст | rewards (см. docs/06-services/rewards/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Агентство (руководитель/администратор агентства; отметка выплаты — руководитель/бухгалтер) |
| Permissions / scope | Политика — rewards.commission-policy.manage; создание/состав поручения — rewards.payouts.manage; утверждение/отметка выплаты/отклонение — rewards.payouts.approve. Scope — Organization(CompanyId агентства) |
| Tenant | TenantId = CompanyId агентства (Type ∈ {Agency, Broker, Pin}, ADR-0052); политика, поручение и все захватываемые строки распределения — в этом tenant (INV-9 payout) |
| Record-level | Агент видит только свои позиции поручений (rewards.payouts.view-own, record-level AgentUserId) — читательская проекция, не этот сценарий |
Предусловие
- Компания-агентство
Status = Active; актор — руководитель/администратор. - Для фазы 2: существуют начисления
Status = MoneyReceived(деньги от застройщика получены — зарегистрирован акт поступления средств, UC-RWD-010) со свободнымиAgent-строками распределения (PayoutOrderItemId is null). Начисления вConfirmedв поручение не попадают — гейт денег не пройден (ADR-0067). - Для валидации оверрайдов политики: пользователи-агенты принадлежат компании (
identity).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | ЛК агентства → «Комиссии» → «Политика распределения» → «Создать/Активировать»; «Выплаты» → «Сформировать поручение» / «Утвердить» / «Отметить выплату» |
Основной Поток
Фаза 1. Настройка комиссионной политики
- Руководитель открывает «Политика распределения» (
GET /rewards/agency/policies, permissionrewards.commission-policy.view) и нажимает «Создать политику». - Фронт вызывает
POST /rewards/agency/policiesс теломCreateAgencyPolicyForm { name (1..256), description? (<= 2000), rules: { defaultRule: { kind: Percent | FixedAmount, value: decimal (Percent: 0..100; FixedAmount: 0..100000000, scale 2), currency?: string (ISO-4217, обязательна при FixedAmount, null при Percent) }, agentOverrides?: [ { agentUserId: Guid, rule: { kind, value, currency? } } ] (<= 1000, agentUserId уникальны) } }. - Система создаёт политику-черновик:
- Доступ.
rewards.commission-policy.manageв scopeOrganization(TenantId агентства). - Проверка входа. INV-2 (диапазоны
valueпоkind;currencyзадана ⇔FixedAmount) →REWARDS_POLICY_RULE_INVALID; уникальностьagentUserIdв оверрайдах (часть INV-3) →REWARDS_POLICY_OVERRIDE_INVALID. Принадлежность агентов компании проверяется при активации (шаг 5). - Создание. Политика создаётся в статусе
Draft(Version = 1):Name/DescriptionиRules { DefaultRule, AgentOverrides }— из формы,TenantId— агентство актора. Поднимается доменное событиеAgencyPolicyCreatedEvent; Kafka-событий у политики нет (agency-commission-policy.md). - Результат.
{ policyId, status = Draft }.
- Доступ.
- Руководитель проверяет черновик и нажимает «Активировать»; фронт вызывает
POST /rewards/agency/policies/{policyId}/activate { rowVersion }. - Система активирует политику:
- Доступ.
rewards.commission-policy.manage. Политика существует в tenant агентства; guardStatus = Draft. - INV-3 (агенты оверрайдов). Все
AgentOverrides[].AgentUserId— активные пользователи компанииTenantId(проверка состава агентства вidentity); иначе —REWARDS_POLICY_OVERRIDE_INVALID(A7). - Атомарная смена (INV-1). Текущая активная политика (если есть) переходит
Active → Archived(DeletedAt = now); новая переходитDraft → Active(EffectiveFrom = now,Version++). Поднимается доменное событиеAgencyPolicyActivatedEvent; Kafka-событий нет. Архив старой и активация новой — в одном согласованном изменении; гонка двух активаций ловится частично-уникальным индексомux_agency_policies_tenant_active→REWARDS_POLICY_ACTIVE_EXISTS(retry). - Результат.
{ policyId, status = Active, effectiveFrom, previousPolicyId? }.
- Доступ.
- Со следующего подтверждения начисления (UC-RWD-004) распределение считается по новой политике;
уже подтверждённые не пересчитываются (
AppliedPolicyIdзафиксирован).
Фаза 2. Платёжное поручение
- Руководитель открывает «Выплаты» → «Сформировать поручение», задаёт период; фронт вызывает
POST /rewards/agency/payout-ordersс теломCreatePayoutOrderForm { periodFrom: DateTimeOffset, periodTo: DateTimeOffset (> periodFrom), title?: string (<= 256) }. - Система формирует черновик поручения:
- Доступ.
rewards.payouts.manage. Проверка периода (periodTo > periodFrom). - Автосборка (INV-1 payout). Захватываются свободные
Agent-строки распределения агентства:RecipientType = Agent,PayoutOrderItemId is null,Amount.Value > 0, родительское начислениеStatus = MoneyReceived(гейт получения денег пройден, ADR-0067) иDealCompletedAt ∈ [periodFrom; periodTo). Захват без взаимной блокировки с параллельным поручением — каждая строка достаётся ровно одному поручению (A6). - Сборка позиций. Поручение создаётся в статусе
Draft:Number = PO-{yyyy}-{seq}(последовательность на tenant-год),Title/PeriodFrom/PeriodTo— из формы. Захваченные строки группируются поAgentUserId→ на каждого агентаPayoutOrderItemEntity { AgentUserId, Amount = Σ строк, DistributionsCount = count }(INV-2 — один агент = одна позиция; INV-4 — единая валюта, иначеREWARDS_CURRENCY_MISMATCH); каждой захваченной строке проставляетсяPayoutOrderItemId; корень:TotalAmount = Σ Item.Amount,ItemsCount = count(Items)(INV-3). Поднимается доменное событиеPayoutOrderCreatedEvent; Kafka на создании черновика не публикуется. Поручение и привязка строк — в одном согласованном изменении. - Результат.
{ orderId, number, status = Draft, totalAmount, itemsCount, items: [ { agentUserId, amount, distributionsCount } ] }.
- Доступ.
- Руководитель проверяет состав (при необходимости — A1) и нажимает «Утвердить»; фронт вызывает
POST /rewards/agency/payout-orders/{orderId}/approve { rowVersion }. - Система утверждает поручение: доступ
rewards.payouts.approve; guard INV-6 (Status = Draft,ItemsCount >= 1); переходDraft → PendingPayment(«ожидается», матрица 5.5), состав заморожен (INV-5),ApprovedAt/ApprovedByUserId. Поднимается доменное событиеPayoutOrderApprovedEvent; в одном согласованном изменении публикуетсяPayoutOrderStatusChangedKafkaEvent(topicpin.rewards.payout-order-status-changed.v1, key =Id; payloadId, TenantId, Number, FromStatus = Draft, ToStatus = PendingPayment, TotalAmount, ItemsCount, PaidAt = null, RejectReason = null, Version). Результат —{ orderId, status = PendingPayment, approvedAt }. - Бухгалтерия выгружает сводный отчёт (
GET /rewards/agency/payout-orders/{orderId}— позиции по агентам, суммы; реквизиты вrewardsне хранятся — payout-order.md) и проводит платежи вне платформы (исполнение отложено, решение №9 ТЗ). - После фактической выплаты руководитель/бухгалтер нажимает «Отметить выплату»; фронт вызывает
POST /rewards/agency/payout-orders/{orderId}/mark-paid { rowVersion }. - Система отмечает выплату (каскад INV-7): доступ
rewards.payouts.approve; guardStatus = PendingPayment. Поручение переходитPendingPayment → Paid,PaidAtпроставляется поручению, каждой его позиции и каждой привязанной строке распределения; для каждого затронутого начисления, у которого всеAgent-строки выплачены (PaidAt != null), начисление переходитMoneyReceived → Paid(INV-7 commission-accrual.md). В одном согласованном изменении публикуютсяPayoutOrderStatusChangedKafkaEvent (ToStatus = Paid, PaidAt, Items[] { PayoutOrderItemId, RecipientUserId, Amount, PaidAt })иCommissionAccrualStatusChangedKafkaEvent (MoneyReceived → Paid)на каждое переведённое начисление. Результат —{ orderId, status = Paid, paidAt, paidAccrualIds[] }. notificationsуведомляет агентов о выплате их позиций и руководителя о смене статуса;walletпо событиюPaidдебетует счетаAgentCommissionагентов на суммы позиций (Items[], идемпотентность(PayoutDebit, ItemId), ADR-0065 — кредит счетов произошёл раньше, по гейту денег UC-RWD-010); история выплат агентства (GET /rewards/agency/payout-orders— фильтры по статусу/периоду) показывает поручение как «выплачено»; в ЛК агента комиссия переходит в «получили».
Альтернативные Потоки
A1. Правка состава черновика поручения
PUT /rewards/agency/payout-orders/{orderId}/items { addDistributionIds?: Guid[], removeDistributionIds?: Guid[], rowVersion }— guard INV-5 (Status = Draft, иначеREWARDS_PAYOUT_COMPOSITION_LOCKED); добавляемые строки проверяются как при автосборке (свободны, родительMoneyReceived, тот же tenant — INV-1/9, иначеREWARDS_PAYOUT_DISTRIBUTION_UNAVAILABLE); исключаемые —PayoutOrderItemId → null; пересборка позиций и пересчётTotalAmount/ItemsCount(INV-3); позиция без строк удаляется. Возвращается к шагу 9.
A2. Пустая выборка за период
- Свободных строк за период не нашлось (в т.ч. когда начисления ещё в
Confirmed— деньги от застройщика не получены, гейт ADR-0067) → поручение создаётся пустымDraft(ItemsCount = 0,TotalAmount = 0); фронт показывает «нет долей, доступных к выплате, за период». Утверждение заблокировано guard-ом INV-6 (ItemsCount >= 1). Руководитель меняет период (пересоздаёт), ждёт регистрации акта (UC-RWD-010) или добавляет строки позже (A1), либо отменяет черновик (A3).
A3. Отмена черновика
POST /rewards/agency/payout-orders/{orderId}/cancel { rowVersion }— guardStatus = Draft; поручение переходит вCancelled, все привязанные строки освобождаются (PayoutOrderItemId → null, INV-8) и доступны следующему поручению. Kafka-событие не публикуется (черновик никому не анонсировался). Завершается.
A4. Отклонение утверждённого поручения
- Бухгалтерия/руководитель отклоняет:
POST /rewards/agency/payout-orders/{orderId}/reject { reason: string (1..1000, обязателен — иначе REWARDS_PAYOUT_REJECT_REASON_REQUIRED), rowVersion }— guardStatus = PendingPayment; поручение переходит вRejected(«отклонено», матрица 5.5),RejectedAt/RejectReason; строки освобождаются (INV-8); публикуетсяPayoutOrderStatusChangedKafkaEvent (ToStatus = Rejected, RejectReason). Освобождённые строки можно включить в новое поручение (шаг 7). Завершается.
A5. Начисление выбыло до включения в поручение
- Начисление, отменённое из
Confirmed(UC-RWD-004 A6 / UC-RWD-003 A4) до регистрации акта, в автосборку не попадает (родитель неMoneyReceived). Начисление вMoneyReceivedне отменяется вовсе (INV-8 accrual, ADR-0067) — конфликт «отмена против сборки поручения» после гейта денег исключён; корректировка долей пула возможна, покаAgent-строка свободна (UC-RWD-011, вид 3).
A6. Конкурентное формирование двух поручений
- Два менеджера параллельно создают поручения за пересекающиеся периоды: захват при автосборке гарантирует, что каждая свободная строка достаётся ровно одному поручению (INV-1 payout); второе поручение получает оставшиеся строки без ошибки блокировки.
A7. Агент оверрайда не принадлежит компании
- На шаге 5 проверка в
identityне подтвердилаAgentUserId→REWARDS_POLICY_OVERRIDE_INVALID(400 со списком невалидных id). Руководитель исправляет оверрайды черновика и повторяет активацию.
Постусловие
- Фаза 1: у агентства ровно одна
Activeполитика (INV-1 policy); предыдущая —Archived; применённые ранее распределения не пересчитаны. - Фаза 2:
PayoutOrderEntity (Kind = AgencyCommission, Status = Paid)с позициями по агентам иNumberдля бухгалтерии; привязанные строки распределения и позиции имеютPaidAt; начисления, у которых всеAgent-строки выплачены, — вStatus = Paid(«получили» в ЛК агента; переход только изMoneyReceived, INV-7). - Кошельки агентов дебетованы по позициям (
wallet, асинхронно по событиюPaid— окно секунды); баланс сходится: кредит по гейту денег (UC-RWD-010) − дебет по выплате. - История выплат (матрица 5.5) — список поручений со статусами
PendingPayment/Paid/Rejected, датами и суммами; события статусов опубликованы атомарно.
Согласованность и межсервисное взаимодействие
- Ожидает на входе. Фаза 1 (активация) синхронно обращается в
identityза составом пользователей агентства — проверкаAgentOverrides[].AgentUserId(INV-3, A7); недоступностьidentity→ активация не проходит (повтор позже), уже активная политика продолжает применяться, начисления не блокируются. Фаза 2 (автосборка) опирается на свободныеAgent-строки распределения начислений вMoneyReceived(строки созданы при подтверждении UC-RWD-004, гейт денег пройден регистрацией акта UC-RWD-010):PayoutOrderItemId is null,Amount.Value > 0, родительStatus = MoneyReceived,DealCompletedAt ∈ [PeriodFrom; PeriodTo). Эти строки на момент захвата иммутабельны — суммы зафиксированы при подтверждении (правка долей пула возможна только пока строка свободна, UC-RWD-011). Kafka-входов у сценария нет: политика и поручение управляются командами руководителя. - Предоставляет на выходе.
PayoutOrderStatusChangedKafkaEvent(topicpin.rewards.payout-order-status-changed.v1, key =Idпоручения,ContractVersion = 1) на переходахApprove(шаг 10),MarkPaid(шаг 13, сItems[]),Reject(A4) — ключ =Idгарантирует порядок переходов одного поручения у потребителя. КаскадMarkPaidдополнительно публикует по каждому переведённому начислениюCommissionAccrualStatusChangedKafkaEvent (MoneyReceived → Paid)(topicpin.rewards.commission-accrual-status-changed.v1, key =Idначисления). Политика Kafka не публикует (только доменныеAgencyPolicyCreated/Activated/Archivedin-proc). Потребители:notifications;wallet— поPaidдебетует счетаAgentCommissionполучателей на суммыItems[](идемпотентность(PayoutDebit, ItemId), недостаток баланса — DLQ + алерт, ADR-0065); self-consume проекциейAgentPerformance— переход начисления→ PaidдаётCommissionPaidTotal += CommissionAmount(agent-performance.md). Создание/правка/отмена черновика поручения Kafka не анонсирует (A2/A3 — черновик никому не виден). - Окно eventual-consistency. Синхронно руководитель видит смену статуса и полный каскад сразу (одно
согласованное изменение). Асинхронно (лаг доставки < 1 мин штатно, alert > 5 мин): агенты получают
уведомления о выплате их позиций; сводки ЛК агента (
get-my-commission-summary) и проекцияAgentPerformanceпереводят суммы «получим → получили» после применения событий. В промежутке агент видит прежнюю сводку — лаг проекции, не ошибка. - Идемпотентность. Отдельные ключи не нужны — идемпотентность естественная через guard-статусы: повтор
Activateупирается в guardDraft; повторApprove— в guardDraft; повторMarkPaid— в guardPendingPayment; повторReject/Cancel— в соответствующий guard (REWARDS_PAYOUT_INVALID_STATUS_TRANSITION). Гонка двух активаций ловится частично-уникальным индексомux_agency_policies_tenant_active(REWARDS_POLICY_ACTIVE_EXISTS, retry). Автосборка захватывает свободные строки под блокировкой — каждая строка достаётся ровно одному поручению даже при параллельной сборке (A6). - Конкурентность (кто побеждает). Все переходы политики и поручения защищены оптимистической блокировкой
по
rowVersion: побеждает первая зафиксированная мутация, проигравший получает409и перечитывает актуальный статус. Две параллельные активации → ровно однаActive(INV-1 policy). Начисление вMoneyReceivedне отменяется вовсе (INV-8 accrual, ADR-0067) — конфликт «отмена против сборки» после гейта денег исключён (A5); гонка «корректировка долей ↔ захват строки поручением» разрешается блокировкой начисления (UC-RWD-011 A4). - Восстановление. События статусов доставляются at-least-once, потребители идемпотентны; публикация
события и мутация неразделимы (одно согласованное изменение) — «выплатили, но не уведомили» невозможно.
Ошибочно утверждённое поручение →
Reject(освобождает строки, INV-8) и пересборка. Ошибочно выплаченное (Paid) — терминально: компенсация внеrewards(сторнирующее ручное начисление застройщика, UC-RWD-003 A3, и новое поручение на оставшиеся доли). Отклонённое/отменённое поручение возвращает свои строки в пул свободных — они попадут в следующую автосборку (A4/A3).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
403 FORBIDDEN | 403 | Нет rewards.commission-policy.manage / rewards.payouts.manage / rewards.payouts.approve | Скрыть соответствующие действия |
ValidationError | 400 | Формат формы / periodTo <= periodFrom / пустая причина отклонения | Подсветить поля |
REWARDS_POLICY_RULE_INVALID | 400 | value вне диапазона по kind; currency не согласована с FixedAmount (INV-2) | Подсветить правило |
REWARDS_POLICY_OVERRIDE_INVALID | 400 | Дубль agentUserId / агент не из компании (INV-3, A7) | Показать невалидные оверрайды |
REWARDS_POLICY_ACTIVE_EXISTS | 409 | Гонка активаций политики (INV-1, unique index) | Повторить (retry) |
REWARDS_POLICY_ACTIVE_IMMUTABLE | 409 | Правка Rules/Name активной политики (INV-4) | Создать новый черновик |
REWARDS_PAYOUT_DISTRIBUTION_UNAVAILABLE | 409 | Добавляемая строка занята / родитель не MoneyReceived / чужой tenant (INV-1/9, A1) | Обновить список доступных строк |
REWARDS_PAYOUT_DUPLICATE_RECIPIENT | 500 | Нарушение «один получатель = одна позиция» (INV-2, guard) | Алерт разработке |
REWARDS_PAYOUT_SUM_MISMATCH | 500 | Расхождение сумм позиций/итога (INV-3, guard) | Алерт разработке |
REWARDS_CURRENCY_MISMATCH | 409 | Разные валюты строк в поручении (INV-4) | Показать конфликтные строки |
REWARDS_PAYOUT_COMPOSITION_LOCKED | 409 | Правка состава не в Draft (INV-5, A1) | Перечитать поручение |
REWARDS_PAYOUT_INVALID_STATUS_TRANSITION | 409 | Approve не из Draft / ItemsCount = 0 (INV-6, A2); MarkPaid/Reject не из PendingPayment (INV-7) | Перечитать статус |
REWARDS_PAYOUT_REJECT_REASON_REQUIRED | 400 | Отклонение без причины (INV-8, A4) | Подсветить поле причины |
409 CONFLICT (RowVersion) | 409 | Конкурентные approve/reject/mark-paid (оптимистическая блокировка) | Перечитать и повторить |
404 NOT FOUND | 404 | Политика/поручение не существует или чужой tenant | Вернуться к списку |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
AgencyPolicyCreatedEvent / AgencyPolicyActivatedEvent / AgencyPolicyArchivedEvent | Фаза 1: создание/активация (+автоархив предыдущей) | in-proc аудит |
PayoutOrderCreatedEvent | Создание поручения (шаг 8) | in-proc аудит |
PayoutOrderApprovedEvent | Draft → PendingPayment (шаг 10) | in-proc аудит |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
PayoutOrderStatusChangedKafkaEvent.v1 | pin.rewards.payout-order-status-changed.v1 | Утверждение (шаг 10), отметка выплаты (шаг 13, с Items[]), отклонение (A4) | notifications — агентам о выплате, руководителю о статусах; wallet — дебет счетов по Paid (Items[], ADR-0065) |
CommissionAccrualStatusChangedKafkaEvent.v1 | pin.rewards.commission-accrual-status-changed.v1 | Каскад выплаты: каждое начисление MoneyReceived → Paid (шаг 13, INV-7) | notifications |
Политика Kafka-событий не публикует (agency-commission-policy.md).
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Активация новой политики атомарно архивирует предыдущую; активная всегда одна (INV-1 policy) | параллельные активации → одна Active; unique index |
| AC2 | Оверрайд с чужим/несуществующим агентом отклоняется при активации (INV-3, A7) | тест с посторонним agentUserId → 400 |
| AC3 | Автосборка включает только свободные Agent-строки начислений в MoneyReceived (INV-1 payout, гейт ADR-0067) | тест: PendingConfirmation/Confirmed/занятые/Agency- и Platform-строки не попадают |
| AC4 | Каждая строка — ровно в одном поручении даже при конкурентной сборке (A6) | параллельное создание двух поручений → строки не пересекаются |
| AC5 | Item.Amount = Σ строк агента, TotalAmount = Σ Items, ItemsCount корректен (INV-3) | unit-тест сборки + db check |
| AC6 | Approve пустого поручения невозможен (INV-6, A2) | ItemsCount = 0 → 409 |
| AC7 | Reject/Cancel освобождают строки для следующего поручения (INV-8); начисления остаются MoneyReceived, кошельки не затронуты | reject → строки с PayoutOrderItemId = null, включаются в новое поручение |
| AC8 | Отметка выплаты каскадно: PaidAt на поручении/позициях/строках; начисления со всеми выплаченными Agent-строками → MoneyReceived → Paid (INV-7) — в одном согласованном изменении | интеграционный тест каскада + проверка атомарности |
| AC13 | По событию Paid кошельки агентов дебетуются на суммы Items[]; повтор доставки не двоит дебет ((PayoutDebit, ItemId)) | replay события → один дебет на позицию |
| AC9 | После отметки выплаты в ЛК агента комиссия — «получили», в истории выплат поручение — «выплачено» (матрица 5.5) | интеграционный тест сводок |
| AC10 | События статусов поручения публикуются атомарно | контракт-тест события + проверка атомарности |
| AC11 | Number уникален в tenant и следует формату PO-{yyyy}-{seq} | тест генерации на смене года |
| AC12 | Повтор Approve/MarkPaid/Reject идемпотентен через guard статуса (не двоит событий/каскада) | второй вызов → 409; событие и каскад — однократны |
Наблюдаемость и НФТ
- Логи (без секретов/PII).
agency-policy-activated(policyId,tenantId,previousPolicyId?,overridesCount,version);payout-order-created(orderId,number,tenantId,itemsCount,distributionsCaptured,periodFrom/periodTo);payout-order-approved/payout-order-paid/payout-order-rejected(orderId,number,fromStatus,toStatus,itemsCount,paidAccrualsCount?для каскада, длинаrejectReason— не текст). Суммы (TotalAmount,Item.Amount, доли) — класс Sensitive, в логи не пишутся; реквизиты и ФИО в сервисе отсутствуют. - Метрики.
rewards_policy_activate_total{result};rewards_payout_order_total{status};rewards_payout_mark_paid_duration(p95 каскада — растёт с числом позиций/начислений);rewards_payout_sum_mismatch_total(guard INV-3, в норме0);rewards_currency_mismatch_total(INV-4);rewards_payout_distribution_unavailable_total(гонки захвата строк, A6). - Трейсы. Span команды → (Фаза 2) захват свободных строк и сборка позиций → публикация события; каскад
MarkPaid— вложенные span по каждому переводимому начислениюMoneyReceived → Paid; сквознойtraceparent, синхронный s2s-span вidentityпри активации (валидация оверрайдов). - НФТ (нагрузка/латентность). API p95 < 300 мс (SLO сервиса); автосборка и каскад ограничены числом
строк периода (десятки-сотни, ADR-0056); поручений — единицы на агентство в месяц; лаг события статуса
< 1 мин (alert > 5 мин);
Number— PostgreSQL sequence per tenant-year, монотонный форматPO-{yyyy}-{seq}. - Доступ/безопасность. Три разграниченных permission —
rewards.commission-policy.manage(политика),rewards.payouts.manage(состав поручения),rewards.payouts.approve(утверждение/выплата/отклонение) — в scopeOrganization(CompanyId агентства); агент видит только свои позиции (record-levelAgentUserId,rewards.payouts.view-own); суммы Sensitive не логируются; банковские реквизиты не хранятся.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (1): US-реестр: Владелец агентства (US-OWN-AG)
- scenarios (1): UC-OVR-010. От завершённой сделки до выплаты комиссии и реферального вознаграждения
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API методы: ../api/agency/create-agency-policy.md, ../api/agency/activate-agency-policy.md, ../api/agency/list-agency-policies.md, ../api/agency/create-payout-order.md, ../api/agency/update-payout-order-items.md, ../api/agency/approve-payout-order.md, ../api/agency/mark-payout-order-paid.md, ../api/agency/reject-payout-order.md, ../api/agency/list-payout-orders.md.
- Domain entities: ../domain/agency-commission-policy.md
(INV-1..7,
ResolveRetentionпо пулу), ../domain/payout-order.md (INV-1..10,PayoutOrderKind, stateDiagram), ../domain/commission-accrual.md (строки распределения, гейтMoneyReceived, каскадPaid). - Кошелёк (кредит по гейту, дебет по выплате): ../../wallet/README.md, ADR-0065.
- Пользователи агентства (валидация оверрайдов):
../../identity/domain/user.md; будущие реквизиты выплат:../../profile/domain/requisite-template.md(по конвенции). - Решение №9 ТЗ (платежи отложены):
../../../00-overview/technical-assignment.md. - Связанные UC: UC-RWD-004 (появление строк распределения), UC-RWD-010 (гейт денег — источник выплачиваемых долей), UC-RWD-011 (per-deal корректировка долей), UC-RWD-003 (источник начислений), UC-RWD-009 (реферальные поручения — платформенный аналог).