顧客担当スタッフリンク (operator_staff_link)
顧客担当スタッフリンク (operator_staff_link)
Section titled “顧客担当スタッフリンク (operator_staff_link)”Document ID: SRS-CUS-004 Parent: SRS-ROOT-001 v0.5 Version: 0.1 Status: Draft Last Updated: 2026-05-05 Depends on: SRS-TEN-002 v0.2, SRS-TEN-003 v0.2, SRS-MST-001 v0.6 依存される: SRS-MST-001(retire 同期), SRS-RES-001 (self-scope), SRS-CUS-001/002 (self-scope), SRS-RES-003 (self-scope)
本書は SRS-ROOT-001 v0.5 に従う。
オペレーター(認証主体)と業務スタッフ(施術担当者)の対応関係を operator_staff_link で管理する。
親 SRS §4.1 の **「スタイリストは自分関連の予約・顧客のみ閲覧」**ペルソナ要件を Phase 1 で成立させるための基盤。
Phase 1 では link は API で管理されるが、self-scope の業務適用範囲は最小限(カレンダーの「自分担当のみフィルタ」のオプション提供のみ)。Phase 2 で予約 / 顧客 / メモの read 権限境界に拡張する。
2. ユーザーストーリー
Section titled “2. ユーザーストーリー”- As a owner, I want to オペレーターを業務スタッフに紐付けたい, so that 「自分担当」フィルタを使える
- As a スタイリスト, I want to カレンダーで自分担当の予約のみ表示できるオプションが欲しい, so that 朝確認の効率を上げる
- As a manager, I want to オペレーターの retire 時に link を解除したい, so that orphan link を残さない
3. ユースケース
Section titled “3. ユースケース”3.1 主シナリオ(link 作成)
Section titled “3.1 主シナリオ(link 作成)”- owner / manager が「設定 > オペレーター」を開く
- 対象 operator の detail で「業務スタッフを紐付け」を選ぶ
- 同店舗の active staff 一覧から選択
POST /api/admin/operator-staff-linksで link を作成- 以後、当該 operator は self-scope オプションを使える
3.2 代替フロー
Section titled “3.2 代替フロー”- AF-1 link 解除:
DELETE /api/admin/operator-staff-links/:linkId(実装はended_atセット) - AF-2 staff retire 時: MST-001 の retire ハンドラが listener で active link の
ended_atをセット - AF-3 同 operator の link 切替: 旧 link を end → 新 link を作成
3.3 例外フロー
Section titled “3.3 例外フロー”- EF-1 同 operator が同 store で複数 active link: 409
OPERATOR_STAFF_LINK.MULTIPLE_ACTIVE - EF-2 link 対象 staff が他店舗: 404
- EF-3 link 対象 operator が他店舗 link 不在: 404
OPERATOR_STAFF_LINK.OPERATOR_NOT_LINKED - EF-4 self-scope 適用済みの operator の link を解除: 警告のみ、解除可(次 request から self-scope 失効)
4. UI仕様
Section titled “4. UI仕様”4.1 画面の目的
Section titled “4.1 画面の目的”operator detail の中で、業務スタッフ紐付けを管理する。
4.2 主要要素
Section titled “4.2 主要要素”- operator detail panel
- 「業務スタッフを紐付け」ドロップダウン
- 現在の link 状態表示
- link 履歴(過去の link は ended_at で表示)
4.3 バリデーション
Section titled “4.3 バリデーション”- 同 operator が同 store で active link を持つのは 1 件のみ
- staff は同 store の active staff のみ選択可
5. API仕様
Section titled “5. API仕様”5.1 エンドポイント一覧
Section titled “5.1 エンドポイント一覧”| Method | Path | 用途 | permission |
|---|---|---|---|
| GET | /api/admin/operator-staff-links?operator_id=&staff_id=&active_only= | link 一覧 | admin:operator_staff_link:read |
| POST | /api/admin/operator-staff-links | link 作成 | admin:operator_staff_link:write |
| DELETE | /api/admin/operator-staff-links/:linkId | link 終了 | admin:operator_staff_link:write |
| GET | /api/admin/auth/me/staff-link | 自分の active link 取得 | session のみ |
5.2 zodスキーマ
Section titled “5.2 zodスキーマ”POST /api/admin/operator-staff-links:
operator_id: uuidstaff_id: uuid
response:
data.link.{operator_id, store_id, staff_id, created_at, ended_at}
5.3 エラーコード
Section titled “5.3 エラーコード”OPERATOR_STAFF_LINK.MULTIPLE_ACTIVEOPERATOR_STAFF_LINK.OPERATOR_NOT_LINKEDOPERATOR_STAFF_LINK.STAFF_NOT_FOUNDOPERATOR_STAFF_LINK.NOT_FOUND
6. データモデル影響
Section titled “6. データモデル影響”6.1 スキーマ
Section titled “6.1 スキーマ”operator_staff_link
Section titled “operator_staff_link”| カラム | 型 | 制約 |
|---|---|---|
store_id | uuid | NOT NULL, FK |
operator_id | uuid | NOT NULL, 複合 FK → operator_store_link(store_id, operator_id) |
staff_id | uuid | NOT NULL, 複合 FK → staff(store_id, id) |
created_at | timestamptz | NOT NULL |
ended_at | timestamptz | NULL |
| PK | (store_id, operator_id, COALESCE(ended_at, 'epoch')) ではなく実装は id uuid PK + UNIQUE(store_id, operator_id) WHERE ended_at IS NULL |
実装上の選択:
- 独立
id uuidを持ち(履歴を 1 行ごとに識別可能) UNIQUE(store_id, operator_id) WHERE ended_at IS NULLで active 1 件制約UNIQUE(store_id, staff_id) WHERE ended_at IS NULLで同 staff に同時 1 operator 制約(self-scope の意味論を破らないため)
| カラム | 型 | 制約 |
|---|---|---|
id | uuid | PK |
store_id | uuid | NOT NULL, FK |
operator_id | uuid | NOT NULL, 複合 FK |
staff_id | uuid | NOT NULL, 複合 FK |
created_at | timestamptz | NOT NULL |
ended_at | timestamptz | NULL |
| 制約 | UNIQUE(store_id, id) | |
| 制約 | UNIQUE(store_id, operator_id) WHERE ended_at IS NULL | |
| 制約 | UNIQUE(store_id, staff_id) WHERE ended_at IS NULL |
6.2 マイグレーション計画
Section titled “6.2 マイグレーション計画”0013_operator_membership_and_staff_link.sql (TEN-003 と共有) で:
operator_store_link作成(TEN-003 担当)operator_staff_link作成(本SRS担当)- RLS ENABLE + FORCE
appロールへSELECT/INSERT/UPDATEGRANT(DELETE なし、ended_atで論理削除)- permission backfill:
admin:operator_staff_link:read,admin:operator_staff_link:write
7. 業務ルール
Section titled “7. 業務ルール”7.1 link の 1:1 制約
Section titled “7.1 link の 1:1 制約”- 同 store 内で同 operator は同時に 1 link のみ active
- 同 store 内で同 staff も同時に 1 operator にのみ link 可
- 切替は「旧 link end → 新 link create」の順で行う
7.2 staff retire との同期
Section titled “7.2 staff retire との同期”staff.retireハンドラ(MST-001)は activeoperator_staff_linkのended_at = now()をセットする- listener で実装、retire の transaction 内で実行
- listener 失敗時は staff retire も rollback
7.3 self-scope の Phase 1 範囲
Section titled “7.3 self-scope の Phase 1 範囲”- カレンダー (RES-001) の「自分担当のみ」フィルタ表示で利用
- 予約・顧客・メモの read 権限境界には Phase 1 では適用しない(API はすべて店舗全体 view を返す)
- Phase 2 で self-scope 強制を導入する場合は別 SRS で OQ 解消
7.4 監査
Section titled “7.4 監査”operator_staff_link.create / endoperator_staff_link.end_by_staff_retire(retire listener 起動時)
8. 非機能要件
Section titled “8. 非機能要件”- link API は p95 200ms
- self-link API(
/api/admin/auth/me/staff-link)はキャッシュ可
9. セキュリティ・認可
Section titled “9. セキュリティ・認可”| key | 用途 | 初期付与 |
|---|---|---|
admin:operator_staff_link:read | link 一覧・self-link 確認 | owner / manager / staff |
admin:operator_staff_link:write | link 作成・終了 | owner / manager |
/api/admin/auth/me/staff-link は session のみで permission 不要(自分の link を見る権利は本人にある)。
10. 受け入れ基準(Given-When-Then)
Section titled “10. 受け入れ基準(Given-When-Then)”- GWT-1: link 作成 → operator_staff_link 1 行追加、
ended_at IS NULL - GWT-2: 同 operator に 2 件目 active link 作成 → 409
OPERATOR_STAFF_LINK.MULTIPLE_ACTIVE - GWT-3: 同 staff に別 operator の active link 作成 → 409
- GWT-4: link 終了 →
ended_atセット、UNIQUE 部分インデックスで再作成可能 - GWT-5: staff retire → 該当 active link が
ended_at = now()で終了 - GWT-6: 自分の link 取得 → session のみで返る
- GWT-7: 別店舗 staff への link → 404
- GWT-8: link なし operator が
/api/admin/reservationsを呼ぶ → 全件返る(self-scope 強制なし、Phase 2 OQ)
11. テスト計画
Section titled “11. テスト計画”- Unit: link 1:1 制約
- Integration: retire listener、UNIQUE 部分インデックス、RLS
12. 関連ジョブ
Section titled “12. 関連ジョブ”- なし(retire listener は MST-001 のハンドラ内で同期実行)
13. Open Questions
Section titled “13. Open Questions”| # | 内容 | 扱い |
|---|---|---|
| OQ-CUS-004-01 | self-scope 強制を Phase 2 で予約・顧客 read に拡張 | Phase 2 SRS |
| OQ-CUS-004-02 | 1 operator が複数 staff を兼任するケース | Phase 2 |
14. 変更履歴
Section titled “14. 変更履歴”| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-05-05 | yudai (with Codex co-design) | 初版。operator_staff_link スキーマ、retire listener、self-scope の Phase 1 範囲を確定。migration 0013 で TEN-003 と共有 |