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

POST /api/v1/platform/receipts — Создание акта поступления средств (черновик)

Создание «Акта поступления средств» в статусе Draft по одному застройщику с подбором позиций — подтверждённых комиссионных начислений (Status = Confirmed) этого DeveloperCompanyId, ещё не включённых в другой не-отменённый акт (domain/platform-receipt.md, INV-RC-1/INV-RC-3). Черновик показывает предпросмотр сходимости Σ позиций против суммы поступления; денежные следствия наступают только при регистрации (register-platform-receipt.md).

Назначение

Финансовый оператор ПИН получил пакетный платёж комиссии от застройщика (одна платёжка за несколько сделок) и заводит акт: указывает застройщика, сумму фактического поступления и реквизит платёжного документа; система подбирает покрываемые начисления — все свободные Confirmed застройщика либо за период сделок, либо по явному списку. Feature group — receipts.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areaplatform
Feature groupreceipts
Статусdraft
Документdocs/06-services/rewards/api/platform/create-platform-receipt.md

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

ПроверкаЗначение
Actor typeUser (финансовый оператор ПИН)
Auth policy / Permissionsrewards.receipts.manage (каталог — permission.md)
Scope (RBAC)платформа
Tenant isolationвне tenant-модели — платформенная сущность (ADR-0052); позиции могут ссылаться на начисления разных агентств одного застройщика
Auditcreate: CreatedByUserId = ActorUserId, Version = 1; финансовый документ — полный след через Version + actor-stamping

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/platform/receipts
Success status201 (+ Location: /api/v1/platform/receipts/{id})
Idempotency headerне применяется (повтор создаёт новый черновик; уже занятые начисления во второй акт не попадут — REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED / REWARDS_RECEIPT_EMPTY)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claims (sub)GuidДаRequiredactor-stamping
DeveloperCompanyIdbodyGuidДаRequired, непустой; логическая ссылка на компанию Type = Developer в identity
AmountbodyMoneyModelДаValue > 0, scale 2; CurrencyId — ссылка на валюту справочника, RUB-only (ADR-0048)Sensitive
PaymentReferencebodystring?Нет<= 256; номер/дата платёжки застройщика
Commentbodystring?Нет<= 2000
AccrualIdsbodyIReadOnlyList<Guid>?Нетуникальны, 1..500; явный состав — взаимоисключим с PeriodFrom/PeriodTo
PeriodFrombodyDateTimeOffset?Нетнижняя граница DealCompletedAt (включительно) для автоподбора
PeriodTobodyDateTimeOffset?Нетверхняя граница DealCompletedAt (исключительно); > PeriodFrom, если обе заданы

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

Полная проекция акта с позициями; дом полей — domain/platform-receipt.md. Операции области receipts ссылаются на эту модель и описывают только дельту.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаPlatformReceiptEntity.IdId акта.
NumberstringДаNumberRC-{yyyy}-{seq} — присвоен при создании.
DeveloperCompanyIdGuidДаDeveloperCompanyIdЗастройщик-плательщик.
AmountMoneyModelДаAmountSensitiveСумма фактического поступления.
ItemsTotalMoneyModelДавычисление (Σ позиций)SensitiveПредпросмотр сходимости: Σ Items[].Amount; в Draft может расходиться с Amount — регистрация расхождение не пропустит (INV-RC-2).
ItemsCountintДаItemsCountЧисло позиций.
PaymentReferencestring?НетPaymentReferenceРеквизит платёжного документа.
Commentstring?НетCommentКомментарий оператора.
Statusstring (enum PlatformReceiptStatus)ДаStatusЗдесь всегда Draft.
RegisteredAtDateTimeOffset?НетRegisteredAtЗаполняется при регистрации.
CancelledAtDateTimeOffset?НетCancelledAtЗаполняется при отмене черновика.
ItemsIReadOnlyList<PlatformReceiptItemModel>Даchild-коллекцияSensitive-суммыПозиции (не пустой).
CreatedAtDateTimeOffsetДаCreatedAtМомент создания.
VersionlongДаVersion1; далее — ExpectedVersion мутаций.

