PR #843 / Backend

サブプラン予約機能の実装

tenmane-hitori-wellness-be — feature/sub-plan-reservation → develop — +3,031 / -359 — 22 files

エンティティ構成
sub_plans
idPKint AUTO
studio_idFKint
namevarchar(100)
descriptiontext NULL
image_urlvarchar(500) NULL
duration_minutesint = 60
is_activebool = true
display_orderint = 0
deleted_attimestamp NULL
sub_plan_slots
idPKint AUTO
sub_plan_idFKint
day_of_weektinyint (0-6)
start_timetime
end_timetime
capacityint
is_activebool = true
sub_plan_slot_overrides
idPKint AUTO
sub_plan_idFKint
datedate
start_timetime
end_timetime
capacityint
sub_plan_reservations
idPKint AUTO
reservation_idFKint
sub_plan_idFKint
user_idFKvarchar(36)
studio_idFKint
reserved_datedate
start_time / end_timetime
statusRESERVED | CANCELLED | COMPLETED

リレーション

Studio SubPlan 1 : N — 店舗が複数のサブプランを持つ
SubPlan SubPlanSlot 1 : N — 曜日ごとの通常枠設定
SubPlan SubPlanSlotOverride 1 : N — 特定日の枠上書き(臨時変更用)
Reservation SubPlanReservation 1 : N — メイン予約に紐づくサブプラン予約
SubPlan SubPlanReservation 1 : N — どのサブプランの予約か
インデックス(Migration)
IDX sub_plans(studio_id)
IDX sub_plan_slots(sub_plan_id, day_of_week)
IDX sub_plan_slot_overrides(sub_plan_id, date)
IDX sub_plan_reservations(reservation_id)
IDX sub_plan_reservations(sub_plan_id, reserved_date, status)
IDX sub_plan_reservations(user_id)
IDX sub_plan_reservations(studio_id, reserved_date)
UNQ sub_plan_reservations(reservation_id, sub_plan_id)
SubPlan 管理 (Admin)
GET /sub-plans/stores/:storeId 店舗のサブプラン一覧 admin/staff reader
GET /sub-plans/:id サブプラン詳細(slots+overrides付き) admin/staff reader
POST /sub-plans/stores/:storeId サブプラン作成 admin/staff writer
PUT /sub-plans/:id サブプラン更新 admin/staff writer
DELETE /sub-plans/:id サブプラン削除(論理削除) admin
Slot 枠設定 (Admin)
GET /sub-plans/:subPlanId/slots 枠設定一覧 admin/staff reader
POST /sub-plans/:subPlanId/slots 枠設定作成 admin/staff writer
PUT /sub-plans/slots/:id 枠設定更新 admin/staff writer
DELETE /sub-plans/slots/:id 枠設定削除 admin/staff writer
Override 特定日上書き (Admin)
GET /sub-plans/:subPlanId/overrides 上書き設定一覧(?date_from, ?date_to) admin/staff reader
POST /sub-plans/:subPlanId/overrides 上書き設定作成 admin/staff writer
PUT /sub-plans/overrides/:id 上書き設定更新 admin/staff writer
DELETE /sub-plans/overrides/:id 上書き設定削除 admin/staff writer
予約検索 (Admin)
POST /sub-plans/stores/:storeId/reservations/search サブプラン予約検索(term_from / term_to) admin/staff reader
ユーザー向け
GET /sub-plans/public/stores/:storeId 公開サブプラン一覧(active のみ) メール確認済みユーザー
GET /sub-plans/:subPlanId/available-slots 空き枠取得(?date, ?reservation_start_time, ?reservation_end_time) メール確認済みユーザー
既存APIの拡張
POST /users/:userId/reservations 予約作成に sub_plan_reservations[] パラメータを追加 ユーザー
予約作成フロー(createReservation 拡張部分)

1. 仮予約作成(Transaction 1)

プログラム取得 → 既存仮予約削除 → PREACTIVE ステータスで Reservation 作成

2. バリデーション & ACTIVE化(Transaction 2)

時間帯チェック、重複チェック、定員チェックなど既存バリデーション実行後、仮予約を ACTIVE に更新

3. サブプラン予約作成(同一 Transaction 2 内)

