Софт для приёма заказов в ресторане обычно ломается в двух местах: там, где чужой человек может заказать на не свой стол, и там, где сумма на экране гостя расходится с суммой на кассе. Модуль еды в Korat проектировался, отталкиваясь от обоих этих мест. Сессию стола выпускает персонал, и она несёт одноразовый токен; счёт — те же строки, что читает касса; а place_dining_order считает каждую позицию на сервере. Опции, таймер буфета и разделение счёта построены поверх этих двух гарантий.
QR стола — это одноразовый токен, а не номер стола
Пути «введите номер стола» здесь нет намеренно. Это очевидная функция и причина, по которой множеством систем обслуживания за столом можно злоупотребить с парковки: если единственный пропуск — номер, напечатанный на столе, то любой, кто хоть раз здесь ел, может вечно заказывать на любой стол. Вместо этого персонал открывает стол из консоли, и это выпускает свежий одноразовый токен, встроенный в показанный на столе QR. Сфотографированный код, скриншот, пересланный другу, или распечатка с прошлого вторника несут токен, который уже не действует, и не работают.
Гость, подошедший к столу, не сотрудник заведения и не хозяин ещё не существующей сессии, поэтому по правилам защиты на уровне строк он не может прочитать таблицу dining_sessions вообще — ни своей строки, ни любой другой. RLS умеет судить о том, кто обращается; она не умеет судить о том, что запрос несёт действительный токен. Поэтому сканирование идёт через join_session_by_token — функцию SECURITY DEFINER, которая сверяет токен на сервере, делает первого сканировавшего хозяином и возвращает null, когда токен не подошёл, — не объясняя причину. Ошибка, отличающая «неверный токен» от «просроченного», — это оракул для подбора.
Внутри хозяин может позвать остальных за стол ссылкой или карточкой в чате, чтобы опоздавший друг вошёл в ту же сессию и его еда попала в тот же счёт.
Заказ: опции, комментарии и меню, знающее, что скоро закрытие
Меню разложено по категориям, и у каждой позиции могут быть группы опций — дополнительная порция эспрессо, острота, вид лапши — с надбавками к цене, признаком обязательности и максимумом выборов. Надбавка привязана к опции, а не вписана в комментарий, — поэтому чек на кухне и строка счёта согласны в том, что заказали. У позиции есть свободный комментарий и количество, а корзина остаётся редактируемой до отправки.
Часы работы, последний заказ и проверка возраста на алкоголь
Заведение задаёт часы работы с буфером последнего заказа. На подходе к нему меню предупреждает, что кухня скоро закроется; после него заказ блокируется, а не принимается, чтобы позже тихо отмениться. Позиции, помеченные как недоступные или закончившиеся, говорят об этом на карточке — до нажатия. У алкоголя свой признак: проверка возраста по дате рождения гостя, и персонал может подтвердить возраст сразу для всей сессии.
Очередь подтверждения: на кухню ничего не попадает, пока человек не сказал «да»
Отправленные заказы не идут прямо на раздачу. Они падают в очередь ожидания подтверждения на экране кухни, и сотрудник подтверждает или отклоняет каждый, прежде чем тот станет работой. Система, позволяющая телефону класть чеки прямо на кухню, отдаёт незнакомцу возможность пускать продукты заведения в расход и убирает момент, когда человек замечает, что шестой стол случайно заказал четыре одинаковых сета.
С экрана кухни персонал двигает заказы по состояниям, а консоль показывает живые значки, обновляющиеся каждые несколько секунд: активные заказы, столы, зовущие официанта, ожидающие брони, новые доставки — плюс красная полоса в тот же миг, когда стол нажал вызов. У гостей вторая половина этой петли: позвать официанта, попросить счёт или покинуть стол.
Живой детализированный счёт и его разделение через QR PromptPay
Живой счёт показывает ровно те строки, что читает касса, и обновляется по мере подтверждения блюд: никакого сюрприза в конце ужина и никакого восстановления по памяти. Когда стол закрывается, приложение выдаёт итоговый экран — вместе с предложением добавить в друзья тех, с кем вы делили сессию, — а потом напоминание об отзыве. Чеки остаются в личной истории счетов.
Разделение счёта: сколько должен каждый
Разделение счёта отвечает на вопрос, из-за которого люди действительно спорят: сколько с меня. Каждый видит свой подытог по блюдам плюс равную долю буфетного пакета, если он был. Для суммы можно сгенерировать PromptPay QR в тайском стандартном формате с контрольной суммой CRC-16.
Разделение счёта — это расчёт, а не отдельные платежи. Оно считает, кто сколько должен; оно не списывает четыре доли с четырёх карт. Деньги по-прежнему делят между собой люди за столом и заведение.
И запасного пути «введите номер стола» нет намеренно. Если QR у гостя не сканируется, персонал заново открывает стол из консоли. Ручной ввод вернул бы ровно ту дыру, ради закрытия которой токен и выпускается.
Буфет: пакет с человека, таймер и плата за остатки
У буфета своя модель, а не подделка через позицию меню с ценой за человека. У пакета есть цена с человека, таймер обратного отсчёта с продлением от персонала и раздельный подсчёт взрослых и детей. Позиции меню несут буфетную роль — включено, только а-ля-карт или включено с надбавкой, — так что одно меню обслуживает оба типа гостей. Поддерживается плата за недоеденное с указанием единиц: правило, которое большинство тайских буфетов уже печатает на столе, а большинство софта делает вид, что его нет.
Доставка, самовывоз и брони столиков
То же меню обслуживает доставку и самовывоз. У заказа есть стоимость доставки, служба, назначенное время выдачи и адрес, и он проходит состояния «новый», «готовится», «в пути», «выполнен» — с уведомлением обеих сторон на каждом переходе. Бронь столика принимает дату, время, число гостей и комментарий и ждёт подтверждения заведения: счётчик ожидающих виден на панели живым значком. Заведение, которое торгует ещё и фасованным товаром, берёт для этого модуль магазина, построенный на том же правиле расчёта цен на сервере.
Два частых варианта. Точка, обслуживающая у стойки и вызывающая по номеру, использует электронную очередь вместо столов. А если сначала хочется понять, чего вообще требует открытие заведения, это целиком описано на странице для бизнеса.
Каждая цена считается на сервере
Правило, которое делает безопасным всё остальное: клиент никогда не задаёт цену. place_dining_order и place_shop_order читают цены позиций, надбавки опций и стоимость доставки из базы и игнорируют присланное телефоном. Поле клиента, которому доверяют, — это скидка себе самому. Тот же принцип действует и с отзывами: написать может только гость, про которого база может доказать, что он ел здесь или заказывал доставку, а рейтинг пересчитывается триггером и никогда не вписывается руками.