コンテンツにスキップ

店舗作成・初期設定

Document ID: SRS-TEN-001 Parent: SRS-ROOT-001 v0.5 Version: 0.4 Status: Approved Last Updated: 2026-05-05 Depends on: なし 依存される: Phase 1 全般

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


店舗(Store)を作成し、マルチテナントの起点となる初期データを一括生成する。

Phase 1 では店舗作成 UI / API は持たず、seed スクリプトで作成する。 本SRSはあわせて store_settings の Phase 1 canonical fields と store.invoice_registration_number を確定する。


  • As a 開発者, I want to seed で店舗と初期ロールを一発作成したい, so that 以後の全機能を tenant 前提で開発できる
  • As a 将来のサロンオーナー, I want to 店舗作成直後から最低限の設定が揃っていてほしい, so that 追加初期化なしで運用開始できる

  1. 開発者が bun run db:seed を実行
  2. seed が環境変数を読む
  3. packages/auth の canonical permission 集合を UPSERT
  4. 1 トランザクションで以下を作成
    • store
    • store_settings
    • preset role 4 種
    • role_permission
  5. 成功時に store.id, store.slug, store.code を出力
  • 同じ slug で再実行した場合は既存店舗を検出してスキップ
  • 複数店舗は環境変数を変えて複数回実行する
  • DB 接続失敗: exit 1
  • slug バリデーション違反: exit 1
  • timezoneAsia/Tokyo 以外: exit 1
  • store.code UNIQUE 衝突: 最大 3 回再生成

Phase 1 ではなし。CLI のみ。


Phase 1 では店舗作成 API を公開しない。

変数名必須既定説明
SEED_STORE_NAME-店舗名
SEED_STORE_SLUG-URL 用 slug、グローバル UNIQUE、冪等キー
SEED_STORE_TIMEZONE-Asia/TokyoPhase 1 はこれのみ許可
DATABASE_URL_MIGRATOR-migrator 接続文字列

カラム制約説明
iduuidPKUUIDv7
slugvarchar(50)UNIQUE, NOT NULL公開 URL / seed 冪等キー
codevarchar(11)UNIQUE, NOT NULLBase64url 11 文字
namevarchar(200)NOT NULL店舗名
timezonevarchar(50)NOT NULL, default Asia/TokyoIANA TZ
planvarchar(20)NOT NULL, default freePhase 1 は free 固定
invoice_registration_numbervarchar(14)NULL適格請求書登録番号。Phase 1 は保存のみ
created_attimestamptzNOT NULL作成時刻
updated_attimestamptzNOT NULL更新時刻
カラム制約説明
store_iduuidPK, FK1:1
reservation_slot_minutessmallintNOT NULL, default 30予約刻み
max_concurrent_reservations_per_staffsmallintNOT NULL, default 1スタッフ並列上限
tentative_expire_hourssmallintNOT NULL, default 24, CHECK 1..168仮予約失効時間
no_show_grace_minutessmallintNOT NULL, default 30, CHECK 0..180no-show 猶予
customer_required_fieldsjsonbNOT NULL, default ["name"]顧客必須項目
customer_no_seqbigintNOT NULL, default 0, CHECK >= 0顧客番号採番カウンタ
rounding_policyjsonbNOT NULL, default {"method":"round","target":"line"}端数処理
cancel_policyjsonbNOT NULL, default {}キャンセル方針
updated_attimestamptzNOT NULL更新時刻
  • 全テナント共通マスタ
  • store_id なし
  • seed が packages/auth から Phase 1 canonical 47 keys を UPSERT
  • owner, manager, staff, receptionist の 4 preset
  • UNIQUE(store_id, key)
  • UNIQUE(store_id, id)
  • PRIMARY KEY (role_id, permission_id)
  • store_id 冗長保持
  • (store_id, role_id) 複合 FK

既存の初期作成は以下で実装済み。

  • 0001_initial.sql
  • 0002_enable_rls.sql

Phase 1 finalization では 0011_store_settings_phase1_expansion.sql を追加し、store / store_settings を拡張するとともに tax_rate master を導入する。

6.2.1 0011_store_settings_phase1_expansion.sql

