GET /api/v1/my/commission-accruals — Мои начисления (агент)
Назначение
Отдаёт агенту в личном кабинете постраничный список начислений по его завершённым сделкам: статус
подтверждения и выплаты, сумму сделки и свою долю комиссии. Агент видит только свои начисления и только
собственную долю: полное распределение и удержание агентства ему не раскрываются — проекция редуцируется до
MyAccrualModel (только собственная Agent-строка), в отличие от agency-версии
(list-agency-accruals.md, view-all). Служит источником для сверки со
сводкой (get-my-commission-summary.md) и историей выплат
(list-my-payouts.md). Feature group — my-commission.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agent |
| Feature group | my-commission |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agent/list-my-accruals.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент) |
| Auth policy / Permissions | rewards.commission.view-own (op28 — реестр README.md) |
| Scope (RBAC) | организация: собственное агентство агента (Type ∈ {Agency, Broker, Pin}) |
| Record-level | AgentUserId = ActorUserId из токена (sub), не из query — начисление принадлежит агенту по полю agent_user_id; доступ ограничивается явным фильтром по этому полю |
| Tenant isolation | TenantId = CompanyId агентства из токена; изоляция по tenant применяется на уровне хранилища независимо от параметров запроса |
| Audit | read-sensitive (суммы комиссий): в trace фиксируются actor + фильтры, суммы не логируются |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/my/commission-accruals |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ActorUserId | токен (sub) | Guid | Да | Required; не принимается из query | record-level фильтр AgentUserId |
TenantId | токен (tenant_id) | Guid | Да | Required; не принимается из query | Tenant isolation |
Status | query | CommissionAccrualStatus? | Нет | enum-строка PendingConfirmation/Confirmed/Paid/Cancelled | — |
Page / PageSize | query | int | Нет | paging-конвенция — README.md (Page >= 1, PageSize 1..100, default 20) | — |
Модель ответа — страница MyAccrualModel
Ответ — страница элементов MyAccrualModel с полями Items, Page, PageSize, TotalCount
(paging-конвенция — README.md). Дом базовой проекции — CommissionAccrualModel
(README.md).
MyAccrualModel — дельта от CommissionAccrualModel
Редуцированная агентская проекция. Наследует все поля CommissionAccrualModel, кроме маскируемых, и
добавляет собственную долю агента:
| Изменение | Поле | Тип | Правило |
|---|---|---|---|
| Убрано (masking) | Distributions | — | Полный список долей (в т.ч. Agency-строка удержания) агенту не отдаётся — Masking-правила README.md |
| Убрано (masking) | AppliedPolicyId | — | Внутренняя ссылка на политику агентства; документ политики агенту не отдаётся |
| Добавлено | MyShareAmount | MoneyModel? | Сумма собственной Agent-строки распределения (CommissionDistributionEntity где RecipientType == Agent и RecipientUserId == AgentUserId, Amount). null до статуса Confirmed (строк распределения ещё нет; прогноз — в get-my-commission-summary.md) |
| Не проецируется | InvestorUserId | — | Никогда не отдаётся в API rewards (как и в базовой модели) |
Сохраняются как есть: Id, DealId, DeveloperCompanyId, AgencyCompanyId, AgentUserId (= ActorUserId),
PropertyObjectId, DealCompletedAt, CalculationBase, CommissionPercent, BasePriceAmount, FinalPriceAmount,
CommissionAmount (gross сделки; фиксируется как «комиссия по сделке»), AppliedRule (ref-модель id+имя),
Status, ConfirmedAt, PaidAt, CancelledAt, CancelReason, CreatedAt, Version. Суммы — Sensitive-класс:
в теле ответа отдаются, в logs не пишутся.
Алгоритм
- Контекст и доступ. Актор — агент; tenant — его агентство (
Type ∈ {Agency, Broker, Pin}); требуется правоrewards.commission.view-own. Агент видит только свои начисления:AgentUserIdберётся из токена, не из запроса. - Проверка входа. Значение enum
Status(если задано) допустимо; paging в границах конвенции (Page >= 1,PageSize 1..100) — иначеValidationError. Тип компании актора вне{Agency, Broker, Pin}→FORBIDDEN. Входных id, требующих существования, нет (агент гарантирован токеном). - Что читаем. Страница начислений агента, отобранных по обязательному record-level условию
AgentUserId = ActorUserIdи опциональному фильтруStatus, отсортированных поDealCompletedAtпо убыванию (свежие сделки первыми). Вместе с начислениями загружаются их строки распределения — они нужны для расчётаMyShareAmount. Ведущий индекс —ix_commission_accruals_agent(tenant_id, agent_user_id). - Бизнес-правила и маппинг. Проекция начисления в
MyAccrualModelполе-в-поле:AgencyCompanyIdберётся из tenant начисления;AppliedRule.RuleName— из снапшота применённого правила ("manual"для ручных начислений);MyShareAmount— сумма единственнойAgent-строки распределения агента (INV-9),nullпока начисление не подтверждено. ПоляDistributions,AppliedPolicyId,InvestorUserIdв проекцию не попадают (masking). Переходов состояний нет. - Результат. Страница
MyAccrualModel(Items,Page,PageSize,TotalCount) → HTTP200.
Диаграмма
Ошибки и failure modes
Специфичных для операции REWARDS_*-кодов нет (read без мутаций и concurrency). Применяются общие коды —
дом README.md:
| Код | HTTP status | Условие | Retry |
|---|---|---|---|
ValidationError | 400 | enum Status / paging вне диапазона | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT агента | no retry |
FORBIDDEN | 403 | нет rewards.commission.view-own / тип компании вне {Agency, Broker, Pin} | no retry |
Предусловия и постусловия
- Предусловия. Агент авторизован (JWT), состоит в агентстве (
Type ∈ {Agency, Broker, Pin}) и имеет правоrewards.commission.view-own. По агенту могут существовать начисления в любом из статусовPendingConfirmation/Confirmed/Paid/Cancelled— либо не существовать вовсе. СобственнаяAgent-строка распределения появляется только с моментаConfirmначисления (INV-4 domain/commission-accrual.md) — до этого доля в строках не зафиксирована. - Постусловия. Операция только читает: состояние
rewardsне меняется, события не публикуются. Возвращена страницаMyAccrualModel, отобранная по record-level условиюAgentUserId = ActorUserIdи опциональномуStatus, отсортированная поDealCompletedAtубыв.MyShareAmountзаполнен дляConfirmed/Paid(сумма собственнойAgent-строки) иnullдляPendingConfirmation. Полное распределение (Distributions), удержание агентства,AppliedPolicyId,InvestorUserIdв ответ не попадают (masking).
Acceptance-критерии
- Happy path. Дано: агент с правом
rewards.commission.view-own, начисления в разных статусах. Когда:GET /api/v1/my/commission-accruals. Тогда:200, страницаMyAccrualModelотсортирована поDealCompletedAtубыв.; дляConfirmed/Paid—MyShareAmount= сумма собственнойAgent-строки, дляPendingConfirmation—MyShareAmount = null;Distributions/AppliedPolicyId/InvestorUserIdв элементах отсутствуют. - Фильтр по статусу. Дано:
Status = Confirmed. Когда:GET. Тогда:200, только начисления в статусеConfirmed;TotalCount= число таких начислений агента. - Неизвестный статус. Дано:
Statusвне enumCommissionAccrualStatus. Когда:GET. Тогда:400ValidationError. - Paging вне диапазона. Дано:
PageSize = 500(илиPage = 0). Когда:GET. Тогда:400ValidationError(PageSize 1..100,Page >= 1). - Пустая выборка. Дано: у агента нет начислений (в т.ч. под фильтром). Когда:
GET. Тогда:200,Items = [],TotalCount = 0— не404. - Доля до подтверждения. Дано: начисление
PendingConfirmation(строк распределения ещё нет, INV-4). Когда:GET. Тогда: элемент присутствует,MyShareAmount = null; прогноз доли доступен в get-my-commission-summary.md. ПослеConfirmто же начисление отдаёт непустойMyShareAmount(eventual consistency — после обработки подтверждения застройщиком). - Только своя доля. Дано: агент пытается передать
AgentUserId/TenantIdв query. Когда:GET. Тогда: параметры игнорируются — ключи берутся из токена (sub/tenant_id); чужие начисления в выборку не попадают (fail-closed record-level). - Нет права / не в агентстве. Дано: нет
rewards.commission.view-ownлибо тип компании актора вне{Agency, Broker, Pin}. Когда:GET. Тогда:403FORBIDDEN.
Совместимость и наблюдаемость
MyAccrualModel— производная проекция общегоCommissionAccrualModel: новые поля additive nullable; удаление/переименование/смена типа — breaking (migration + rollout). Unknown-значение enumStatusв query →ValidationError.- Logs:
TenantId,AgentUserId, применённыйStatus,TotalCount(суммы и доли не логируются); metrics:rewards_api_request_duration_seconds{route}; traces: сквозной trace черезtraceparent; dashboardrewards-overview; runbook при деградации выборки —docs/06-services/rewards/runbooks/(по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): GET /api/v1/my/agent-performance — Моя личная статистика (агент, user-centric), GET /api/v1/my/commission-summary — Моя сводка комиссий (агент), GET /api/v1/my/payouts — Мои выплаты (агент), POST /api/v1/my/commission-accruals/{accrualId}/adjustment-requests — Заявка агента на корректировку своей доли, Rewards API — реестр операций и общие контрактные модели
- Отчёты (1): Отчет/Export: Вознаграждения агентства по сотрудникам и сделкам (agency-rewards-by-employee-per-deal)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность и инварианты распределения: domain/commission-accrual.md
(индекс
ix_commission_accruals_agent,Agent-строкаCommissionDistributionEntityINV-9, maskingInvestorUserId/Distributions— раздел «Безопасность»). - Базовая модель
CommissionAccrualModel,MoneyModel, paging и Masking-правила — README.md. - Смежные agent-операции: get-my-commission-summary.md (агрегаты и прогноз
доли), list-my-payouts.md (история выплат). Полный список tenant-а —
list-agency-accruals.md (agency-area,
view-all). - Permissions — каталог ../../identity/domain/permissions.md; решение №2 (tenancy) — ../README.md; ADR-0052.