Уровни (тиры)¶
Уровень участия (тир) — это статус клиента в программе лояльности. Клиент получает уровень по накоплению — сумме и/или количеству покупок за период, который задаёт конфигурация программы. Уровень определяет, какие акции начисляют клиенту баллы, и может задавать свой лимит списания и свой срок жизни баллов.
Уровни — в составе конфигурации программы
Отдельного экрана «Уровни» больше нет. Набор уровней, пороги, акции уровней, лимиты списания и сроки жизни баллов уровня задаются в карточке конфигурации — см. Конфигурации программы. Уровень конкретного клиента рассчитывается автоматически при покупках и на пересмотре, но может быть назначен вручную — см. Ручное назначение уровня.
Уровни конфигурации¶
Каждый уровень принадлежит своей конфигурации программы: код, название и порог уникальны в её пределах, а не во всей инсталляции. Расчёт уровня клиента, карточка клиента и отчёты используют уровни конфигурации, по которой живёт клиент; каталог уровней для интеграций без параметра отдаёт уровни активной конфигурации. Черновик, на котором нет клиентов, на реальных клиентов не влияет.
| Поле | Назначение |
|---|---|
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 — уровни указанной:
[
{ "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 час).
Смена активной конфигурации¶
Клиент живёт по конфигурации, в которой получил уровень: до перехода пороги и акции берутся из неё. После активации другой конфигурации клиент переходит на неё:
- при первой операции — покупке, резерве или списании баллов; уровень пересчитывается по порогам новой конфигурации и может понизиться, ручное назначение снимается; в истории переход записывается с источником «Перенос системой»;
- если операций нет — на своём пересмотре.
Пока клиент не перешёл, прежняя конфигурация остаётся черновиком «с клиентами» — её нельзя ни править, ни удалить. На вкладке «История уровней» карточки клиента тогда видно, по какой конфигурации он живёт: «по конфигурации «…» до пересмотра».
Текущий уровень и прогресс до следующего¶
| Поле ответа | Назначение |
|---|---|
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 |
«Перенос системой» | Переход клиента на новую активную конфигурацию при первой операции |
«Накоплено» показывается по оси механики перехода: сумма, количество покупок или оба значения.
Ответ — конверт {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} удалены.
Доступ работает по принципу «запрещено по умолчанию»: без соответствующей роли запрос отклоняется.