導入(問題提起)
小規模店舗・クリニック・サロンの予約運用は、電話や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/キュー/定時ジョブで時間的に起こる処理(リマインド/再来フォロー)を自動化します。
設計のポイント
- 予約SST(唯一の正)
- LINE連携(Webhook)
- 競合制御と在庫(枠)管理
- 通知の体系化
- 運用の見える化
- 予約/顧客/メニュー/スタッフ/設備/枠(スロット)を1つのデータモデルに正規化。 - すべての変更はAPI経由。直接DB更新を禁止し、監査ログを必ず残す。
- 友だち登録→顧客IDとLINE userIdを紐付け。 - 予約/変更/取消・メニュー照会・空き枠確認をリッチメニューまたはキーワードで起動。
- トランザクションで二重予約を防止。スタッフ/設備の同時利用不可をロックで担保。
- 受付直後、前日、当日朝、来店後(レビュー/次回予約/クーポン)のテンプレート化とA/Bテスト。
- 無断キャンセル率、来店率、リマインド開封率、再来率をダッシュボードで可視化。
具体例
業種別の成功パターンをいくつか紹介します。
- 美容室:スタッフ別カレンダーとメニュー別所要時間を考慮した枠生成。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エラー→指数バックオフで再試行、一定回数超過で運用チャットへ通知。
導入の流れ
- 現状把握(2週間)
- 最小実装(2–4週間)
- 本番化(2週間)
- 拡張(継続)
- 予約チャネル・台帳・リマインド運用・KPI(無断キャンセル率/来店率)を棚卸し。
- 予約SST(API+DB)を構築。LINE連携は受付/前日通知に限定して開始。
- スタッフ割当/設備競合/問診/同意/テンプレA/Bを導入。監査・バックアップを整備。
- クーポン/ポイント、会計・在庫・カレンダーAPI連携、ダッシュボード可視化を段階導入。
まとめ
- 予約運用は「Webで確定・LINEで伴走」が最小コストで最大効果。
- Pythonを用いた小規模な業務システムなら、Excel Web化の延長で始められ、必要十分な拡張性を確保できます。
- 監査ログ・RBAC・失敗時の救済を最初に組み込み、運用で止まらない仕組みを作りましょう。
問い合わせ導線
LINE連携の予約管理・顧客フォローのWebシステム開発はお任せください。要件整理から段階導入まで支援します。お問い合わせ
Webシステム開発のご相談は monou まで