Skip to content

Repository files navigation

Контроль смен для сети магазинов (MAX)

Чат-бот в MAX и подключённое к нему мини-приложение для владельца небольшой сети магазинов одежды (3–15 точек). Хакатон MAX, трек «Эффективный бизнес».

Бот в MAX max.ru/t185_hakaton_max_bot
Мини-приложение открывается из чата с ботом, кнопка «Открыть приложение» (адрес https://task-controller.ru)
Собственный API https://task-controller.ru/api — описание в openapi.yaml, проверки в DATA-API.yaml
Тестовые данные backend/testdata/demo_network.json — все точки и люди выдуманы

Назначение

Кто пользователь. Владелец сети из нескольких магазинов, который не может каждый день быть в каждой точке, и продавцы-кассиры этих точек.

Проблема. Владелец узнаёт, открылся ли магазин вовремя, приняли ли поставку и закрыли ли кассу, только если сам позвонит или напишет в общий чат. Отчёты в чате теряются, фото приходят без привязки к задаче, а о сбое владелец узнаёт поздно — когда клиент уже увидел закрытую дверь.

Что делает решение.

  • Сотрудник отмечает задачи смены прямо в боте: кнопка у каждой задачи и фото-подтверждение. Отметить может только тот, кто по графику сейчас на смене.
  • Бот сам напоминает о сроках и сам сообщает владельцу о сбое: задача просрочена или её никто не взял.
  • Владелец в мини-приложении ведёт точки, сотрудников, задачи и график смен и видит сводку по всем магазинам за день: проблемные точки сверху.

В штатном режиме владелец не получает ни одного сообщения. Любое уведомление означает, что нужно вмешаться.

Основной пользовательский сценарий

Владелец

  1. Открывает бота, отправляет /start, нажимает «Я владелец». Бот создаёт сеть и присылает кнопку «Открыть приложение».
  2. В приложении, экран «Точки и сотрудники»: добавляет точку (часы работы, выходные) и сотрудника. Рядом с сотрудником появляется одноразовый код приглашения.
  3. Экран «Задачи точки»: добавляет задачи — ежедневные или разовые на дату. У задачи есть плановое время, время, с которого её можно отметить, допустимая задержка, нужно ли фото и нужно ли сначала взять её кнопкой «Беру».
  4. Экран «График смен»: расставляет смены на неделю, нажимает «Проверить покрытие» — приложение показывает часы, когда точка открыта, а на смене никого. Затем публикует неделю.
  5. Экран «Сводка»: видит все точки за день. Красные — есть просроченная или не взятая задача, жёлтые — задачи сделаны, но с опозданием, зелёные — всё в порядке. Из точки открывается карточка дня: кто и когда отметил каждую задачу, фото, кто сейчас на смене.

Сотрудник

  1. Открывает бота, нажимает «Я сотрудник» и присылает код от владельца. Бот отвечает: «Готово. Вы подключены к точке …».
  2. В начале смены бот присылает доску смены: список задач с временем и кнопку «Выполнить» у каждой.
  3. Перед задачей приходят напоминания: за 15 и за 5 минут до планового времени и за 15 и за 5 минут до срока (плановое время плюс допуск).
  4. Сотрудник нажимает «Выполнить», бот просит фото именно этой задачи, сотрудник присылает фото. Бот отвечает обновлённой доской, её же получают остальные на смене — чтобы никто не делал работу дважды.
  5. Если задача требует «Беру», бот спрашивает смену «Кто принимает?». Первый нажавший получает задачу, у остальных кнопка пропадает.
  6. После смены приходит итог: «Выполнено N из M» и что осталось не отмеченным.

Что получает владелец

  • Срок задачи прошёл, отметки нет — сообщение «Точка: задача не отмечена» с кнопкой, которая открывает карточку этой точки на этот день.
  • Задачу с «Беру» никто не взял к плановому времени — сообщение «никто не взял».
  • Просроченную задачу всё-таки выполнили — отдельное сообщение: когда, кто и на сколько опоздал.

Один аккаунт MAX может быть одновременно владельцем сети и сотрудником своей точки — как владелец небольшого магазина, который сам стоит за прилавком. После привязки по коду он отмечает задачи в боте, а приложение владельца остаётся доступным. Командой /role меняется только то, с чем бот работает в диалоге.

Состав и архитектура

Компонент Где Назначение
Бот backend/apps/bot (run_bot) Получает события MAX через Long Polling: роли, привязка по коду, отметки, фото, «Беру», «что осталось»
Планировщик backend/apps/bot/scheduler.py (run_scheduler) Каждые 30 секунд: доска в начале смены, напоминания, вопросы «Кто принимает?», просрочки, уведомления владельцу, итоги смен
Домен и модели backend/apps/core Точки, сотрудники, задачи, смены, отметки; право на отметку по смене; поиск незакрытых часов в графике; статус задачи
REST API backend/apps/api API мини-приложения: проверка подписи запуска MAX, доступ только к своей сети
Мини-приложение frontend React 19, MAX UI, MAX Bridge; 5 экранов владельца
База данных PostgreSQL 16 (SQLite при локальной разработке без Docker) Все данные решения
Хранилище фото volume media Фото-подтверждения задач
https Caddy (профиль prod) Сертификат и проксирование на сервере

Как связаны части. Бот и планировщик работают с базой через модели Django и с MAX через клиент Bot API. Мини-приложение ходит только в REST API. Django в одном контейнере отдаёт и API (/api/), и собранное мини-приложение (/), поэтому у приложения один https-адрес. Уведомления владельцу содержат ссылку startapp, которая открывает мини-приложение сразу на карточке нужной точки и дня.

Сотрудник ─── MAX ───► бот (run_bot) ──────┐
                        ▲                  ▼
                        └── планировщик ─► PostgreSQL ◄── REST API ◄── мини-приложение ◄── Владелец
                            (run_scheduler)                (Django)       (React, MAX Bridge)

Запуск одной командой (Docker)

cp .env.example .env      # Windows: copy .env.example .env
docker compose up --build

Поднимаются db, migrate (применяет миграции и завершается), web, bot, scheduler. Сборка занимает около 1–2 минут.

После запуска:

  • http://localhost:8000/api/health/ отвечает {"status":"ok"};
  • http://localhost:8000/ — мини-приложение. Вне MAX оно показывает «Откройте приложение из чата с ботом в MAX»: API пускает только запуски, подписанные MAX;
  • без BOT_TOKEN сервис bot завершается с ошибкой BOT_TOKEN is not set и перезапускается — это ожидаемо, остальное работает.

Важно для проверки. Если запустить локальную копию с тем же BOT_TOKEN, что и на сервере, два бота начнут перехватывать сообщения друг у друга, и работающий бот в MAX станет терять события. Для проверки в MAX пользуйтесь ботом на сервере, а локальный Docker запускайте без BOT_TOKEN.

Проверить API локально можно токеном проверки (см. «Проверка собственного API»): задайте REVIEW_API_TOKEN в .env, перезапустите и загрузите тестовые данные:

docker compose up -d --build
docker compose exec web python manage.py seed_demo --owner-max-id 900000001
curl -H "Authorization: Bearer <REVIEW_API_TOKEN>" http://localhost:8000/api/dashboard/

Параметры окружения

  • Docker с Compose v2; свободный порт 8000 (для профиля prod — 80 и 443).
  • Для работы бота — токен бота MAX (выдают организаторы хакатона).
  • Для открытия мини-приложения в MAX — публичный https-адрес, который организаторы привязывают к боту. Внутри Docker это не воспроизводится, поэтому для проверки в MAX используется развёрнутый сервер https://task-controller.ru.

Переменные окружения

Полный список с комментариями — в .env.example.

Переменная Назначение
BOT_TOKEN Токен бота (секрет, выдаёт организатор)
BOT_USERNAME Ник бота для ссылки на мини-приложение
MAX_API_BASE Адрес Bot API MAX (https://platform-api2.max.ru)
MAX_CA_BUNDLE Корневой сертификат Минцифры, которым подписан API MAX
DJANGO_DEBUG, DJANGO_SECRET_KEY Режим и секретный ключ Django (при DEBUG=0 ключ обязателен)
DJANGO_ALLOWED_HOSTS, DJANGO_CSRF_TRUSTED_ORIGINS Публичные хосты мини-приложения
FRAME_ANCESTORS Кто может встраивать мини-приложение во фрейм (веб-клиент MAX)
PUBLIC_BASE_URL, DOMAIN Публичный https-адрес и домен для Caddy на сервере
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT Доступ к БД; пустой POSTGRES_HOST включает SQLite
INIT_DATA_MAX_AGE_SECONDS Сколько действует подпись запуска мини-приложения
REVIEW_API_TOKEN, REVIEW_OWNER_MAX_ID Токен автоматической проверки API и MAX id тестового владельца. Пустой токен выключает этот вход
REMINDER_FIRST_MINUTES_BEFORE, REMINDER_FINAL_MINUTES_BEFORE За сколько минут напоминать (15 и 5)
CLAIM_ESCALATION_MINUTES_BEFORE За сколько минут повторить «никто не взял» смене (15)
PHOTO_WAIT_MINUTES Сколько ждать фото, прежде чем задача снова станет открытой (10)
SHIFT_BOUNDARY_TOLERANCE_MINUTES Допуск на границах смены для отметки (5)
SCHEDULER_INTERVAL_SECONDS, LOG_LEVEL Период планировщика (30 с) и уровень логов

Порты

Порт Сервис
8000 web: API, мини-приложение, админка Django (/admin/). Опубликован только на 127.0.0.1
80, 443 Caddy, только в профиле prod на сервере
5432 PostgreSQL, только внутри сети Docker

Зависимости

  • Python 3.13: backend/requirements.txt — Django 5.2 LTS, Django REST framework, httpx, psycopg, WhiteNoise, gunicorn. Версии зафиксированы. Корневой requirements.txt ссылается на него.
  • Node 22: frontend/package.json и package-lock.json — React 19.2.8, @maxhub/max-ui, Vite. Версии зафиксированы.
  • Все библиотеки с открытыми лицензиями.

Внешние сервисы и интеграции

  • MAX Bot API (platform-api2.max.ru): получение событий бота (Long Polling), отправка и правка сообщений, ответы на кнопки, загрузка фото. Сертификат API подписан корневым сертификатом Минцифры — он лежит в certs/ и подключается в клиенте бота.
  • MAX Bridge (st.max.ru/js/max-web-app.js): подпись запуска мини-приложения (initData), системная кнопка «Назад», параметр startapp для открытия карточки точки из уведомления.

Других интеграций нет: касса, учётные системы и геолокация в MVP не подключаются.

Работа с данными

  • Все данные вводит владелец в мини-приложении или сотрудник в боте: точки, сотрудники, задачи, график, отметки, фото.
  • Владелец видит только свою сеть: чужая точка, сотрудник или фото отвечают 404. Фото отдаются только через API владельцу этой сети.
  • Уволенный сотрудник теряет доступ к отметкам, его будущие смены удаляются, а история отметок остаётся: «Отметил Пётр С.» не пропадает из прошлых дней.
  • Итоги прошедших задач не пересчитываются: если после опоздания владелец увеличил допуск, прошлый день всё равно покажет опоздание.
  • Из персональных данных хранятся только MAX id, имя из MAX и имя сотрудника, которое ввёл владелец.

Тестовые данные

Все точки, адреса и люди в тестовых данных выдуманы; к реальному бизнесу они не относятся.

Данные описаны в backend/testdata/demo_network.json: три точки, семь сотрудников и один уволенный, четыре задачи на каждую точку, график на две недели (текущая опубликована, следующая — черновик с одним незакрытым окном, которое находит проверка покрытия). Сегодняшний день первой точки выглядит как типичный проблемный день: открытие с опозданием на 40 минут, подготовка зала вовремя, поставку никто не взял.

Загрузка (повторный запуск безопасен, --reset сначала удаляет прежние демо-данные):

docker compose exec web python manage.py seed_demo --owner-max-id <MAX id владельца>

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

Пошаговый сценарий проверки

Проверяющий сам создаёт точку и сотрудника, поэтому готовые коды и демо-данные не нужны: код сотрудника появляется в приложении на шаге 4.

  • Два аккаунта MAX — самый наглядный вариант: первый владелец, второй сотрудник.
  • Один аккаунт тоже подходит: на шаге 6 переключитесь в «Я сотрудник» и отправьте свой же код. Дальше роль переключать не нужно — приложение владельца открывается кнопкой «Открыть приложение» из чата, а фото и «что осталось» бот принимает. Уведомления владельцу придут в тот же чат.
# Действие Ожидаемый результат
1 Открыть бота, отправить /start, нажать «Я владелец» «Вы владелец. Настройки и сводка по точкам — в приложении.» и кнопка «Открыть приложение»
2 Открыть приложение Экран «Сводка», пока без точек, с кнопкой «Добавить точку»
3 «Точки и сотрудники» → «Добавить точку»: название, часы работы, выходные Точка в списке, под ней часы работы и выходные, например «10–22 · без выходных»
4 «Добавить сотрудника» в этой точке Сотрудник со статусом «ждёт привязки» и кодом из 8 символов
5 Открыть точку → «Задачи точки» → «Добавить разовую задачу на дату»: сегодня, плановое время через 20–30 минут, с фото Задача в списке; в карточке дня — «Ожидается»
6 Со второго аккаунта: /start → «Я сотрудник» → отправить код из шага 4. С одного аккаунта: /role → «Я сотрудник» → код «Готово. Вы подключены к точке …»; в приложении статус сотрудника меняется на «в боте»
7 Владелец: «График смен» → смена этому сотруднику на сегодня, которая уже идёт → «Проверить покрытие» → «Опубликовать» Неделя опубликована; незакрытые часы подсвечены
8 Подождать до 30 секунд Сотруднику приходит «Смена началась» со списком задач и кнопкой «Выполнить · ЧЧ:ММ название»
9 Сотрудник: «Выполнить» → прислать фото «Фото принято, «…» отмечено в ЧЧ:ММ, вовремя» и обновлённая доска; в карточке дня у задачи — кто, когда и «Посмотреть фото»
10 Сотрудник: написать «что осталось» Текущая доска смены
11 Владелец: добавить ещё одну разовую задачу на сегодня через 2–3 минуты с допуском 0; сотрудник её не отмечает Когда срок пройдёт, в течение 30 секунд владельцу приходит «Точка: задача «…» не отмечена» с кнопкой в карточку точки; в сводке точка красная
12 Сотрудник отмечает эту задачу Владельцу приходит «задача выполнена в …, с опозданием на N минут. Кто отметил: …»; точка в сводке становится жёлтой

Примеры ожидаемого поведения

Ситуация Что происходит
Сотрудник нажимает «Выполнить» в свой выходной «Сегодня у вас выходной. Ближайшая смена — …» — отметка не принимается
Нажал раньше, чем задачу можно отмечать Бот называет время, с которого можно отметить
Два сотрудника одной смены После отметки одного второй получает обновлённый список, кнопка выполненной задачи пропадает
Задача с «Беру» Смене приходит «Кто принимает?»; первый нажавший получает задачу, у остальных вопрос меняется на «… выполняет Имя»; за 15 минут до планового времени, если никто не взял, — повтор, к плановому времени — сообщение владельцу
Нажал «Выполнить», но не прислал фото за 10 минут Задача снова открыта для отметки
Владелец добавил или удалил задачу посреди смены Смене приходит «Список задач изменился» с новым списком
В графике есть часы без людей «Проверить покрытие» показывает окна по дням; выходные дни точки не считаются окнами
Владелец пытается изменить прошедшую задачу «Итог этой задачи уже записан и не меняется» — история не переписывается
API получает неверные данные 400 и понятный текст в detail, например «Время указано неверно, ожидается ЧЧ:ММ»
Запрос к чужой точке 404 «Точка не найдена»

Проверка собственного API

  • Адрес: https://task-controller.ru/api.
  • Контракт: openapi.yaml (OpenAPI 3.1).
  • Обязательные проверки: DATA-API.yaml (DATA-API 1.0, проходит валидатор организаторов). Сценарий: сводка, точки, карточка дня, добавление сотрудника и задачи, правка задачи, неверный ввод, график и проверка покрытия. Всё созданное удаляется в cleanup.
  • Роль owner: заголовок Authorization: Bearer <REVIEW_API_TOKEN>. Токен передаётся организаторам отдельно, в репозитории его нет. Запросы идут от имени тестового владельца с отдельной сетью — реальные данные ему недоступны.
  • Мини-приложение авторизуется иначе: заголовком X-Max-Init-Data с подписанной MAX строкой запуска.

Известные ограничения

  • Бот получает события через Long Polling. События, пришедшие, пока бот перезапускается, могут потеряться. Для промышленного запуска MAX рекомендует Webhook.
  • Все точки работают в часовом поясе Europe/Moscow: поле часового пояса есть в модели, но в приложении не редактируется.
  • Смена не может переходить через полночь: самое позднее окончание — 24:00 (хранится как 23:59).
  • Фото хранятся в volume сервера, без внешнего хранилища и без срока хранения.
  • Касса, учётные системы, геопривязка отметки и автоматическое построение графика в MVP не реализованы.
  • Мини-приложение рассчитано на владельца. Всё, что делает сотрудник, происходит в боте.
  • Демо-данные выдуманы (см. «Тестовые данные»).

Остановка и повторный запуск

docker compose down        # остановить, данные БД и фото сохраняются
docker compose up -d       # запустить снова
docker compose down -v     # остановить и удалить данные БД и фото

Деплой на сервер (Ubuntu, https)

Нужны сервер с Ubuntu 24.04 (от 2 ГБ RAM), открытые порты 22, 80 и 443 и домен, A-запись которого указывает на IP сервера. Сертификат получает и продлевает Caddy.

git clone https://github.com/Shipovmax/hackathon_max.git
cd hackathon_max
bash deploy/server-setup.sh     # Docker, Compose и swap; после него перезайти по SSH
cp .env.example .env && nano .env

Значения для сервера:

BOT_TOKEN=<токен бота>
DJANGO_DEBUG=0
DJANGO_SECRET_KEY=<python3 -c "import secrets; print(secrets.token_urlsafe(50))">
DJANGO_ALLOWED_HOSTS=<домен>,localhost,127.0.0.1
DJANGO_CSRF_TRUSTED_ORIGINS=https://<домен>
DOMAIN=<домен>
PUBLIC_BASE_URL=https://<домен>
POSTGRES_PASSWORD=<длинный случайный пароль>
REVIEW_API_TOKEN=<python3 -c "import secrets; print(secrets.token_urlsafe(32))">
docker compose --profile prod up -d --build
curl https://<домен>/api/health/                 # {"status":"ok"}
docker compose logs -f bot                       # в логе: polling as @...

Обновление: git pull && docker compose --profile prod up -d --build (миграции применяет сервис migrate). Смена домена: bash deploy/switch-domain.sh <домен>. Если Docker Hub недоступен, добавьте зеркало {"registry-mirrors": ["https://mirror.gcr.io"]} в /etc/docker/daemon.json и перезапустите Docker.

Структура репозитория

backend/          Django: apps/core (модели, домен), apps/api (REST), apps/bot (бот, планировщик)
backend/testdata/ тестовые данные (JSON)
frontend/         мини-приложение: React + MAX UI + MAX Bridge
certs/            корневой сертификат для API MAX
deploy/           Caddyfile (https), настройка сервера, смена домена
openapi.yaml      контракт собственного API
DATA-API.yaml     обязательные проверки API для платформы оценки
Dockerfile, compose.yaml, .env.example, .dockerignore

About

Smart shift and task control platform for retail networks. Automates daily store operations, validates employee coverage, and notifies owners about missed or delayed tasks through a MAX bot and connected mini-app.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages