学園祭のテーブルオーダーシステムを作成してみた！

# 学園祭で使うテーブルオーダーシステムを作成してみた

こんにちは！今回は学園祭の模擬店向けに作成したテーブルオーダーシステムについて紹介します。

技術系ではない１大学生が学園祭模擬店の音楽喫茶で使用するためのテーブルオーダーシステムを開発してみました。独特な支払い、注文形態に適応するための工夫等を記事にしてみました。
簡単な技術で実装が可能です。
フロントエンドにHTML/CSS/JavaScript、バックエンドにPHPを使用しています。

## システム概要

このシステムは以下の機能を持っています：

1. お客様がスマホから商品を選んで注文
2. 卓番ごとに注文を管理
3. キッチン側で注文状況の確認・管理
4. 注文履歴の表示
5. 在庫管理

## 画面構成

- index.html - お客様用の注文画面
- order-status.html - 注文状況確認画面
- manage.html - 店舗スタッフ用の管理画面

## 技術スタック

- フロントエンド: HTML, CSS, JavaScript
- バックエンド: PHP
- データベース: MySQL

## 実装のポイント

### 1. お客様側の注文画面

お客様は卓番を入力し、メニューから商品を選択してカートに入れることができます。フードとドリンクはセットでの注文を想定しています。

```html
<!-- index.htmlの一部 -->
<div class="container">
    <div id="tableNumber"></div>
    <button id="table-number-button" style="display: none;">卓番を入力</button>
    <div id="logo">
        <img src="logo.png" alt="logo" id="logo_img">
    </div>

    <div class="menu">
        <h2>フードメニュー</h2>
        <div id="foodProduct-list">
            <!-- 商品一覧がここに表示される -->
        </div>
        <h2>ドリンクメニュー</h2>
        <div id="drinkProduct-list">
            <!-- 商品一覧がここに表示される -->
        </div>
        <button id="cart-toggle-button" class="cart-toggle">カートを表示</button>
    </div>
    <div class="cart">
        <h2>カートの中身</h2>
        <div id="cart-items">
            <!-- カートの中身がここに表示される -->
        </div>
        <button id="order-button">注文する</button>
        <button id="cart-close-button" class="cart-toggle">カートを閉じる</button>
    </div>
</div>
```

### 2. 注文処理のJavaScript

カートに商品を追加して注文を確定する処理です。在庫チェックも行います。

```javascript
// script.jsの重要な部分
function proceedWithOrder() {
    const orderData = {
        tableNumber: tableNumber,
        items: [...cartContentFood, ...cartContentDrink].map(item => ({
            name: item.name,
            quantity: item.quantity
        }))
    };

    // 在庫チェックを行う
    fetch('php/check_stock.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(orderData)
    })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 在庫が十分な場合に注文を確定
                placeOrder(orderData);
            } else {
                // 在庫が不足している商品がある場合
                let insufficientMessage = '在庫が不足している商品があります:\n';
                data.insufficient_items.forEach(item => {
                    insufficientMessage += `${item.name}: 注文数 ${item.quantity}, 在庫数 ${item.stock}\n`;
                });
                swal({
                    title: "在庫不足",
                    text: insufficientMessage,
                    icon: "error",
                });
            }
        })
        .catch(error => {
            // エラーハンドリング
        });
}
```

### 3. 注文状況確認画面

お客様が注文後に遷移する画面で、注文状況をリアルタイムに確認できます。

```html
<!-- order-status.htmlの一部 -->
<div class="container">
    <div id="logo">
        <img src="logo.png" alt="logo" id="logo_img">
    </div>
    
    <h1 style="margin: 0; padding: 0;">ただいま準備中です。</h1>
    <h2 style="margin: 0; padding: 0;">しばらくお待ちください...</h2>
    
    <div class="order-info">
        <div id="order-number"></div>
        <div id="table-number"></div>
    </div>
    
    <h2>ご注文内容</h2>
    <div id="order-items">
        <!-- 注文内容がここに表示されます -->
    </div>
    
    <div id="status-message" class="preparing">
        <p>調理中...</p>
    </div>
</div>
```

```javascript
// order-status.jsの一部
function checkOrderStatus() {
    fetch(`php/get_order_status.php?uid=${uid}&order_id=${orderId}`)
        .then(response => {
            if (!response.ok) {
                throw new Error('注文情報の取得に失敗しました');
            }
            return response.json();
        })
        .then(data => {
            // 注文内容を表示
            renderOrderItems(data.items);
            
            // 全アイテムが完了したら状態メッセージを更新
            if (data.all_completed) {
                statusMessageElement.className = 'ready';
                statusMessageElement.innerHTML = '<p>ご注文の準備ができました！</p>';
            } else {
                // まだ準備中の場合はアニメーションを更新
                updatePreparingAnimation();
            }
        })
        .catch(error => {
            // エラーハンドリング
        });
}
```

