GET /api/admin/v1/promotion-packages — Управленческий список пакетов (admin)
Назначение
Возвращает администратору платформы управленческий список платформенных пакетов продвижения
PromotionPackage (promotion-package.md) для управления каталогом
монетизации (оплата отложена — решение №10). Отличие от витрины застройщика
(../developers/list-promotion-packages.md): та отдаёт только
Status=Active; управленческий список отдаёт пакеты всех статусов (Draft/Active/Archived,
включая архив) плюс полную аудит-проекцию и счётчик действующих покупок — чтобы админ видел черновики до
публикации и последствия архивации. Чтение; создание/правка/архивация — соседние admin-операции.
| Поле | Значение |
|---|
| Сервис/контекст | promotion |
| API area | admin (ADR-0053, m2m) |
| Feature group | admin-promotions |
| Статус | draft |
| Документ | docs/06-services/promotion/api/admin/list-promotion-packages.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | ExternalSystem (администратор платформы; машинный доступ admin API — ADR-0053) |
| Auth policy / Permissions | policy AdminSystemOnly; permission promotion.packages.admin (identity/domain/permission.md) |
| Scope (RBAC) | платформа (пакеты — платформенный каталог, не tenanted — ADR-0052 п.3) |
| Record-level | не применяется |
| Tenant isolation | не применяется — PromotionPackage не привязан к tenant. Счётчик активных покупок читается по всем компаниям (cross-tenant, шаг 3) |
| Audit | read-sensitive: чтение управленческого списка логируется; глубокий аудит мутаций — в create/update/archive |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/admin/v1/promotion-packages |
| Success status | 200 |
| Idempotency header | не применяется (чтение) |
| Correlation | сквозной trace через заголовок traceparent; X-Admin-User-Id (Guid админа) — обязательный заголовок admin-области |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | лог admin-доступа |
Status | query | string? (enum PromotionPackageStatus) | Нет | ∈ {Draft, Active, Archived}; null — все не-архивные (см. шаг 3) | — |
Kind | query | string? (enum PromotionPackageKind) | Нет | ∈ {ProjectBoost, PropertyObjectBoost, PostBoost}; null — все | — |
IncludeArchived | query | bool | Нет (default false) | true — включить soft-deleted-архив (Status=Archived); при Status=Archived подразумевается true | — |
Page | query | int | Нет (default 1) | >= 1 | — |
PageSize | query | int | Нет (default 50) | 1..200 | — |
Постраничный список (Items, TotalCount, Page, PageSize).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | PromotionPackage.Id | — | Пакет. |
Code | string | Да | PromotionPackage.Code | — | Стабильный код. |
Name | string | Да | PromotionPackage.Name | — | Название. |
Description | string? | Нет | PromotionPackage.Description | — | Маркетинговый текст. |
Kind | string (enum PromotionPackageKind) | Да | PromotionPackage.Kind | — | ProjectBoost / PropertyObjectBoost / PostBoost. |
BoostWeight | int | Да | PromotionPackage.BoostWeight | — | Вес буста (1..100). |
DurationDays | int | Да | PromotionPackage.DurationDays | — | Срок действия покупки (1..365). |
MaxTargetsPerPurchase | int? | Нет | PromotionPackage.MaxTargetsPerPurchase | — | Лимит целей (multi-target Kind). |
PriceAmount | decimal | Да | PromotionPackage.Price.Amount | — | Прейскурант (информационно; оплата не требуется). |
PriceCurrencyId | short | Да | PromotionPackage.Price.CurrencyId | — | Ссылка на запись справочника валют handbook (RUB — ADR-0048). |
Status | string (enum PromotionPackageStatus) | Да | PromotionPackage.Status | — | Draft / Active / Archived. |
ActivePurchasesCount | int | Да | computed (шаг 3) | — | Число покупок Status=Active, ссылающихся на пакет (последствия архивации). |
CreatedAt | DateTimeOffset | Да | PromotionPackage.CreatedAt | — | Создан. |
CreatedByUserId | Guid? | Нет | PromotionPackage.CreatedByUserId | — | Автор создания (admin). |
UpdatedAt | DateTimeOffset? | Нет | PromotionPackage.UpdatedAt | — | Последняя правка. |
UpdatedByUserId | Guid? | Нет | PromotionPackage.UpdatedByUserId | — | Автор последней правки. |
ArchivedAt | DateTimeOffset? | Нет | PromotionPackage.DeletedAt | — | Момент архивации (soft-delete); null — не архивирован. |
Version | long | Да | PromotionPackage.Version | — | Счётчик правок для optimistic-check в мутациях. |
Алгоритм
- Контекст и доступ. Операцию выполняет администратор платформы через admin API; требуется permission
promotion.packages.admin. Пакеты — платформенный каталог, к tenant не привязаны.
- Проверка входа.
AdminUserId не пуст; Status/Kind ∈ соответствующих enum; Page/PageSize в
границах → иначе ValidationError. Правило видимости архива: архив включается, если задан
IncludeArchived или запрошен Status = Archived.
- Выборка. Отобрать страницу пакетов по фильтрам
Status/Kind, сортировка по (Status, Kind,
BoostWeight), пагинация (Page, PageSize). Архив (Archived, soft-deleted) включается только по
правилу шага 2. Для каждого пакета страницы посчитать число покупок в статусе Active, ссылающихся на
пакет (одним агрегатом на страницу, не по одной) — покупки принадлежат компаниям, поэтому счётчик для
платформенного управленческого списка считается по всем компаниям (cross-tenant).
- Маппинг. Поля пакета →
AdminPromotionPackageListItem (Price → PriceAmount/PriceCurrencyId;
DeletedAt → ArchivedAt); ActivePurchasesCount ← подсчёт шага 3 (0, если покупок нет).
- Результат. Страница
AdminPromotionPackageListItem → 200; freshness — актуальное состояние строк
(source of truth пакетов/покупок — этот контекст, лага нет).
Граничные случаи. Пустой каталог или отсутствие совпадений по фильтрам → 200 с пустым Items,
TotalCount = 0. ActivePurchasesCount > 0 допустим и для Archived-пакета: архивация — soft-delete,
FK restrict, действующие покупки доживают срок (это и есть сигнал «последствия архивации» для админа).
ActivePurchasesCount считает только покупки Status = Active (истёкшие/отменённые не учитываются) и по
всем компаниям (cross-tenant — пакет платформенный).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | Невалидные Status/Kind/paging/X-Admin-User-Id | Ошибка валидации запроса | no retry |
UNAUTHORIZED | 401 | Нет/просрочен m2m-токен | Требуется авторизация | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system / нет permission promotion.packages.admin | Доступ запрещён | no retry |
PROMOTION_PACKAGE_NOT_FOUND | 404 | Только для дельты get-package: пакет не существует | Пакет не найден | no retry |
Совместимость и наблюдаемость
- Новые optional поля item-а (будущие платёжные атрибуты) — additive; удаление/переименование/смена типа
— breaking. Неизвестные значения enum
Status/Kind в query → ValidationError.
- Logs:
admin-promotion-packages-listed (adminUserId, фильтры, count); metrics:
promotion_admin_operations_total{operation="list-promotion-packages"}, duration; traces — сквозной
trace через traceparent. Dashboard promotion-overview.
Та же область/доступ и та же проекция. Отличия: PackageId из route (!= Guid.Empty); отдаётся один
пакет, включая архив; ActivePurchasesCount — число покупок Active по этому пакету (по всем компаниям);
если пакет не найден → PROMOTION_PACKAGE_NOT_FOUND (404). Success 200; metrics
promotion_admin_operations_total{operation="get-promotion-package"}.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы