HoReCa и еда
Сентябрь 2026
Dolina Coffee это сеть кофеен. Бонусы гостей живут в iiko: касса считает баллы, кассир видит карту, гость получает кэшбек. Программа работает, но существует она только внутри кассы.
Гость вне кофейни не знает ничего. Сколько у него баллов, какой у него статус, когда он был в последний раз. Чтобы узнать баланс, нужно прийти и спросить на кассе.
У сети та же слепота с другой стороны. Менеджер видит выручку и чеки, а вот кто из постоянных гостей перестал ходить, уже нет. Гость просто исчезает, и заметить это некому.
Нам заказали три вещи сразу: приложение гостя с картой и балансом, инструмент менеджера для работы с уходящими гостями и бэкенд, связавший это с iiko.
Главное условие поставил клиент. Источником истины по баллам остаётся iiko. Приложение показывает баланс и инициирует операции, но само ничего не считает: расхождение между приложением и кассой обошлось бы дороже любого удобства.
Собрали три независимых продукта с общим бэкендом:
PWA гостя: вход через VK ID, карта с QR, баланс, история, уровни, дистанционный заказ.
Бэкенд: прокси к iiko, собственный журнал операций, вебхуки, ночная синхронизация, аналитика.
Админ-панель: задания менеджерам, недельная аналитика, OLAP-отчёты, выгрузки.
Импорт исторических данных из iikoWeb со сведением личности гостя.
Алгоритм поиска гостей под риском по их личному ритму визитов.
Гость заходит через VK ID: один тап, PKCE, секрет в браузере не нужен. VK возвращает код и идентификатор устройства, бэкенд меняет их на номер телефона, по номеру ищется карта в iiko.
Дальше отдельное решение. Регистрацию карты мы в приложение не понесли. Незарегистрированный гость видит экран «приходите в кофейню».
Соблазн был обратный: дать регистрацию онлайн и собрать больше карт. Но карта в iiko заводится на кассе вместе с первым чеком, и карта, созданная в приложении без визита, породила бы дубли и пустые записи. Порядок регистрации мы оставили кассе, а приложение подключается к уже существующей карте.

Главный экран это карта лояльности. QR с треком карты, кассир сканирует его в iikoFront. Рядом баланс, размер кэшбека, условия программы.
Вёрстка подстраивается по высоте экрана: QR остаётся квадратным и не уезжает за границу на коротких дисплеях. Мелочь, но приложение у кассы открывают ради одного только QR, и он обязан быть виден целиком.
Уровней три. СТАНДАРТ с серебряной обводкой аватара есть у всех. ПРЕТЕНДЕНТ НА ПРО с бронзовой обводкой даётся за 7 визитов в течение 30 дней и держится до конца текущего месяца и весь следующий. ПРО с золотой обводкой даёт кэшбек 15% и остаётся навсегда.

В момент первого набора семи визитов гость один раз видит модальное окно: статус уже есть, но за ПРО нужно прийти в кофейню, категорию ставят на кассе. Так приложение не обещает того, чего не может выдать.
iikoCloud отдаёт баланс, но не отдаёт историю транзакций. А профиль гость открывает именно ради истории.
Пришлось вести собственный журнал: он наполняется вебхуками iiko и нашими же вызовами пополнения и списания. Хранилище простое, файл на томе Docker, потому что источник истины всё равно в iiko, а журнал играет роль витрины.
Журнал решает задачу вперёд. Но вебхуки копят данные только с момента подключения, а список прошлых заказов через Transport API получить нельзя. Сеть работала годы, и вся эта история осталась бы за бортом.
Историю залили импортом «Журнала операций» из iikoWeb в формате xlsx. Там своя сложность: гость в выгрузке известен только по номеру телефона, без идентификатора iiko.
Решение неочевидное, зато аналитика заработала сразу на исторических данных. Иначе их пришлось бы копить месяцами.
Push-вебхуки iiko оказались ненадёжными: часть событий не доходит. Строить недельную аналитику на потоке с пропусками нельзя.
Поэтому каждую ночь в 03:15 крон подтягивает чеки известных гостей за две последние недели и пересчитывает недельные окна. Пропущенное вебхуком восстанавливается на следующую ночь.
Пул запросов намеренно щадящий. Резкий всплеск обращений к iiko приводит к бану IP, и тогда встаёт весь сервис. Скорость синхронизации здесь стоит дешевле доступа.
У iiko два разных флоу получения токена: обычный ресторанный ключ и partner-доступ через marketplace. Какой достанется на конкретном объекте, заранее неизвестно.
Клиент к iiko поддерживает оба и выбирает по набору переменных окружения. Токен кэшируется на час и обновляется сам. Переезд между типами доступа не требует правок в коде.
Самая содержательная часть проекта это правило отбора гостей для звонка.
Очевидный подход: сравнить эту неделю с прошлой и взять тех, у кого визитов стало меньше. Мы от него отказались. У нерегулярного гостя переход с трёх визитов на два это шум и регрессия к среднему, отток тут ни при чём. Список из такого сравнения состоит в основном из людей без всяких проблем.
Сравниваем другое: насколько долго гость молчит относительно своего обычного интервала между визитами.
Алгоритм считает по каждому гостю медианный разрыв между визитами за 90 дней. Затем смотрит, сколько дней прошло с последнего визита. Гость попадает в группу риска, если молчит дольше полутора своих обычных интервалов. Отдельный пол в четыре дня защищает ежедневных гостей: человека, заходящего каждое утро, не дёргают из-за пары пропущенных дней.
Гости с числом визитов меньше четырёх за окно отсеиваются: ритма у них нет, оценивать нечего.
Отдельно решается когорта. Её берут по обычной частоте гостя, минуя просевший счётчик визитов. Иначе затихший завсегдатай утёк бы в мелкий сегмент из-за самого факта тишины. То есть выпал бы из списка ровно тогда, когда он там нужнее всего.
У менеджера ограниченное время, поэтому список обрезается восьмьюдесятью задачами в неделю. Сортировка идёт по тратам гостя за окно: ограниченную ёмкость отдаём самым ценным.
Кулдаун считается по факту взаимодействия. Позвонили гостю, пауза две недели. Показали в списке, но до звонка не дошло, пауза неделю. Так один и тот же человек не попадает в обзвон каждую неделю, а поток задач ровно распределяется.
К каждой задаче прикладываются любимый напиток и блюдо гостя. Они собираются автоматически из OLAP-отчёта «кто что купил». Менеджеру важно, с чем звонить, и «ваш латте и círка» работает лучше, чем «мы заметили, что вы давно не заходили».
Причина звонка формулируется человеческим языком: «Обычно заходит раз в 5 дн., не был уже 9 дн.». Для ежедневных гостей формулировка меняется на «Заходил почти каждый день».
Правило поиска кандидатов вынесено в отдельный модуль без единого обращения к базе: на вход карта визитов и карта трат, на выходе список кандидатов.
Это позволяет гонять один и тот же код и в проде, и в офлайн-диагностике на копии данных. Проверка гипотезы о настройках отбора не требует трогать рабочую систему, а результат проверки гарантированно совпадает с поведением прода.
Гости, чеки и средний чек разложены по сегментам частоты визитов: 7+, 6, 5, 4, 3, 2, 1. На графике шесть недель, текущая выделена, пять прошлых серые.
Неделя выбирается барабаном. Недели без данных и будущие недоступны: выбрать пустой период просто нельзя.
Панель заказа собрана целиком: любимые позиции гостя сверху, ниже меню по категориям, корзина внизу. Работает через внешнее меню iiko.
Реальное создание заказа закрыто флагом и ждёт подключения онлайн-оплаты. Пока панель работает в режиме заглушки. Мы сознательно не стали выкатывать половину сценария: заказ, обрывающийся на оплате, хуже отсутствия заказа.

Dolina Coffee получила программу лояльности с приложением для гостя и инструментом для менеджера. Учёт баллов остался в iiko, расхождений между кассой и приложением нет по построению.
Гость видит баланс, статус, историю и QR для кассы. Менеджер получает недельный список гостей под риском с причиной звонка и любимыми позициями. Аналитика по сегментам частоты визитов строится на полной истории, включая период до запуска приложения.
Собственная разработка заняла около трёх недель от первого коммита до продакшена.