Экраны нарисованы «как влитые» в реальный batele_vendor_flutter (ветка development, монохромный ребрендинг):
шрифт Inter, палитра из palette.dart (бренд/акцент = чёрный #222222, без фиолетового),
белый AppBar с заголовком слева, нижняя навигация из 4 вкладок (Заказы · Доставки · Каталог · Настройки),
карточки с рамкой 1.5px rgba(0,0,0,.08) и радиусом 16. Раздел живёт под вкладкой «Настройки».
Названия полей и enum — из DTO модуля vendor-api/loyalty; суммы в сомах, в API — *InCents (тыйын).
Права: vendor.loyalty.view / vendor.loyalty.manage.
Полная логика — в десктоп-прототипе.
Раздел встраивается в список «Настройки» (плитка после «Клиенты»). На главном экране — статус (isEnabled),
режим (mode: скидка / кешбек — один на заведение), список уровней. Кнопки управления — только при vendor.loyalty.manage.
modewindowMonths6 месbenefitPercentmanageВыберите режим (скидка или кешбек) и добавьте уровни — клиенты начнут копить выгоду в вашем заведении.
settings = nullСоздание/правка уровня — модальный нижний лист с drag-handle. Поля из create-loyalty-level:
name, thresholdInCents, benefitPercent (1–100). Тип выгоды не задаётся — наследуется из режима заведения (mode).
namethresholdInCentsbenefitPercentПороги не пересекаются, ровно один уровень с порогом 0 (базовый), процент 1–100.
create-loyalty-levelСписок с фильтром по computedStatus (хранится active/paused; scheduled/expired/exhausted — вычисляются).
Создание — bottom-sheet по create-promocode. Аналитика — из promocode-analytics.
computedStatus · usageCountcode
benefitTypebenefitValuestartsAtendsAtminOrderInCents · опц.Лимиты и мин. заказ можно оставить пустыми (∞ / без минимума). Пустой код → сервер сгенерирует 8-символьный [A-Z0-9].
create-promocodeordersSumInCentsbenefitSumInCentsbyDaynewClients
34 существующих · existingClients
promocode-analyticsАкция = условие «заказ от суммы → выгода». Поля из create-promotion:
name, description, minOrderInCents, benefitType + benefitValue, период.
Применяется автоматически в корзине. Комбо / спеццена наборов — на стороне каталога, не в этом модуле.
computedStatus): active / paused / scheduled / expired.namedescription · опц.minOrderInCentsbenefitTypebenefitValuestatusstartsAtendsAtcreate-promotionРасширяет существующий экран client_loyalty.dart (карточка со статами «Заказов / Средний чек» и «Является клиентом с»):
ниже добавляются уровень (levelName/levelPercent), кешбек-баланс (cashbackBalanceInCents),
прогресс (amountToNextLevelInCents) и история операций (history) — в той же карточной вёрстке.
amountToNextLevelInCentshistoryreversesEntryIdclient_loyalty.dartВ карточке заказа — блок «Выгоды»: уровень клиента на момент заказа и каждая применённая выгода отдельной строкой (акция, промокод, скидка уровня, оплата кешбеком, начисленный кешбек).
С правом vendor.loyalty.view экраны read-only — кнопки создания и «+» скрыты; управление — только с
vendor.loyalty.manage. Ошибки применения промокода/кешбека приходят с ключами errors.loyalty.* (тексты из i18n ru.json).
vendor.loyalty.manage.ru.jsonКлючевые правила модели. Развёрнутая логика, edge cases, порядок применения выгод и открытые вопросы — в десктоп-прототипе (раздел «Логика и правила»).
У клиента отдельный профиль лояльности в каждом заведении: уровень, траты, кешбек, промокоды и акции границу вендора не пересекают.
mode = скидка либо кешбек, общий для всех уровней. Уровень задаёт порог трат (thresholdInCents) и процент (benefitPercent) в скользящем окне windowMonths.
Баланс кешбека — в микросервисе финансов, начисляется после успешного заказа, идемпотентно по заказу. Оплата кешбеком — не более cashbackPaymentLimitPercent от суммы.
Хранится только active/paused; computedStatus промокода — active / paused / scheduled / expired / exhausted, акции — без exhausted.
Комбо (каталог) → акция (условие «от суммы») → промокод → скидка уровня → оплата кешбеком → начисление кешбека.
vendor.loyalty.view — просмотр (кнопки скрыты), vendor.loyalty.manage — создание/правка/удаление уровней, промокодов и акций.
batele_vendor_flutter (ветка development) и DTO модуля vendor-api/loyalty.