PlatformReceiptItemModel

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаPlatformReceiptItemEntity.IdId позиции.
CommissionAccrualIdGuidДаCommissionAccrualIdПокрываемое начисление (domain/commission-accrual.md).
AgencyCompanyIdGuidДаAgencyCompanyIdАгентство начисления (денормализация для состава).
DealIdGuidДаначислениеСделка начисления (сверка с платёжкой застройщика).
AmountMoneyModelДаAmount (= CommissionAmount начисления на момент включения)SensitiveСумма позиции.

Алгоритм

  1. Контекст и доступ. Операцию выполняет финансовый оператор ПИН в платформенном scope; требуется право rewards.receipts.manage.
  2. Проверка входа. DeveloperCompanyId непустой; Amount.Value > 0; AccrualIds (если заданы) — уникальны, 1..500, и тогда PeriodFrom/PeriodTo не заданы (взаимоисключение); PeriodTo > PeriodFrom, если обе заданы; длины PaymentReference/Comment; иначе ValidationError.
  3. Подбор позиций. Кандидаты читаются под блокировкой:
    • явный состав (AccrualIds): каждое начисление существует, Status = Confirmed и его DeveloperCompanyId равен застройщику акта (INV-RC-1) — иначе REWARDS_RECEIPT_ITEM_INVALID; не входит в другой не-отменённый акт (Draft/Registered, INV-RC-3) — иначе REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED;
    • автоподбор (без AccrualIds): все свободные Confirmed-начисления застройщика (не входящие в не-отменённые акты); при заданном периоде — дополнительно DealCompletedAt ∈ [PeriodFrom, PeriodTo). Если позиций не набралось ни одной → REWARDS_RECEIPT_EMPTY — пустой черновик не создаётся.
  4. Валюта. CurrencyId суммы акта и CommissionAmount всех позиций един — иначе REWARDS_CURRENCY_MISMATCH (валюта акта = валюте покрываемых начислений).
  5. Создание черновика. Акт создаётся в Status = Draft, Version = 1, CreatedByUserId = актор; номер Number = "RC-{yyyy}-{seq}" — из платформенной последовательности per year; на каждое отобранное начисление — позиция с Amount = CommissionAmount на момент включения. Акт, позиции и захват начислений (unique-индекс по CommissionAccrualId, INV-RC-3) фиксируются в одном согласованном изменении; гонка двух актов за одно начисление разрешается конфликтом индекса — проигравший получает REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED и пересобирает состав. Статусы начислений на этом шаге не меняются (переход Confirmed → MoneyReceived — только при регистрации); интеграционные события не публикуются.
  6. Результат. Полная проекция акта с позициями и предпросмотром ItemsTotal против Amount → HTTP 201 + Location. Расхождение сумм в Draft допустимо: оператор лечит его до регистрации — правкой состава, корректировкой gross начисления (adjust-commission-accrual.md) или исправлением Amount акта.

Диаграмма

Ошибки и failure modes

Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — дом README.md «Общие ошибки», здесь не дублируются. Специфичные для операции:

КодHTTP statusУсловиеСообщениеRetry
REWARDS_RECEIPT_ITEM_INVALID409Явно указанное начисление не существует / не Confirmed / чужого застройщика (INV-RC-1)«Начисление недоступно для включения в акт»no retry (исправить состав)
REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED409Начисление уже в составе другого не-отменённого акта (INV-RC-3; в т.ч. гонка двух создаваемых актов)«Начисление уже включено в другой акт»no retry (пересобрать состав)
REWARDS_RECEIPT_EMPTY409Ни одной позиции не набралось (нет свободных Confirmed под условия подбора)«Нет начислений, готовых к включению в акт»no retry (сменить условия подбора)
REWARDS_CURRENCY_MISMATCH400Валюта суммы акта и валюты начислений не совпадают«Валюта акта не совпадает с валютой начислений»no retry

