Перейти к содержанию

Жизненный цикл и арбитраж

Акция в CM Ocean Loyalty проходит управляемый жизненный цикл — от черновика до завершения — и участвует в детерминированном арбитраже, когда на одну покупку претендует несколько акций. Эта страница описывает статусы и переходы, согласование запуска, архив, удаление, версионирование правил без ретропересчёта и правила стэкинга (совмещения).

Статусы акции

Акция всегда находится в одном из пяти статусов. Архив статусом не является — это отдельный обратимый признак (см. Архив). Статус согласования запуска тоже хранится отдельно от статуса акции (см. Согласование запуска).

Статус Подпись Назначение Применяется к покупкам
DRAFT Черновик акция создаётся и настраивается нет
SCHEDULED Запланирована запуск подтверждён, акция ждёт даты начала нет
ACTIVE Активна участвует в начислениях в пределах своего периода да (в окне startsAt … endsAt)
PAUSED Пауза временно исключена из арбитража нет
COMPLETED Завершена период закончился; терминальный статус, обратно акция не возвращается нет

Статуса ARCHIVED больше нет

Раньше архив был статусом. Теперь это независимый признак archived. Акции, лежавшие в старом статусе ARCHIVED, при обновлении получили признак архива и статус «Пауза»: прежний статус не восстановим, а «Пауза» безопасна — после возврата из архива акция сама применяться не начнёт.

Правка акции

Право править акцию на месте определяет не статус сам по себе, а факт применения. Пока по акции не было ни одного начисления, правка безопасна; как только появилось первое — только новая версия.

Статус Применений не было Применения были
Черновик правится случай невозможен
Запланирована правится случай невозможен
Пауза правится не правится — только «Новая версия»
Активна не правится не правится
Завершена не правится не правится

Архивная акция не правится ни в каком статусе — сначала «Разархивировать».

Правила проверяются на сервере в момент сохранения, а не только при открытии формы: если между открытием формы и сохранением акция успела сработать, сохранение отклоняется кодом 409 promotion_not_editable. Интерфейс закрывает форму заранее — решение «можно ли править» приходит вместе с акцией и строкой каталога (editable).

Исключения:

  • Метки меняются в любом статусе, включая активную, завершённую и архивную акцию, — они не влияют на начисления. См. Метки акций.
  • Уровни программы. Когда в настройках программы лояльности акцию назначают уровню или снимают с него, сервер сам правит только список уровней условия «Уровень клиента» — во всех версиях линии акции (при назначении — кроме завершённых), на месте, без новой версии и без сброса согласования. Остальные условия, статус и период акции не меняются.

Если включено согласование запуска, содержательная правка акции (тела акции или лимита участий) возвращает согласование в статус «В работе», а запланированную акцию — в черновик. Сохранение без фактических изменений ничего не сбрасывает.

Переходы статусов

Переходы выполняются командами каталога, шапки формы или страницы акции. Запуск требует права LOYALTY_PROMOTION_ACTIVATE, остальные изменяющие действия — права LOYALTY_PROMOTION_WRITE.

Действие Эндпоинт Право Из каких статусов Результат
Запустить POST /v1/promotions/{id}/activate LOYALTY_PROMOTION_ACTIVATE Черновик, Пауза период уже идёт → ACTIVE; период в будущем → SCHEDULED
Отменить запуск POST /v1/promotions/{id}/unschedule LOYALTY_PROMOTION_WRITE Запланирована → DRAFT
Приостановить POST /v1/promotions/{id}/pause LOYALTY_PROMOTION_WRITE Активна → PAUSED
Архивировать POST /v1/promotions/{id}/archive LOYALTY_PROMOTION_WRITE любой признак архива, статус не меняется
Вернуть из архива POST /v1/promotions/{id}/unarchive LOYALTY_PROMOTION_WRITE любой признак архива снят
Удалить DELETE /v1/promotions/{id} LOYALTY_PROMOTION_WRITE Черновик, Запланирована запись удаляется, ответ 204
Новая версия POST /v1/promotions/{id}/revise LOYALTY_PROMOTION_WRITE Активна, Пауза (не в архиве) новая акция-ревизия в DRAFT
Сохранить как POST /v1/promotions/{id}/copy LOYALTY_PROMOTION_WRITE любой независимая копия-черновик

Создание акции (POST /v1/promotions) всегда даёт черновик. Недопустимый переход отклоняется кодом 409 promotion_status_invalid, удаление акции не в статусе «Черновик» или «Запланирована» — 409 promotion_not_deletable, новая версия черновика, запланированной, завершённой или архивной акции — 409 promotion_not_revisable.

Изменение 26.3.0: запуск — отдельное право

Запуск акции (activate и массовый bulk-activate) больше не входит в право LOYALTY_PROMOTION_WRITE и требует LOYALTY_PROMOTION_ACTIVATE — независимо от того, включено ли согласование. Без него запуск отвечает 403 access_denied, а интерфейс показывает «У вас нет права запускать акции.». Группам, которые запускают акции, это право нужно выдать до обновления.

Проверки при запуске. Кроме статуса, сервер проверяет:

Проверка Код ошибки Текст в интерфейсе
Согласование включено, а черновик (или поправленная после запуска пауза) не согласован 409 approval_required «Акция не согласована. Отправьте её на согласование.»
У механики черновика не заведены параметры — награду задать негде 409 promotion_mechanic_not_configured «Настройки этой механики ещё не заведены — акцию на ней пока не собрать. Обратитесь к администратору»
В условиях есть удалённые значения (теги, метки, акции, сегменты) 400 promotion_audience_unresolved «Акцию нельзя запустить: в условиях есть значения, удалённые из справочника.»
Субсчёт начисляемых баллов закрыт или больше не принимает начисления 422 promotion_points_subaccount_invalid «Акцию нельзя запустить: тип начисляемых баллов недоступен. Откройте акцию и выберите другой на вкладке «Механика»»

Приостановленную акцию на механике без параметров возобновить можно: проверка механики стоит только на первом запуске черновика.

stateDiagram-v2
    [*] --> DRAFT: создать
    DRAFT --> DRAFT: изменить
    DRAFT --> ACTIVE: запустить (период идёт)
    DRAFT --> SCHEDULED: запустить (период в будущем)
    SCHEDULED --> DRAFT: отменить запуск
    SCHEDULED --> ACTIVE: наступила дата начала (автоматически)
    ACTIVE --> PAUSED: приостановить
    PAUSED --> ACTIVE: запустить
    ACTIVE --> COMPLETED: прошла дата окончания (автоматически)
    PAUSED --> COMPLETED: прошла дата окончания (автоматически)
    COMPLETED --> [*]

Автоматические переходы по датам

Систему не нужно просить запустить акцию в день старта: границы периода отрабатывают сами.

Момент Переход Из каких статусов
наступил startsAt → ACTIVE SCHEDULED
прошёл endsAt → COMPLETED ACTIVE, PAUSED

Фоновая обработка идёт с интервалом в одну минуту. Автоматический переход не меняет «кто изменил акцию» — там остаётся последний правивший человек.

Включение на контуре

Планировщик жизненного цикла включается настройкой loyalty.promotion.lifecycle.enabled (переменная LOYALTY_PROMOTION_LIFECYCLE_ENABLED), интервал — loyalty.promotion.lifecycle.interval (LOYALTY_PROMOTION_LIFECYCLE_INTERVAL, по умолчанию PT1M). По умолчанию выключен; на тестовых контурах включён, на продуктивном включается вместе с выкаткой каталога.

Активное окно vs. статус

Статус ACTIVE означает, что акция допущена к арбитражу. Фактически бонус начисляется лишь в пределах периода действия: момент покупки должен попадать в промежуток startsAt ≤ now ≤ endsAt — обе границы включительны.

Согласование запуска

Согласование реализует принцип «четырёх глаз»: акцию готовит сотрудник с правом записи, согласует другой сотрудник с правом согласующего, запускает сотрудник с правом запуска. Согласование включается настройкой развёртывания — loyalty.promotion.approval.enabled на сервере (переменная LOYALTY_PROMOTION_APPROVAL_ENABLED, по умолчанию выключено) и признаком PROMOTION_APPROVAL_ENABLED в конфигурации интерфейса — их включают вместе. Пока оно выключено, акция запускается без согласования, а действия согласования отвечают 409 approval_disabled.

Статус согласования Подпись Смысл
UNDERWAY В работе акция готовится или возвращена в работу
SUBMITTED На согласовании отправлена и ждёт решения
APPROVED Согласована черновик можно запустить
REJECTED Отклонена автор может доработать акцию и отправить снова
Действие Кнопка Из статуса Результат Право
submit «Отправить на согласование» В работе, Отклонена (акция — черновик или пауза) На согласовании LOYALTY_PROMOTION_WRITE
cancel «Отменить отправку на согласование» На согласовании В работе LOYALTY_PROMOTION_WRITE
approve «Согласовать» На согласовании Согласована LOYALTY_PROMOTION_APPROVE
reject «Отклонить» На согласовании Отклонена LOYALTY_PROMOTION_APPROVE

