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

Уровни (тиры)

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

Уровни — в составе конфигурации программы

Отдельного экрана «Уровни» больше нет. Набор уровней, пороги, акции уровней, лимиты списания и сроки жизни баллов уровня задаются в карточке конфигурации — см. Конфигурации программы. Уровень конкретного клиента рассчитывается автоматически при покупках и на пересмотре, но может быть назначен вручную — см. Ручное назначение уровня.

Уровни конфигурации

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

Поле Назначение
code Технический код уровня (уникален в пределах конфигурации)
name Отображаемое название
ordinal Порядок уровня (от младшего к старшему) — сервер выводит его из порогов
thresholdMinor Порог по сумме покупок, в минорных единицах базовой денежной валюты
entryCount Порог по количеству покупок
configId Конфигурация, которой принадлежит уровень

По какой оси сравнивать порог — сумме, количеству или обеим сразу, — задаёт механика перехода конфигурации (tierMetric: amount, count, both); у оси, которую механика не использует, порог равен нулю. Базовый уровень — с нулевым порогом по каждой оси: на него клиент попадает при регистрации.

Ставку начисления задают акции уровня

Уровень не несёт собственной ставки: баллы начисляют акции, назначенные уровню в конфигурации. Поля accrualRate и segments в ответах /v1/tiers и статуса уровня сохранены для совместимости и на начисление не влияют. Строка «Множитель начисления» в карточке клиента показывает это же accrualRate: у уровней, созданных в конфигурации, он равен нулю.

На новой установке действует конфигурация «Основная» (механика — сумма покупок, окно 365 дней с пересмотром раз в месяц) с предустановленными уровнями:

Уровень Код Порог (минорные ед.)
Бронза BRONZE 0
Серебро SILVER 2 000 000
Золото GOLD 6 000 000
Платина PLATINUM 15 000 000

Каталог уровней для интеграций — только чтение, отсортирован по ordinal. Без параметра отдаёт уровни активной конфигурации, с configId — уровни указанной:

GET /v1/tiers
GET /v1/tiers?configId=2
[
  { "code": "BRONZE",   "name": "Бронза",  "ordinal": 0, "thresholdMinor": 0,        "entryCount": 0, "configId": 1 },
  { "code": "SILVER",   "name": "Серебро", "ordinal": 1, "thresholdMinor": 2000000,  "entryCount": 0, "configId": 1 },
  { "code": "GOLD",     "name": "Золото",  "ordinal": 2, "thresholdMinor": 6000000,  "entryCount": 0, "configId": 1 },
  { "code": "PLATINUM", "name": "Платина", "ordinal": 3, "thresholdMinor": 15000000, "entryCount": 0, "configId": 1 }
]

В примере опущены легаси-поля accrualRate и segments.

Как клиент получает и теряет уровень

Накопление считается по журналу покупок: сумма чеков за вычетом возвращённого и количество чеков, возвращённых не целиком. Какой период берётся, задаёт режим пересчёта конфигурации:

Режим Что считается Когда пересмотр
Скользящее окно («Считать покупки за период») Покупки за последние N дней (по умолчанию 365) Раз в заданный интервал (по умолчанию раз в месяц) после перехода на уровень
Персональный период («Уровень на срок с даты получения») Покупки с начала периода, плюс перенесённый излишек, если перенос включён В конце срока уровня; чтобы сохранить уровень, нужно за период снова набрать его порог

Дата пересмотра своя у каждого клиента — вся база в один день не пересматривается.

  • Регистрация — клиент получает базовый уровень активной конфигурации и первую дату пересмотра.
  • Повышение — сразу при покупке: уровень проверяется один раз на чек, после применения акций. Привилегии нового уровня действуют со следующей операции.
  • Понижение — только на пересмотре: уровень считается заново по накоплению и может снизиться, удержаться или продлиться на новый период. Между пересмотрами уровень не понижается.
  • Возврат по чеку уменьшает накопление, но уровень сразу не понижает — это произойдёт на пересмотре, если накопления окажется недостаточно.
  • Слияние клиентов переносит журнал покупок и возвратов выжившему клиенту и проверяет повышение его уровня; понижения при слиянии нет.
flowchart LR
    P[Покупка] --> A[Накопление за период<br/>конфигурации]
    A --> C{Порог следующего<br/>уровня достигнут?}
    C -- да --> U[Повышение сразу]
    C -- нет --> K[Уровень сохраняется]
    R[Дата пересмотра клиента] --> V[Пересчёт по накоплению]
    V --> D[Понижение, удержание<br/>или продление]

Пересмотр включается при развёртывании

Фоновый пересмотр выполняется по расписанию LOYALTY_TIER_REVIEW_CRON и по умолчанию выключен. Пока расписание не задано, понижений по дате пересмотра не происходит. Размер пачки — LOYALTY_TIER_REVIEW_BATCH_SIZE (500), предельная длительность одного прогона — LOYALTY_TIER_REVIEW_MAX_DURATION (1 час).

