Жизненный цикл и арбитраж¶
Акция в 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 вне архива. Проверки идут в фиксированном
порядке, и акция, выбывшая по любой из них, места в арбитраже не занимает:
- период — момент покупки попадает в
startsAt ≤ now ≤ endsAt(обе границы включительны); - условия акции (
criteria) — категории, артикулы, регион, город, магазин, мерчант, канал продаж, сегменты, сумма чека и дополнительные условия: событие должно выполнить все условия зоны «Акция применяется, если» и ни одного условия зоны «Не применяется, если»; незнакомое условие акцию не применяет; - условия по клиенту — статус, уровень (в пределах конфигурации программы клиента), баланс, затем теги клиента и участие в других акциях, см. Условия акции;
- расчёт бонуса по механике — результат должен быть строго больше нуля;
- максимальный бонус за начисление;
- лимит участий клиента;
- общий лимит участий;
- бюджет акции — остатка должно хватать на полное начисление, частичного начисления нет.
Ограничения и лимиты подробно описаны в Ограничениях и лимитах акции.
Подходящие акции упорядочиваются по возрастанию 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]
Результатом арбитража является набор применённых акций; для каждой фиксируется идентификатор акции, код механики, величина бонуса и настройки начисляемых баллов (срок жизни, субсчёт, область списания). Этот набор передаётся далее в начисление, а каждое применение записывается участием клиента в линии версий акции — по нему считаются лимиты участий и условия аудитории других акций.
Идемпотентность на линии версий
Защита от повторной обработки одного и того же события опирается на линию версий акции, а не на конкретную версию: создание новой версии не открывает возможность начислить по тому же событию дважды.