### 4. 店舗スタッフ用の管理画面

注文の確認、調理状態の更新、在庫管理など店舗スタッフ向けの機能を提供します。

```html
<!-- manage.htmlの一部 -->
<h1>注文管理画面</h1>
<button id="toggleViewButton">表示スタイルを切り替え</button>
<div class="container" id="ordersContainer">
    <!-- 注文ごとのテーブルがここに挿入されます -->
</div>

<h1>統計情報</h1>
<div class="container" id="stockContainer">
    <table id="stockTable">
        <thead>
            <tr>
                <th>商品名</th>
                <th>残り</th>
                <th>注文数</th>
                <th>入荷数</th>
                <th>セット</th>
            </tr>
        </thead>
        <tbody>
            <!-- 統計情報がここに表示されます -->
        </tbody>
    </table>
</div>
```

```javascript
// manage.jsの一部
function fetchOrders(skipColorChange = false) {
    fetch("php/get_orders.php")
        .then(response => response.json())
        .then(data => {
            const ordersContainer = document.getElementById("ordersContainer");
            const compareContent = ordersContainer.innerText;

            ordersContainer.innerHTML = ''; // 既存のコンテンツをクリア

            // order_idごとにデータをグループ化
            const groupedOrders = data.reduce((groups, order) => {
                if (!groups[order.order_id]) {
                    groups[order.order_id] = [];
                }
                groups[order.order_id].push(order);
                return groups;
            }, {});

            // グループ化された注文ごとにテーブルを作成
            Object.keys(groupedOrders).forEach(order_id => {
                // テーブルの生成処理
            });

            // 更新があった場合、背景色を変更して注意を引く
            if (!skipColorChange && compareContent !== ordersContainer.innerText) {
                document.body.style.backgroundColor = '#eeff00';
                setTimeout(() => {
                    document.body.style.backgroundColor = '';
                }, 1000);
            }
        })
        .catch(error => console.error(error));
}
```

### 5. バックエンド処理（PHP）

注文を受け取り、データベースに保存する処理例です。

```php
// place_order.phpの例
<?php
require 'db.php';

// 受け取ったJSONデータをデコード
$data = json_decode(file_get_contents('php://input'), true);

try {
    // UIDを生成
    $uid = uniqid();
    $tableNumber = $data["tableNumber"];
    $orders = $data["items"];

    // orderStatsテーブルに挿入
    $stmt = $pdo->prepare('INSERT INTO orderStats (uid, tableNumber) VALUES (?, ?)');
    if ($stmt->execute([$uid, $tableNumber])) {
        $last_id = $pdo->lastInsertId();
    } else {
        throw new Exception('Failed to insert into orderStats');
    }

    // orderStatsDetailテーブルに各注文アイテムを挿入
    $stmt = $pdo->prepare('INSERT INTO orderStatsDetail (uid, menu, quantity) VALUES (?, ?, ?)');
    foreach ($orders as $order) {
        if (isset($order['name']) && isset($order['quantity'])) {
            $stmt->execute([$uid, $order['name'], $order['quantity']]);
        } else {
            throw new Exception('Invalid order item data');
        }
    }

    // 成功した場合のレスポンス
    echo json_encode(['success' => true, 'order_number' => $last_id, 'uid' => $uid]);

} catch (Exception $e) {
    echo json_encode(['error' => $e->getMessage()]);
}
?>
```

## 学園祭での運用ポイント

### 1. 在庫管理の工夫

在庫数と注文数を管理することで、売り切れを防ぎながら効率的な販売が可能になりました。また、フードとドリンクをセットで管理することで、バランスの良い提供ができました。

### 2. 注文状況の視覚化

調理スタッフ向けに注文状況が一目でわかるよう、状態によって色分けを行いました。また、新しい注文があった際には背景色を一時的に変更して注意を引くようにしました。

### 3. ユーザーフレンドリーなUI

お客様が迷わず操作できるよう、シンプルなUIを心がけました。特に、カートの操作やステータス確認画面では、直感的に使えるアニメーション効果を取り入れています。

### 4.テーブル上のQRコードから注文

テーブルごとにQRコードを配置し、卓版が自動で取得されるようにしました。
これによってお客様は卓番を意識せずに注文に入ることができます。

## 今後の開発や課題
今回の開発では支払いは完全にお客様への信用で成り立っていました。
均一料金前払いを導入していましたが、今後は受付にて支払い次にQRコードを発行&アクティベートし支払った分のみしか注文できないようにするなどが考えられます。

## まとめ

学園祭というイベント向けのテーブルオーダーシステムを開発しました。当日は多くの来場者に利用していただき、スムーズな運営に貢献できたと思います。

このシステムは、小規模な飲食店やイベントでも応用できる設計になっていますので、ぜひ参考にしてみてください！

## 参考情報

- [SweetAlert](https://sweetalert.js.org/) - 使いやすいアラート表示ライブラリ