Acceptance criteria

  • AC-1 (автоподбор за период). Given у застройщика есть свободные Confirmed-начисления с DealCompletedAt в [PeriodFrom, PeriodTo); When оператор создаёт акт с периодом; Then заводится Draft с Number (RC-{yyyy}-{seq}), на каждое начисление — позиция с Amount = CommissionAmount, ответ 201 + предпросмотр ItemsTotal; статусы начислений не изменились. Постусловие: включённые начисления заняты этим актом — второй акт их не подберёт (INV-RC-3).
  • AC-2 (явный состав). Given список AccrualIds из свободных Confirmed-начислений застройщика; When создание; Then в акт попали ровно перечисленные позиции; лишние начисления автоподбором не добавляются.
  • AC-3 (чужое/неподтверждённое начисление в явном составе). Given в AccrualIds есть начисление другого застройщика или в статусе PendingConfirmation/MoneyReceived; When создание; Then REWARDS_RECEIPT_ITEM_INVALID (409) — акт не создаётся целиком (частичного создания нет).
  • AC-4 (занятое начисление). Given начисление уже в Draft-акте другого оператора; When создание с ним в составе; Then REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED (409).
  • AC-5 (пустой подбор). Given под условия подбора нет ни одного свободного Confirmed; When создание; Then REWARDS_RECEIPT_EMPTY (409) — пустой черновик не создаётся.
  • AC-6 (расхождение сумм допустимо в Draft). Given Σ позиций ≠ Amount; When создание; Then 201, ItemsTotal показывает расхождение — блокируется только регистрация (INV-RC-2), не создание.
  • AC-7 (границы периода). Given начисление с DealCompletedAt == PeriodFrom и начисление с DealCompletedAt == PeriodTo; When автоподбор; Then первое включено (нижняя граница включительна), второе — нет (верхняя исключительна).
  • AC-8 (конкурентное создание). Given два параллельных акта по одному застройщику; When оба подбирают позиции; Then каждое свободное начисление попадает ровно в один акт (unique-индекс INV-RC-3); проигравший явный состав получает REWARDS_RECEIPT_ACCRUAL_ALREADY_INCLUDED, автоподбор — оставшиеся свободные либо REWARDS_RECEIPT_EMPTY.
  • AC-9 (доступ). Given актор без rewards.receipts.manage; When создание; Then FORBIDDEN (403). Агентства и застройщики актов не видят — им виден только эффект по своим начислениям.

Смежные операции (тонкие дельты)

  • updatePlatformReceiptItemsPUT /api/v1/platform/receipts/{receiptId}/items (rewards.receipts.manage): полная замена состава Draft-акта списком AccrualIds (те же проверки INV-RC-1/3 и валюты, что на шаге 3–4; исключённые позиции удаляются — начисления освобождаются; Version + 1). Вне DraftREWARDS_RECEIPT_COMPOSITION_LOCKED (409); Registered-акт неизменяем → REWARDS_RECEIPT_REGISTERED_IMMUTABLE (409).
  • cancelPlatformReceiptPOST /api/v1/platform/receipts/{receiptId}/cancel (rewards.receipts.manage): Draft → Cancelled, позиции физически удаляются — начисления доступны другому акту (domain/platform-receipt.md, stateDiagram); из RegisteredREWARDS_RECEIPT_REGISTERED_IMMUTABLE (409), повтор из CancelledREWARDS_RECEIPT_INVALID_STATUS_TRANSITION (409). События не публикуются (черновик наружу не анонсировался).

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

  • Контракт additive-only (PaymentReference/Comment/период — optional); PlatformReceiptModel — дом этого документа, смежные операции описывают только дельту; breaking-изменение проекции — версия route.
  • Logs: ReceiptId, Number, DeveloperCompanyId, ItemsCount, id начислений (суммы не логируются — Sensitive); metrics: rewards_receipt_create_total{result}, rewards_receipt_items_count (гистограмма); traces: OTel, correlation через traceparent; dashboard rewards-overview.
  • НФТ (ADR-0056): подбор — по ix_commission_accruals_developer (developer_company_id, status); объём — десятки-сотни начислений на застройщика; целевая латентность p95 < 500 мс; частота — единицы актов в неделю на застройщика (back-office финансового оператора).

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

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

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