導入(問題提起)

小規模店舗・クリニック・サロンの予約運用は、電話やExcel台帳、フォーム返信メールの転記に依存しがちです。営業時間外の取りこぼし、二重予約、前日リマインド漏れ、来店後のフォロー不足は、売上と顧客体験に直結する課題です。そこで、LINE公式アカウントとPythonで作る小さなWebシステム開発を組み合わせ、予約受付→確認→前日/当日リマインド→来店後フォロー(レビュー/再来促進)までを一気通貫で自動化します。Excel Web化の延長として始められ、段階的に高度化できるのが現実解です。

本記事では、業務システムとしての要件を整理し、設計・データモデル・API/Webhookの流れ・権限/RBAC・監査ログ・失敗時の救済まで、現場で止まらない仕組みを具体的に解説します。SEOキーワードである「Webシステム開発」「業務システム」「Python」「Excel Web化」を自然に織り込みつつ、実装可能な粒度で説明します。

課題の詳細説明

電話/メール/チャット/表計算の“多窓運用”は、以下のような構造的問題を生みます。

  • 二重予約・取り違い:単一の予約カレンダーがないため、同時刻に別チャネルで予約が入る。
  • リマインド漏れ:人手で配信するため、繁忙日や休業日前後で抜け漏れが発生。
  • スタッフ割当の属人化:スキルや設備要件を考慮した割当が暗黙知に依存。
  • 変更・取消の反映遅延:メールや電話での変更が台帳に即時反映されず、空き枠が死蔵。
  • 事前問診・事前決済の未整備:当日受付が滞留し、回転率や体験が低下。
  • 追客が続かない:来店後のレビュー依頼、次回予約の提案、休眠掘り起こしが断続的。

さらに、バックヤードでは次の技術・運用課題が顕在化します。

  • データが分散:Excel/スプレッドシート/外部予約サイト/LINEの間で二重入力。
  • 失敗が見えない:通知失敗、Webhook失敗、在庫(枠)更新失敗が検知しづらい。
  • セキュリティ/権限:個人情報を扱うが、最小権限や監査の仕組みが弱い。
  • 拡張性:店舗数・スタッフ数の増加、メニュー追加、クーポン・ポイント連携への追従が困難。

解決方法

解決の核心は「単一の真実源(Single Source of Truth)」と「イベント駆動」です。Pythonで小さな予約APIを立て、そこに全チャネル(LINE/フォーム/電話代行)を収束させます。その上で、Webhook/キュー/定時ジョブで時間的に起こる処理(リマインド/再来フォロー)を自動化します。

設計のポイント

  1. 予約SST(唯一の正)
  2. - 予約/顧客/メニュー/スタッフ/設備/枠(スロット)を1つのデータモデルに正規化。 - すべての変更はAPI経由。直接DB更新を禁止し、監査ログを必ず残す。

  3. LINE連携(Webhook)
  4. - 友だち登録→顧客IDとLINE userIdを紐付け。 - 予約/変更/取消・メニュー照会・空き枠確認をリッチメニューまたはキーワードで起動。

  5. 競合制御と在庫(枠)管理
  6. - トランザクションで二重予約を防止。スタッフ/設備の同時利用不可をロックで担保。

  7. 通知の体系化
  8. - 受付直後、前日、当日朝、来店後(レビュー/次回予約/クーポン)のテンプレート化とA/Bテスト。

  9. 運用の見える化
  10. - 無断キャンセル率、来店率、リマインド開封率、再来率をダッシュボードで可視化。

具体例

業種別の成功パターンをいくつか紹介します。

  • 美容室:スタッフ別カレンダーとメニュー別所要時間を考慮した枠生成。LINEで前日18時に自動通知+当日朝に再通知。無断キャンセル率を8%→3%に削減。
  • 整骨院・クリニック:初回のみWeb問診を事前入力し、電子同意を取得。当日受付時間を平均7分短縮し、回転率を向上。予約変更はLINEからワンタップで反映。
  • 教室・スクール:回数券残数と有効期限をLINEで配信。欠席時の振替枠を自動提案し、消化率を最大化。
  • レンタルスペース:設備(プロジェクタ/キッチン)の同時占有制御。入退室PINの自動発行と、利用前日通知をLINEで送付。

導入直後に効く“小さな工夫”

  • 「直前空き枠」の自動配信(特定ハッシュタグを付けて友だち一斉送信)。
  • 口コミURLを来店当日に自動送信(開封や投稿のトラッキング)。
  • 来店後7日でクーポン提案を自動送付(再来率の向上)。

技術的な解説

ここからは、Pythonでの最小実装イメージを示します。小規模で始めて、必要に応じて段階的に強化できます。

データモデル(例:SQLite→将来PostgreSQL)


-- 顧客
CREATE TABLE customer (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  phone TEXT,
  line_user_id TEXT UNIQUE,
  created_at TEXT
);

-- スタッフ/設備/メニューは割愛

