作品一覧へ増田 和樹制作事例 / 02
制作事例 02 — 個人SaaS / 予約・スクール運営運営業務を、画面・データ・確認条件へ

LESTIQ企画と設計の
記録

予約の要望を、 条件と例外へ具体化する。

個人で企画・開発し、本番公開した予約管理SaaS。予約画面の背後にある受講権、重複、残数の扱いを、画面とDB設計から紹介します。

PostgreSQLマルチテナント予約・チケット
設計の過程を読む
管理画面 / 今日のレッスン・運営状況出典
課題の整理 → 構成の設計 → 技術の選択画像・データ構造・説明用デモで読み解く
01 / 課題から仕様・成果物へ

要望を整理し、
確かめられる形に。

  1. 要望・設計上の問い

    設計上の問い:予定を登録するだけでなく、受講できる条件、重複予約、キャンセル後の残数をどう扱うか。

  2. 要件・仕様に変える

    予約と受講権を分け、同じ開始枠の重複制約とチケットの増減履歴を持つ。開始時刻が異なる重複は別の確認条件にする。

    設計判断を読む
  3. 成果物で確かめる

    公式デモの予約・受講者画面。バックアップDBでは、同じ開始枠の重複を止める条件、チケット残数と増減履歴を確認できます。

    実画面を見る

この事例の担当範囲

個人で企画し、生成AIを活用して設計・実装から本番公開まで担当。予約・受講権の画面と保存設計を紹介します。

使う人:オンライン英会話スクール運営者 / 講師 / 受講者
02 / 体験と処理の流れ

画面の操作と、
裏側の処理をつなぐ。

公式デモ画面 / 週間スケジュール出典
画面の見どころ / 01

予約できる時間を、ひと目で。

  1. 01週の予定と対応可能な枠を可視化。
  2. 02予約を講師・受講者のデータと結びつける。
画面を押すと拡大できます
操作に対応する内部処理を読む
利用者の操作内部の処理
  1. 01

    スクールを整える

    運営者が講師・受講者・ブランドを管理する。

    内部の処理

    テナントを軸に利用者や講師プロフィールを関連づける。

  2. 02

    受講権を持つ

    利用可能なチケットと期限を確認する。

    内部の処理

    ウォレットに残数・総数・期限、履歴に増減と処理後残高を保持。

  3. 03

    レッスンを予約する

    講師と開始枠を選ぶ。

    内部の処理

    予約は講師・受講者・テナントを参照。同一開始枠の重複は部分一意制約の対象。

  4. 04

    授業後につなぐ

    授業記録や次の課題を振り返る。

    内部の処理

    授業ノート、宿題、学習目標、連続学習日数などを記録する構造。

03 / AI・APIの役割

サービスの間を、
APIでつなぐ。

スクール運営を支えるAPI連携

予約の先を、 外部APIでつなぐ。

StripeZoom APIGoogle CalendarLINE
公式デモ画面 / チケット設定・テスト商品出典
  1. 01予約・受講権

    レッスンとチケットを管理

  2. 02外部サービスと連携

    決済・会議URL・カレンダー

  3. 03受講と運営を支える

    予約確認・リマインド・通知

AI・APIの役割と、確認できる範囲

予約を起点に、決済、オンライン会議、カレンダー、通知へ。スクール運営で行き来するサービスを連携させるSaaSです。

Stripeで決済をつなぐ

公式サイトでStripe決済とサブスクリプション連携を紹介。DBではチケットやウォレットを運営データとして扱う。

会議と予定をつなぐ

公式機能としてZoom APIによる会議URLの発行、Google Calendar同期を紹介。予定を登録した後の受講準備までを対象にする。

LINEで予定を知らせる

公開ガイドではLINE連携による予約確認・リマインド・キャンセル通知を説明。予約データと連絡の流れを結びつける。

本番公開済みのサービスです。連携機能は公式サイト・ガイド、保存構造はDB設計をもとに紹介しています。

04 / データのつながり

体験を支える、
データの関係。

  1. 予約・受講者講師・日時を選ぶ
  2. 予約と受講権PostgreSQLで関連づけ
  3. 外部サービスStripe・Zoom・通知

予約の前後に続く運営まで、一つの流れに。

資料から整理した構成図
データ項目と関連を詳しく見る
データ構成図

項目を選ぶと、そのデータにつながる関係を確認できます。

主要なデータの関係