Действия выполняются на вкладке «Согласование» страницы акции (POST /v1/promotions/{id}/approvals/{type}, type — submit, approve, reject или cancel). Каждое действие подтверждается окном с необязательным полем «Комментарий (необязательно)» — до 1000 символов. Под кнопками — история: «Дата», «Статус», «Пользователь», «Комментарий», новые записи сверху (GET /v1/promotions/{id}/approvals). Статус согласования виден рядом со статусом акции, а в каталоге — в колонке «Статус согласования» (по умолчанию скрыта, включается в настройке колонок).

  • Решение по своей же отправке невозможно: 409 approval_self_forbidden — «Согласовать акцию не может тот, кто отправил её на согласование.». Интерфейс автору кнопок «Согласовать» и «Отклонить» не показывает.
  • Без права согласующего решение отклоняется 403 approval_forbidden; если при отправке через API названы конкретные согласующие (approverIds), решать могут только они — иначе 403 approval_not_assigned. Интерфейс отправляет «по роли»: согласовать может любой сотрудник с правом LOYALTY_PROMOTION_APPROVE.
  • Действие не из того статуса, в том числе повтор выполненного, — 409 approval_status_invalid («Действие недоступно: статус согласования уже изменился. Обновите страницу.»).
  • Содержательная правка во время согласования возвращает акцию в работу: в историю пишется отмена с комментарием «Акция была изменена».
  • Нетронутая пауза возобновляется без согласования; пауза, поправленная после запуска, — только после нового согласования.
  • Новая версия и копия акции согласуются заново — они создаются в статусе «В работе».

Каждое действие согласования, включая автоматическую отмену правкой, пишется в журнал аудита.

Архив

Архив — обратимый признак «убрана из рабочего списка», допустимый при любом статусе; статус акции он не меняет. Архивная акция:

  • не участвует в расчёте начислений, даже если её статус «Активна»;
  • не редактируется и не версионируется;
  • не показывается в каталоге, пока не включён переключатель «Показать архивные»;
  • возвращается действием «Разархивировать» в любой момент.

Повторное архивирование уже архивной акции (и наоборот) ошибкой не считается.

Удаление

Удалить можно только черновик и запланированную акцию — статусы, в которых начислений структурно не было; признак архива на это не влияет. Удаление необратимо: запись пропадает вместе с лимитом участий, метками, историей меток и историей согласования. Последнюю версию линии, выбранной в условиях аудитории работающей акции («Активна», «Пауза», «Запланирована»), удалить нельзя — 409 promotion_in_use; сначала уберите её из условий (у линии с несколькими версиями удаление промежуточной версии этим не блокируется).

Акцию, которая входит в программу лояльности (привязана к конфигурации программы), удалить тоже нельзя — 422 promotion_bound_to_config с названиями конфигураций. Интерфейс заранее делает кнопку «Удалить» недоступной с пояснением «Акция входит в программу лояльности. Снимите привязку в настройках программы, затем удалите»; признак приходит в поле boundToConfig. Свободно удаляется черновик новой версии привязанной линии — привязка остаётся за линией.

Версионирование правил

Изменение правила работающей акции оформляется как новая версия, а не правка действующей. Команда «Новая версия» (POST /v1/promotions/{id}/revise) создаёт черновик, привязанный к той же линии версий.

  • rootId — идентификатор линии версий (группы ревизий). Для первой версии совпадает с её собственным идентификатором.
  • revision — порядковый номер ревизии в рамках линии.
  • Новая версия получает новый код, наследует название, параметры, тип события, настройки начисляемых баллов, ограничения, лимит участий, условия, условия аудитории и метки исходной версии, создаётся вне архива, со своим автором и со статусом согласования «В работе».
  • Счётчики бюджета и участий считаются по линии версий: новая версия не обнуляет израсходованный бюджет и не открывает обход лимита.
  • Привязка к программе лояльности тоже держится на линии версий: новая версия её не теряет.

Завершённая акция новой версии не получает (promotion_not_revisable) — повторить её можно копией («Сохранить как»).

Без ретропересчёта

Журнал начислений пополняется только вперёд: прошлое никогда не пересчитывается. Новая версия правила применяется только к будущим покупкам. Ранее начисленные бонусы остаются неизменными.

Копия акции («Сохранить как»)

Копия — другая акция: новый черновик с новым кодом, собственной линией версий и нулевой статистикой. Копия наследует параметры, тип события, настройки начисляемых баллов, ограничения, лимит участий, условия и условия аудитории, но не метки исходной акции. Условия, скрытые или удалённые в настройке акции, в копию не переносятся. Название копии должно быть свободным; по умолчанию подставляется «‹название› (копия)», при занятом — «(копия 2)» и далее.

Сохранение акции — полная замена

Тело запроса PUT /v1/promotions/{id} полностью заменяет параметры акции: отсутствующий блок лимита участий снимает лимит, отсутствующий бюджет — снимает бюджет, отсутствующие настройки начисляемых баллов возвращают значения по умолчанию. Исключения:

  • Условия. Отсутствующие (или null) поля criteria и audience условия не трогают — правка остальных полей не стирает таргетинг молча. Очистка — явным пустым списком criteria и пустыми зонами включения и исключения audience, см. Условия акции.
  • Тип события. Отсутствующий eventType сохранённое значение не меняет.
  • Уровни. params.conditions.client без ключа levels сохранённые уровни не стирает.
  • Метки. PUT меток не принимает: запрос с ними отклоняется кодом 400 promotion_labels_not_accepted. Метки меняются отдельными операциями, см. Метки акций.

Оптимистическая блокировка защищает акцию от конкурентного изменения: при параллельной правке одна из операций будет отклонена и должна быть повторена на свежих данных. В массовых операциях такой отказ приходит строкой отчёта с кодом promotion_concurrent_update.

Арбитраж конкурирующих акций

Когда покупка подходит сразу под несколько активных акций, применяется детерминированный арбитраж. Он выполняется в два шага: отбор подходящих акций и разрешение конкуренции по приоритету и стэкингу.

Шаг 1. Отбор подходящих акций

Кандидатами считаются акции в статусе ACTIVE вне архива. Проверки идут в фиксированном порядке, и акция, выбывшая по любой из них, места в арбитраже не занимает:

  1. период — момент покупки попадает в startsAt ≤ now ≤ endsAt (обе границы включительны);
  2. условия акции (criteria) — категории, артикулы, регион, город, магазин, мерчант, канал продаж, сегменты, сумма чека и дополнительные условия: событие должно выполнить все условия зоны «Акция применяется, если» и ни одного условия зоны «Не применяется, если»; незнакомое условие акцию не применяет;
  3. условия по клиенту — статус, уровень (в пределах конфигурации программы клиента), баланс, затем теги клиента и участие в других акциях, см. Условия акции;
  4. расчёт бонуса по механике — результат должен быть строго больше нуля;
  5. максимальный бонус за начисление;
  6. лимит участий клиента;
  7. общий лимит участий;
  8. бюджет акции — остатка должно хватать на полное начисление, частичного начисления нет.

Ограничения и лимиты подробно описаны в Ограничениях и лимитах акции.

Подходящие акции упорядочиваются по возрастанию priority (меньшее значение — выше приоритет), а при равенстве — по идентификатору. Такой порядок делает результат арбитража воспроизводимым.

Шаг 2. Стэкинг: STACKABLE и EXCLUSIVE

Каждая акция объявляет режим совмещения в поле stacking.

Режим Подпись в интерфейсе Поведение
EXCLUSIVE Только одна Применяется в одиночку — несовместима с другими акциями
STACKABLE Совмещается Совмещается с другими совмещаемыми акциями

Правило разрешения (по списку, упорядоченному по приоритету):

  • если акция с наивысшим приоритетом — EXCLUSIVE, применяется только она одна, все прочие отбрасываются;
  • иначе применяются все STACKABLE-акции из списка совместно; EXCLUSIVE-акции с более низким приоритетом отбрасываются.
flowchart TD
    A[Подходящие акции,<br/>отсортированы по priority] --> B{Список пуст?}
    B -- да --> Z[Бонус не начисляется]
    B -- нет --> C{Первая акция<br/>EXCLUSIVE?}
    C -- да --> D[Применить только первую]
    C -- нет --> E[Применить все STACKABLE,<br/>отбросить EXCLUSIVE]

Результатом арбитража является набор применённых акций; для каждой фиксируется идентификатор акции, код механики, величина бонуса и настройки начисляемых баллов (срок жизни, субсчёт, область списания). Этот набор передаётся далее в начисление, а каждое применение записывается участием клиента в линии версий акции — по нему считаются лимиты участий и условия аудитории других акций.

Идемпотентность на линии версий

Защита от повторной обработки одного и того же события опирается на линию версий акции, а не на конкретную версию: создание новой версии не открывает возможность начислить по тому же событию дважды.