Смена активной конфигурации

Клиент живёт по конфигурации, в которой получил уровень: до перехода пороги и акции берутся из неё. После активации другой конфигурации клиент переходит на неё:

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

Пока клиент не перешёл, прежняя конфигурация остаётся черновиком «с клиентами» — её нельзя ни править, ни удалить. На вкладке «История уровней» карточки клиента тогда видно, по какой конфигурации он живёт: «по конфигурации «…» до пересмотра».

Текущий уровень и прогресс до следующего

GET /v1/customers/{externalId}/tier
Поле ответа Назначение
current Текущий уровень клиента
accumulatedMinor / accumulatedCount Накопление за период: сумма покупок (минорные единицы) и количество покупок
next Следующий уровень; null, если достигнут наивысший
toNextMinor / toNextCount Остаток до следующего уровня по каждой оси; 0, если следующего нет
manual, overrideReason, overrideReasonCode, overrideComment Признак и причина ручного назначения
assignedAt Когда клиент получил текущий уровень
nextReviewAt Дата ближайшего пересмотра
periodEndsAt Конец персонального периода; пусто в режиме скользящего окна
config Конфигурация клиента: id, name, active (активна ли она сейчас)
{
  "current": { "code": "GOLD", "name": "Золото", "ordinal": 2, "thresholdMinor": 6000000, "entryCount": 0, "configId": 1 },
  "accumulatedMinor": 7500000,
  "accumulatedCount": 12,
  "next": { "code": "PLATINUM", "name": "Платина", "ordinal": 3, "thresholdMinor": 15000000, "entryCount": 0, "configId": 1 },
  "toNextMinor": 7500000,
  "toNextCount": 0,
  "manual": false,
  "assignedAt": "2026-09-20T12:00:00+03:00",
  "nextReviewAt": "2026-10-20T12:00:00+03:00",
  "periodEndsAt": null,
  "config": { "id": 1, "name": "Основная", "active": true }
}

BREAKING (26.3.0)

Статус уровня отдаёт даты и конфигурацию клиента (assignedAt, nextReviewAt, periodEndsAt, config) и вторую ось накопления (accumulatedCount, toNextCount). Поле metric у уровня удалено: механика перехода принадлежит конфигурации (tierMetric). Помесячное накопление с ключом периода YYYY-MM больше не используется.

В карточке клиента уровень показывается в строке «Уровень» с подписью остатка по осям механики: «До уровня «…» — ещё …» (сумма, количество покупок или оба условия) либо «Максимальный уровень».

Ручное назначение уровня

Оператор может назначить уровень вручную, например для статусов по договорённости или компенсаций. В карточке клиента такой уровень помечается бейджем «Назначен вручную»; в подсказке к бейджу — причина и комментарий оператора.

Метод и путь Назначение Требуемая роль
PUT /v1/customers/{externalId}/tier Назначить уровень вручную (с причиной) LOYALTY_TIER_WRITE
DELETE /v1/customers/{externalId}/tier Снять ручное назначение, вернуться к расчётному уровню LOYALTY_TIER_WRITE

Тело запроса назначения: tierCode, reasonCode (код причины из справочника «Причины операций») и необязательный comment (обязателен при причине «Прочее»). Ответ (TierStatus) отдаёт признак manual, подпись причины overrideReason, её код overrideReasonCode и комментарий overrideComment; у назначений, сделанных до перехода на справочник, код и комментарий пусты.

Уровень ищется в конфигурации клиента: кода, которого в ней нет, — 404 tier_not_found. Окно назначения в карточке предлагает уровни активной конфигурации, поэтому клиенту, который ещё живёт по прежней, можно назначить только уровень с кодом, который есть и в ней. Назначить уровень заблокированному клиенту нельзя — 422 customer_blocked.

BREAKING (26.3.0): причина — из справочника

Свободный текст причины (reason) заменён на код справочника reasonCode плюс комментарий. Причина «Прочее» требует отдельного права LOYALTY_OPERATION_REASON_OTHER_WRITE, а чтение справочника — LOYALTY_DICTIONARY_READ. Правила и коды ошибок — в разделе Ручные операции.

Ручной уровень — «пол» до ближайшего пересмотра

Действует высший из назначенного и заработанного уровней: если клиент накопил на уровень выше назначенного, он получает заработанный. Назначение снимается на ближайшем пересмотре клиента или при его переходе на другую конфигурацию — дальше уровень снова расчётный. Снятие назначения оператором (DELETE) сразу возвращает клиента на расчётный уровень, в том числе ниже.

Массовый перевод на уровень

Перевести на один уровень сразу набор клиентов можно из каталога клиентов (действие «Перевод уровня») или на экране «Ручные операции», вкладка «Перевод уровня»: список задаётся вставкой идентификаторов или файлом.

