Веб-приложение для заказа из ресторана по QR-коду

Общие положения по работе веб-приложения

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

Все изображения приведены для примера и не содержат обязательных требований, если иное не отражено в тексте ТЗ.

Описание требований к программному обеспечению

  • Программное обеспечение должно работать на WEB платформе
  • ПО должно легко масштабироваться и принимать нужный формат исходя из разрешения экрана и особенностей мобильного телефона (браузеров)
  • Серверное программное обеспечение должно обрабатывать до 200 заказов единовременно
  • Оплата заказа должна производиться сразу, после оформления заказа. Оплата производится через систему ЮKassa
  • Фискальный чек выдается системой ЮKassa в электронном виде
  • Применение корпоративных скидок будет осуществляться на базе существующих разработок, идентификация клиента будет производиться по номеру телефона
  • В веб-приложении будет задействовано актуальное меню ресторана, но в меню могут быть позиции, которые нельзя будет заказать через веб-приложение.

Принципиальная схема работы

  1. Сканирование QR-кода
  2. Выбор блюд из меню
  3. Проверка и корректировка заказа
  4. Регистрация клиента (сотрудника). Если не был ранее сохранён в приложении
  5. Расчёт всех скидок, согласно настройкам в системе Юпитер
  6. Оплата заказа (ЮKassa)
  7. Создание заказа в системе Юпитер и отправка заказа на выполнение
  8. Завершение работы приложения

Архитектура системы

  • Сервер ресторана (сервер Юпитер)
    • Расположен непосредственно в ресторане
    • Имеет доступы ко всем принтерам в ресторане
    • Имеет белый ip для доступа из сети Internet
    • Протокол HTTP
    • Порт 7788
  • Веб-сервер
    • Размещается на хостинге
    • Доменное имя любое 
    • Протокол HTTPS
  • Веб-приложение
    • Открывается на телефоне клиента через сканирование QR-кода.
    • Отображает меню, позволяет оформить заказ
    • Позволяет получить скидки (в т.ч. корпоративную скидку сотрудника)
    • Позволяет оплатить заказ
    • Передаёт заказ на выполнение в систему Юпитер.

Меню

Структура меню

  • Меню представляет собой список групп и вложенных в них товаров (группа - товар). Вложенность групп друг в друга не предусмотрена
  • Один товар может относится только к одной группе
  • У товаров могут быть модификаторы (товары, дополняющие базовый товар – например состав бизнес-ланча)
    • Модификаторы не имеют цены (на первом этапе разработки, в дальнейшем можно будет добавить эту логику работы в рамках отдельной задачи)
    • Модификаторы сгруппированы по группам модификаторов внутри базового товара (например, для бизнес-ланча: «Салат», «Первое блюдо», «Горячее», «Напиток»)
    • Из каждой группы модификаторов можно выбрать только один модификатор, т.е. нельзя выбрать два супа или два напитка
    • Группы модификаторов могут быть
      • Обязательные - в которых необходимо сделать какой-то выбор, чтобы добавить бизнес-ланч в корзину.
      • Необязательные - где выбор не обязателен
      • В рамках данного ТЗ все группы модификаторов считаются обязательными
    • Товары с модификаторами могут быть в любой группе. Для удобства пользователей первая отображаемая группа может быть названа «Бизнес-ланч» и содержать все действующие сегодня бизнес-ланчи. Бизнес-ланчей в рамках одного дня может быть несколько – за разную стоимость и с разным составом.

Обновление меню

  • Меню приложения автоматически обновляется Юпитером каждый час, чтобы учесть возможные автоматические изменения меню по расписанию.
  • При необходимости, меню в приложении также можно обновить вручную из складской части Юпитера на сервере ресторана
  • Меню веб-приложения формируется по тем же раскладкам меню, которые используется на кассе в этом ресторане
  • У пользователя есть возможность отключить передачу отдельных кнопок раскладки в веб-приложение, если какие-то блюда не должны быть в меню приложения.
  • Иерархия раскладок принудительно приводится к двухуровневой структуре (группа - товар)
  • Если товар присутствует на нескольких раскладках, то в веб-приложение он будет передан только по первой раскладке
  • Если на самом первом уровне меню будут занесены товары, они будут игнорироваться (на первом уровне должны быть группы товаров, а не сами товары)
  • Список товаров для меню формируется с учётом расписания замен раскладок
      • При настройке расписания замен нужно учитывать, что меню в веб-приложении обновляется 1 раз в час 
  • Меню для приложения берётся с текущего рабочего меню кассы, которое появляется в системе после выполнения действия "Обновить меню на кассах". Т.о. можно внести изменения в меню и они не будут переданы на приложение, пока меню не будет актуализировано для ресторана в целом. 

Фотографии

  • В качестве фотографий в веб-приложении используются картинки заданные на кнопках в раскладках меню. Те же, что и для кассы
  • Картинки товаров привязываются к товарам в системе Юпитер и хранятся в специальной папке со специальными именами.
  • Для обновления фотографий в веб-приложении, требуется вручную загрузить новые файлы из этой папки на сервер веб-приложения (через техподдержку). Предполагается, что фотографии блюд меняются достаточно редко. В дальнейшем можно будет реализовать автоматическую загрузку (в рамках отдельной задачи).

Стоп-лист

  • Стоп-лист формируют на кассе вручную
  • Меню доступное в веб-приложении формируется с учётом текущего стоп-листа
  • Если товар добавят в стоп-лист в момент, когда клиент уже начал оформлять заказ, то этот товар будет доступно для заказа. При переходе в режим оплаты клиенту будет выведено сообщение о том, что товар из корзины более не доступен для заказа
  • На кассе могут указать "Оставшееся кол-во" для одного или нескольких товаров. Когда "Оставшееся кол-во" будет распродано, товар автоматически добавится в стоп-лист
    • Веб-приложение не контролирует "Оставшееся кол-во" в процессе ввода заказа, контроль будет происходить при переходе в режим оплаты
      • Если клиент заказал больше чем "Оставшееся кол-во" у одного или нескольких товаров, ему будет выведено сообщение с наименованиями этих товаров
    • В конце рабочего дня все оставшееся кол-во для всех товаров обнуляется