Section titled “6.2.1 0011_store_settings_phase1_expansion.sql”
ALTER TABLE store
ADD COLUMN invoice_registration_number varchar(14) NULL;
ALTER TABLE store_settings
ADD COLUMN tentative_expire_hours smallint NOT NULL DEFAULT 24,
ADD COLUMN no_show_grace_minutes smallint NOT NULL DEFAULT 30,
ADD COLUMN customer_no_seq bigint NOT NULL DEFAULT 0,
ADD COLUMN rounding_policy jsonb NOT NULL DEFAULT '{"method":"round","target":"line"}'::jsonb;
ALTER TABLE store_settings
ADD CONSTRAINT store_settings_tentative_expire_hours_chk
CHECK (tentative_expire_hours BETWEEN 1 AND 168),
ADD CONSTRAINT store_settings_no_show_grace_minutes_chk
CHECK (no_show_grace_minutes BETWEEN 0 AND 180),
ADD CONSTRAINT store_settings_customer_no_seq_nonnegative_chk
CHECK (customer_no_seq >= 0);
-- tax_rate master (Phase 1 空運用)
CREATE TABLE tax_rate (
id uuid PRIMARY KEY,
store_id uuid NOT NULL REFERENCES store(id),
kind varchar(20) NOT NULL CHECK (kind IN ('standard','reduced')),
rate_pct smallint NOT NULL CHECK (rate_pct BETWEEN 0 AND 100),
effective_from date NOT NULL,
effective_to date NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (store_id, id)
);
ALTER TABLE tax_rate ENABLE ROW LEVEL SECURITY;
ALTER TABLE tax_rate FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_select ON tax_rate FOR SELECT
USING (store_id = current_setting('app.current_store_id')::uuid);
CREATE POLICY tenant_isolation_modify ON tax_rate FOR ALL
USING (store_id = current_setting('app.current_store_id')::uuid)
WITH CHECK (store_id = current_setting('app.current_store_id')::uuid);
GRANT SELECT, INSERT, UPDATE ON tax_rate TO app;
-- 親 SRS §7.16 に従い、既存ハードコード CHECK を撤去
ALTER TABLE reservation_menu_lines
DROP CONSTRAINT IF EXISTS reservation_menu_lines_tax_rate_allowed;
  1. env validate
  2. permission UPSERT (canonical 47 keys)
  3. slug 既存判定
  4. store.code 生成
  5. store INSERT
  6. store_settings INSERT
  7. preset role 4 件 INSERT
  8. canonical PRESET_ROLE_PERMISSIONS に従って role_permission INSERT

  • 8 バイト暗号学的乱数を Base64url 11 文字へ変換
  • 冪等キーには使わない(slug が冪等キー)
  • UNIQUE 衝突時は再生成
  • seed は packages/auth が export する Phase 1 canonical permission 全件を UPSERT する
  • TEN-001 自身が所有する key は次の 5 件
    • admin:store:read
    • admin:store:update
    • admin:store_settings:read
    • admin:store_settings:update
    • admin:role:read
  • 既存店舗への新 permission backfill は各所有 SRS の migration 責務
設定既定
reservation_slot_minutes30
max_concurrent_reservations_per_staff1
tentative_expire_hours24
no_show_grace_minutes30
customer_required_fields["name"]
customer_no_seq0
rounding_policy{"method":"round","target":"line"}
cancel_policy{}
  • Phase 1 は Asia/Tokyo のみ許可
  • Phase 1 は free 固定
  • 正規表現: ^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$
  • store, store_settings, permission, role, role_permission には deleted_at を持たせない
  • 店舗削除が必要になったら別 SRS で扱う
  • store.invoice_registration_number は Phase 1 で保存のみ。表示・帳票連携は Phase 2 SRS-REG-003(領収書 PDF)で扱う
  • 形式は T + 13 桁数字を想定するが、Phase 1 では DB バリデーションを緩く(varchar(14))にする
  • tax_rate テーブルは Phase 1 では作成のみ
  • 行は seed しない
  • 予約・会計の税率スナップショットは行から引かず、整数値直接保持
  • Phase 3 で実効期間検索を有効化(SRS-ANL-001 起票時)

  • seed 1 回の実行時間は 1 秒前後を目標
  • 同一 slug での再実行は安全であること

  • 店舗作成は migrator / seed 実行のため、アプリ層認可対象外
  • 将来 API 化する場合の system 権限は別 SRS で定義する
  • storeid = current_setting('app.current_store_id')::uuid
  • その他 tenant table は store_id = ...
  • permission は RLS なし
  • seed / bootstrap / migration は親SRS §7.8.1 に従い監査対象外
  • default privileges は default-deny
  • 各テーブル migration で必要権限を明示 GRANT する

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

Section titled “10. 受け入れ基準(Given-When-Then)”
  • GWT-1: seed 実行で store 1 行、store_settings 1 行、preset role 4 行、canonical permission rows、対応する role_permission が生成される
  • GWT-2: store.code は Base64url 11 文字
  • GWT-3: 同じ slug で再実行しても重複しない
  • GWT-4: SEED_STORE_TIMEZONE=Asia/Tokyo 以外は失敗
  • GWT-5: tentative_expire_hours / no_show_grace_minutes / customer_no_seq / rounding_policy が既定値で入る
  • GWT-6: invoice_registration_number は NULL を許容する
  • GWT-7: RLS により tenant 分離が成立する
  • GWT-8: permission は tenant 非依存で全件取得できる(47 keys)
  • GWT-9: bootstrap 後、新規テーブルに対して app は blanket DML を持たない
  • GWT-10: 0011 適用後、reservation_menu_lines.tax_rate_pct CHECK IN (8,10) が存在しないこと
  • GWT-11: 0011 適用後、tax_rate テーブルが RLS ENABLE + FORCE で作成されていること
  • GWT-12: tentative_expire_hours = 169 の更新は CHECK 違反

  • Unit
    • generateStoreCode()
    • slug validator
    • timezone validator
  • Integration
    • seed 冪等性
    • RLS
    • default-deny
    • 0011 適用後の既定値確認
    • tax_rate RLS 検証

Phase 1 ではなし。


#内容結論
OQ-TEN-001-01role_permissionstore_id を持たせるかClosed
OQ-TEN-001-02seed 入力形式Closed
OQ-TEN-001-03store_settings canonical fields の最終集合Closed v0.4
OQ-TEN-001-04tax_rate を Phase 1 で空運用とするかClosed v0.4(空運用採用)

VersionDateAuthorChange
0.12026-04-23yudai初版
0.22026-04-25yudaislug / role_permission / default-deny / RLS 整備
0.32026-04-25yudaitentative_expire_hours / no_show_grace_minutes を子SRS同期で追記
0.42026-05-05yudai (with Codex co-design)Parent v0.5 同期。invoice_registration_numbercustomer_no_seqrounding_policy を追加し、0011_store_settings_phase1_expansion.sql を canonical として明記。tax_rate master を Phase 1 空運用で導入。reservation_menu_lines.tax_rate_pct CHECK IN (8,10) ハードコード撤去。permission seed を Phase 1 canonical 47 集合前提に更新