コンテンツにスキップ

営業時間・定休日・祝日

Document ID: SRS-MST-003 Parent: SRS-ROOT-001 v0.5 Version: 0.2 Status: Implemented (API + Web UI 最小) Last Updated: 2026-05-06 Depends on: SRS-TEN-001 v0.4, SRS-TEN-002 v0.2, SRS-TEN-003 v0.2 依存される: SRS-RES-002 v0.5, SRS-RES-003, SRS-RES-005 v0.4

本書は SRS-ROOT-001 v0.5 に従う。


店舗の営業時間判定に必要な 3 層ルールを確定する。

  1. day_override — 特定日例外
  2. holiday business hours — 祝日用
  3. weekday business hours — 平日用

本SRSは checkBusinessHours の canonical source である。

祝日判定は Phase 1 では @holiday-jp/holiday_jp ライブラリ依存とし、内閣府 CSV テーブル化は Phase 3 OQ。


  • As a 店長, I want to 曜日別営業時間を設定したい, so that 予約受付の基準を固定できる
  • As a 店長, I want to 祝日だけ別時間にしたい, so that 平日と祝日で運用を分けられる
  • As a 店長, I want to 年末年始だけ臨時休業にしたい, so that 単発例外を上書きできる
  • As a 受付, I want to 営業時間外登録時に明確な警告が欲しい, so that 誤操作を減らせる

3.1 主シナリオ(平日ルール更新)

Section titled “3.1 主シナリオ(平日ルール更新)”
  1. オペレーターが「設定 > 営業時間」を開く
  2. 曜日別 row を編集する
  3. 必要なら holiday row を追加または編集する
  4. PUT /api/admin/business-hours で collection replace する
  5. ETag 一致なら保存成功
  6. RES-002 / RES-003 の営業時間判定が新ルールを使う
  • AF-1 単日例外を登録: POST /api/admin/day-overrides
  • AF-2 単日例外を削除: DELETE /api/admin/day-overrides/:id
  • AF-3 holiday row 未設定: 祝日でも weekday row に fallback
  • EF-1 cross_midnight = true: Phase 1 では 422 BUSINESS_HOURS.CROSS_MIDNIGHT_NOT_SUPPORTED
  • EF-2 closed=false なのに open_at/close_at 欠落: 422
  • EF-3 closed=true なのに open_at/close_at あり: 422
  • EF-4 open_at >= close_at: 422
  • EF-5 ETag 不一致: 409
  • EF-6 同一 target_date override 重複: 409

「曜日別」「祝日別」「単日例外」を分けて設定し、予約可否判定の基準を一元化する。

  • 曜日別 business hours grid
  • holiday row toggle / editor
  • day override 一覧
  • day override 作成ダイアログ
  • 保存ボタン
  • ETag 競合時の再読込導線
  • weekday: ISO DOW 1..7
  • is_holiday: boolean
  • open_at, close_at: time
  • cross_midnight: Phase 1 は常に false
  • target_date: date

MethodPath用途permission楽観ロック
GET/api/admin/business-hours週次 + 祝日ルール取得admin:business_hours:read-
PUT/api/admin/business-hours週次 + 祝日ルール一括更新admin:business_hours:updateIf-Match: <collection_etag>
GET/api/admin/day-overrides?from=&to=単日例外一覧admin:business_hours:read-
POST/api/admin/day-overrides単日例外作成admin:business_hours:update-
DELETE/api/admin/day-overrides/:id単日例外削除admin:business_hours:update-
export type BusinessHoursRule = {
weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7;
isHoliday: boolean;
closed: boolean;
openAt: string | null;
closeAt: string | null;
crossMidnight: false;
};
export type DayOverride = {
targetDate: string;
closed: boolean;
openAt: string | null;
closeAt: string | null;
crossMidnight: false;
};
export type CheckBusinessHoursInput = {
startsAt: Temporal.Instant;
endsAt: Temporal.Instant;
timezone: 'Asia/Tokyo';
weeklyRules: BusinessHoursRule[];
dayOverride: DayOverride | null;
isHoliday: boolean;
};
export type CheckBusinessHoursOutput = {
ok: boolean;
appliedLayer: 'day_override' | 'holiday' | 'weekday' | 'none';
reason: 'inside' | 'closed' | 'outside_window' | 'cross_day' | 'missing_rule';
};
  1. day_override があれば最優先
  2. なければ isHoliday=true かつ (weekday, is_holiday=true) row があれば採用
  3. なければ (weekday, is_holiday=false) row を採用
  4. weekday row がない場合は missing_rule とし、実質 closed 扱い
code意味HTTP
BUSINESS_HOURS.COLLECTION_ETAG_MISMATCHETag 不一致409
BUSINESS_HOURS.TIME_RANGE_INVALIDopen/close 不正422
BUSINESS_HOURS.CROSS_MIDNIGHT_NOT_SUPPORTEDcross-midnight 禁止422
DAY_OVERRIDE.DUPLICATE_DATE同日重複409
DAY_OVERRIDE.NOT_FOUND行なし404

