Entity / Aggregate: PromotionPackage
Дом полей платформенного пакета продвижения (контекст promotion). Все проекции (модели ответов admin/
developers API, снапшот в promotion-purchase.md, event payload) ссылаются
сюда и перечисляют только дельту, а не переописывают поля.
Назначение
PromotionPackage — каталог пакетов продвижения платформы: различные пакеты (продвигать проект
застройщика чаще; продвигать объекты проекта чаще), которые застройщик может купить, но без оплаты
(решение №10 — структуры монетизации проектируются, реализация оплаты отложена). Пакет описывает вид
продвижения (Kind), допустимый набор целей (PromotionTargetKind), вес буста, длительность действия
покупки и будущую прейскурантную цену.
Пакет — платформенная сущность, не tenanted (каталог платформы, ADR-0052 п.3): управляет им
администратор платформы (admin API, ADR-0053), а не застройщик/агентство. Покупка пакета — отдельный
агрегат PromotionPurchase, который снапшотит Kind/BoostWeight/Price на
момент приобретения (INV снапшота — правки пакета не трогают действующие покупки).
Классификация
| Поле | Значение |
|---|
| Контекст | promotion |
| Kind | AggregateRoot |
| Source of truth | этот контекст |
| Таблица | promotion.promotion_packages |
| Tenant isolation | не tenanted (каталог платформы, ADR-0052 п.3) |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|
Id | Guid | Да | PK | app | Идентификатор пакета. |
Code | string | Да | varchar(64), unique ux_promotion_packages_code where deleted_at is null, pattern ^[a-z0-9][a-z0-9-]*$ | admin | Стабильный код (project-boost-week, property-object-boost-month, post-boost-week) для SEED/аналитики. |
Name | string | Да | varchar(256), min 1 | admin | Отображаемое название пакета в ЛК покупателя. |
Description | string? | Нет | varchar(2000) | admin | Маркетинговый текст пакета. |
Kind | PromotionPackageKind | Да | varchar(16), index ix_promotion_packages_kind | admin | Вид продвижения; определяет допустимый PromotionTargetKind (таблица ниже). |
BoostWeight | int | Да | 1..100 (check ck_promotion_packages_boost) | admin | Вес буста, добавляемый целям на время действия покупки (снапшотится в покупку). |
DurationDays | int | Да | 1..365 (check) | admin | Срок действия покупки: EndsAt = StartsAt + DurationDays. |
MaxTargetsPerPurchase | int? | Нет (обязателен при multi-target Kind) | 1..500 (check) | admin | Максимум целей в одной покупке для PropertyObjectBoost/PostBoost; null при ProjectBoost (ровно 1 цель). |
Price | PromotionPrice (owned) | Да | см. VO | admin | Прейскурантная цена. Оплата не реализуется (решение №10) — цена информационная и снапшотится в покупку для будущего биллинга. |
Status | PromotionPackageStatus | Да | varchar(16), index ix_promotion_packages_status | app | Draft / Active / Archived. |
CreatedAt / CreatedByUserId / UpdatedAt / UpdatedByUserId / DeletedAt / DeletedByUserId | — | стандарт | — | app | Аудит + архивация (soft-delete). |
Version | long | Да | >= 1 | app | Версия для optimistic-check в admin-мутациях. |
Дом PromotionTargetKind — promotion-purchase.md (VO PromotionTarget).
Набор допустимых целей покупки ограничивается видом пакета (проверка — INV-1 покупки).
Kind | Допустимый TargetKind | Кол-во целей в покупке | Consumer буста |
|---|
ProjectBoost | Project | ровно 1 | catalog — карточка проекта + все Published-объекты проекта |
PropertyObjectBoost | PropertyObject | 1..MaxTargetsPerPurchase | catalog — карточки перечисленных объектов недвижимости |
PostBoost | Post | 1..MaxTargetsPerPurchase | news — Post.PromotionBoost перечисленных постов |
| Значение | Описание |
|---|
Draft | Черновик: невидим покупателям, покупка невозможна. |
Active | Доступен к покупке в ЛК покупателя. |
Archived | Снят с продажи (DeletedAt заполнен); действующие покупки доживают срок (FK restrict). |
Value objects
| Поле | Тип | Обязательность | Ограничения | Колонка |
|---|
Amount | decimal(14,2) | Да | >= 0 (0 = бесплатный пакет) | price_amount |
CurrencyId | short | Да | default — RUB; логическая ссылка на запись справочника валют (handbook, без FK); допустим только RUB (ADR-0048 — типизированная ссылка, не мультивалютность) | price_currency_id |
CurrencyId валидируется на write-path admin-мутаций через internal resolve справочника
(resolve-references.md): неизвестный/архивный id →
PROMOTION_CURRENCY_UNKNOWN (INV-5). В событиях покупок цена уходит как CurrencyId + CurrencySnapshot
(единый дом snapshot-моделей — snapshots.md); отдельных строковых
полей кода валюты контракты не несут.
Модели JSONB
JSONB-модели не используются.
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|
PromotionPackage → PromotionPurchase | reference | FK package_id | чтение пакета при покупке (снапшот полей) | restrict (архив пакета не трогает покупки) |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|
| INV-1 CodeUnique | Code уникален среди живых пакетов (where deleted_at is null) | проверка + уникальный индекс | PROMOTION_PACKAGE_CODE_DUPLICATE |
| INV-2 MaxTargetsMatchesKind | MaxTargetsPerPurchase задан ⇔ Kind ∈ {PropertyObjectBoost, PostBoost}; null при ProjectBoost | проверка при создании | PROMOTION_PACKAGE_MAX_TARGETS_RULE |
| INV-3 ImmutableIdentity | Code и Kind неизменяемы после создания | поля не принимаются операцией правки | PROMOTION_PACKAGE_IMMUTABLE_FIELD |
| INV-4 StatusTransition | переходы только по матрице (см. update-promotion-package.md) | проверка перехода по матрице | PROMOTION_PACKAGE_INVALID_STATUS_TRANSITION |
| INV-5 CurrencyRef | Price.CurrencyId существует в справочнике валют и не архивен (internal resolve handbook); допустима только RUB (ADR-0048) | admin create/update | PROMOTION_CURRENCY_UNKNOWN (неизвестный/архивный id); не-RUB → ValidationError |
Переходы статусов (из / в, условие, инициатор, эффект)
Дом матрицы допустимых переходов и её проверки — update-promotion-package.md
(guard PROMOTION_PACKAGE_INVALID_STATUS_TRANSITION). Здесь — бизнес-смысл каждого перехода.
| Из | В | Условие | Инициатор | Эффект |
|---|
[*] | Draft | создание пакета (пройдены INV-1 CodeUnique, INV-2 MaxTargets⇔Kind) | администратор (create) | пакет заведён, но невидим покупателям; покупка невозможна |
Draft | Draft | правка полей без смены статуса | администратор (update) | обновлены Name/Description/BoostWeight/DurationDays/MaxTargetsPerPurchase/Price; Code/Kind неизменяемы (INV-3) |
Draft | Active | публикация | администратор (update) | пакет появляется на витрине застройщика (Active), становится покупаемым немедленно (source of truth — лага нет) |
Draft | Archived | архивация черновика | администратор (delete/update) | soft-delete (DeletedAt/DeletedByUserId); покупок на пакет нет |
Active | Active | правка полей действующего пакета | администратор (update) | новые значения применяются только к будущим покупкам; действующие покупки держат снапшот (INV-2 покупки) — событий пересчёта нет |
Active | Archived | снятие с продажи | администратор (delete/update) | soft-delete; пакет пропадает с витрины; действующие покупки доживают срок (FK restrict, истекают штатно) |
Active | Draft | не поддерживается (unpublish снятого с продажи нет) | — | отказ PROMOTION_PACKAGE_INVALID_STATUS_TRANSITION |
Archived | любой | терминально: архивный пакет неотличим от несуществующего | — | отказ PROMOTION_PACKAGE_NOT_FOUND (soft-deleted → 404) |
Изменение пакета не порождает интеграционных событий и не затрагивает потребителей буста: единственный
канал влияния на потребителей — события покупок (promotion-purchase.md).
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|
| — | — | — | Изменения самого пакета интеграционных событий не эмитят (v1): интеграционные события есть только у покупок (promotion-purchase.md, PromotionPurchaseChangedKafkaEvent). Доменных событий пакет тоже не поднимает. |
Индексы, хранение, безопасность
| Аспект | Значение |
|---|
| Индексы | PK; ux_promotion_packages_code unique where deleted_at is null; ix_promotion_packages_kind; ix_promotion_packages_status |
| Партиционирование / retention | none; пакеты живут бессрочно (архив — soft-delete) |
| Concurrency | оптимистическая блокировка + Version (optimistic-check в admin-мутациях) |
| Permissions / PII | чтение витрины (только Active) — developers API (promotion.packages.view); управление пакетами — только admin API (promotion.packages.admin, ADR-0053). PII нет; Price — прейскурант, не секрет |
| Audit | отметка автора + Version; глубокий аудит админ-мутаций; архив — soft-delete |
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы