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

GET /api/developers/v1/promotions — Мои покупки продвижения

Назначение

Возвращает менеджеру компании его покупки продвижения (PromotionPurchase, promotion-purchase.md) с контекстом пакета (код/название) — экран «Продвижение → Мои покупки» ЛК (оплата не требуется — решение №10, PaymentStatus = NotRequired). Читается для отслеживания активных бустов, срока действия и статуса (в т.ч. Cancelled/Expired с причиной).

Metadata

ПолеЗначение
Сервис/контекстpromotion
API areadevelopers
Feature grouppromotions
Статусdraft
Документdocs/06-services/promotion/api/developers/list-my-promotions.md

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

ПроверкаЗначение
Actor typeUser (менеджер компании — JWT identity)
Auth policy / Permissionspermission promotion.purchases.view в scope организации
Scope (RBAC)организация (компания-покупатель)
Record-levelне применяется (изоляция — tenant)
Tenant isolationTenantId = CompanyId из identity claims (= PromotionPurchase.TenantId, ADR-0052); выборка покупок автоматически ограничивается компанией актора — компания видит только свои покупки
Auditnone (чтение собственных данных)

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired (= CompanyId)tenant isolation
Statusquerystring? (enum PromotionPurchaseStatus)Нет∈ {Pending, Active, Expired, Cancelled}; null — все
Kindquerystring? (enum PromotionPackageKind)Нет∈ {ProjectBoost, PropertyObjectBoost, PostBoost}; фильтр по PackageKind; null — все
TargetKindquerystring? (enum PromotionTargetKind)Нет∈ {Project, PropertyObject, Post}; фильтр «есть цель такого типа»; null — любой
PagequeryintНет (default 1)>= 1
PageSizequeryintНет (default 50)1..200

Enum-значения — дом promotion-purchase.md / promotion-package.md; неизвестное значение enum → ValidationError.

Модель ответа: страница MyPromotionItem

Постраничный список (Items, TotalCount, Page, PageSize). Пагинация — offset (покупок у компании десятки); при росте истории — keyset по (StartsAt desc, Id) без смены контракта.

Модель MyPromotionItem (проекция PromotionPurchase)

ПолеТипОбязательностьИсточникMaskingОписание
PurchaseIdGuidДаPromotionPurchase.IdПокупка.
PackageIdGuidДаPromotionPurchase.PackageIdКупленный пакет.
PackageCodestringДаPromotionPurchase.PackageCode (снапшот)Стабильный код (project-boost-week).
PackageNamestringДаPromotionPackage.Name (батч-джойн шага 3)Отображаемое название пакета.
PackageKindstring (enum PromotionPackageKind)ДаPromotionPurchase.PackageKind (снапшот)ProjectBoost / PropertyObjectBoost / PostBoost.
TargetsPromotionTargetModel[]ДаPromotionPurchaseTarget.Target (owned-коллекция)Цели покупки ({ TargetKind, TargetId }).
Statusstring (enum PromotionPurchaseStatus)ДаPromotionPurchase.StatusPending/Active/Expired/Cancelled.
PaymentStatusstring (enum PromotionPaymentStatus)ДаPromotionPurchase.PaymentStatusСейчас всегда NotRequired (решение №10).
BoostWeightintДаPromotionPurchase.BoostWeight (снапшот)Вес буста покупки (1..100).
PricePromotionPriceModelДаPromotionPurchase.Price (owned снапшот)Снапшот цены (информационно; оплата не требуется).
StartsAtDateTimeOffsetДаPromotionPurchase.StartsAtНачало действия.
EndsAtDateTimeOffsetДаPromotionPurchase.EndsAtКонец действия (job expire-promotionsExpired).
CancelReasonstring?Нет (обязателен при Status=Cancelled)PromotionPurchase.CancelReasonПричина отмены; null в прочих статусах.
VersionlongДаPromotionPurchase.VersionДля optimistic-check при отмене (cancel-promotion.md).

PromotionPriceModel ({ Amount: decimal(14,2) >= 0, CurrencyId: short }) — проекция снапшота цены PromotionPrice (дом — promotion-package.md); CurrencyId — ссылка на запись справочника валют handbook (RUB — ADR-0048), display-код/знак клиент берёт из lookup справочника.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер компании; tenant — его компания; требуется permission promotion.purchases.view.
  2. Проверка входа. Status/Kind/TargetKind ∈ соответствующих enum, Page/PageSize в границах → иначе ValidationError. Принадлежность гарантирует tenant-изоляция (компания видит только свои покупки).
  3. Выборка. Отобрать страницу покупок компании по фильтрам: Status; PackageKind = Kind; «есть цель типа TargetKind»; сортировка StartsAt desc, затем Status; пагинация (Page, PageSize). Цели покупок читаются вместе с покупкой. Для отображаемого названия пакета подтянуть пакеты по их id, включая архивные: Name в покупку не снапшотится, а пакет мог быть архивирован при живой покупке (PackageCode — из снапшота покупки).
  4. Маппинг. Поля PromotionPurchase → одноимённые поля MyPromotionItem; PackageName ← название найденного пакета (fallback "(пакет архивирован)"); Targets ← цели покупки; PricePromotionPriceModel; CancelReason — только при Status = Cancelled, иначе null.
  5. Результат. Страница MyPromotionItem200; freshness — source of truth этого контекста; Expired проставляет job expire-promotions (свежесть статуса — в пределах интервала job-а).

Граничные случаи. Компания без покупок (или без совпадений по фильтрам) → 200 с пустым Items, TotalCount = 0 (не ошибка). Page за пределами данных → пустая страница с корректным TotalCount. Фильтры комбинируются по И (StatusKindTargetKind). Покупка с истёкшим EndsAt, ещё не обработанная job-ом, показывается как Active до ближайшего цикла истечения (свежесть статуса — в пределах интервала job-а); поле Version в item-е нужно для optimistic-check при последующей отмене.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Невалидные enum (Status/Kind/TargetKind), pagingОшибка валидации запросаno retry
UNAUTHORIZED401Нет/просрочен JWTТребуется авторизацияno retry
FORBIDDEN403Нет permission promotion.purchases.view в scope организацииДоступ запрещёнno retry

Доменных PROMOTION_*-ошибок нет: операция читает только собственные данные tenant-а.

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

  • Новые optional поля item-а (будущие платёжные атрибуты, новые TargetKind) — additive; удаление/ переименование/смена типа — breaking. Unknown enum в фильтре → ValidationError.
  • Logs: my-promotions-listed (tenantId, фильтры, count); metrics: promotion_operations_total{operation="list-my-promotions"}, duration; traces — сквозной trace через traceparent. Dashboard promotion-overview.

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

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

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