sub_plan_reservations[] が渡された場合に実行。重複サブプランチェック → メイン予約時間範囲内かチェック → サブプランの存在確認 & 店舗一致確認 → Override / Slot から枠容量を取得 → 空きチェック → SubPlanReservation 作成(status: RESERVED)

4. RemoteLock API 呼び出し

トランザクション外で RemoteLock の Booking を作成。失敗時は予約を CANCELLED に戻し、チケットを復元

5. RemoteLock 情報更新 & 後処理

booking_id / pin / qr_code を reservation に保存(Transaction 3) → 通知購読 → QRコード生成 & アップロード → メール送信

キャンセルフロー(cancelReservation 拡張部分)

1. RemoteLock 解除 & キャンセル(単一 Transaction)

RemoteLock booking deactivate(失敗してもログのみで続行) → Reservation を CANCELLED に更新 → cancelSubPlanReservationsForReservation でサブプラン予約も一括 CANCELLED → チケット復元 → メール送信

空き枠取得フロー(getAvailableSlots)

1. 枠情報取得

対象日の曜日から SubPlanSlot(is_active)を取得。同日の SubPlanSlotOverride があればそちらを優先

2. 空き計算

各枠の時間帯に対して既存の RESERVED/COMPLETED 件数をカウント。capacity - reserved_count = available_count として返却。reservation_start_time / reservation_end_time が指定されていれば、メイン予約時間内の枠のみにフィルタ

入力バリデーション(Ajv スキーマ)

SubPlan 作成/更新

name: string, 1-100文字, 必須
description: string | null, 最大10,000文字
image_url: string | null, 最大500文字
duration_minutes: integer, 30-180, デフォルト60
is_active: boolean
display_order: integer, 0-9999

Slot 作成/更新

day_of_week: integer, 0-6 (日-土)
start_time: HH:mm 形式(正規表現)
end_time: HH:mm 形式(正規表現)
capacity: integer, 1-100
is_active: boolean

Override 作成/更新

date: string, date フォーマット
start_time / end_time: HH:mm 形式
capacity: integer, 0-100(0で実質閉鎖)

予約時のサブプラン入力

sub_plan_id: number(※ type:'integer' が望ましい)
start_time / end_time: HH:mm 形式
ビジネスロジック バリデーション

予約作成時チェック

同一予約内でサブプランの重複は不可
サブプラン予約時間がメイン予約時間内であること
サブプランが存在し、is_active=true であること
サブプランのstudio_idが予約先と一致すること
該当日時の枠容量に空きがあること

枠容量の決定ロジック

対象日の Override が存在すればそちらの capacity を使用
Override がなければ曜日の Slot の capacity を使用
マッチする Slot も Override もなければエラー

認可チェック

管理API: admin/staff ロール + スタジオ所属チェック(一部欠落あり)
削除API: admin ロールのみ(staffは不可)
ユーザー向けAPI: メールアドレス確認済みユーザー
新規ファイル
NEWcontrollers/dto/sub-plans.dto.ts+147
NEWcontrollers/sub-plans.controller.ts+611
NEWmodels/sub-plan.ts+66
NEWmodels/sub-plan-slot.ts+59
NEWmodels/sub-plan-slot-override.ts+44
NEWmodels/sub-plan-reservation.ts+85
NEWpersistence/sub-plans.persistence.ts+376
NEWservices/sub-plans.service.ts+557
NEWdatabase/migrations/create_sub_plans_tables.ts+372
NEWdatabase/seeder/seeds/sub_plans.ts+307
変更ファイル
MODcontrollers/dto/users.dto.ts+8
MODcontrollers/users.controller.ts+15
MODcontrollers/user-payment-methods.controller.ts-1 ログ絵文字削除
MODmodels/reservation.ts+5 OneToMany 追加
MODmodels/change-log.ts+8 テーブル名定義追加
MODmodels/index.ts+8 エンティティ登録
MODmodules/api.module.ts+2 Controller登録
MODpersistence/reservations.persistence.ts+2 JOIN追加
MODservices/reservations.service.ts+350 -356 大幅リファクタ + サブプラン連携
MODconfig/constants.ts+4
MODdatabase/seeder/index.ts+4 -1
MODdatabase/migrations/add_indexes_for_search_performance.ts+1 -1