カラム制約
iduuidPK
store_iduuidNOT NULL, FK
weekdaysmallintNOT NULL, CHECK 1..7
is_holidaybooleanNOT NULL DEFAULT false
closedbooleanNOT NULL DEFAULT false
open_attimeNULL
close_attimeNULL
cross_midnightbooleanNOT NULL DEFAULT false, CHECK = false
created_attimestamptzNOT NULL
updated_attimestamptzNOT NULL
制約UNIQUE(store_id, id)
制約UNIQUE(store_id, weekday, is_holiday)
制約CHECK(closed=true → open_at IS NULL AND close_at IS NULL)
制約CHECK(closed=false → open_at IS NOT NULL AND close_at IS NOT NULL AND open_at < close_at)
カラム制約
iduuidPK
store_iduuidNOT NULL, FK
target_datedateNOT NULL
closedbooleanNOT NULL DEFAULT false
open_attimeNULL
close_attimeNULL
cross_midnightbooleanNOT NULL DEFAULT false, CHECK = false
memovarchar(200)NULL
created_attimestamptzNOT NULL
updated_attimestamptzNOT NULL
制約UNIQUE(store_id, id)
制約UNIQUE(store_id, target_date)
制約同上の closed/open/close 整合 CHECK

0015_business_hours_and_day_overrides.sql で 2 テーブル新設、RLS / GRANT、permission backfill (admin:business_hours:read/update)。


  • Phase 1 は @holiday-jp/holiday_jp を canonical provider とする
  • checkBusinessHours は holiday 判定結果を input として受ける
  • holiday provider 自体を domain 純関数に埋め込まない
  • 比較は Asia/Tokyo の local date/time で行う
  • 半開区間 [open_at, close_at) を採用
  • starts_atends_at が別日になる candidate は Phase 1 では cross_day / 不可
  • day_override は必ず強い
  • holiday row は存在するときのみ weekday より優先
  • holiday row 不在時の fallback を認める
  • 必須初期データは weekday 7 行。holiday 7 行は任意
  • checkBusinessHours判定のみ
  • force_business_hours permission による押し通し可否は RES-002 の責務
  • 本SRSは ok=false, reason='outside_window' 等を返すだけ
  • business_hours.replace
  • day_override.create / delete

  • GET /api/admin/business-hours は常に全件返却
  • GET /api/admin/day-overrides は date range 必須、最大 62 日

key用途初期付与
admin:business_hours:read週次・祝日ルール・単日例外読取全 role
admin:business_hours:update週次・祝日ルール・単日例外編集owner / manager

RLS / FORCE / 複合 FK / GRANT 明示は必須。


10. 受け入れ基準(Given-When-Then)

Section titled “10. 受け入れ基準(Given-When-Then)”
  1. Given weekday 月曜 10:00-18:00 / When 月曜 11:00-12:00 を判定 / Then ok=true, appliedLayer='weekday'
  2. Given weekday 月曜 10:00-18:00 / When 月曜 18:00-18:30 を判定 / Then ok=false, reason='outside_window'
  3. Given 月曜 weekday 10:00-18:00 と holiday 月曜 12:00-17:00 / When 祝日月曜 11:00-12:00 / Then holiday row が優先され ok=false
  4. Given 月曜 weekday 10:00-18:00 と holiday 月曜 12:00-17:00 / When 祝日月曜 12:00-13:00 / Then ok=true, appliedLayer='holiday'
  5. Given holiday row 不在 / When 祝日火曜を判定 / Then weekday 火曜 row に fallback する
  6. Given weekday open だが day_override.closed=true / When 対象日を判定 / Then appliedLayer='day_override' で closed
  7. Given weekday closed だが day_override が open / When 対象日を判定 / Then day_override が勝つ
  8. Given cross_midnight=true payload / When 保存 / Then 422 BUSINESS_HOURS.CROSS_MIDNIGHT_NOT_SUPPORTED
  9. Given stale ETag / When PUT /api/admin/business-hours / Then 409
  10. Given target_date 既存 / When duplicate day override 作成 / Then 409 DAY_OVERRIDE.DUPLICATE_DATE
  11. Given 店舗 X セッション / When 店舗 Y row 取得 / Then 0 件または 404
  12. Given RES-002 が営業時間外候補を保存 / When checkBusinessHoursok=false を返す / Then RES-002 側で force_business_hours permission 判定に進める

  • Unit: priority resolution, holiday fallback, half-open boundary
  • Integration: PUT with ETag, duplicate override, RLS, holiday provider wiring
  • Cross-SRS: RES-002 force flow と結合

  • なし

#内容扱い
OQ-MST-003-01holiday row を UI 上 7 本見せるか単一テンプレに見せるかUI 設計課題
OQ-MST-003-02Phase 2 の cross-midnight 有効化時の time 型Phase 2
OQ-MST-003-03内閣府 CSV テーブル化への移行Phase 3

VersionDateAuthorChange
0.12026-05-05Codex / yudai初版ドラフト
0.22026-05-05yudai (with Codex co-design)Round 2 反映: migration 番号 0015 確定、weekday を ISO DOW (1..7) + is_holiday boolean に確定、business_hours/day_override を UUIDv7 へ(親 §7.2.2 例外から削除)、cross_midnight=false 固定