-- 予約(スタッフ・設備の重複を禁止するためにユニーク制約を活用)
CREATE TABLE reservation (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  staff_id INTEGER,
  menu_id INTEGER,
  start_at TEXT NOT NULL,
  end_at TEXT NOT NULL,
  status TEXT CHECK(status IN ('pending','confirmed','canceled','completed')) NOT NULL DEFAULT 'confirmed',
  note TEXT,
  created_at TEXT,
  updated_at TEXT,
  UNIQUE(staff_id, start_at, end_at),
  FOREIGN KEY(customer_id) REFERENCES customer(id)
);

-- 監査ログ
CREATE TABLE audit_log (
  id INTEGER PRIMARY KEY,
  actor TEXT,
  action TEXT,
  entity TEXT,
  entity_id INTEGER,
  payload TEXT,
  created_at TEXT
);

BottleによるAPI/Webhook(最小例)


from bottle import Bottle, request, response
import sqlite3, json, datetime as dt

app = Bottle()

def db():
    return sqlite3.connect('app.db')

def audit(actor, action, entity, entity_id, payload):
    with db() as conn:
        conn.execute(
            'INSERT INTO audit_log(actor, action, entity, entity_id, payload, created_at) VALUES (?,?,?,?,?,?)',
            (actor, action, entity, entity_id, json.dumps(payload), dt.datetime.utcnow().isoformat())
        )

@app.post('/webhook/line')
def line_webhook():
    body = request.json or {}
    event = body.get('events', [{}])[0]
    user_id = event.get('source', {}).get('userId')
    text = event.get('message', {}).get('text', '')
    # 例: 「予約 2026-04-01 13:00 60分」をパース
    # 実務ではリッチメニューや日時選択UIを使用
    # ここでは最小処理だけを示す
    if text.startswith('予約'):
        start_at = parse_start(text)  # 実装省略
        end_at = calc_end(start_at, minutes=60)
        with db() as conn:
            cur = conn.execute('SELECT id FROM customer WHERE line_user_id=?', (user_id,))
            row = cur.fetchone()
            if not row:
                conn.execute('INSERT INTO customer(name, line_user_id, created_at) VALUES (?,?,?)',
                             ('LINEユーザー', user_id, dt.datetime.utcnow().isoformat()))
                customer_id = conn.execute('SELECT last_insert_rowid()').fetchone()[0]
            else:
                customer_id = row[0]
            conn.execute('INSERT INTO reservation(customer_id, start_at, end_at) VALUES (?,?,?)',
                         (customer_id, start_at, end_at))
        audit(user_id, 'create', 'reservation', None, {'start_at': start_at})
        return {'ok': True}
    return {'ok': False}

@app.get('/api/reservations')
def list_resv():
    with db() as conn:
        rows = conn.execute('SELECT id, customer_id, start_at, end_at, status FROM reservation ORDER BY start_at').fetchall()
    return {'items': [dict(zip(['id','customer_id','start_at','end_at','status'], r)) for r in rows]}

ジョブと通知設計

  • 予約作成時:即時受付メッセージ(詳細/地図/注意事項)をLINE通知。
  • 前日18時:翌日の予約者に一斉リマインド(テンプレート+変更/取消ボタン)。
  • 当日朝:再通知(短文)で来店率を底上げ。
  • 来店後:レビューURL/次回予約リンク/クーポン配布を自動。

RBAC/個人情報保護

  • 役割:管理者/受付/スタッフ/閲覧のみを分け、予約詳細(メモ/問診)へのアクセスを最小化。
  • ログ:予約の作成/変更/取消/閲覧をすべて監査。エクスポート時は誰が/いつ/どこへを記録。

失敗時の救済

  • Webhook失敗→デッドレターへ退避し、手動再送キューから復旧。
  • LINE APIエラー→指数バックオフで再試行、一定回数超過で運用チャットへ通知。

導入の流れ

  1. 現状把握(2週間)
  2. - 予約チャネル・台帳・リマインド運用・KPI(無断キャンセル率/来店率)を棚卸し。

  3. 最小実装(2–4週間)
  4. - 予約SST(API+DB)を構築。LINE連携は受付/前日通知に限定して開始。

  5. 本番化(2週間)
  6. - スタッフ割当/設備競合/問診/同意/テンプレA/Bを導入。監査・バックアップを整備。

  7. 拡張(継続)
  8. - クーポン/ポイント、会計・在庫・カレンダーAPI連携、ダッシュボード可視化を段階導入。

まとめ

  • 予約運用は「Webで確定・LINEで伴走」が最小コストで最大効果。
  • Pythonを用いた小規模な業務システムなら、Excel Web化の延長で始められ、必要十分な拡張性を確保できます。
  • 監査ログ・RBAC・失敗時の救済を最初に組み込み、運用で止まらない仕組みを作りましょう。

問い合わせ導線

LINE連携の予約管理・顧客フォローのWebシステム開発はお任せください。要件整理から段階導入まで支援します。お問い合わせ

Webシステム開発のご相談は monou まで