Release Notes¶
История ключевых возможностей модуля CM Ocean Loyalty по доменам. Изменения фиксируются в формате Keep a Changelog.
Версионирование — календарное (CalVer) в формате YY.x.y, единое для продуктов CM Ocean:
YY— две последние цифры года выпуска;x— порядковый номер функционального выпуска в течение года (новая функциональность);y— номер хотфикса в рамках функционального выпуска (исправления без новой функциональности).
Релиз-кандидаты обозначаются суффиксом -rc.N (например, 26.0.0-rc.1). Несовместимые изменения
помечаются меткой BREAKING.
Как читать
Раздел ниже отражает текущее состояние сервиса биллинга баллов (loyalty-billing).
Возможности, ещё не реализованные, явно вынесены в раздел «В дорожной карте».
26.3.0 (в разработке)¶
Версия ещё не выпущена
Раздел описывает функциональность, накопленную в основной ветке репозиториев loyalty,
loyalty-ui и loyalty-requirements после релиза 26.2.0, — по состоянию на 30.09.2026.
Текущий публичный релиз — 26.2.0; следующая фича-версия — 26.3.0, дата выпуска не
объявлена.
Крупный функциональный выпуск. Главное: конфигурации программы лояльности (уровни, акции программы и уровней, правила списания), переработанная форма акции с условиями из реестра («Настройка акции»), согласованием запуска и настройками начисляемых баллов, каталог клиентов и теги, карточка клиента по версионируемой настройке, субсчета баллов, срок жизни баллов по цепочке и сгорание по срокам, ручные операции, справочник валют и пары обмена, раздел «Отчёты». Кроме того — сегменты клиентов из системы кампаний, справочники городов, торговых точек, мерчантов и каталог товаров, возврат по чеку, очередь исходящих уведомлений, реферальная программа, достижения, мобильный кошелёк, очередь приёма команд, журнал аудита с защитой от подделки и передачей в SIEM, обмен данными с внешними системами. Выпуск содержит много BREAKING-изменений контракта API и ролевой модели, а также изменения схемы БД, которые требуют эксплуатационных шагов при обновлении контуров с данными, — см. «Совместимость и обновление» ниже.
Состав релиза по доменам¶
| Домен | Что входит в выпуск |
|---|---|
| Акции | Переработанный каталог (серверные поиск/фильтры/сортировка, массовые действия, колонки статистики и статуса согласования), форма акции с вкладками «Общие данные», «Механика», «Условия», «Ограничения», тип события и настройки начисляемых баллов, условия из реестра условий (товары, место покупки, мерчант, канал, сегменты, сумма чека, клиент, теги, участие в акциях, дополнительные условия), метки акций, ограничения и лимиты, механики на справочнике параметров, согласование запуска и отдельное право запуска, автоведение по периоду (статус «Запланирована»), удаление и копия акции, предпросмотр импорта |
| Настройки программы | Конфигурации программы вместо экрана «Уровни»: уровни в составе конфигурации, акции программы и уровней, правила списания, механика перехода и режим пересчёта уровня, активация с историей; уровень клиента хранится и считается по журналу покупок, понижается на пересмотре в личную дату клиента |
| Клиенты | Каталог клиентов (поиск, фильтры, массовые операции, импорт из CSV), теги клиента, карточка клиента по версионируемой настройке, вкладки «Участие в акциях» и «Сроки сгорания», часовой пояс клиента, история смены статуса (API), мягкое удаление, обезличивание, валидация телефона/email |
| Баллы и операции | Ручные операции (включая теги по списку клиентов), субсчета баллов с базовым субсчётом и областью списания, срок жизни баллов по цепочке и сгорание по срокам с предупреждениями по шагам, справочник валют и пары обмена, счета во всех включённых валютах, доступный остаток без баллов с наступившим сроком, резерв со сроком по умолчанию, кеш баланса в Redis |
| Вовлечённость | Реферальная программа, достижения и бейджи, карта в мобильном кошельке, улучшения личного кабинета (B2C): витрина акций совпадает с движком начисления, описание операции, staff-предпросмотр, ограничение частоты |
| Партнёрские предложения | Справочник мерчантов вместо MID/MCC в карточке партнёра, запрет удаления денежной истории, отчёт взаиморасчётов по окну, номинал вознаграждения по точности валюты, код оффера из последовательности БД, точное время периода |
| Администрирование | Раскрывающееся меню «Администрирование», экраны «Настройка акции», «Профиль клиента», «Валюты», «Сегменты клиентов», справочники городов, торговых точек, мерчантов и каталог товаров (API), очередь приёма, журнал аудита (обращения к ПДн, доменные события, защита от подделки, SIEM), перенос справочников и механик с проверками админки, часовой пояс программы на сервере |
| Отчёты и мониторинг | Раздел «Отчёты» (обзор, баллы, уровни, акции с выгрузкой CSV, рефералы), произвольный период и плитка «Сгорело» в мониторинге, сгорание и сторно не считаются тратой клиента, настоящий total в списках, оповещения Prometheus |
| Платформа и интеграции | Асинхронный приём команд через очередь и Kafka, возврат по чеку POS, обязательный код торговой точки и переменные покупки, приём сегментов из системы кампаний, исходящие события (смена уровня, уведомления клиентам), обмен данными с внешними системами, сплошное ограничение частоты и проверка загружаемых файлов, сверка итогов и контроль целостности учёта, SBOM backend и веб-интерфейса |
Новое в 26.3.0¶
Акции. Подробности — Каталог акций, Создание и редактирование акции, Условия акции, Ограничения и лимиты акции, Механики начисления, Жизненный цикл и арбитраж, Метки акций, Настройка акции.
- Каталог акций переработан: серверные поиск, множественные фильтры и сортировка по всей базе,
настраиваемые колонки, выбор строк с панелью массовых действий, отчёт частичного успеха. Новые
колонки «Применений», «Начислено» и «Расход бюджета» (скрыты по умолчанию) и «Статус согласования»
с фильтром и сортировкой; строка
GET /v1/promotionsнесётapplications,bonusMinor(по версии) иbudgetSpentMinor(по линии версий). Пока выполняется массовая операция, открыто окно «Выполняется операция». - Форма акции переработана. Вкладки — «Общие данные», «Механика», «Условия» и «Ограничения». В шапке — название, код, «Версия: N», статус и панель действий: «Создать новую версию», «Запустить», «Отменить запуск», «Приостановить», «Сохранить как…», «Архивировать»/«Разархивировать» и «Удалить»; каждое действие подтверждается отдельным окном. Справа — колонка «Расход бюджета» и чек-лист «Превью акции» («Готово к запуску: N из 6»). Кнопка «Сохранить черновик» теперь называется «Сохранить». Плашка режима чтения зависит от состояния акции: архивная, завершённая или запущенная.
- Тип события акции — поле «Тип события» (
eventType) из справочникаevent-type, по умолчанию «Покупка». Код вне справочника —422 promotion_event_type_invalid. Движок поле пока не читает: акции применяются к событиям покупки. - Начисляемые баллы акции: «Срок жизни начисляемых баллов» (
pointsLifetimeDays, 1–36 500 дней) и «Тип начисляемых баллов» (pointsSubaccountCode, по умолчанию «Акционные баллы»). Пустой срок берётся по цепочке «субсчёт → уровень клиента → валюта баллов», действующий срок показан серым в пустом поле. У механики, которая не начисляет баллы, настройки недоступны (422 promotion_points_settings_not_applicable). - Условия акции из реестра. Вкладка «Условия» строится по экрану «Настройка акции» и делится на зоны «Акция применяется, если» и «Не применяется, если». Коробочные условия: «Категории товаров», «Артикулы (SKU)», «Регион», «Город», «Магазин» (по коду торговой точки из покупки), «Мерчант» (по MID), «Канал продаж», «Сегменты клиентов», «Сумма чека» (перенесена из ограничений и доступна в обеих зонах), статус, уровень и баланс клиента, «Теги клиента», «Участие в акциях». Под подзаголовком «Дополнительные условия» — условия внедрения на переменную покупки или поле профиля клиента. Скрытое в настройке условие продолжает действовать в своих акциях (серая строка «Скрыто в настройке»), «Сохранить как» его не переносит. Значения из справочников показываются с пояснением записи: путь категории, город магазина, партнёр мерчанта. Окно подтверждения запуска предупреждает, если переменная дополнительного условия не приходила в покупках за последние 7 дней.
- BREAKING. Условия акции передаются списком
criteria({code, mode, values | range}, режимыINCLUDE/EXCLUDE) вместоscope; границы суммы чека больше не читаются изparams.minCheckMinor/maxCheckMinor, кодinvalid_check_rangeзаменён наpromotion_criterion_range_invalid, добавлены кодыpromotion_criterion_*. Файлы переноса со старымscopeимпортируются с конвертацией. - BREAKING. Условие «Уровень клиента» — список пар
params.conditions.client.levels[{configId, code}]вместо строкиlevel: прежний ключ —400 validation_failed, неизвестная конфигурация или уровень (в том числе при импорте) —422 promotion_level_unknown. Условие по уровням черновой конфигурации действует только на клиентов этой конфигурации. - Условия аудитории — «Теги клиента» и «Участие в акциях» (по меткам, конкретным акциям и самой
акции — «Эта акция») в обеих зонах, с порогом числа участий и периодом. Удалённые значения в условиях — битые
ссылки: черновик сохранить можно, запустить акцию нельзя (
400 promotion_audience_unresolved). Условие «Сегменты клиентов» сверяется только с составом, залитым из системы кампаний; удалённый сегмент — тоже битая ссылка (kind: SEGMENT), а сегмент из условий работающей акции удалить нельзя (409 segment_in_use). Та же логика доступна интеграциям —POST /v1/integration/rtflow/condition-checks, включая тип условияCUSTOMER_SEGMENT. - Механики — на справочнике параметров. Для новой акции форма предлагает только механики с
заведёнными параметрами; размер награды задаётся параметром механики, поле
valueне используется. Черновик на механике без параметров не запускается (409 promotion_mechanic_not_configured). У «Кэшбэка» удалены параметры «Макс. бонус», «Мин. сумма чека» и «Сегменты клиентов»; у «1+1=3» параметры задаются фразой, добавлена «Стоимость дополнительной единицы». - BREAKING. Награда «Множителя», «Подарка» и «Начисления за единицу» обязательна: без
multiplier,giftMinorилиpointsPerUnit—422 promotion_param_required. Потолок бонуса — только поле акцииmaxBonusMinor, параметрparams.maxBonusMinorбольше не читается. Существующие акции миграция переводит без изменения начисления. - Согласование запуска по принципу «четырёх глаз» — включается настройкой развёртывания
loyalty.promotion.approval.enabledвместе с признаком интерфейсаPROMOTION_APPROVAL_ENABLED. Вкладка «Согласование» на странице акции: отправка, отмена отправки, согласование и отклонение, история. Статусы — «В работе», «На согласовании», «Согласована», «Отклонена»; содержательная правка сбрасывает согласование. Согласует сотрудник с правомLOYALTY_PROMOTION_APPROVE, не автор отправки. Новая версия и копия акции согласуются заново. - BREAKING. Запуск акции (
activate,bulk-activate) требует отдельного праваLOYALTY_PROMOTION_ACTIVATE, даже если согласование выключено: праваLOYALTY_PROMOTION_WRITEбольше недостаточно (403 access_denied). - Ограничения и лимиты акции: общий лимит участий, лимит участий на клиента с окном «За всё время акции», «По календарю» (день, неделя, месяц) или «За последние N дней» (N ≥ 2), бюджет как полноценное ограничение с возвратом в бюджет по сторно (в том числе при возврате покупки по чеку). Все ограничения передаются одним запросом вместе с акцией; «Сумма чека» переехала в условия, поэтому ограничений четыре. Календарные границы считаются в часовом поясе программы.
- Лимит участий принимает только окна, которые показывает форма: единица «год» —
422 invalid_period, календарный период из нескольких единиц и скользящее окно не в днях или короче 2 дней —422 invalid_participation_window. Уже сохранённые окна продолжают считаться. - BREAKING.
budgetMinorакции — деньги, а не баллы; каждое начисление пересчитывается по курсу валюты баллов программы (points.currency) на момент начисления, настройкаloyalty.promotion.budget.point-currencyудалена. Бюджет в форме можно ввести в баллах: при сохранении он пересчитывается в деньги по текущему курсу, рядом — подсказка «по текущему курсу ≈ N». - BREAKING. Приоритет акции (
priority) стал обязательным, какname,mechanicCode,startsAtиendsAt: без любого из нихPOST/PUT /v1/promotionsотвечает400 validation_failed; значение приоритета по умолчанию100больше не подставляется. - BREAKING. Тело акции — полная замена: отсутствующий
participationLimitвPUTснимает лимит участий (как и отсутствующий бюджет). Исключения —criteria,audience,eventTypeи ключlevelsусловия «Уровень клиента»: отсутствующее поле сохранённое значение не меняет. - BREAKING. Правка акции на месте зависит от факта применения, а не от статуса: приостановленная
акция, по которой уже было хотя бы одно начисление, правится только новой версией
(
409 promotion_not_editable). - BREAKING. Статусная модель акции изменена: статус
ARCHIVEDубран из перечисления (архив — отдельный обратимый признакarchived), добавлен статусSCHEDULED. Фильтрstatusстал множественным;createdBy/updatedByстали объектами{id, displayName}; массовые запросы каталога акций принимают не больше 500 id за раз. - Метки акций — новый справочник со своим экраном (переименование с контролем версии, слияние
синонимов, массовое удаление, перенос бандлом); поле «Метки» в карточке акции правится в любом
статусе, массовое проставление/снятие в каталоге — не больше 200 меток в пакете
(
400 bulk_size_exceeded), метка, удалённая или слитая во время операции, больше не теряется молча. Новые ролиLOYALTY_PROMOTION_LABEL_READ/WRITE. - Новый статус «Запланирована» и автоведение акции по периоду (включается настройкой) — акция сама переходит в «Активна» по дате начала и в «Завершена» по дате окончания.
- Удаление акции (черновик и запланированная) и копия «Сохранить как» с нулевой статистикой.
Акцию, которая входит в программу лояльности, удалить нельзя:
422 promotion_bound_to_configс названиями конфигураций, признакboundToConfig, кнопка «Удалить» недоступна с пояснением. - Завершённая акция новой версии не получает — только копией (
409 promotion_not_revisable). - Уникальность названия акции между акциями (
409 promotion_name_taken). - Точное время начала и конца периода акции (обе границы включительны, по умолчанию 00:00/23:59) — в часовом поясе программы.
- Мастер импорта акций/механик/справочников с предпросмотром (
dryRun=true). - Механики «Множитель» и «Подарок» начали начислять (ранее давали нулевой бонус молча); механика «бонус за натуральный объём» заработала на кассовом канале POS.
- В API акции добавлено поле
pointsRedemptionScope— область списания баллов, начисленных акцией (товары, категории, мерчанты, торговые точки). Сервер проверяет и хранит её и при начислении записывает на партию баллов (422 promotion_redemption_scope_invalidдля пустых, повторённых значений и больше 1000 значений), но при списании пока не проверяет. В форме акции поля нет.
Настройки программы. Подробности — Конфигурации программы, Уровни (тиры), Операционная конфигурация.
- Конфигурации программы лояльности. Раздел «Конфигурации программы» (право
LOYALTY_PROGRAM_CONFIG_READ) заменил экран «Уровни»: каталог с поиском, фильтрами по дате и автору («ФИО (логин)»), колонками «Как считается уровень» и «Механика» (диапазон ставок акций программы); мастер создания из четырёх шагов («Основное», «Акции программы», «Правила списания», «Уровни»); карточка конфигурации, панель «Как это сработает» с примером клиента и сводка «Как живут баллы». Действует ровно одна конфигурация; активная правится через дублирование и активацию копии. - Активация и история активаций. Отдельное право
LOYALTY_PROGRAM_CONFIG_ACTIVATE, флажок «Отправлять клиентам сообщения о смене уровня», неизменяемый журнал активаций со снимком настроек и уровней. Новые клиенты сразу попадают на новые правила, текущие переходят при первой покупке или списании либо в дату своего пересмотра. Черновик, на котором ещё живут клиенты (customersCount), открывается только для просмотра, удалить его нельзя (config_has_customers). - Акции программы и акции уровней. К конфигурации привязываются акции базового начисления; каждому уровню назначаются свои «Акции начисления». Ставку начисления уровня задают акции, а не поле уровня.
- Правила списания в конфигурации: область списания (любые покупки, покупки от суммы, товары или
категории из списка), максимум за покупку (единый или свой на уровне), минимальное списание,
минимальный баланс, кратность. Списание и резерв проверяются по правилам активной конфигурации и
отклоняются своим кодом (
redemption_below_min_balance,redemption_scope_not_eligible,redemption_not_multiple,redemption_items_required,redemption_purchase_amount_required); вhold/redeemпоявилось необязательноеitems[]. Если у конфигурации нет своих правил, действует общая политика списания раздела «Настройка ПЛ». - Механика перехода и режим пересчёта уровней. Уровень считается по сумме, количеству или сумме и
количеству покупок (
tierMetric, второй порог —entryCount). Период — скользящее окно с пересмотром раз в заданный интервал либо персональный период с переносом излишка (tierRecalc). Отсутствующие в запросеtierMetricиtierRecalcсбрасываются на значения по умолчанию («сумма покупок», скользящее окно) — интеграциям нужно передавать их при каждом сохранении. Свой срок жизни баллов у уровня необязателен: пустой срок берётся из валюты баллов. - Уровень клиента хранится и считается по журналу покупок. Помесячного сброса больше нет.
Повышение — сразу при покупке, один раз на чек после акций; понижение — на пересмотре в личную дату
клиента (
LOYALTY_TIER_REVIEW_CRON, по умолчанию выключен) или при переносе на новую активную конфигурацию («Перенос системой»). Возврат по чеку уменьшает накопление, но уровень сразу не понижает. Ручное назначение работает как «пол» до ближайшего пересмотра. - BREAKING. Уровни записываются только в составе конфигурации (
POST/PATCH /v1/program-configs):PUT/DELETE /v1/tiers/{code}и кодtier_ordinal_takenудалены без периода депрекации.GET /v1/tiersостался только для чтения и принимаетconfigId. У уровня нет поляmetric(механика перехода принадлежит конфигурации), появился порогentryCount. - Статус уровня клиента (
GET /v1/customers/{externalId}/tier) отдаётassignedAt,nextReviewAt,periodEndsAt,configи вторую ось накопления (accumulatedCount,toNextCount). Вкладка «История уровней» карточки показывает источник, сроки и конфигурацию клиента. - BREAKING. История переходов уровня (
GET /v1/customers/{externalId}/tier/transitions) отдаётся постранично ({items, page, size, total}), параметрlimitудалён. В записи — названия уровней на момент перехода (fromName/toName), количество покупок (accumulatedCount), конфигурация, оператор и причина ручного перехода, источникsource: «При покупке», «На пересмотре», «Вручную», «Перенос системой». Ручное назначение, массовый перевод и снятие назначения тоже попадают в историю. - Событие о смене уровня
tiers.customer.tier-changedчерез очередь исходящих: доставка «не менее одного раза» сeventId, отправка выключена по умолчанию (LOYALTY_TIER_EVENTS_ENABLED), событие, не отправленное дольше 7 дней, списывается. - «Настройка ПЛ» содержит ограничения на списание, лимиты по разрезам и глобальную механику; блок «Валюты и баллы» убран — балльные валюты ведутся в «Администрирование → Валюты».
Клиенты. Подробности — Каталог клиентов, Карточка клиента, Теги клиента, Настройка карточки клиента.
- Каталог клиентов — серверные поиск, мультизначные фильтры (статус, уровень, теги), период регистрации, серверная сортировка; массовые операции (блокировка, разблокировка, архивирование, удаление) из меню строки или панели выделения; импорт клиентов из CSV с предварительной проверкой. Фильтр и колонка «Уровень» показывают последний посчитанный уровень клиента (в том числе уровень прежней конфигурации), колонка «Сегменты» — названия сегментов. Форма «Новый клиент» больше не содержит выбора бонусных счетов.
- BREAKING. Параметр фильтра по уровню переименован
tierCode→tier, стал списком кодов; тумблер «Показать архивные» теперь возвращает всю базу с архивными первым блоком. - Теги клиента стали справочником: собственный экран с переименованием (контроль версии),
слиянием синонимов, массовым удалением с блокировкой по условиям работающих акций; присвоение и
снятие тегов клиенту (одиночно, до 10 000 клиентов из каталога и пакетом по списку на экране «Ручные
операции»). Действие каталога «Удалить теги» переименовано в «Снять теги». Тег, удалённый между
выбором и записью, отклоняет запрос
422 unknown_tag, а не выпадает молча. Новые ролиLOYALTY_TAG_READ,LOYALTY_TAG_WRITE,LOYALTY_CUSTOMER_TAG_WRITE. - Карточка клиента собирается по активной настройке конфигуратора «Настройка профиля клиента»: слева — карточки с полями (по умолчанию «Клиент» и «Баллы и уровень»), справа — разделы «Баллы и уровень», «Клиент», «Активность и коммуникации» с вкладками продукта и таблицами данных из источников. Новые поля: «Сегменты» (из системы кампаний, с датой входа), «Дата рождения», «Часовой пояс» (только чтение, с подсказкой источника пояса).
- Вкладка «Участие в акциях»: дата, акция (ссылка на карточку акции), механика, бонус и ID транзакции; фильтр по механике и сортировка по дате. Сторно участием не считается.
- BREAKING. Значения атрибутов карточки — списки строк:
attributesвGET /v1/customers/{externalId}/cardи телоPUT /v1/customers/{externalId}/attributesперешли с «ключ → строка» на «ключ → список строк». Значение проверяется по формату типа поля (400 validation_failed); запись в неизвестное, преднастроенное, привязанное к источнику, архивное или скрытое поле отклоняется (unknown_attribute,system_field_immutable,field_archived,field_hidden), больше одного значения без множественного выбора —attribute_not_multiple. - BREAKING. Сегменты клиента берутся только из состава, залитого системой кампаний: поле
segmentsудалено из профиля MDM (identity) в карточке и ответе записи профиля; сегменты клиента —GET /v1/customers/{externalId}/segments. ОбезличиваниеSEGMENTSи удаление клиента выводят его из всех сегментов. - Карточка в API отдаёт теги (
tags), флаг архива (archived) и дату рождения (identity.birthDate);PUT /v1/customers/{externalId}/profileпринимаетbirthDate,cityиtimezone(IANA, иначе422 customer_timezone_invalid). Пояс клиента определяется по первому доступному источнику: пояс профиля → город профиля → частый город покупок → пояс программы. - История смены статуса клиента: блокировка, разблокировка, удаление и слияние записываются с
автором (у слияния — с причиной); чтение —
GET /v1/customers/{externalId}/status-history. Блокировка больше не «оживляет» клиента, удалённого одновременно с ней (404 customer_not_found). - Слияние клиентов переносит выжившему журнал покупок и возвратов и пересчитывает его уровень.
Конфликт
merge_account_conflict— только если операции в одной валюте были у обоих клиентов. BREAKING: слияние уже слитого в другого клиента источника отвечает409 version_conflict, удалённого —404 customer_not_foundвместо прежнего200. - Карточка клиента (бывший «Профиль участника») получила мягкое удаление (
DELETE /v1/customers/{externalId}), обезличивание персональных данных, валидацию и нормализацию телефона и email. - BREAKING. Недоступность внешнего реестра клиентов (MDM) —
503 mdm_unavailableвместо404 customer_not_found.
Баллы и операции. Подробности — Ручные операции, Субсчета баллов, Справочник валют программы, Сгорание баллов, Баланс и операции.
- Субсчета баллов — баланс клиента разбит на целевые счета (приветственные, партнёрские,
акционные) со своим правилом сгорания и местом в очереди списания; списание может затронуть
несколько субсчетов, сторно возвращает баллы на исходный. В каждой балльной валюте есть субсчёт
«Акционные баллы» (
PROMO); рядом с валютой субсчёта показан её курс со ссылкой «Изменить курс». Закрыть субсчёт, в который начисляют незавершённые акции, нельзя (409 subaccount_used_by_promotions). - Базовый субсчёт (
BASE, «Основной») закреплён за валютой: его нельзя переназначить или закрыть, своего срока и области списания у него нет. BREAKING: признак —isBaseвместоisDefault, операцияPOST /v1/points-subaccounts/{id}/defaultудалена, отказ закрытия —409 subaccount_base_cannot_close. - BREAKING. Режим сгорания субсчёта без своего срока называется «Общий срок»: баллы сгорают по
сроку уровня клиента или по базовому сроку валюты, а не хранятся бессрочно. Срок «На дату»
задаётся датой и временем («Действуют до», по умолчанию 23:59:59): поле
expiryDateзаменено наexpiryAt. Период в ответе приходит в днях (P30D). - Область списания субсчёта: «Товары», «Категории», «Мерчанты», «Торговые точки» из справочников.
Действует при списании за покупку: резерв, списание и команды
HOLD/REDEEMпринимаютstoreCode,merchantCodeи позицииitems. - Срок жизни баллов по цепочке: срок начисления (акция или запрос) → своё правило субсчёта → срок
уровня клиента в его конфигурации → базовый срок валюты → не сгорают. Срок и его источник
(
expirySource) фиксируются в партии и не пересчитываются. У балльной валюты — базовый срок «Срок жизни баллов, дней» (pointsLifetimeDays, 1–3650, по умолчанию 365). - Сгорание по сроку. Баллы с наступившим сроком недоступны сразу, а не с ночного прогона. Прогон
сгорания идёт пачками (
LOYALTY_EXPIRY_BATCH_SIZE), каждый счёт — в своей транзакции, и сжигает свободный остаток: резервы, созданные до срока, сохраняют свои баллы. Добавлены пробный прогон?dryRun=true, запись в истории по каждому субсчёту и сообщение клиенту о факте сгорания. Ручное сгорание оператором пишется с источникомMANUAL_EXPIRY(«Сгорание оператором») и не считается тратой клиента. - Предупреждения о скором сгорании по шагам (
LOYALTY_EXPIRY_NOTIFY_BEFORE, напримерP30D,P7D,P1D): по каждой партии и шагу — одно предупреждение с текстом «Через 7 дней…», «Завтра…», «Сегодня…»; дата указывается в часовом поясе программы, сообщения идут через очередь исходящих уведомлений. Ручной запуск —POST /v1/maintenance/points/expiry-notice. - В карточке клиента — вкладка «Сроки сгорания»: строка «Ближайшее сгорание» и партии баллов от
ближайшего срока с источником срока (
GET /v1/customers/{externalId}/points-lots). В балансе заполняютсяnearestExpiryAtиnearestExpiryAmountMinor(раньше поле срока всегда было пустым). - Доступный остаток считается как
max(0, баланс − резервы − баллы с наступившим сроком + покрытое): резерв, созданный после срока партии, больше не обещает те же баллы дважды. Та же формула — в корректировке оператора и проверке пакета ручных операций: строка, готовая по проверке, не отказывает на выполнении из-за баллов с наступившим сроком. - Счета во всех включённых валютах: при заведении клиента и фоновой задачей при создании или
включении валюты; разблокировка открывает пропущенные счета, ещё не открытый счёт показывается
строкой с нулями. Поле
preCreateAccountsустарело и игнорируется. Счёт без проводок больше не мешает удалить валюту. - Вкладка «Счета и балансы»: при двух субсчетах и больше — плитки «Доступно — <субсчёт>»; счета во всех включённых валютах, включая денежные; суммы — с точностью своей валюты.
- Вкладка «Бонусные операции»: фильтры по периоду, типу операции, сумме «от — до» и источнику,
сортировка по дате, сторно со знаком «−». В API
GET /v1/customers/{externalId}/operations— также отбор по признаку акции и коду механики, в строке —subaccountиaccountNumber. - Ручные операции с баллами — новый экран, мастер из трёх шагов (список клиентов → операция →
результат): начисление, корректировка, перевод уровня, «Добавить теги» и «Снять теги»; пакет по
списку или файлу (до 20 МБ, 1 000 000 строк), асинхронное выполнение с ходом на экране и
CSV-отчётом, отложенный запуск. Экран открывается и по одной роли
LOYALTY_CUSTOMER_TAG_WRITE— тогда доступны только операции с тегами. - BREAKING. Причина ручной операции — код справочника «Причины операций» плюс комментарий
(обязателен при причине «Прочее»); свободный текст
reasonубран. РольLOYALTY_CORRECTION_OTHER_WRITEпереименована вLOYALTY_OPERATION_REASON_OTHER_WRITE; операторам начисления, корректировки и перевода уровня нужна рольLOYALTY_DICTIONARY_READ(справочник причин). - BREAKING. Единая форма ответа массовых операций
bulk-accruals/bulk-corrections/tier-bulk:{batchId, status, total, succeeded[], errors[]}. Полеsourceубрано и из тел запроса этих трёх точек — источник операции сервер определяет сам. - Предел массовых операций поднят до 1000 идентификаторов в одном запросе.
- Справочник валют программы — деньги и баллы одной записью с типом, курс балльной валюты с
историей, ровно одна базовая денежная валюта; экран «Администрирование → Валюты» с вкладками
«Валюты» и «Обмен валют». BREAKING: в
GET /v1/currenciesпоявилось полеkind,display.symbolможет бытьnull, неизвестный код валюты —404вместо500; прежний/v1/admin/point-currenciesработает поверх справочника:colorиsortOrderне сохраняются, удаление включённой валюты —409 currency_enabled, валюты со ссылками —409 currency_in_use. Начисление в выключенную валюту отклоняется. - Пары обмена между балльными валютами (
/v1/admin/exchange-pairs, сводка и базовый курс направления): режимы курса, округление, остаток, направление и ограничения. Операция обмена пары пока не применяет. - BREAKING. У резерва баллов (
hold) всегда есть срок: безexpireAtдействует срок по умолчанию (redemption.hold-ttl, 30 минут), просроченные резервы снимаются автоматически. - Покупка считается по чеку, а не по его позициям: чек из десяти товаров увеличивает счётчик покупок клиента на один. Сумма покупки сохраняется и связывается с начислениями; уже завышенные счётчики не пересчитываются.
- Границы лимита списания округляются внутрь допустимого промежутка (было — обе «к ближайшему»).
- Кеш баланса в Redis (выключен по умолчанию) — ускоряет чтение под нагрузкой, не меняя контракт.
- Срок и источник срока партии баллов согласованы и на уровне базы: у партии «не сгорают» срока нет,
у остальных источников он есть. Партия, где это нарушено ручной правкой данных, читается с ошибкой
500 lot_expiry_inconsistent, в том числе при списании. - Без Kafka неисполненные части пакета ручных операций после перезапуска пода публикуются заново: брошенный пакет подхватывается на старте и раз в минуту, если 5 минут от него нет признаков жизни; повторной проводки строк нет.
Вовлечённость. Подробности — Реферальная программа, Достижения и бейджи, Карта в мобильном кошельке, Личный кабинет клиента (B2C).
- Реферальная программа — реферальный код клиента, связь «реферер → реферал» при регистрации, активация первой покупкой приглашённого, дуальное вознаграждение механикой «Реферальная». Оба начисления получают «Срок жизни начисляемых баллов» и «Тип начисляемых баллов» реферальной акции.
- Достижения и бейджи — маркетолог настраивает правила «условие → бейдж»; клиент видит бейджи в
карточке и в личном кабинете. Роли
LOYALTY_ACHIEVEMENT_READ/WRITEзаведены в Keycloak и входят в группуLOYALTY_ADMINS. Чек из кассы считается одной покупкой на сумму документа; сторно и ручное сгорание не увеличивают показатель «Списано баллов». - Карта участника в мобильном кошельке — Apple Wallet и Google Кошелёк; функция поставляется
выключенной до настройки сертификата/ключа подписи заказчика (
loyalty.wallet.enabled). - Личный кабинет (B2C): вкладка «Акции» показывает ровно те акции, которые применит движок, — условия,
известные до покупки, проверяются общим правилом начисления, акция с незнакомым условием или
удалённым сегментом не показывается. Описание операции из
reasonинтеграционных начислений в истории, явный staff-предпросмотр поpreviewExternalId, ограничение частоты запросов кабинета (429 b2c_rate_limited).
Партнёрские предложения. Подробности — Партнёрские предложения, Партнёрские предложения и взаиморасчёты.
- BREAKING. Поля
merchantIds/mccудалены из контракта партнёра (POST/PUT /v1/partners). MID и MCC ведутся в справочнике мерчантов/v1/admin/merchants(праваLOYALTY_MERCHANT_READ/WRITE); существующие MID перенесены миграцией. Транзакция находит партнёра по MID через справочник, по MCC — только если MCC однозначно указывает на одного партнёра. - Денежная история партнёра больше не удаляется: оффер с атрибуциями удалить нельзя —
409 partner_offer_in_use(только архивировать), партнёра с проводками по счёту финансирования или с мерчантами —409 partner_in_use. - Отчёт о взаиморасчётах больше не смешивает периоды: окно отчёта возвращается в ответе
(
from/to), добавленоfundedInPeriodMinor— финансирование за окно;fundedTotalMinorпо-прежнему считается за всё время. - Номинал вознаграждения
POINTS/FIXEDпереводится в минорные единицы по точности валюты баллов, а не умножением на 100; вознаграждение округляетсяHALF_UP, как начисление по механикам акций. - Сегмент аудитории оффера выбирается только из справочника сегментов, неизвестный код —
422 partner_offer_segment_not_found. - Код оффера — из последовательности БД (
PO-0000001), исправляет редкую коллизию кодов на старой формуле; точное время начала/конца периода оффера. Период вводится и показывается в часовом поясе программы (подсказка «Часовой пояс: UTC+…»), прошедшие дни при создании выбрать нельзя.
Администрирование. Подробности — Единое окно и навигация, Настройка акции, Настройка карточки клиента, Справочники (НСИ), Очередь приёма, Журнал аудита, Справочники и НСИ.
- Меню. «Администрирование» — раскрывающийся пункт левого меню с десятью подпунктами (в Core UI —
подменю хоста, в автономном режиме — подменю оболочки), в том числе новые «Валюты» и «Настройка
акции»; горизонтальных вкладок у экрана больше нет. Адрес
/loyalty/adminбез раздела и устаревшие ссылки ведут в «Справочники». В меню модуля появились «Конфигурации программы» и «Отчёты», раздела «Уровни» больше нет. - «Настройка акции» — реестр условий акции: администратор скрывает ненужные коробочные условия, меняет их порядок перетаскиванием и добавляет свои — на переменную из покупки или на поле профиля клиента, со способом «Из справочника», «Ручной ввод» или «Диапазон». Под названием переменной — статистика за 7 дней. Условие, заполненное в работающих акциях, удалить нельзя — откроется окно со списком акций. Правки сохраняются одним запросом с защитой от одновременной правки.
- «Профиль клиента» (в автономном режиме — «Настройка профиля клиента») — конфигуратор карточки
клиента: настройка версионируется целиком (черновики, копия, сравнение с активной версией,
активация отдельным правом
LOYALTY_CLIENT_PROFILE_ACTIVATE), вкладка «Структура профиля клиента» задаёт карточки левой колонки, разделы и таблицы данных из таблиц-источников, вкладка «Атрибуты» — поля с типами «Строка», «Число», «Дата», «Дата со временем», «Логический», выпадающими списками из справочника, множественным выбором, колонкой источника, архивацией и пакетным удалением; есть предпросмотр карточки. Удаление версий — только через API (правоLOYALTY_CLIENT_PROFILE_DELETE). - BREAKING. API конфигуратора карточки: операции над содержимым требуют
configurationId, изменяющие — ещёIf-Matchс ревизией версии;fieldType: LISTотклоняется (выпадающий список —inputSource: DROPDOWN+refDict); ключ нового поля проверяется по формату^[a-z_][a-z0-9_]*$(до 64 символов), а не исправляется, и после создания не меняется (field_key_immutable); удалить можно только незаполненное поле (field_has_values), заполненное архивируется; поле, на котором построено условие акции, не удаляется (profile_field_used_in_criterion). - Сегменты клиентов — собственный экран в реестре справочников с вкладками «Сегменты» и «Заливки»: сегмент можно архивировать, вернуть из архива и удалить, если он не стоит в действующих акциях. Сегменты заводит только внешняя система кампаний.
- Собственные справочники продукта: категории товаров и SKU — каталог с деревом категорий до трёх
уровней и CSV-импортом (
/v1/admin/product-categories,/v1/admin/product-items, ролиLOYALTY_PRODUCT_CATALOG_*) вместо внешнегоdictionary-api; города (регион и часовой пояс), торговые точки (город и адрес) и мерчанты (MID, партнёр, MCC, город) — свой API и пара ролей у каждого. Их значения используются в условиях акции; экрана в интерфейсе у этих справочников нет. - BREAKING. Общие ручки
/v1/admin/dictionaries/{key}/...по ключамcustomer-segment,product-categoryиskuотвечают409 dictionary_own_screen— эти справочники ведутся своим API. - Справочники: запрет правки записи в статусе «Архив» без предварительного восстановления, защита от
удаления записи, на которую ссылаются акции, — теперь и для типов событий, регионов, каналов и
записей, выбранных в дополнительных условиях акции (признаки
usedByPromotionsиdeletableу всех записей общего экрана). Перенос справочников и механик между контурами с предпросмотром (dryRun=true) проверяет записи и механики так же, как админка. - Очередь приёма — экран для разбора застрявших асинхронных команд начисления/списания/резерва:
статусы, карточка команды, возврат в работу, отмена (в т. ч. ещё не наступившей отложенной
команды). Роли
LOYALTY_INTAKE_READ/WRITE. - Журнал аудита — отдельный экран: изменяющие вызовы и обращения к персональным данным (просмотр
карточки, поиск, выгрузка) с ролями субъекта на момент действия и источником запроса; доменные
события — согласование запуска акции (
PROMOTION_APPROVAL_*), смена курса валюты (currency.rate.change) и пары обмена (exchange-pair.rate.change); выгрузка за период потоком в CSV; защита от подделки задним числом (цепочка печатей, выключена по умолчанию; проверка целостности показывает и часть журнала, ещё не покрытую печатями) и опциональная передача потока в SIEM заказчика по syslog/CEF. Фильтр «Время» отбирает записи по суткам в часовом поясе программы. Общая рольLOYALTY_MONITORING_READ. - Часовой пояс программы применяет сервер: в нём режутся календарные границы лимитов, итогов и
отчётов, подписываются даты в текстах и выгрузках, трактуются расписания сгорания и выгрузок.
Допустим только идентификатор IANA (
422 program_timezone_invalid);GET /v1/program/timezoneдоступен любому пользователю интерфейса. Все даты в интерфейсе показываются и вводятся в поясе программы, а не браузера. - Системные счета по умолчанию разведены:
MRK— источник начислений,RDM— списание,BRK— сгорание.
Отчёты и мониторинг. Подробности — Отчёты, Мониторинг, Наблюдаемость.
- Раздел «Отчёты» — сводные величины программы за период на вкладках «Обзор», «Баллы», «Уровни»,
«Акции», «Рефералы». Период задаётся списком «Глубина» (7 дней, 30 дней, 90 дней, год), общим для
всех вкладок. Доступ — по роли
LOYALTY_MONITORING_READ. - API отчётов
/v1/reports/*: пульс программы, баллы (движение, доля погашения, обязательства на конец периода, предстоящее сгорание 7/30/90 дней), акции за период с итогами и динамикой по дням, уровни (распределение, клиенты прежних конфигураций, переходы, включая ручные назначения и перенос системой), реферальная программа (период, когорта и срез состояния на момент запроса). Окно задаётся параметрамиfrom/to, перевёрнутое окно —422 invalid_period. Тестовые клиенты не учитываются. - Отчёт по акциям: строка на линию версий акции с колонками «Период», «Покупок» и «Выручка».
Выручка считается за вычетом возвратов, по строкам не суммируется и остаётся пустой, если покупки
были в разных валютах. Сортировка серверная по всем колонкам; выгрузка «Скачать таблицу (CSV)»
(
GET /v1/reports/promotions/export) берёт весь период, а не текущую страницу. - Сгорание и сторно не считаются тратой клиента. У сторно — свой тип движения
REVERSAL, у ручного сгорания — свой источникMANUAL_EXPIRY. В сводке мониторингаredeemedMinorвключает только траты клиентов, добавленыexpiredMinor/expiredCountи сгорание в дневной динамике; в «Пульсе» и отчёте по баллам сторно вынесено вreversedMinor/reversedCount. Контракт расширен без удаления полей, но на данных со сгоранием или сторно значениеredeemedMinorстановится меньше. - Экран «Мониторинг»: к ширине окна 7/30/90 дней добавлен произвольный период (поля «Начало» и
«Конец»), подпись «Показано за …» и плитка «Сгорело»; величины движения подписаны в баллах, а не в
рублях. Выдачи
/v1/monitoring/*принимаютfrom/to, сводка возвращает фактическое окно; неположительная ширина или перевёрнутое окно —422 invalid_period. - BREAKING. Списки мониторинга (аномалии, сгорающие лоты, аудит ручных начислений) переведены на
конверт
{items, page, size, total}с настоящимtotalвместо усечённого потолком в 500 массива. - Оповещения Prometheus по техническим и бизнес-показателям — недоступность, вставшая очередь
приёма, команды в разборе, доля ответов 5xx, всплеск аномалий и ручных начислений, «Прогон сгорания
баллов давно не отрабатывал» (
LoyaltyExpiryStalled, заменило правило о крупном объёме баллов к сгоранию), «Уведомления клиентам не уходят» (LoyaltyNotificationsStuck). Выключены по умолчанию, включаются на контуре заказчика. - Операционный день заполнен у каждой операции журнала бонусных операций (прежние строки дозаполнены по связанной проводке), поэтому дневной отчёт и сверка итогов дня больше не теряют старые операции.
- Итоги субсчёта за период больше не выдумывают разбивку «списано / сгорело»: признак
expirySeparableпоказывает, выделяется ли сгорание; даты периода — в часовом поясе программы. - Проверка целостности учёта больше не краснеет постоянно: равенство баланса и суммы проводок сверяется по клиентским счетам, а для системных проверяется нулевая проекция баланса.
- Метрики приёма сегментов клиентов:
loyalty.segments.fills(по статусу заливки),loyalty.segments.fill.complete(длительность применения),loyalty.segments.members.entered/.exited.
Платформа и интеграции. Подробности — Интеграции, Конфигурация, Безопасность.
- Асинхронный приём команд изменения баланса через очередь и опциональный Kafka-транспорт; порядок
исполнения гарантирован в пределах одного клиента; отложенное исполнение («начислить в день
рождения»). В примерах values чарта для dev/qa/prod Kafka-транспорты приёма команд и чанков ручных
операций включены (JAAS из секрета
loyalty-kafka). - BREAKING. Код торговой точки обязателен:
storeCodeвPOST /v1/integration/pos/receiptsиexternalStoreCodeвPOST /v1/events/purchase; для покупки без физической точки передаётся код виртуальной точки из справочника торговых точек. Новые необязательные поля: каналchannel, MIDmerchantCodeи переменные покупкиattributes— на весь чек и у каждой позиции (до 20 переменных, название — латиница, цифры, «_» и «-» до 64 символов, значение — до 256 символов). - Возврат по чеку POS:
POST /v1/integration/pos/returnsсторнирует баллы по исходному чеку пропорционально сумме возврата и уменьшает накопление для уровня. Баланс в минус не уходит: потраченные баллы остаются вunreversedPointsMinor. Отмена возврата —POST /v1/integration/pos/returns/{returnId}/cancel. - Сегменты клиентов из внешней системы (Batch Flow): регистрация, пачки участников и завершение
заливки (
/v1/integration/customer-segments/**, рольLOYALTY_SEGMENT_INTAKE); пока заливка не завершена, действует прежний состав. Сегменты клиента для внешних систем —GET /v1/integration/customers/{externalId}/segments. - BREAKING. Залитый внешней системой состав — единственный источник сегментов клиента: поле
segmentsвPOST /v1/events/purchaseигнорируется (помеченоdeprecated), сегменты профиля MDM не учитываются. До первой заливки сегментные акции не применяются ни к кому. - Исходящие события через очередь исходящих: смена уровня
tiers.customer.tier-changedи уведомления клиентамnotifications.customer.message-requested(момент отправки — по поясу клиента, новый повод — списание баллов). Доставка «не менее одного раза», повтор определяется поeventId; отправка включается отдельно (LOYALTY_TIER_EVENTS_ENABLED,LOYALTY_NOTIFICATIONS_ENABLED). Разбор очереди уведомлений — через API/v1/admin/notifications(ролиLOYALTY_NOTIFICATION_READ/WRITE). - BREAKING. Отказ валидации тела и параметров отвечает
400 validation_failedсо списком нарушений вdetail, а неbad_request, — в том числе на интеграционных точках POS, Promo, RT Flow и лент изменений. - Обмен данными с внешними системами: лента изменений с курсорной пагинацией и регламентные задания
выгрузки витрин, по умолчанию выключены. Возобновление ленты больше не теряет и не дублирует
строки (
watermark— только на последней странице окна,changedSinceстрого исключающий); витринаDWH_TRANSACTIONSотдаёт клиента и субсчёт по каждой ноге проводки. - Интеграционный периметр защищён сплошным ограничением частоты (GCRA), проверкой загружаемых файлов до разбора содержимого (сигнатура, кодировка, антивирус) и сверкой итогов/целостности баланса.
- BREAKING. Ключ идемпотентности действителен только для операции, которая его заняла: повторное
использование на другой операции —
422 idempotency_key_reused. - Аренда instance-id генератора идентификаторов между репликами — исключает коллизии ID при масштабировании.
- Ресурсы пода backend подняты до 2 CPU / 2 GiB; чарт получил liveness/readiness/startup-пробы и
опциональные правила оповещений Prometheus. Корневая ФС контейнера — только на чтение
(
readOnlyRootFilesystem), пояс среды исполнения закреплён UTC (TZ=UTC), base-chartuniform-service1.0.29 рендерит grace period 120 с. - Состав ПО (SBOM, CycloneDX): образ backend несёт его внутри, для веб-интерфейса перечень
зависимостей по
package-lock.jsonпубликуется отчётом GitLab в CI. - BREAKING. Переоткрытие операционного дня убрано из контракта (
POST /v1/business-day/{date}/reopenудалён); инвариант «открыт ровно один день» — ограничение базы. Исправление ошибки закрытого дня — только встречной операцией текущим днём. При пустом справочнике рабочих дней учётная дата берётся в поясе программы.
Модель данных¶
Ключевые новые таблицы:
- Акции:
promotion_label+promotion_label_link+promotion_label_history,promotion_audience_condition+promotion_audience_ref,promotion_participation_limit, реестр условийcriterion_def+criterion_registry_revision, журнал согласованияapproval_event, снимки области списанияredemption_scope+redemption_scope_item. Условия акции хранятся вcriteria_json, колонкиscope_jsonи границ суммы чека удалены. - Конфигурация программы и уровни:
program_config,program_config_activation,program_config_promotion,program_config_redemption(+_item,_tier), уровень клиентаcustomer_tier, очередь событийtier_event_outbox. Уровниloyalty_tierпринадлежат конфигурации;customer_tier_progressудалена. - Покупки:
purchase,purchase_return,purchase_return_line, статистика переменных покупкиpurchase_attribute_daily+purchase_attribute_value. - Клиенты:
tag+customer_tag_history, версии настройки карточкиclient_profile_configuration(+ разделы, карточки и таблицы, столбцы, реестр ключей полейclient_profile_field_key),customer_status_history,customer_timezone; сегментыcustomer_segment,customer_segment_member(+_history),segment_fill(+_member). Колонкаidentity_profile.segmentsудалена. - Баллы и валюты:
points_subaccount(+points_subaccount_restriction), пары обменаexchange_pair(+_limit,_subaccount), задачи открытия счетовaccount_opening_run, базовый срокcurrency.points_lifetime_days, источник срока партииaccount_lot.expiry_source. Таблицаpoint_currencyудалена, слита в единый справочникcurrency(kindMONEY/POINTS, курс, базовая валюта). - Справочники:
city,store,merchant(MID и MCC перенесены изpartner),product_category,product_item. - Операции и платформа:
operation_batch(+_row,_chunk),command_inbox,outbound_notification,exchange_job+exchange_run,reconciliation_run+reconciliation_diff,audit_seal,siem_cursor, реестр сотрудниковapp_user. - Вовлечённость:
referral_link,achievement+customer_achievement(+_metric).
Производные и нечитаемые колонки удалены из схемы (в том числе account.balance и
chart_of_accounts.parent). Полный состав и ключевые поля — Модель данных.
Совместимость и обновление¶
BREAKING (внутренняя схема): переход леджера на партиционированную схему
Первичный ключ проводки леджера стал составным (posted_at, entry_id), таблица
партиционирована помесячно по времени проводки. На пустой базе миграция применяется
автоматически. На контуре с уже накопленными данными нужно до деплоя новой версии, в окно
обслуживания, выполнить deploy/sql/ledger-time-model-swap.sql — иначе старт приложения
останавливается. Подробности — Установка и развёртывание.
BREAKING (ролевая модель): запуск акции — отдельное право
POST /v1/promotions/{id}/activate и POST /v1/promotions/bulk-activate требуют роль
LOYALTY_PROMOTION_ACTIVATE независимо от того, включено ли согласование. До выкатки перезапустите
import.sh и выдайте новую роль тем, кто запускает акции, — иначе «Запустить» ответит
403 access_denied.
- Роли Keycloak. Число ролей
LOYALTY_*выросло (в 26.2.0 был 21, стало 54). Новые, среди прочих:LOYALTY_PROMOTION_ACTIVATE,LOYALTY_PROMOTION_APPROVE,LOYALTY_PROGRAM_CONFIG_*,LOYALTY_CLIENT_PROFILE_ACTIVATE/DELETE,LOYALTY_SEGMENT_*,LOYALTY_NOTIFICATION_*,LOYALTY_CITY_*,LOYALTY_STORE_*,LOYALTY_MERCHANT_*,LOYALTY_PRODUCT_CATALOG_*,LOYALTY_ACHIEVEMENT_*. Новые роли входят в группуLOYALTY_ADMINS, кроме техническихLOYALTY_INTEGRATION_READ,LOYALTY_EXCHANGE_READ/WRITE(их получают интеграционные клиенты) иLOYALTY_SEGMENT_INTAKE(только сервисная учётная запись системы, заливающей сегменты). На уже провижиненном realm заведение ролей требует повторного запускаimport.sh—helm upgradeроли не заводит. РольLOYALTY_CORRECTION_OTHER_WRITEпереименована вLOYALTY_OPERATION_REASON_OTHER_WRITE— старую роль нужно снять вручную, повторный импорт только заводит новую. Подробности — Ролевая модель. - Миграции применяются автоматически при старте. Перед обновлением контура с данными прочитайте «Что учесть при обновлении на текущую версию»: значения атрибутов клиента по ключам, которых нет в схеме полей, удаляются безвозвратно (проверочный запрос приведён там); пока в кластере живут поды прежней версии, часть запросов у них отвечает ошибкой на удалённых колонках; отдельные проверки данных откладываются до исправления; после выкладки идут разовые фоновые задачи (открытие счетов во всех включённых валютах). Существующая настройка карточки клиента становится активной версией, условия «Уровень клиента» существующих акций переписываются на конфигурацию «Основная».
- Бэкенд и веб-интерфейс выкатываются одной версией: интерфейс 26.3.0 читает новые форматы ответов
(постраничная история уровней, условия
criteria, вкладки продукта карточки клиента), а прежний интерфейс с новым бэкендом работает неверно. - PostgreSQL — 15 или новее: на этой версии работают стенды и интеграционные тесты, более ранние версии не проверяются.
- Настройки развёртывания (подробности — Конфигурация):
loyalty.promotion.budget.point-currencyиcustomer.default.currencyбольше не читаются — бюджет акций и счета новых клиентов считаются по валюте баллов программы (points.currency);- системные счета по умолчанию разведены (
MRK,RDM,BRK; переопределяютсяLOYALTY_ACCRUAL_SOURCE_ACCOUNT,LOYALTY_REDEMPTION_SINK_ACCOUNT,LOYALTY_EXPIRY_BREAKAGE_ACCOUNT); прежние проводки остаются наMRK; - часовой пояс программы (
LOYALTY_PROGRAM_TIMEZONE, только IANA) применяется сервером: расписания сгорания, предупреждений и выгрузок трактуются в нём, смена пояса применяется к расписаниям после перезапуска; пояс среды исполнения — UTC; - первое включение сгорания на контуре: включить предупреждения → выждать самый длинный шаг
LOYALTY_EXPIRY_NOTIFY_BEFORE→ пробный прогонdryRun=true→ задатьLOYALTY_EXPIRY_CRON; - пересмотр уровней (
LOYALTY_TIER_REVIEW_CRON) выключен по умолчанию: пока расписание не задано, уровни клиентов не понижаются и ручные назначения не снимаются; - отправка событий смены уровня и уведомлений клиентам выключена по умолчанию
(
LOYALTY_TIER_EVENTS_ENABLED,LOYALTY_NOTIFICATIONS_ENABLED) — события копятся в очереди исходящих; - согласование запуска акций включается
LOYALTY_PROMOTION_APPROVAL_ENABLEDбэкенда в паре сPROMOTION_APPROVAL_ENABLEDвеб-интерфейса.
- Интеграции. До обновления каналы покупок должны передавать код торговой точки, а система кампаний — заливать сегменты: до первой заливки сегментные акции не применяются ни к кому.
- Несовместимые изменения контракта API (BREAKING). Сверьте интеграции перед обновлением; полный
список с пояснениями — API Reference → «Существенные изменения контракта в
26.3.0», коды ошибок —
Коды ошибок:
- ошибки валидации —
400 validation_failedвместоbad_request, в том числе на точках POS, Promo, RT Flow и лент изменений; - обязательный код торговой точки в покупке; поле
segmentsв событии покупки игнорируется, из профиля клиента (identity) оно удалено; - акции:
criteriaвместоscope, парыlevelsв условии «Уровень клиента», обязательная награда механики и потолок бонуса только полемmaxBonusMinor, обязательныйpriority, полная замена тела приPUT, правка на месте только без применений, статусная модель (archived,SCHEDULED),budgetMinorв деньгах; - запуск акции — право
LOYALTY_PROMOTION_ACTIVATE; - уровни:
PUT/DELETE /v1/tiers/{code}удалены, у уровня нетmetric, история переходов — постранично; - клиенты: фильтр
tierвместоtierCode, атрибуты карточки — списки значений с проверкой по типу поля, API конфигуратора карточки требуетconfigurationIdиIf-Match, слияние в гонках отвечает404/409, недоступность MDM —503 mdm_unavailable; - баллы: базовый субсчёт (
isBase, безPOST .../default,subaccount_base_cannot_close), срок субсчётаexpiryAt, режим без своего срока — «Общий срок», срок у резерва по умолчанию, причина ручной операции — код справочника, единый ответ массовых операций,kindвGET /v1/currencies, прежний/v1/admin/point-currenciesповерх справочника валют; - партнёры:
merchantIds/mccудалены из контракта партнёра; - справочники: общие ручки по ключам
customer-segment,product-category,skuотвечают409 dictionary_own_screen; - мониторинг: списки в конверте
{items, page, size, total}; - платформа: ключ идемпотентности — только для своей операции (
422 idempotency_key_reused), переоткрытие операционного дня удалено. - спецификация: схема ответа журнала аудита
GET /v1/admin/auditназываетсяAuditEntryView(под именемAuditViewтеперь описана история изменений справочника) — клиенты, сгенерированные поopenapi.yml, нужно перегенерировать.
- ошибки валидации —
Открытые вопросы и известные несоответствия¶
- Мастер импорта клиентов в интерфейсе пока доходит только до шага «Проверка» — серверная проверка и сама загрузка CSV доступны через API, но не как последний шаг мастера в UI.
- Область списания баллов акции (
pointsRedemptionScope) задаётся только через API, а тело акции — полная замена: сохранение акции из интерфейса снимает область, заданную через API. - Бюджет акции в баллах: форма берёт курс первой включённой валюты баллов, а сервер считает расход
по курсу валюты баллов программы (
points.currency). Если балльных валют несколько, бюджет надёжнее задавать в рублях. - Пары обмена пока не применяются операцией обмена
POST /v1/exchange, хотя подсказка на вкладке «Обмен валют» описывает целевое поведение. - Итоги субсчёта при
expirySeparable = falseпоказывают «Сгорело за период» = 0 без пояснения. - Карточка клиента: история смены статуса, теги и флаг архива пока доступны только в API; признак чувствительности атрибута не маскирует значения.
- Экранов нет у справочников городов, торговых точек, мерчантов и каталога товаров, у очереди исходящих уведомлений и у отчёта о взаиморасчётах с партнёром — они доступны через API.
- Согласование запуска акции: e-mail-оповещений согласующих и выбора согласующих по именам в интерфейсе нет, включение — только настройкой развёртывания.
- Оповещение
LoyaltyExpiryStalledсчитается по каждому поду, а показатели прогона сгорания хранятся в памяти пода: оповещение срабатывает после каждой выкладки или перезапуска до первого прогона на этом поде и не гаснет у реплики, которой прогон не достаётся. - Переливка в DWH: поставляемый запрос вставки витрины
DWH_TRANSACTIONSссылается на прежние поляcustomerExternalIdиsubaccountCode— перед включениемLOYALTY_DTP_ENABLEDзадайте собственныйmerge-statementпод поля по ногам проводки. - Очередь событий смены уровня (
tier_event_outbox) не убирается и растёт, пока отправка событий выключена (LOYALTY_TIER_EVENTS_ENABLED=false). - Форма партнёра ещё показывает поля «MID» и «MCC», но бэкенд их больше не принимает: мерчанты
ведутся только через API
/v1/admin/merchants. - Валюту с субсчётом «Акционные баллы» (
PROMO) удалить нельзя: удаление отвечает409 currency_in_useс видом ссылкиSUBACCOUNTS, закрытый субсчёт тоже считается ссылкой. - Сертификат Apple Pass Type ID и ключ сервисного аккаунта Google для мобильного кошелька — секреты заказчика; до их подключения выпущенный пакет Apple Wallet структурно верен, но не подписан, а сохранение в Google Кошелёк не собирается вовсе.
26.2.0 (2026-07-05) — инженерный релиз качества¶
Функциональность 26.1.0 не менялась: релиз закрепляет качество кода и сборки по итогам двух архитектурных ревью (frontend-аудит и чек-лист backend-архитектора). Обратная совместимость полная, контракт API не тронут, миграций БД нет.
Состав релиза¶
| Область | Что вошло |
|---|---|
| Backend | Рефакторинг партнёрского пакета: константы вместо литералов, методы записей вместо статик-хелперов, устранение дублей и лишних запросов (identity-профиль и история — лениво). Поведение эквивалентно, интеграционные тесты зелёные |
| Frontend: архитектура | Типизированный runtime-конфиг, auth-bridge c одноразовым обновлением токена по 401, единый адаптер ошибок, Public API модулей, константы маршрутов MF-манифеста, раздельные локали ru/en, фабрики query-ключей |
| Frontend: сборка | Миграция CRA/CRACO → чистый Webpack 5 (контракт Module Federation remote сохранён 1:1), Jest-раннер с юнит-тестами хелперов, детерминированная установка (lock-файл), registry-токен вынесен из исходников в CI-переменную |
| Frontend: страницы | Пять крупных экранов разобраны на компонентную структуру (мастер акции, настройки ПЛ, карточка клиента, справочники, мониторинг) |
| Качество | Комплексный прогон: unit (backend полный + UI-хелперы), 17 функциональных API-сценариев, e2e по экранам — пройдено; подготовлен план нагрузочного тестирования |
Совместимость и обновление¶
- Обратно совместим с 26.1.0; ручных шагов нет (миграции БД в релизе отсутствуют).
- Для CI сборки UI теперь требуется переменная
NPM_AUTH(masked) вместо токена в Dockerfile.
26.1.0 (2026-07-03) — B2C-кабинет и партнёрские предложения¶
Второй функциональный выпуск. Открывает B2C-контур (личный кабинет клиента: API, SDK и референс-веб-кабинет — чтобы ритейлер собрал клиентский сайт или приложение поверх лояльности) и целиком закрывает эпик партнёрских предложений — совместные акции лояльности на продуктах партнёров (например, банк показывает в своём кабинете скидку партнёрского кафе), от настройки до финансовых взаиморасчётов. Доменное ядро (леджер, лоты, проводки, обмен) не менялось — обратная совместимость с 26.0.0 сохранена, миграции применяются автоматически.
Состав релиза по доменам¶
| Домен | Что входит в выпуск |
|---|---|
| Партнёрские предложения | Реестр партнёров (MID/MCC); офферы по четырём осям — доставка (промокод / расчёт кэшбэка), акцепт (акцептное / безакцептное), аудитория (все / сегмент), финансирование (партнёр / программа / совместно); экран администратора и витрина офферов в кабинете клиента |
| Card-linked начисление | Начисление кэшбэка по транзакциям у партнёра: резолв партнёра по коду/MID/MCC, подбор предложений, расчёт вознаграждения, лимиты и бюджет с авто-паузой, идемпотентность |
| Взаиморасчёты | Partner-funded вознаграждения отражаются на счёте партнёра в леджере; отчёт взаиморасчётов с партнёром (атрибуции и фактически профинансировано) |
| Личный кабинет (B2C) | Клиентский API /v1/b2c/me/**, JS/TS-SDK и референс web-кабинет: карта, баланс, уровень, история, доступные и партнёрские предложения |
| Акции | Наполняемый справочник механик (= диаграммы RT Flow); копия акции («Сохранить как») |
Новое в 26.1.0¶
- Партнёрские предложения. Экран «Администрирование → Партнёрские предложения»: реестр партнёров и
кампании по независимым осям — доставка
PROMOCODE | CALCULATED, акцептOPT_IN | AUTO, аудиторияALL | SEGMENT, финансированиеPARTNER | PROGRAM | SHARED. Жизненный цикл оффераDRAFT → ACTIVE → PAUSED → ARCHIVED; промокод виден клиенту только дляAUTOили после акцепта. Управление —/v1/partnersи/v1/partner-offers, ролиLOYALTY_PROMOTION_READ/WRITE. - Card-linked начисление кэшбэка.
POST /v1/integration/partner/transactions(рольLOYALTY_INTEGRATION_WRITE): транзакция клиента у партнёра резолвит партнёра по коду/MID/MCC, подбирает применимыеCALCULATED-офферы (период, аудитория, акцепт, минимальная сумма, «только новым»), считает вознаграждение (PERCENTс ограничением /POINTS/FIXED), проверяет лимит на клиента и бюджет с авто-паузой оффера. Начисление помечается источникомPARTNER. - Взаиморасчёты с партнёром. Ось финансирования определяет счёт-источник в леджере:
PARTNERиSHAREDдебетуют счёт партнёраPRT-<code>(статья плана счетовPRTF),PROGRAM— системный источник. ОтчётGET /v1/partners/{id}/settlementдаёт атрибуции по офферам, итоги по финансированию и фактически профинансировано (сумма дебет-проводок партнёра — основа для счёта). РольLOYALTY_PROMOTION_READ. - Личный кабинет клиента (B2C). Клиентский API
/v1/b2c/me/**с отдельной security-цепочкой (скоупloyalty.b2c; идентификатор клиента берётся из токена — доступ только к своим данным): карта, баланс, уровень, история операций, доступные акции и партнёрские предложения с активацией. JS/TS-SDK и референс web-кабинет для быстрой интеграции клиентского сайта/приложения. - Справочник механик = диаграммы RT Flow. Каталог механик стал наполняемым справочником, синхронизированным с диаграммами RT Flow (первая реализация — ведётся вручную); механика выбирается при создании акции.
- Копия акции. Кнопка «Сохранить как» создаёт независимую копию существующей акции.
Идемпотентность card-linked начислений¶
Ровно один раз — три слоя
Приём транзакции у партнёра защищён от повторов на трёх уровнях: (1) заголовок Idempotency-Key
на границе контроллера (по умолчанию — бизнес-ключ транзакции externalRef); (2) атрибуция
partner_offer_redemption с ограничением UNIQUE (offer, transaction_ref) — повтор той же
транзакции не даёт второго вознаграждения по офферу; (3) backstop ядра леджера
(ledger_posting_reference, ADR 0002). Ретраи вызывающей стороны безопасны.
Совместимость и обновление¶
- Обратно совместим с 26.0.0: доменное ядро не менялось, миграции БД (
partner,partner_offer,partner_offer_acceptance,partner_offer_redemption, статья плана счетовPRTF) применяются автоматически на старте сервиса — ручных шагов нет. - Промокоды остаются за CM Ocean Promo; партнёрские card-linked начисления — за Loyalty (источник
PARTNER). - Для B2C-кабинета нужен клиентский OAuth-скоуп
loyalty.b2cв Keycloak (клиентские токены отдельно от staff). Если B2C не разворачивается — на остальной функционал не влияет.
26.0.0 (2026-06-27) — первый публичный релиз¶
Первый публичный выпуск модуля CM Ocean Loyalty. Версия фиксирует продуктовый контракт API (плоские DTO, RFC-7807, идемпотентность) и собирает воедино функциональность по всем доменам. Доменный слой (леджер, лоты, проводки, обмен) сохраняет бизнес-логику начислений и списаний, выверенную в трёх волнах рефакторинга контракта.
Состав релиза по доменам¶
| Домен | Что входит в выпуск |
|---|---|
| Ядро-леджер | Двойная бухгалтерия над счетами, лотами и проводками; начисление, двухфазное и прямое списание, перевод, обмен, сгорание по срокам; идемпотентность денежных операций |
| Акции | Каталог механик, мастер создания, жизненный цикл, детерминированный арбитраж и версионирование, статистика; экспорт-импорт акций между контурами |
| Уровни | Каталог уровней, расчёт уровня по обороту за период; ручное назначение уровня и near-RT журнал переходов уровня |
| Клиенты | Карточка профиля (MDM identity + лоялти); правка и создание профиля клиента, слияние клиентов «1 телефон = 1 профиль» |
| Администрирование | Справочники (НСИ) — гибридная модель; операционная конфигурация; лимиты списания по разрезам |
| Безопасность | OAuth2 resource server (Keycloak, JWT), deny-by-default, роли LOYALTY_*, гейтинг прав в UI, сквозной журнал аудита |
Новое в 26.0.0¶
- Правка и создание профиля клиента. Запись «золотого» профиля в MDM через бэкенд
(
PUT /v1/customers/{externalId}/profile); в UI — действия «Редактировать» и «Новый клиент». Требует рольLOYALTY_CUSTOMER_WRITE. - Ручное назначение уровня. Override расчётного уровня с причиной и аудитом (
set_by/set_at) и бейджем «Назначен вручную»:PUT /v1/customers/{externalId}/tierиDELETE /v1/customers/{externalId}/tier. Near-RT журнал переходов уровня —GET /v1/customers/{externalId}/tier/transitions. Требует рольLOYALTY_TIER_WRITE. - Слияние клиентов «1 телефон = 1 профиль».
POST /v1/customers/{externalId}/mergeпереносит данные источника на выжившего клиента; источник переводится в статусMERGED. Guard на пересечение валют счетов даёт422. Требует рольLOYALTY_CUSTOMER_WRITE. - Лимиты списания по разрезам. Per-scope лимиты
TIER/CURRENCY/SEGMENTповерх глобальной политики погашения по правилу «частное бьёт общее»; нарушение отклоняетсяRedemptionGuardс кодом422. В UI — раздел «Настройка ПЛ» → «Лимиты по разрезам». Требует рольLOYALTY_DICTIONARY_WRITE. - Сквозной журнал аудита.
GET /v1/admin/auditфиксирует все write-операции, включая отклонённые (403/422/404). Требует рольLOYALTY_MONITORING_READ. - Экспорт-импорт акций между контурами. Перенос акций по натуральному коду, идемпотентно:
GET /v1/admin/promotions/exportиPOST /v1/admin/promotions/import; в UI «Акции» — кнопки «Экспорт» и «Импорт». Требует ролиLOYALTY_PROMOTION_READ/LOYALTY_PROMOTION_WRITE. - Гейтинг прав в UI (RBAC). Все write-кнопки в интерфейсе неактивны без соответствующей роли
LOYALTY_*_WRITE(с тултипом-подсказкой); итоговая проверка прав выполняется на бэкенде (403).
Контракт и доменный слой (выверено к релизу)¶
Сервис приведён к продуктовому контракту CM Ocean — выполнены три волны рефакторинга. Доменный слой (леджер, лоты, проводки, обмен) при этом не менялся: бизнес-логика начислений и списаний сохранена.
Леджер (двойная бухгалтерия)¶
- Балльный леджер на двойной бухгалтерии над счетами, лотами и проводками: операции пополнения
(
top-up), списания (withdraw), перевода (transfer) и обмена валют (exchange). - Идемпотентность денежных операций: заголовок
Idempotency-Keyи таблицаidempotency_recordдают гарантию «ровно один раз» на уровне БД — повтор того же ключа возвращает сохранённый результат, а операция выполняется однократно. - Деньги в API и ответах представлены в минорных единицах.
Защита денежных операций
Эндпоинты /v1/top-up, /v1/withdraw, /v1/transfer, /v1/exchange требуют заголовок
Idempotency-Key.
Контракт API¶
- BREAKING (API): ответы переведены с HATEOAS/HAL на плоские DTO и конверт коллекций
{items, page, size, total}. Убраны_embedded/_links; HAL-индекс (RootController) удалён — обзор API теперь через OpenAPI / Swagger UI. - Версионирование API: все контроллеры размещены под префиксом
/v1(снаружи —/api/v1). - Обработка ошибок по RFC-7807: ответ
application/problem+jsonсо стабильным кодом ошибкиcodeи идентификатором трассировкиtraceId. Бизнес-нарушения и «не найдено» теперь дают корректные коды4xxвместо500.
| Аспект контракта | Было | Стало |
|---|---|---|
| Формат ответа | HATEOAS / HAL (_embedded, _links) |
Плоские DTO + конверт {items, page, size, total} |
| Версионирование | — | Префикс /v1 |
| Ошибки | в т. ч. 500 на бизнес-нарушениях |
RFC-7807 problem+json, code + traceId, корректные 4xx |
| Деньги в ответах | — | Минорные единицы |
Безопасность¶
- OAuth2 resource server на Keycloak (JWT), модель deny-by-default.
- Ролевой доступ
@PreAuthorizeс ролямиLOYALTY_*; роли берутся изresource_access[...].rolesнастроенного клиента. - Секреты (пароль БД и пр.) подаются только из окружения; трассировка стека в ответах отключена
(
server.error.include-stacktrace: never).
Наблюдаемость и корреляция¶
- Корреляция запросов: фильтр проставляет
traceIdв MDC и возвращает эхо-заголовокX-Request-Id. - JSON-логи в прод-профиле (logback + logstash-encoder).
- Management-эндпоинты на отдельном порту 9090: метрики Prometheus (
/metrics), liveness / readiness-пробы (/health).
Схема данных и миграции¶
- Схема БД версионируется Liquibase и применяется приложением на старте; добавлено ограничение
UNIQUEна код валюты (currency.code). - На уже засеянных стендах baseline помечается как применённый (
MARK_RAN) через preconditions.
Исправление: применение миграций
В Spring Boot 4 автоконфигурация Liquibase переехала в модуль spring-boot-liquibase
(вместо liquibase-core). Подключён правильный модуль — миграции снова применяются на старте.
OpenAPI и качество¶
- Спецификация OpenAPI генерируется из кода (springdoc) и коммитится в репозиторий; на каждом
эндпоинте задан
operationIdвидаloyalty.<resource>.<action>. - Введён diff-gate (
OpenApiSpecIT): сборка падает при расхождении кода и зафиксированной спеки. - Тестовый харнес на Testcontainers (PostgreSQL); интеграционные тесты идемпотентности и diff-gate OpenAPI выполняются через Failsafe. CI больше не пропускает тесты.
Удалено¶
- Зависимости HATEOAS (
spring-boot-starter-hateoas,hal-explorer) и HAL-слой контроллеров.
В дорожной карте¶
Запланировано, ещё не реализовано
Перечисленные ниже возможности находятся в проработке и в текущем выпуске недоступны:
- Публикация изменений справочников и акций через outbox → Kafka в потоковый decisioning (SDE); кеш справочников в памяти реплик уже реализован.
- Механики акций как параметры — конфигурируемые шаблоны механик.
- Сертификаты — выпуск и погашение.