外部キー キー・保存先
  • userstenant_id接続先:tenants
  • coach_profilesuser_id接続先:users
  • bookingscoach_id / student_id接続先:users
  • ticket_walletsstudent_id接続先:users
  • ticket_historywallet_id接続先:ticket_wallets
  • ticket_historybooking_id接続先:bookings

主要テーブルの関係を抜粋。実線はSQL定義の外部キーです。図では予約・受講権の保存構造を示し、画面とDBをつなぐAPI処理は省略しています。

05 / 設計判断と制約

その設計には、
理由と制約がある。

画面の先まで考える

複数の申請が同じ開始枠を取り合う場面でも、保存先に一意性の条件を持たせられる。

01

同じ開始枠を、DB側でも重複させない。

しくみ
テナント・講師・日付・開始時刻の組に、キャンセル以外の予約を対象とする部分ユニークインデックスを定義。
この構成でできること
複数の申請が同じ開始枠を取り合う場面でも、保存先に一意性の条件を持たせられる。
制約・考慮点
終了時刻は条件に含まれない。開始時刻が異なる時間帯の重複は、この制約だけでは防げない。

参照:lestiq.sql / bookings_coach_slot_unique

02

残数と、残数が変わった理由を分ける。

しくみ
ticket_walletsに残数・期限を持ち、ticket_historyに購入・利用・付与・返却などの増減と予約IDを残す。
この構成でできること
いま何回受講できるかと、なぜその残数になったかを別々に説明できる。
制約・考慮点
残数と履歴を同じトランザクションで更新するか、キャンセル時の二重返却をどう防ぐかはAPI側の確認が必要。

参照:lestiq.sql / ticket_wallets・ticket_history

03

予約がなくなっても、履歴を参照できる余地を残す。

しくみ
履歴から予約への外部キーはON DELETE SET NULL。予約の削除時に、その参照を空にできる。
この構成でできること
予約情報の削除と、チケット増減の記録を切り離せる。
制約・考慮点
ウォレットの削除時は履歴もCASCADE削除される。不変の監査台帳として設計されているわけではない。

参照:lestiq.sql / ticket_historyの参照制約

06 / 場面ごとの動き

こんなとき、
どう動く?

ボタンで見る、しくみの例

同じ時間に、予約が重なったら?

予約の重複を防ぐチェックを、4つの例で紹介します。説明用のため、実際の予約は入りません。

下の状況を選ぶと、図や説明が切り替わります。

同じスクール・講師・日の予約を比べる例
すでにある予約
予約なし
新しく入れたい予約
10:00〜11:00
この例の結果

同じ時刻の予約がないので、重複チェックを通ります。

10時からの予約はまだありません。このチェックは通りますが、予約の確定にはチケットや営業時間などの確認も必要です。

しくみと、実運用で確認したいこと
  1. 10時に予約を希望この例では完了・保持
  2. 同じ開始時刻を確認この例の注目点
  3. 重複なしこの例では完了・保持
新しく入れたい予約
スクールA / 講師A / 10月5日 10:00
同じ条件の予約
なし
同じ開始時刻のチェック
重複なし

実運用で確認したいことチケットの有無・有効期限・営業時間も確認する必要があります。

07 / 担当と次の検証

担当したことと、
今後の改善。

この作品で取り組んだこと

01

要件を切り分ける

予約だけで完結しないスクール運営を、機能と情報の関係へ整理する。

02

設計・実装から本番公開へ

目的・仕様・画面構成を自分で決め、コード作成に生成AIを活用。画面と処理を動かして検証・改善し、本番公開まで担当する。

03

確認条件を説明する

予約の重複、受講権、履歴をDB構造に対応づけ、同時予約や残数更新の失敗を次に確かめる条件として示す。

掲載資料と確認できる範囲

画面で示せること

公式サイトが公開する管理・スケジュールのデモ画面。表示名称や数値はデモとして掲載。

DBで示せること

PostgreSQL 16.13の予約・チケットの関係、外部キー、同一開始枠の部分一意制約を確認。業務の条件と保存上の制約を対応づけています。

動作確認の範囲

本番環境へ公開済みです。掲載の図と条件切替デモは、予約と受講権の保存設計を説明するためのものです。

運用と動作確認

運用で確かめる観点

  1. 同時予約の拒否を画面まで一貫して伝える。
  2. 異なる開始時刻の重複や期限切れチケットを確認する。
  3. 残数と履歴の更新途中で失敗した場合、二重消費・返却が起きないか確認する。