飲食店の注文ソフトウェアはたいてい二つの場所で壊れます。見知らぬ他人が自分のものではないテーブルに注文できてしまう瞬間と、客の画面の合計とレジの合計が食い違う瞬間です。Korat の飲食モジュールは、その両方から逆算して設計しました。テーブルのセッションはスタッフが発行し、使い捨てのトークンを持ちます。伝票はレジが読むのと同じ行です。そして place_dining_order はすべての行をサーバーで値付けします。オプション、ビュッフェのタイマー、割り勘の画面は、この二つの保証の上に載っています。
テーブルの QR は使い捨てトークンであって、テーブル番号ではありません
「テーブル番号を入力する」経路は意図的に用意していません。それは作るのが当たり前の機能で、そして多くの店内オーダーが駐車場から悪用できる理由でもあります。資格情報がテーブルに印刷された番号だけなら、一度でもその店で食べた人は、以後ずっとどのテーブルにでも注文を出せます。代わりに、スタッフがコンソールからテーブルを開けます。開けるたびに新しい使い捨てトークンが発行され、そのテーブルに表示される QR に埋め込まれます。撮影した QR、友だちに転送されたスクリーンショット、先週火曜の印刷物が運んでいるのは、もう現行ではないトークンです。動きません。
テーブルに歩み寄った客は、その店のメンバーでもなければ、まだ存在しないセッションのホストでもありません。だから行レベルセキュリティのもとでは dining_sessions テーブルを一行も読めません——自分の行も、他の誰の行も。RLS は誰が呼んでいるかは判定できますが、リクエストが有効なトークンを持っていることは判定できません。そこでスキャンは join_session_by_token を通ります。トークンをサーバー側で照合し、最初にスキャンした人をホストにし、一致しなければ理由を言わずに null を返す SECURITY DEFINER 関数です。「トークンが違う」と「期限切れ」を区別するエラーは、当てものの神託になります。
中に入れば、ホストはリンクかチャットのカードで同席者を招けます。遅れて来た友だちも同じセッションに入り、その料理は同じ伝票に載ります。
テーブルの QR は https のリンク——アプリでもウェブでも開きます
テーブルの QR は https の URL で、アプリ専用のスキームではありません。 Korat が入っている端末はドメイン検証済みの App Link でアプリを開き、入っていない端末——iPhone を含めて——はウェブの注文ページを開きます。これは製品全体でいちばん死んでいた瞬間の修正です。客が席に着き、カメラを構えてスキャンし、そして何も起きない。そのページはサインインしていない状態でメニュー、ビュッフェの残り時間、酒類の判定をすべて読み、注文を実際に送る瞬間——避けようのない唯一の瞬間——にだけサインインを求めます。古い korat://dine を載せて既に印刷された QR も、これからずっとスキャンできます。
注文——オプション、備考、そして閉店を知っているメニュー
メニューはカテゴリで整理され、どの品目にもオプショングループを持たせられます。追加のショット、辛さの段階、麺の種類。それぞれに価格差、必須かどうか、選べる最大数が付きます。価格差は備考欄に手書きするのではなくオプションに紐づくので、厨房の伝票と会計の行が、何が注文されたかについて一致します。品目には自由記入の備考と数量も付けられ、カートは送信するまで編集できます。
営業時間、ラストオーダー、そして酒類の年齢確認
店は営業時間とラストオーダーの余裕時間を設定します。近づけばメニューが「もうすぐ厨房が閉まります」と警告し、過ぎれば注文は受け付けたあとで静かに取り消されるのではなく、その場でブロックされます。品切れや取り扱いなしの品目は、タップする前にカード上でそう言います。酒類には専用のフラグがあり、客の生年月日による年齢確認と、スタッフがセッションごとまとめて年齢確認する手段があります。
確認キュー——人が「はい」と言うまで厨房には何も届きません
送信された注文は、そのまま調理場には行きません。厨房ディスプレイの「確認待ち」のキューに入り、スタッフが一件ずつ承認するか却下してから仕事になります。端末から厨房に伝票を直接入れられるシステムは、見知らぬ他人に店の食材を消費させる力を渡していますし、6番テーブルがうっかり同じセットを4つ頼んだことに人間が気づく瞬間を消しています。
厨房ディスプレイからスタッフは注文の状態を進めます。コンソールには数秒ごとに更新されるライブのバッジ——進行中の注文、スタッフを呼んでいるテーブル、未確認の予約、新しいデリバリー——が並び、テーブルが呼び出しを押した瞬間に赤いバナーが出ます。客側にはその輪の反対側があります。スタッフを呼ぶ、会計を頼む、席を立つ。
ライブの明細伝票と、PromptPay QR での割り勘
ライブの伝票は、レジが読むのとまったく同じ行を、料理が確認されるたびに更新して表示します。食後の答え合わせも、記憶からの再構成もありません。テーブルが閉じるとアプリは締めのシートを出し——同席した人を友だちに追加するかどうかの提案を含みます——あとからレビューの案内が来ます。レシートは自分の会計履歴に残ります。
割り勘——自分はいくら払うのか
割り勘の画面は、人が実際に揉める問いに答えます。自分はいくら払うのか。各自に、自分のアラカルト分の小計と、そのテーブルのビュッフェ代の均等割りが表示されます。金額に対して PromptPay の QR を生成でき、CRC-16 チェックサム付きのタイ標準 QR 形式です。
割り勘は内訳であって、別々の決済ではありません。 各自の負担額を計算しますが、4人分のカードに4つの請求を立てるわけではありません。お金を分けるのは、あくまでテーブルの人たちと店のあいだで行われます。
そして「テーブル番号を入力する」逃げ道は、意図的にありません。客の QR が読めないときは、スタッフがコンソールからテーブルを開け直します。手入力の経路を足すことは、トークンが塞ぐために作られた穴を、そのまま返すことです。
ビュッフェ——一人あたりのパッケージ、タイマー、食べ残しの課金
ビュッフェは、一人前いくらのメニュー品目で偽装するのではなく、専用のモデルを持っています。パッケージには一人あたりの価格、スタッフが延長できるカウントダウンタイマー、そして大人と子どもの別々の人数があります。メニューの品目にはビュッフェ上の役割——込み、アラカルト専用、追加料金つきで込み——が付くので、一つのメニューで両方の客をさばけます。食べ残しの課金も単位ラベル付きで扱えます。タイのビュッフェの多くがすでに卓上に掲示していて、ソフトウェアの多くが存在しないふりをしているルールです。
デリバリー、テイクアウト、テーブル予約
同じメニューがデリバリーとテイクアウトも支えます。注文は配送料、業者、指定の受け取り時刻、住所を持ち、新規 → 調理中 → 配達中 → 完了と進み、遷移のたびに両側に通知が飛びます。テーブル予約は日付、時刻、人数、備考を取り、店の確認を待ちます。未確認の件数はダッシュボードにライブのバッジとして出ています。
値段はすべてサーバーで計算されます
残り全部を安全にしているルールがこれです。クライアントは決して価格を決めません。place_dining_order と place_shop_order は品目の価格、オプションの価格差、配送料をデータベースから読み、端末が送ってきた値を無視します。信用されたクライアントの項目は、セルフサービスの割引です。同じ原則がレビューにも及びます。店内で食べた、あるいはデリバリーを注文したとデータベースが証明できる客だけが書けて、星の評価はトリガーが再計算します。誰かが入力する数字ではありません。
席に着かせずに番号だけ配りたいお店は順番待ちを見てください。厨房の外で売るもの(持ち帰り、物販、作り置き)はネットショップ側で、店内と同じ在庫を読みます。
厨房とホールの今日の出勤と休みはマイ職場の同じシフト表から来ます。実際に動くお店を初めて開くところは店舗のかたへのページです。