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

GET /api/v1/agency/commission-summary — Сводка комиссий агентства

Денежная сводка ЛК агентства «сколько комиссии получим/получили × подтверждено/ожидает» одним объектом: четыре статусные корзины (сумма+количество) плюс разбивка удержания агентства и доли агентов (матрица «Агенство» 5.3/5.6). Агрегация считается на лету по начислениям и строкам распределения агентства (ADR-0056, без ClickHouse): одна выборка по статус-корзинам, одна — по распределению.

Назначение

Руководитель/администратор агентства на дашборде ЛК видит финансовую картину по своим начислениям за период: сколько ожидает подтверждения застройщиком, сколько подтверждено (но не выплачено), сколько уже выплачено, сколько отменено; и в разрезе распределения — сколько удержано агентством и сколько причитается агентам. Owning-сервис — rewards, area agency, feature group agency-commission. Точечная сводка по одному агенту — get-agent-commission-summary.md; сводка агента о себе — get-my-commission-summary.md.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (руководитель/администратор агентства)
Auth policy / Permissionsrewards.commission.view-all (op19)
Scope (RBAC)организация: компания Type ∈ {Agency, Broker, Pin} актора
Record-levelне применяется — сводка по всем начислениям агентства-tenant-а
Tenant isolationTenantId = CompanyId агентства из identity claims; выборка начислений и распределения автоматически ограничена tenant-ом агентства (ADR-0052)
Auditread-sensitive (суммы комиссий): в trace — actor + период/фильтры; суммы не логируются

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/agency/commission-summary
Success status200
Idempotency headerне применяется (read)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired (tenant агентства); не из querytenant isolation
DealCompletedFromqueryDateTimeOffset?Нетнижняя граница периода по DealCompletedAt (включительно)
DealCompletedToqueryDateTimeOffset?Нет> DealCompletedFrom, если обе заданы; верхняя граница (исключительно)
DeveloperCompanyIdqueryGuid?Нетопц. сужение до одного застройщика-плательщика

Период необязателен: без границ считается вся история агентства. Границы применяются к CommissionAccrualEntity.DealCompletedAt (индекс ix_commission_accruals_deal_completed_at).

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

Расширяет CommissionSummaryModel (README.md) двумя дельта-полями распределения. Каждая статусная метрика — пара сумма (MoneyModel) + количество; база суммирования агентства — CommissionAccrualEntity.CommissionAmount (gross). Дельта-поля суммируют CommissionDistributionEntity.Amount по RecipientType (строки существуют только у Confirmed/Paid начислений — commission-accrual.md, INV-4).

ПолеТипОбязательностьИсточникОписание
PendingConfirmationTotal / PendingConfirmationCountMoneyModel / intДаΣ/count CommissionAmount где Status = PendingConfirmation«получим, ожидает подтверждения»
ConfirmedTotal / ConfirmedCountMoneyModel / intДаStatus = Confirmed«получим, подтверждено, не выплачено»
PaidTotal / PaidCountMoneyModel / intДаStatus = Paid«получили»
CancelledTotal / CancelledCountMoneyModel / intДаStatus = Cancelledсправочно
AgencyRetainedTotalMoneyModelДаΣ Amount строк RecipientType = Agency в периодеΔ: удержано агентством (доход агентства)
AgentsShareTotalMoneyModelДаΣ Amount строк RecipientType = Agent в периодеΔ: причитается агентам

Валюта всех MoneyModel — платформенная RUB (INV-6, мультивалютность не проектируется); InvestorUserId в сводке отсутствует (Masking-правила README.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании Agency/Broker/Pin); scope — организация (TenantId из claims, не из query); требуется право rewards.commission.view-all. Иначе — FORBIDDEN.
  2. Проверка входа. Если заданы обе границы периода — DealCompletedTo > DealCompletedFrom; иначе ошибка валидации. Существование сущностей по id не проверяется (агрегатный read).
  3. Что читаем. Две агрегации по данным агентства (обе ограничены tenant-ом):
    • по начислениям: группировка по Status → сумма и количество CommissionAmount в разрезе статуса; фильтр — период по DealCompletedAt (если задан) и опц. DeveloperCompanyId. Опорные индексы ix_commission_accruals_tenant_status, ix_commission_accruals_deal_completed_at;
    • по строкам распределения начислений Confirmed/Paid: группировка по RecipientType → сумма Amount для Agency (удержано) и Agent (причитается). Индекс ix_commission_distributions_recipient.
  4. Сборка модели. Развернуть агрегации в AgencyCommissionSummaryModel: каждой статусной корзине — пара сумма/количество (отсутствующая корзина → 0/0); AgencyRetainedTotal/AgentsShareTotal — из строк Agency/Agent. Все суммы — MoneyModel в валюте RUB (INV-6). Если в выборке встретились суммы в разных валютах (нарушение INV-6) → REWARDS_CURRENCY_MISMATCH.
  5. Результат. AgencyCommissionSummaryModel → HTTP 200. Пустой период → все суммы 0 RUB, счётчики 0 (не 404).

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone
External callsnone
Cache / projections / searchnone (агрегация на лету по индексам PostgreSQL)
Notificationsnone

Ошибки и failure modes

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

КодHTTP statusУсловиеСообщениеRetry
ValidationError400DealCompletedTo <= DealCompletedFrom«Проверьте границы периода»no retry
REWARDS_CURRENCY_MISMATCH409в выборке встретились суммы в разных валютах (нарушение INV-6; не ожидается на RUB-платформе)«Несогласованная валюта в сводке»no retry

Кросс-сервисная свежесть и согласованность корзин

Сводка агрегируется на лету из OLTP rewards, поэтому статусные корзины строго согласованы между собой на момент запроса: сумма по всем корзинам равна сумме CommissionAmount начислений за период, а AgencyRetainedTotal + AgentsShareTotal по Confirmed+Paid равно сумме CommissionAmount этих же начислений (INV-5 распределения). Новые начисления приходят асинхронно консюмером pin.crm.deal-completed.v1 — только что завершённая в CRM сделка может ещё не попасть в PendingConfirmationTotal (окно обработки события — секунды). Изменения статусов (Confirm/MarkPaid/ Cancel) отражаются в корзинах немедленно после коммита.

Acceptance criteria

  • AC-1 (полная сводка). Given у агентства есть начисления во всех статусах; When руководитель запрашивает сводку без периода; Then 200 с четырьмя парами сумма/количество (PendingConfirmation, Confirmed, Paid, Cancelled) плюс AgencyRetainedTotal и AgentsShareTotal; все суммы — MoneyModel в RUB.
  • AC-2 (согласованность распределения). Given есть Confirmed/Paid начисления; Then AgencyRetainedTotal + AgentsShareTotal (по строкам этих начислений) равно сумме их CommissionAmount (INV-5) — сводка внутренне непротиворечива.
  • AC-3 (фильтр по периоду и застройщику). Given заданы DealCompletedFrom/DealCompletedTo и/или DeveloperCompanyId; When запрос; Then агрегируются только начисления с DealCompletedAt в [From, To) и указанного застройщика-плательщика; To исключителен.
  • AC-4 (пустой период). Given за период нет начислений; When запрос; Then 200, все суммы 0.00 RUB, все counts 0 (не 404).
  • AC-5 (валидация периода). Given обе границы заданы и DealCompletedTo <= DealCompletedFrom; When запрос; Then ValidationError (400).
  • AC-6 (доступ и изоляция). Given актор без rewards.commission.view-all или не роль агентства — FORBIDDEN (403); Given актор агентства A — сводка считается только по начислениям A (TenantId из claims, не из query); суммы в теле есть, в логи не пишутся.
  • AC-7 (кросс-сервисное окно). Given сделка завершена в CRM только что; When сводка запрошена до обработки события консюмером; Then её сумма ещё не в PendingConfirmationTotal — появится после обработки deal-completed; это ожидаемое поведение.

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

  • Модель расширяет общий CommissionSummaryModel: новые поля — additive; удаление/переименование/смена типа — breaking (migration + rollout).
  • Logs: TenantId, период/DeveloperCompanyId, итоговые counts (суммы не логируются — Sensitive); metrics: rewards_api_request_duration_seconds{route}; traces — OTel, correlation через traceparent; dashboard rewards-overview.
  • НФТ (ADR-0056): две индексные агрегации (SUM/COUNT) по данным одного агентства — объём = число сделок агентства (сотни-тысячи), без ClickHouse; целевая латентность p95 < 300 мс; операция дашборда — вызывается при открытии ЛК (низкая частота, back-office).

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

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

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