営業時間・定休日・祝日
営業時間・定休日・祝日
Section titled “営業時間・定休日・祝日”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 層ルールを確定する。
day_override— 特定日例外- holiday business hours — 祝日用
- weekday business hours — 平日用
本SRSは checkBusinessHours の canonical source である。
祝日判定は Phase 1 では @holiday-jp/holiday_jp ライブラリ依存とし、内閣府 CSV テーブル化は Phase 3 OQ。
2. ユーザーストーリー
Section titled “2. ユーザーストーリー”- 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. ユースケース
Section titled “3. ユースケース”3.1 主シナリオ(平日ルール更新)
Section titled “3.1 主シナリオ(平日ルール更新)”- オペレーターが「設定 > 営業時間」を開く
- 曜日別 row を編集する
- 必要なら holiday row を追加または編集する
PUT /api/admin/business-hoursで collection replace する- ETag 一致なら保存成功
- RES-002 / RES-003 の営業時間判定が新ルールを使う
3.2 代替フロー
Section titled “3.2 代替フロー”- AF-1 単日例外を登録:
POST /api/admin/day-overrides - AF-2 単日例外を削除:
DELETE /api/admin/day-overrides/:id - AF-3 holiday row 未設定: 祝日でも weekday row に fallback
3.3 例外フロー
Section titled “3.3 例外フロー”- EF-1
cross_midnight = true: Phase 1 では 422BUSINESS_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_dateoverride 重複: 409
4. UI仕様
Section titled “4. UI仕様”4.1 画面の目的
Section titled “4.1 画面の目的”「曜日別」「祝日別」「単日例外」を分けて設定し、予約可否判定の基準を一元化する。
4.2 主要要素
Section titled “4.2 主要要素”- 曜日別 business hours grid
- holiday row toggle / editor
- day override 一覧
- day override 作成ダイアログ
- 保存ボタン
- ETag 競合時の再読込導線
4.3 バリデーション
Section titled “4.3 バリデーション”weekday: ISO DOW 1..7is_holiday: booleanopen_at,close_at:timecross_midnight: Phase 1 は常に falsetarget_date: date
5. API仕様
Section titled “5. API仕様”5.1 エンドポイント一覧
Section titled “5.1 エンドポイント一覧”| Method | Path | 用途 | permission | 楽観ロック |
|---|---|---|---|---|
| GET | /api/admin/business-hours | 週次 + 祝日ルール取得 | admin:business_hours:read | - |
| PUT | /api/admin/business-hours | 週次 + 祝日ルール一括更新 | admin:business_hours:update | If-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 | - |
5.2 checkBusinessHours 契約
Section titled “5.2 checkBusinessHours 契約”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';};5.3 優先度ルール
Section titled “5.3 優先度ルール”day_overrideがあれば最優先- なければ
isHoliday=trueかつ(weekday, is_holiday=true)row があれば採用 - なければ
(weekday, is_holiday=false)row を採用 - weekday row がない場合は
missing_ruleとし、実質 closed 扱い
5.4 エラーコード
Section titled “5.4 エラーコード”| code | 意味 | HTTP |
|---|---|---|
BUSINESS_HOURS.COLLECTION_ETAG_MISMATCH | ETag 不一致 | 409 |
BUSINESS_HOURS.TIME_RANGE_INVALID | open/close 不正 | 422 |
BUSINESS_HOURS.CROSS_MIDNIGHT_NOT_SUPPORTED | cross-midnight 禁止 | 422 |
DAY_OVERRIDE.DUPLICATE_DATE | 同日重複 | 409 |
DAY_OVERRIDE.NOT_FOUND | 行なし | 404 |
6. データモデル影響
Section titled “6. データモデル影響”6.1 スキーマ
Section titled “6.1 スキーマ”business_hours
Section titled “business_hours”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
store_id | uuid | NOT NULL, FK |
weekday | smallint | NOT NULL, CHECK 1..7 |
is_holiday | boolean | NOT NULL DEFAULT false |
closed | boolean | NOT NULL DEFAULT false |
open_at | time | NULL |
close_at | time | NULL |
cross_midnight | boolean | NOT NULL DEFAULT false, CHECK = false |
created_at | timestamptz | NOT NULL |
updated_at | timestamptz | NOT 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) |
day_override
Section titled “day_override”| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
store_id | uuid | NOT NULL, FK |
target_date | date | NOT NULL |
closed | boolean | NOT NULL DEFAULT false |
open_at | time | NULL |
close_at | time | NULL |
cross_midnight | boolean | NOT NULL DEFAULT false, CHECK = false |
memo | varchar(200) | NULL |
created_at | timestamptz | NOT NULL |
updated_at | timestamptz | NOT NULL |
| 制約 | UNIQUE(store_id, id) | |
| 制約 | UNIQUE(store_id, target_date) | |
| 制約 | 同上の closed/open/close 整合 CHECK |
6.2 マイグレーション計画
Section titled “6.2 マイグレーション計画”0015_business_hours_and_day_overrides.sql で 2 テーブル新設、RLS / GRANT、permission backfill (admin:business_hours:read/update)。
7. 業務ルール
Section titled “7. 業務ルール”7.1 holiday 判定
Section titled “7.1 holiday 判定”- Phase 1 は
@holiday-jp/holiday_jpを canonical provider とする checkBusinessHoursは holiday 判定結果を input として受ける- holiday provider 自体を domain 純関数に埋め込まない
7.2 時間帯判定
Section titled “7.2 時間帯判定”- 比較は
Asia/Tokyoの local date/time で行う - 半開区間
[open_at, close_at)を採用 starts_atとends_atが別日になる candidate は Phase 1 ではcross_day/ 不可
7.3 day_override > holiday > weekday
Section titled “7.3 day_override > holiday > weekday”day_overrideは必ず強い- holiday row は存在するときのみ weekday より優先
- holiday row 不在時の fallback を認める
- 必須初期データは weekday 7 行。holiday 7 行は任意
7.4 RES-002 との境界
Section titled “7.4 RES-002 との境界”checkBusinessHoursは判定のみforce_business_hourspermission による押し通し可否は RES-002 の責務- 本SRSは
ok=false, reason='outside_window'等を返すだけ
7.5 監査
Section titled “7.5 監査”business_hours.replaceday_override.create / delete
8. 非機能要件
Section titled “8. 非機能要件”GET /api/admin/business-hoursは常に全件返却GET /api/admin/day-overridesは date range 必須、最大 62 日
9. セキュリティ・認可
Section titled “9. セキュリティ・認可”| 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)”- Given weekday 月曜 10:00-18:00 / When 月曜 11:00-12:00 を判定 / Then
ok=true,appliedLayer='weekday' - Given weekday 月曜 10:00-18:00 / When 月曜 18:00-18:30 を判定 / Then
ok=false,reason='outside_window' - Given 月曜 weekday 10:00-18:00 と holiday 月曜 12:00-17:00 / When 祝日月曜 11:00-12:00 / Then holiday row が優先され
ok=false - Given 月曜 weekday 10:00-18:00 と holiday 月曜 12:00-17:00 / When 祝日月曜 12:00-13:00 / Then
ok=true,appliedLayer='holiday' - Given holiday row 不在 / When 祝日火曜を判定 / Then weekday 火曜 row に fallback する
- Given weekday open だが
day_override.closed=true/ When 対象日を判定 / ThenappliedLayer='day_override'で closed - Given weekday closed だが
day_overrideが open / When 対象日を判定 / Thenday_overrideが勝つ - Given
cross_midnight=truepayload / When 保存 / Then 422BUSINESS_HOURS.CROSS_MIDNIGHT_NOT_SUPPORTED - Given stale ETag / When
PUT /api/admin/business-hours/ Then 409 - Given
target_date既存 / When duplicate day override 作成 / Then 409DAY_OVERRIDE.DUPLICATE_DATE - Given 店舗 X セッション / When 店舗 Y row 取得 / Then 0 件または 404
- Given RES-002 が営業時間外候補を保存 / When
checkBusinessHoursがok=falseを返す / Then RES-002 側でforce_business_hourspermission 判定に進める
11. テスト計画
Section titled “11. テスト計画”- Unit: priority resolution, holiday fallback, half-open boundary
- Integration: PUT with ETag, duplicate override, RLS, holiday provider wiring
- Cross-SRS: RES-002 force flow と結合
12. 関連ジョブ(graphile-worker)
Section titled “12. 関連ジョブ(graphile-worker)”- なし
13. Open Questions
Section titled “13. Open Questions”| # | 内容 | 扱い |
|---|---|---|
| OQ-MST-003-01 | holiday row を UI 上 7 本見せるか単一テンプレに見せるか | UI 設計課題 |
| OQ-MST-003-02 | Phase 2 の cross-midnight 有効化時の time 型 | Phase 2 |
| OQ-MST-003-03 | 内閣府 CSV テーブル化への移行 | Phase 3 |
14. 変更履歴
Section titled “14. 変更履歴”| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-05-05 | Codex / yudai | 初版ドラフト |
| 0.2 | 2026-05-05 | yudai (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 固定 |