POST /v1/customers/tier-bulk — роль LOYALTY_TIER_WRITE. Тело: externalIds[], tierCode, reasonCode, необязательный comment. Заголовок Idempotency-Key необязателен; с ним повтор возвращает сохранённый отчёт.

  • Ответ — та же форма, что у остальных массовых операций: {batchId, status, total, succeeded[], errors[{externalId, fullName, code, reason}]}. По batchId доступен CSV-отчёт с исходом каждой строки.
  • Обработка поштучная: отказ по одному клиенту не отменяет остальных; неизвестный клиент попадает в errors, неизвестный уровень отклоняет весь запрос (404 tier_not_found).
  • Предел одного запроса — LOYALTY_MANUAL_OPERATIONS_BULK_MAX_IDS (по умолчанию 1000 идентификаторов), сверх — 400 bulk_size_exceeded.
  • Отложенного выполнения у перевода уровня нет.

Подробности пакетов, отчётов и причин — Ручные операции.

История переходов уровня

Каждая смена уровня — расчётная или ручная — записывается в историю с источником, накоплением на момент перехода и конфигурацией. В карточке клиента история показывается на вкладке «История уровней»: в шапке — «Текущий уровень», срок его действия («действует до …» в персональном периоде или «следующий пересмотр …») и, если клиент ещё живёт по неактивной конфигурации, её название. Колонки таблицы: «Дата», «Переход», «Накоплено», «Источник».

Источник (source) Подпись Когда
purchase «При покупке» Покупка довела накопление до порога более высокого уровня
review «На пересмотре» Смена уровня в дату пересмотра (как правило, понижение) или переход на новую активную конфигурацию, если до пересмотра операций не было. Удержание и продление уровня в историю не пишутся
manual «Вручную» Назначение, массовый перевод или снятие назначения оператором; в подсказке — оператор и причина
migration «Перенос системой» Переход клиента на новую активную конфигурацию при первой операции

«Накоплено» показывается по оси механики перехода: сумма, количество покупок или оба значения.

GET /v1/customers/{externalId}/tier/transitions?page=0&size=20&sort=occurredAt,desc

Ответ — конверт {items, page, size, total}; size по умолчанию 20, максимум 100; сортировка только по occurredAt, по умолчанию «новые сверху». Строка (TierTransitionView): fromCode/fromName, toCode/toName (название уровня на момент перехода — переименование уровня историю не меняет), accumulatedMinor, accumulatedCount (пусто у переходов, записанных до появления поля), occurredAt, source, configId, operator, reasonCode, reason. Неизвестный клиент — пустая страница.

BREAKING (26.3.0)

История переходов отдаётся постранично конвертом PageResponse вместо массива; параметр limit удалён. Поле kind (ORGANIC/MANUAL) заменено на source (purchase, review, manual, migration).

Событие о смене уровня

О каждом переходе модуль публикует событие tiers.customer.tier-changed для системы взаимодействия с клиентом: клиент, уровни «из» и «в», направление (up/down/same), источник перехода, накопление по обеим осям и конфигурация с признаком «Отправлять клиентам сообщения о смене уровня». Событие уходит всегда — отправлять ли клиенту сообщение, решает получатель.

  • Строка истории и событие записываются одной транзакцией в очередь исходящих; доставка at-least-once, повтор отличается по eventId, события одного клиента приходят по порядку (ключ сообщения — клиент).
  • Отправка выключена по умолчанию (LOYALTY_TIER_EVENTS_ENABLED); топик — LOYALTY_TIER_EVENTS_TOPIC (по умолчанию loyalty.tiers.tier-changed). Пока отправка выключена, события копятся.
  • Событие, прождавшее отправки дольше LOYALTY_TIER_EVENTS_PENDING_TTL (7 дней), списывается и получателю не уходит — клиент не получит поздравление с уровнем, который уже потерял.

Роли и доступ

Эндпоинт Метод Роль
Каталог уровней GET /v1/tiers LOYALTY_TIER_READ
Уровень и прогресс клиента GET /v1/customers/{externalId}/tier LOYALTY_TIER_READ
История переходов уровня GET /v1/customers/{externalId}/tier/transitions LOYALTY_TIER_READ
Назначить уровень вручную PUT /v1/customers/{externalId}/tier LOYALTY_TIER_WRITE
Снять ручное назначение DELETE /v1/customers/{externalId}/tier LOYALTY_TIER_WRITE
Массовый перевод на уровень POST /v1/customers/tier-bulk LOYALTY_TIER_WRITE

Создание, изменение и удаление уровней — только в составе конфигурации программы, под ролями LOYALTY_PROGRAM_CONFIG_* (см. Конфигурации программы). PUT/DELETE /v1/tiers/{code} удалены.

Доступ работает по принципу «запрещено по умолчанию»: без соответствующей роли запрос отклоняется.