Маркированный и подакцизный товар (Честный знак, ЕГАИС)

  • В программе должна быть возможность исполнения требований государства по работе с кодами маркировки (КМ) и  акцизными марками (АМ) в заказах оформленных в веб-приложении
  • Заказы, в которых есть алкоголь или маркированные товары, не должны закрываться пока в них не будут считаны все КМ или АМ

Корпоративные клиенты и скидки

    • Основное принцип – Юпитер должен применить к заказу с веб-приложения все те же скидки, как будто бы этот заказ оформили на станции кассира
      • Все автоматические скидки
        • Настройка "Когда применять автоматическую скидку" игнорируется
        • Если используются скидки по расписанию, то "Способ определения скидки" должен быть всегда "Время открытия заказа"
      • Скидки клиента. Если клиент определен
      • Подарочные товары не применяются
      • Бонусная система (кэшбек) через веб-приложение может работать только в режиме начисления бонусов. Потратить бонусы пока можно только через официанта
    • «Корпоративные клиенты» — это скидка для действующих сотрудников, которая уже внедрена и работает по алгоритму (например, скидка 90% от базовой ставки 500 рублей). Базы данных, структура обмена и отчетов остаются прежними. Идентификация клиента в корпоративной скидке будет являться номер телефона клиента, который он зафиксировал в договоре о корпоративном питании.

Кухня (взаимодействие) 

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

Общий зал и VIP зал (взаимодействие)

В общем зале, банкетном зале буду стоят дополнительные моноблоки для оформления заказов официантами вручную, которые будут автоматически уходить на кухню или в бар, в зависимости от состава. (Аналог работы кафе «Чайка»).

Вопросы безопасности

  • Чтобы не могли получить скидку за счёт другого клиента.
    • Для этого авторизация клиента происходит через SMS с проверочным кодом, чтобы никто не мог воспользоваться тем, что знает номер телефона другого клиента
      • SMS отправляется через сервис https://stream-telecom.ru/
      • Проверочный код состоит из 5 цифр
      • Для защиты от подбора проверочного кода сервер позволяет только три попытки его ввода
      • Срок жизни проверочного кода 3 минуты. Т.е. после запроса проверочного кода, у клиента будет 3 минуты, чтобы получить SMS и ввести код
      • Запросить проверочный код можно не чаще чем 1 раз в 5 минут
    • После успешного оформления заказа клиенту на телефон приходит SMS с информацией, что по его номеру телефона оформлен заказ на такую-то сумму
  • Чтобы не могли сделать заказ по неверной цене (хакнув приложение или перехватив запрос)
    • Юпитер, получив заказ, берёт не те цены, которые пришли из приложения, а те цены, которые установлены в системе Юпитер. От них считаются и скидки, и сумма к оплате. Таким образом, подмена цен в запросе ничего не даст. Заказ всегда будет создан на правильную сумму.
  • Проверка подлинности уведомлений
    • Нужно проверять уведомление об успешном платеже от ЮKassa на подлинность
    • Рекомендации ЮKassa

Веб-приложение

Начало работы

  • Клиент своим телефоном сканирует QR-код, размещённый на столике в заведении
  • В QR-коде закодирована ссылка на веб-приложение, которая открывается в браузере на телефоне клиента
    • Пример ссылки в QR-коде: https://site.ru/?token=9990775155c3518a0d7917f7780b24aa
    • В URL содержатся Токен авторизации
      • По Токену авторизации система будет определять для какого стола и в каком ресторане формируется заказ
      • Токен авторизации создается и регистрируется специалистами технической поддержки системы Юпитер
    • Токен авторизации должен передаваться во всех запросах от веб-приложения к веб-серверу, через заголовок X-Jup-Api-Key
    • Веб-приложение должно получить Токен авторизации из URL и сохранить локальное хранилище
      • По возможности, после сохранения Токена авторизации, веб-приложение должно удалить токен из URL (window.location.replace), чтобы в истории браузера URL сохранился без токена. Это нужно, чтобы запретить вход в веб-приложение из истории браузера, без сканирования QR.
  • На один стол можно одновременно оформлять несколько заказов, например если за столом несколько гостей.
  • Когда веб-приложение открывается в браузере, оно выполняет следующие действия:
    • Отправляет на сервер запрос /start, в ответ получает:
      • Уникальный идентификатор сессии, session_id
        • session_id в дальнейшем всегда передаётся во всех запросах серверу, в заголовке X-Jup-Session-Id
      • Название организации и ресторана (на двух языках)
      • Актуальное меню для этого ресторана (на двух языках)
    • Если клиент авторизован в веб-приложении, то в запросе серверу также передается идентификатор клиента client_id
      • Получив client_id, сервер проверит, что в системе существует клиент с таким идентификатором
      • Если клиента не существует, то сервер вернет в ответе параметр client_verified = false. Это означает, что клиент не авторизован и веб-приложение отменит текущую авторизацию
        • Удалит client_id из LocalStorage
    • Если ответ от сервера не получен, выдаёт сообщение об ошибке
    • Открывает меню (menu_view) – по умолчанию на русском языке

    Страница с меню (menu_view)

     

    image-1723734237423.jpg

    Рис. 1

    • Страница с меню (menu_view) является основным интерфейсом приложения
    • В самом верху экрана находится системная зона - узкая полоска, содержащая:
      • Название организации
      • Иконки (флаги) выбора языка интерфейса и меню – русский и английский
        • При нажатии на флаг система заменяет все названия на названия на выбранном языке
      • Кнопка профиля клиента. Открывается окно авторизации (client_auth)
    • Вверху экрана находится зона с кнопками выбора категорий (header)
      • Зона закреплена от вертикальной прокрутки
      • Зона имеет горизонтальную прокрутку для размещения большего числа кнопок, чем помещаются по ширине экрана.
    • Внизу экрана расположена зона итогов (footer), где находятся:
      • Сумма заказа
      • Кнопка «К заказу» (to_cart_button)
      • Зона footer закреплена от вертикальной прокрутки. Чтобы клиент всегда видел актуальную сумму своего выбора
    • Между зонами header и footer расположен список блюд из выбранной категории
      • Каждое блюдо выводится в отдельной карточке (см. рис. 1)
      • Карточки блюд выводятся одной колонкой
      • Карточка блюда занимает всю ширину экрана
    • В карточке блюда отображаются
        • Фотография блюда
        • Название блюда
        • Вес порции
        • Цена
        • Кнопка добавления в корзину
    • При нажатии на кнопку добавления в корзину
      • Если блюдо имеет модификаторы, то открывается модальное окно выбора модификаторов (modif_view)
      • Если блюдо не имеет модификаторов, оно просто добавляется в список выбранных (в корзину)
      • Вместо кнопки добавления в корзину, отображаются кнопки изменения количества «+» [текущее кол-во] «-»
      • Пересчитывается итог по заказу в зоне footer
    • При нажатии на кнопку увеличения количества «+»
      • Если у блюда нет модификаторов, то просто увеличивается количество блюда в корзине и текущее кол-во увеличивается на 1
      • Если у блюда есть модификаторы (бизнес-ланч), то предполагается, что клиент выбирает ещё один бизнес-ланч и отображается модальное окно выбора модификаторов (modif_view)
        • После закрытия окна modif_view отображается общее количество таких блюд в корзине (с любыми модификаторами). Т.е. если блюдо называлось «Бизнес-ланч №1», то просто будет количество 2, даже если выбран разный состав. Увидеть бизнес-ланчи разного состава по отдельности можно будет в корзине. Как работает такая логика можно посмотреть на сайте https://shoko.ru/
    • При нажатии на кнопку уменьшения количества «-»
      • Если товар без модификаторов, то его количество в корзине уменьшается на 1
      • Если товар с модификаторами, то из корзины удаляется последний добавленный товар с его модификаторами
      • Если новое количество 0, то вместо кнопок изменения количества отображается кнопка добавления в корзину
    • При нажатии на карточку блюда (не на кнопку добавления в корзину) открывается модальное окно с описанием блюда (item_view)

    Окно выбора модификаторов (состава бизнес-ланча) (modif_view)

    • Выводится в модальном режиме
    • В окне размещены
      • Название базового товара (бизнес-ланча)
      • Цена базового товара
      • Составляющие товара (бизнес-ланча) в виде групп radio-button (выбор только одной позиции из группы)
      • В строке модификатора отображается:
        • Иконка выбора (кружочек)
        • Название модификатора.
        • Иконка получения информации о компоненте, при нажатии на которую открывается item_view.
        • При нажатии на на название модификатора происходит его выбор (в кружочке появляется точка)
    • Внизу окна (footer) размещена кнопка
      • «Подтвердить», при нажатии на которую:
        • Базовый товар (бизнес-ланч) добавляется в корзину со всеми выбранными модификаторами. При добавлении в корзину товары с одинаковыми модификаторами группируются в одну строку, товары с разными модификаторами оказываются в разных строках корзины. Модификаторы при добавлении в корзину сортируются по порядку следования групп модификаторов при выборе (модификатор из первой группы идёт первым, из второй – вторым и т.д.).
        • Окно выбора модификаторов
        • Бизнес-ланч сохраняется в корзину вместе со всеми выбранными компонентами
        • Общая сумма заказа пересчитывается
      • «Отменить», при нажатии на которую:
        • Окно выбора бизнес-ланча закрывается
        • В корзине ничего не меняется
        • Общая сумма заказа не пересчитывается

    Окно описания блюда (item_view)

    image-1723734364175.jpg

    Рис. 2

    • Выводится в модальном режиме
      • Нажатие кнопки "Назад" в браузере, должно закрывать модальное окно
    • В окне размещены
      • Фотография блюда
      • Название блюда
      • Описание блюда
      • Цена блюда
      • Крестик в верхнем правом углу для закрытия окна

    Кнопка «К заказу» (to_cart_button)

    При нажатии кнопки «К заказу» (to_cart_button):

    • Исчезает зона header выбора категорий, вместо него отображается другой header, содержащий:
      • Стрелку влево (возврат в меню)
      • Надпись «Проверьте ваш заказ»
    • Footer с суммой заказа остаётся и содержит теперь:
      • Сумму заказа
      • Кнопку «Оплата» (to_payment_button)
    • Между ними отображается список товаров из корзины
      • Товары без модификаторов показываются в том же формате, как в меню
      • Товары с модификаторами показываются в том же формате, в дополнение третьей строкой выводится список названий модификаторов, чтобы клиент видел, что именно выбрано. Модификаторы просто перечисляются строкой (только названия – без цен и количества, поскольку цены нет, а количество всегда 1).
      • В этом списке точно также работают кнопки + и – для изменения количества с пересчётом итога.
        • Для товаров с модификаторами кнопки + и – влияют на именно на эту строку – именно с этими модификаторами.
        • Если количество становится 0, то строка просто удаляется из корзины.
      • Изменить выбранные модификаторы нельзя. Можно только добавить ещё один товар с другими модификаторами, а ненужный удалить. Удалить можно либо из меню (нажав «-» - тогда удалится последняя добавленная запись), либо из корзины – явно удалив нужную запись.

    Окно авторизации клиента (client_auth)

      • Выводится в модальном режиме
        • Нажатие кнопки "Назад" в браузере, должно закрывать модальное окно, а не отрабатывать переход на предыдущую страницу
      • В окне размещены
        • Крестик в правом верхнем углу для закрытия модального окна
        • Текст «Укажите номер телефона»
        • Поле ввода номера телефона. При вводе номера клиента для удобства должна быть задействована цифровая клавиатура телефона, без букв
          • Если клиент уже авторизован, то поле с номером телефона клиента заполнено номером телефона клиента текущего клиента
        • Кнопка «Ок» (client_start_check), при нажатии на которую
          • Отправляется запрос на сервер /client_start_check с введённым номером телефона
            • Сервер отправляет на этот номер SMS с проверочным кодом
            • Сервер может вернуть сообщение об ошибке, которое нужно будет показать в приложении
          • Если сервер вернул успех, всё содержимое окна исчезает (кроме крестика) и появляется:
            • Текст "Мы отправили SMS с проверочным кодом на телефон [номер телефона]. Введите проверочный код"
            • Поле для ввода проверочного кода
              • При вводе проверочного кода для удобства должна быть задействована цифровая клавиатура телефона, без букв
            • Кнопка «Ок», при нажатии на которую:
              • Отправляется запрос на сервер /client_check с введённым кодом
                • Сервер верифицирует проверочный код и возвращает ответ
              • Если сервер вернул ошибку, она выводится пользователю, под полем ввода кода
              • В случае успеха сервер вернет client_id , который сохраняется в LocalStorage браузера и используется в последующих запросах
                • Признаком, того что клиент авторизован является наличие client_id в LocalStorage браузера
                • Маловероятно, но если зачем-то нужно будет отменить регистрацию клиента, нужно будет почистить кеш браузера
        • Кнопка "Отправить код еще раз"
          • Делает то же самое, что и кнопка (client_start_check)

      Кнопка «Оплата» (to_payment_button)

      При нажатии на кнопку «Оплата» (to_payment_button):

      • Отправляет запрос серверу на создание предзаказа /preorder и получает обратно:
        • Сумму заказа до скидок
        • Сумму всех скидок (одной суммой)
        • Итоговую сумму заказа
        • Ссылку на оплату в системе ЮKassa
        • После получения ответа открывается модальное окно оплаты (payment_view)

        Окно оплаты (payment_view)

            •  Заголовок
              • Стрелка влево, которая возвращает в корзину
                • В случае возврата в корзину при повторном заходе на страницу оплаты повторяются все те же действия и формируется новая ссылка на оплату.
                • Нажатие кнопки "Назад" в браузере, должно также возвращать клиента в корзину
              • Надпись «Оплата»
            • Тело страницы:
              • «Сумма заказа:» [Сумма заказа]
              • «Скидка:» [Сумма всех скидок]
              • «Итого к оплате:» [Сумма к оплате]
            • Кнопка «Оплатить» (payment_button) со ссылкой на оплату в системе ЮKassa
            • При нажатии на эту кнопку, в этой же вкладке браузера, открывается сайт ЮKassa
            • На этом работа с приложением завершается
              • Все данные сессии (локальной хранилище), кроме client_id, очищаются

             

            • Попав на сайт ЮKassa пользователь совершает оплату удобным ему способом
            • Если оплата была успешной, то ЮKassa сообщает об этом клиенту и делает следующее:
                  • Отправляет сообщение об успешной оплате серверу. Сервер, получив это сообщение, находит в системе Юпитер, сохранённый на этапе расчёта скидки предзаказ, отмечает его в Юпитере как оплаченный и вызывает действие «Отправить в работу»
                  • Перенаправляет клиента на фиксированную страницу веб-приложения успешной оплаты (success_payment_view), на которой выводится:
                    • Текст «Ваш заказ был успешно оплачен и принят в работу»
                    • Кнопку «Закрыть приложение» при нажатии на которую эта вкладка браузера закрывается.

                Веб-сервер

                Авторизация запросов

                Трудозатраты: 3

                Описание:

                • Для реализации данного функционала, на веб-сервере будет развернута база данных (MySQL)
                  • Таблица "Организации"
                    • ID
                    • Название
                  • Таблица "Торговые залы"
                    • ID
                    • ID организации
                    • Название
                    • Название на английском языке
                    • Адрес сервера ресторана
                    • Текущее меню
                  • Таблица "Столы"
                    • ID
                    • ID торгового зала
                    • Название (номер)
                • Во всех запросах к веб-серверу передается Токен авторизации через заголовок X-Jup-Api-Key
                • Веб-сервер по Токену авторизации находит Стол по ID
                  • Если Стол не найдет, то ищет Торговый зал по ID
                  • Если Стол найден, то определяет к какому Торговому залу он принадлежит
                • По Торговому залу устанавливает адрес сервера ресторана
                • Если не удалось найти Стол или Торговый зал,  то сервер вернет ошибку 401 Unauthorized

                  Запрос на открытие сессии (/start)

                  Трудозатраты: 5

                  Описание:

                  • Веб-приложение отправляет запрос на открытие сессии
                  • Веб-сервер формирует уникальный идентификатор сессии, далее session_id
                  • Если передан client_id, то отправляет запрос на сервер ресторана /jup/client для верификации клиента
                    • Если клиента с таким client_id не прошел верификацию, то в ответе будет передан параметр client_verified = false
                    • В других случаях этот параметр будет иметь значение true
                  • Веб-сервер возвращает
                    • Идентификатор сессии
                    • Статус верификации клиента
                    • Наименование торгового зала (на двух языках)
                    • Текущее меню по торговому залу (на двух языках)
                      • Меню учитывает текущий стоп-лист торгового зала. Товары которые находятся в стопе не передаются в веб-приложение
                        • Для этого веб-приложение, перед тем как передавать ответ в веб-приложение, должно убрать из меню все товары, которые есть в стоп-листе

                  Адрес:  /start

                  Метод: GET. Параметры:

                  • client_id - Не обязательный. Передается если клиент авторизован

                    Ответ успех:

                    {
                        "session_id": "5595236c62914db69d5c07e064530700", // Идентификатор сессии
                        "client_verified": true, // Статус верификации клиента. Если false, то нужно сбросить авторизацию в веб-приложении
                        "restaurant": {
                            "name": "Ресторан № 1", // Наименование торгового зала
                            "name_eng": "Restaurant № 1" // Наименование торгового зала на английском языке
                        },
                        "menu": { // Меню ресторана по концепции
                            "lastChange": "2024-05-21T16:19:13.000+03:00", // Дата последнего изменения меню, в формате RFC3339
                            "categories": [ // Список категорий меню
                                {
                                    "id": "5:3:0:832141", // Идентификатор категории
                                    "name": "Роллы", // Название категории
                                    "name_eng": "Rolls", // Название на английском языке
                                    "sortOrder": 0, // Порядок сортировки от меньшего к большему
                                    "images": [ // Изображение категории
                                        {
                                            "url": "string", // Ссылка на изображение для скачивания
                                            "updatedAt": "2024-05-21T16:19:13.000+03:00" // Дата обновления изображения, в формате RFC3339
                                        }
                                    ]
                                }
                            ],
                            "items": [ // Список товаров
                                {
                                    "id": "T20231", // Идентификатор товара
                                    "categoryId": "5:3:0:832141", // Идентификатор категории к которой относится товар
                                    "name": "Филадельфия Гранд", // Название
                                    "name_eng": "Philadelphia Grand", // Название на английском языке
                                    "description": "Рис, нори, сыр сливочный, огурец, лосось. Подается с соевым соусом (30 г), имбирём (30 г), васаби (10 г).", // Описание для меню
                                    "description_eng": "Rice, nori, cream cheese, cucumber, salmon. Served with soy sauce (30 g), ginger (30 g), wasabi (10 g).", // Описание для меню на английском языке
                                    "price": 500, // Цена
                                    "vat": 0, // НДС, включенный в стоимость, в процентах, если не указан, считается за 0
                                    "measure": 240, // Вес или объем
                                    "measureUnit": "г", // Единица измерения. Допустимые значения - граммы (г) и миллилитры (мл)
                                    "sortOrder": 0, // Порядок сортировки от меньшего к большему
                                    "nutrients": [ // Параметры КБЖУ на 100г. Необязательный параметр
                                        {
                                            "calories": 142, // Калории
                                            "proteins": 10, // Белки
                                            "fat": 7, // Жиры
                                            "carbohydrates": 11 // Углеводы
                                        }
                                    ],
                                    "modifiers": [ // Группы модификаторов для товара. Необязательный параметр
                                        {
                                            "id": "5:3:0:11821", // Идентификатор группы модификаторов
                                            "name": "Доп. гарниры", // Название группы модификаторов
                                            "name_eng": "Side dishes", // Название на английском языке
                                            "minSelectedModifiers": 0, // Минимальное количество модификаторов, которые необходимо выбрать для данной группы
                                            "maxSelectedModifiers": 3, // Максимальное количество модификаторов, которые можно выбрать для данной группы
                                            "sortOrder": 0, // Порядок сортировки от меньшего к большему
                                            "modifiers": [ // Список модификаторов
                                                {
                                                    "id": "6021", // Идентификатор товара-модификатора
                                                    "name": "Соевый соус (30 г)", // Название
                                                    "name_eng": "Soy sauce (30 g)", // Название на английском языке
                                                    "price": 50, // Цена
                                                    "vat": 0, // НДС, включенный в стоимость, в процентах, если не указан, считается за 0
                                                    "minAmount": 0, // Минимальное количество указанного модификатора для блюда в заказе
                                                    "maxAmount": 3, // Максимальное количество указанного модификатора для блюда в заказе
                                                    "sortOrder": 0 // Порядок сортировки от меньшего к большему
                                                },
                                                {
                                                    "id": "6042",
                                                    "name": "Имбирь (30 г)",
                                                    "name_eng": "Ginger (30 g)", // Название на английском языке
                                                    "price": 50,
                                                    "vat": 0,
                                                    "minAmount": 0,
                                                    "maxAmount": 3,
                                                    "sortOrder": 1
                                                }
                                            ]
                                        }
                                    ],
                                    "images": [ // Изображение товара
                                        {
                                            "url": "https://url.url", // Ссылка на изображение для скачивания
                                            "hash": "55247fb2135f11ef8a4fd00db080bd69" // SHA1-хэш от содержимого файла изображения. Служит признаком уникальности. В случае если он меняется, сайт перезагружает картинку
                                        }
                                    ],
                                    "tags": [ // Список тегов товара. Необязательный параметр
                                        {
                                            "name": "Хит" // Название тега
                                        }
                                    ]
                                }
                            ]
                        }
                    }

                    Ответ ошибка:

                    {
                      "result": "error",
                      "desc": "Что-то пошло не так"
                    }

                      Создание запроса на авторизацию клиента (/client_start_check)

                      Трудозатраты: 1

                      Описание:

                      • Веб-приложение отправляет запрос с введённым номером телефона
                      • Веб-сервер авторизует запрос по Токену авторизации
                      • Предаёт запрос на сервер ресторана /jup/client_start_check
                      • Запрос обрабатывается сервером ресторана
                      • Веб-сервер транслирует ответ сервера ресторана веб-приложению

                      Метод: POST. Тело запроса:

                      {
                        "phone": "79998886655", // Введенный номер телефона. Обязательный
                        "session_id": "5595236c62914db69d5c07e064530700" // Идентификатор сессии. Обязательный
                      }

                      Ответ успех:

                      {
                        "result": "success"
                      }

                      Ответ ошибка:

                      {
                        "result": "error",
                        "desc": "Повторный запрос кода возможен через 2 мин."
                      }

                        Авторизация клиента по проверочному коду (/client_check)

                        Трудозатраты: 1

                        Описание:

                        • Веб-приложение отправляет запрос с введённым проверочным кодом
                        • Веб-сервер авторизует запрос по Токену авторизации
                        • Предаёт запрос на сервер ресторана /jup/client_check
                        • Запрос обрабатывается сервером ресторана
                        • Веб-сервер транслирует ответ сервера ресторана веб-приложению

                        Метод: POST. Тело запроса:

                        {
                          "code": "12345", // Введенный проверочный код
                          "session_id": "5595236c62914db69d5c07e064530700" // Идентификатор сессии. Обязательный
                        }

                        Ответ успех:

                        {
                          "result": "success",
                          "client_id": "00000000-0000-0000-9001-000000000805"
                        }

                        Ответ ошибка:

                        {
                          "result": "error",
                          "desc": "Введен неверный проверочный код. Осталось попыток: 1"
                        }

                          Создание предзаказа (/preorder)

                          Трудозатраты: 1

                          Описание:

                          • Веб-приложение отправляет запрос на создание предзаказа
                          • Веб-сервер авторизует запрос по Токену авторизации
                          • Предаёт запрос на сервер ресторана /jup/preorder
                          • Запрос обрабатывается сервером ресторана
                          • Веб-сервер транслирует ответ сервера ресторана веб-приложению

                          Метод: POST. Тело запроса:

                          {
                              "date": "2024-04-08 15:21:03", // Дата\время оформления заказа
                              "client_id": "00000000-0000-0000-9001-000000000805", // Идентификатор клиента
                              "cart": { // Содержимое заказа
                                  "items": [ // Товарная часть
                                      {
                                          "id": "T20231", // Идентификатор товара
                                          "count": 1 // Количество
                                      },
                                      {
                                          "id": "555333111",
                                          "count": 3,
                                          "modifiers": [ // Массив вложенных модификаторов к этому товару
                                              {
                                                  "id": "6021", // Идентификатор товара
                                                  "count": 1 // Количество
                                              },
                                              {
                                                  "id": "6042",
                                                  "count": 1
                                              }
                                          ]
                                      }
                                  ]
                              }
                          }

                          Ответ успех:

                          {
                            "result": "success",
                            "pretotal": 5000.00,
                            "discount": 100.00,
                            "total": 4900.00,
                            "confirmation_url": "https://yoomoney.ru/api-pages/v2/payment-confirm/epl?orderId=23d93cac-000f-5000-8000-126628f15141"
                          }

                          Ответ ошибка:

                          {
                            "result": "error",
                            "desc": "Что-то пошло не так"
                          }

                          Изменение статуса оплаты, от ЮKassa (/payment)

                          Трудозатраты: 1

                          Описание:

                          • ЮKassa уведомляет веб-сервер об изменении статуса платежа
                            • Отправляет запрос на URL веб-сервера, в параметрах URL передается Токен авторизации 
                          • Веб-сервер авторизует запрос по Токену авторизации
                          • Предаёт запрос на сервер ресторана /jup/payment
                          • Веб-сервер транслирует ответ сервера ресторана в ЮKassa

                          Метод: POST. Параметры:

                          • token - Обязательный. Токен авторизации

                          Обновить меню, от сервера ресторана (/update_menu)

                          Трудозатраты: 1

                          Описание:

                          • Сервер ресторана периодически или вручную передает меню в формате JSON
                          • Веб-сервер по Токену авторизации определяет торговый зал
                          • Валидирует и сохраняет полученное меню с привязкой к торговому залу

                          Адрес:  /update_menu

                          Метод: POST

                           

                          Обновить стоп-лист, от сервера ресторана (/update_stop-list)

                          Трудозатраты: 1

                          Описание:

                          • Сервер ресторана при каждом изменении стоп-листа (в т.ч. автоматическом) передает стоп-лист в формате JSON
                          • Веб-сервер по Токену авторизации определяет торговый зал
                          • Валидирует и сохраняет полученный стоп-лист с привязкой к торговому залу

                          Адрес:  /update_stop-list

                          Метод: POST

                          Сервер ресторана (Юпитер)

                          Меню на двух языках

                          Трудозатраты: 1

                          Описание:

                          • Добавить в Юпитер возможность задавать наименование на дополнительном языке для:
                            • Наименования торгового зала
                            • Наименования ТМЦ
                            • Описания ТМЦ
                            • Наименования раскладок (разделов меню)
                            • Наименования групп модификаторов
                            • Наименования модификаторов
                            •  

                          Регистрация торгового зала и столов на веб-сервере

                          Трудозатраты: 2

                          Описание:

                          • Регистрация выполняется вручную администратором сервера веб-приложения (сотрудник Юпитер)
                          • Для выполнения регистрации сотрудники технической поддержки Юпитер должны представать данные для регистрации
                          • Чтобы получить данные для регистрации, нужно запустить специальный отчёт из складской части Юпитера "Данные для регистрации в веб-приложении"
                          • Отчёт запросит торговый зал
                          • Отчёт выводит
                            • ID организации (задается в настройках холдинга)
                            • ID торгового зала (закодированный UID)
                            • Название торгового зала
                            • Название торгового зала на английском языке
                            • Список столов торгового зала
                              • ID стола (закодированный UID)
                              • Название (номер)

                          Выдача URL для QR-кодов

                          Трудозатраты: 1

                          Описание:

                          • Отчёт, который выводит список столов и URL который нужно разместить в виде QR-кода
                          • Отчёт запросит торговый зал
                          • Отчёт выводит
                            • Название торгового зала
                            • Название стола
                            • URL для QR-кода

                          Автоматическая печать на кухню поступивших заказов

                          Трудозатраты: 1

                          Описание:

                          • Процедура автоматически печатает и переводит заказы в статус "В работе"
                            • Отбирает заказы в статусе "Новый" с признаком Нужна автоматическая печать в работу
                            • Для каждого такого заказа вызывает стандартную процедуру смены статуса на Выполняется
                            • Снимает признак Нужна автоматическая печать в работу
                          • Процедура будет запускаться как задание планировщика

                          Передача меню на сервер веб-приложения

                          Трудозатраты: 3

                          Описание:

                          • Процедура подготавливает меню в формате JSON и передает на сервер веб-приложения /update_menu
                          • Для работы процедуры, в торговом зале должен быть прописан Токен авторизации
                          • Подготовка меню
                            • Меню формируется по таблице меню текущего торгового зала
                            • Иерархия раскладок принудительно приводится к двухуровневой структуре
                            • Если товар присутствует на нескольких раскладках, то в веб-приложение он будет передан только по первой раскладке
                            • Учитывается расписание замен на текущий момент (по серверу)
                          • Отправят запрос на веб-сервер /update_menu
                            • Результат запроса не контролируется
                          • Процедура может запускаться как задание планировщика или вручную из складской части программы

                          Передача стоп-листа на сервер веб-приложения

                          Трудозатраты: 2

                          Описание:

                          • Задание для планировщика, запускается каждую минуту
                          • Выполняется проверка, поменялся ли стоп-лист с даты последней передачи стоп-листа на веб-сервер
                          • Если не поменялся, то ничего не происходит
                          • Если поменялся, то запускается процедура выгрузки стоп-листа
                            • Процедура подготавливает стоп-лист текущего ТЗ в формате JSON и передает на сервер веб-приложения /update_stop-list
                            • В случае успешной передачи, фиксируется дата последней передачи стоп-лист на веб-сервер

                          Отложенное считывание кодов маркировки (Честный знак)

                          Трудозатраты: 8

                          Описание:

                          • Изменить структуру хранения кодов маркировки (КМ), чтобы для одной строки можно было задать несколько КМ
                            • Реализовать МЕМЕ-структуру по аналогии с алкоголем
                          • Разрешить сворачивание строк с КМ
                            • Добавление новых АМ в МЕМО-структуру сворачиваемой строки
                            • Сворачивание как отдельная функция и сворачивание при добавлении
                              • Учесть добавление через прямой ввод КМ и отдельный ввод КМ после добавления 
                          • Разделение строк с КМ
                            • Разделять МЕМО-структуру при разделении строки
                          • Добавить интерфейс управления КМ (просмотр, удаление, добавлением КМ в строки заказа)
                          • Добавить инструмент контроля наличия КМ
                            • По аналогии с алкоголем будет появляться кнопка "Маркировка", если в заказе есть товары без маркировки
                          • Добавить запрет на печать чека по заказу, где есть товары без маркировки

                          Верификация клиента (/jup/client)

                          Трудозатраты: 1

                          Метод: GET. Параметры:

                          • client_id - Обязательный. Идентификатор клиента

                          Описание:

                          • Дешифрует client_id, получает UID клиента 
                          • Выполняет поиск клиента в базе по UID
                          • Если клиент не найден, то возвращает ошибку
                          • Если клиент найден, то возвращает успех

                          Ответ успех:

                          {
                            "result": "success"
                          }

                          Ответ ошибка:

                          {
                            "result": "error",
                            "desc": "Клиент не найден"
                          }

                          Создание запроса на авторизацию клиента (/jup/client_start_check)

                          Трудозатраты: 3

                          Описание:

                          • По session_id находит Запрос на авторизацию клиента
                          • Если такой запрос есть, и он был в течении последних 5 минут, то возвращает ошибку. Чтобы нельзя было отправлять SMS чаще чем 1 раз в 5 минут
                            • Текст ошибки: "Повторный запрос кода возможен через [n] мин."
                          • По переданному номеру телефона находит клиента в базе
                          • Если данные не найдены, возвращает ошибку. Авторизоваться могут только те клиенты, которые есть в базе Юпитера
                            • Текст ошибки: "Номер телефона не найден в системе"
                          • Формирует проверочный код, который состоит из пяти цифр, и отправляет его по SMS на номер телефона клиента (через сервис https://stream-telecom.ru/)
                          • Создает объект Запрос на авторизацию клиента, в объекте фиксируется
                            • Клиент
                            • Проверочный код
                            • session_id с которого был создан запрос
                            • Дата\время создания запроса (по серверу)
                            • Кол-во проверок. Начальное значение 0
                          • Возвращает успешный ответ

                            Метод: POST. Тело запроса:

                            {
                              "phone": "79998886655", // Введенный номер телефона. Обязательный
                              "session_id": "5595236c62914db69d5c07e064530700" // Идентификатор сессии. Обязательный
                            }

                              Ответ успех:

                              {
                                "result": "success"
                              }

                              Ответ ошибка:

                              {
                                "result": "error",
                                "desc": "Повторный запрос кода возможен через 2 мин."
                              }

                              Авторизация клиента по проверочному коду (/jup/client_check)

                              Трудозатраты: 1

                              Описание:

                              • Сервер ресторана по session_id находит Запрос на авторизацию клиента
                              • Если Запрос на авторизацию клиента не найден, то возвращает ошибку
                                • Текст ошибки "Проверочный код не найден, запросите новый код"
                              • Если Запрос на авторизацию клиента был создан более чем 3 минуты назад, то возвращает ошибку и удаляет Запрос на авторизацию клиента
                                • Текст ошибки: "Проверочный код устарел, запросите новый код"
                                • Нужно, чтобы пользователь не мог использовать старый код
                              • Если кол-во проверок в Запросе на авторизацию клиента >= 3, то возвращает ошибку и удаляет Запрос на авторизацию клиента
                                • Текст ошибки: "Введен неверный проверочный код. Осталось попыток 0. Запросите новый код"
                              • Сверяет сверяет code с кодом из Запроса на авторизацию клиента
                              • Если коды не совпадают, то возвращает ошибку и увеличивает кол-во проверок на 1
                                • Текст ошибки: "Введен неверный проверочный код. Осталось попыток: [n]"
                              • Если коды совпадают то возвращает успешный ответ

                              Метод: POST. Тело запроса:

                              {
                                "code": "12345", // Введенный проверочный код
                                "session_id": "5595236c62914db69d5c07e064530700" // Идентификатор сессии. Обязательный
                              }

                                    Ответ успех:

                                    {
                                      "result": "success",
                                      "client_id": "00000000-0000-0000-9001-000000000805"
                                    }

                                    Ответ ошибка:

                                    {
                                      "result": "error",
                                      "desc": "Введен неверный проверочный код. Осталось попыток: 1"
                                    }

                                    Создание предзаказа (/jup/preorder)

                                    Трудозатраты: 24

                                    Описание:

                                    • Проверяет все товары из заказа на стоп-лист
                                      • Если один или несколько товаров в стопе, то возвращает ошибку
                                        • Текст ошибки "Товары SSS, SSS, SSS в данный момент не доступны для заказа"
                                    • Проверят все товары по механизму оставшегося кол-ва
                                      • Если один или несколько товаров превышают "Оставшееся кол-во", то возвращает ошибку
                                        • Текст ошибки "Товары SSS, SSS, SSS в данный момент не доступны для заказа"
                                    • Формирует предварительный заказ
                                      • На кассе в Юпитере его не видят
                                      • Просмотреть предзаказы можно только через бэкенд Юпитера
                                      • Вручную корректировать предзаказы нельзя
                                      • Перевести предзаказа в заказ вручную нельзя
                                      • Предварительный заказ не влияет на "Оставшееся кол-во"
                                    • Цены для формирования заказа определяются по аналогичному алгоритму из кассы
                                      • Цена с кнопки
                                      • Прейскурант для клиента
                                      • Прейскуранта для торгового зала ТЗ
                                      • Цена из таблицы раскладок
                                    • Применяет все возможные скидки к предзаказу
                                      • Все автоматические скидки
                                        • Настройка "Когда применять автоматическую скидку" игнорируется
                                        • Если используются скидки по расписанию, то "Способ определения скидки" должен быть всегда "Время открытия заказа"
                                      • Скидки клиента. Если клиент определен
                                      • Подарочные товары не применяются
                                    • Создает платеж в ЮKassa
                                      • Если создать платеж не получилось, то возвращает ошибку
                                      • Текст ошибки: "Не удалось создать платеж в ЮKassa"
                                      • ID платежа сохраняется в предзаказе
                                    • Возвращает успешный ответ

                                    Метод: POST. Тело запроса:

                                    {
                                        "date": "2024-04-08 15:21:03", // Дата\время оформления заказа
                                        "client_id": "00000000-0000-0000-9001-000000000805", // Идентификатор клиента
                                        "cart": { // Содержимое заказа
                                            "items": [ // Товарная часть
                                                {
                                                    "id": "T20231", // Идентификатор товара
                                                    "count": 1 // Количество
                                                },
                                                {
                                                    "id": "555333111",
                                                    "count": 3,
                                                    "modifiers": [ // Массив вложенных модификаторов к этому товару
                                                        {
                                                            "id": "6021", // Идентификатор товара
                                                            "count": 1 // Количество
                                                        },
                                                        {
                                                            "id": "6042",
                                                            "count": 1
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    }

                                    Ответ успех:

                                    {
                                      "result": "success",
                                      "pretotal": 5000.00,
                                      "discount": 100.00,
                                      "total": 4900.00,
                                      "confirmation_url": "https://yoomoney.ru/api-pages/v2/payment-confirm/epl?orderId=23d93cac-000f-5000-8000-126628f15141"
                                    }

                                    Ответ ошибка:

                                    {
                                      "result": "error",
                                      "desc": "Что-то пошло не так"
                                    }

                                    Изменение статуса оплаты (/jup/payment)

                                    Трудозатраты: 4

                                    Описание:

                                    • Находит предзаказа по ID платежа
                                    • Изменяет статус платежа
                                    • Когда статус платежа становится succeeded (подробнее о статусах в ЮKassa), Юпитер изменяет тип с Предзаказа на Заказ
                                      • Заказ появляется на кассе в статусе Новый
                                      • Изменяется "Оставшееся кол-во"
                                      • В заказ проставляется признак Нужна автоматическая печать в работу
                                      • Отправляется SMS на номер телефона клиента с текстом: "Оформлен заказ № NN-NN-NN на сумму NNNN руб., сидка NN руб."
                                        • SMS отправляется через сервис https://stream-telecom.ru/
                                        • Результат отправки SMS не контролируется

                                    Метод: POST. Тело запроса: https://yookassa.ru/developers/using-api/webhooks#using

                                    Ответ успех:

                                    {
                                      "result": "success"
                                    }

                                    Ответ ошибка:

                                    {
                                      "result": "error",
                                      "desc": "Платеж не найден"
                                    }

                                    Система JUPITER                                 www.jupiter.systems                                 (с) 2024г.