# 機能設計書 41-マルチテナント

## 概要

本ドキュメントは、Payload CMSにおけるマルチテナント機能（plugin-multi-tenant）の設計を記載する。本機能は、単一のPayloadインスタンスで複数のテナント（顧客・組織）のデータを分離管理するための包括的なソリューションを提供する。

### 本機能の処理概要

**業務上の目的・背景**：SaaSアプリケーションやマルチクライアント向けCMSでは、複数の顧客（テナント）のデータを論理的に分離し、各テナントが自分のデータのみアクセスできるようにする必要がある。本機能は、コレクションレベルでのテナント分離、アクセス制御、管理画面でのテナント切り替え機能を提供し、マルチテナントアーキテクチャを実現する。

**機能の利用シーン**：
- SaaSプロダクトで顧客ごとに独立したコンテンツ管理を提供する場合
- 企業内の複数部門・ブランドごとにコンテンツを分離管理する場合
- ホワイトラベル製品として異なるクライアントにCMSを提供する場合
- 代理店が複数クライアントのコンテンツを一元管理する場合

**主要な処理内容**：
1. テナントコレクションの自動構成とアクセス制御の適用
2. 有効化されたコレクションへのテナントフィールド自動追加
3. ユーザーコレクションへのテナント配列フィールド追加
4. テナントに基づくドキュメントフィルタリング（baseFilter）
5. 管理画面へのテナントセレクターコンポーネント注入
6. テナント削除時の関連ドキュメント・ユーザー関連のクリーンアップ
7. グローバルとして動作するコレクション（テナントごとに1ドキュメント）のサポート
8. 多言語対応の翻訳リソース統合

**関連システム・外部連携**：
- Admin UI: テナントセレクターコンポーネントの表示
- 認証システム: ユーザーに割り当てられたテナントの管理
- 各種コレクション: テナントフィールドの自動注入とフィルタリング

**権限による制御**：
- `userHasAccessToAllTenants`関数でスーパーアドミン権限を判定
- 各コレクションのCRUD操作にテナントベースのアクセス制御を適用
- テナント配列フィールドにユーザーが所属可能なテナントを制限

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 8 | ダッシュボード画面 | 参照画面 | テナントセレクターによるフィルタリング |
| 9 | 一覧画面 | 主画面 | テナントに基づくドキュメント一覧表示 |
| 10 | ドキュメント編集画面 | 主画面 | テナントフィールドの表示・編集 |

## 機能種別

データ連携 / アクセス制御 / CRUD操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| enabled | boolean | No | プラグインの有効/無効 | デフォルト: true |
| tenantsSlug | string | No | テナントコレクションのslug | デフォルト: 'tenants' |
| collections | object | Yes | マルチテナント対象コレクションの設定 | コレクションslugをキーとするオブジェクト |
| tenantField | object | No | テナントフィールドのオーバーライド設定 | RelationshipFieldの部分型 |
| tenantsArrayField | object | No | ユーザーのテナント配列フィールド設定 | arrayFieldName, arrayTenantFieldName等 |
| userHasAccessToAllTenants | function | No | 全テナントアクセス権限判定関数 | (user) => boolean |
| cleanupAfterTenantDelete | boolean | No | テナント削除時のクリーンアップ | デフォルト: true |
| debug | boolean | No | デバッグモード | デフォルト: false |

### 入力データソース

- Payload設定ファイル（payload.config.ts）でのプラグイン設定
- リクエストヘッダーのCookieからのテナント選択状態
- ログインユーザーの`tenants`配列フィールド

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| config | Config | 変更されたPayload設定 |
| tenant | Tenant | 選択されたテナント情報 |
| filteredDocuments | Document[] | テナントでフィルタリングされたドキュメント |

### 出力先

- 管理画面: テナントセレクター、テナントフィールド表示
- API: テナントフィルタリングされたレスポンス
- データベース: テナントフィールドを含むドキュメント

## 処理フロー

### 処理シーケンス

```
1. プラグイン初期化
   └─ enabled === false の場合は設定を変更せず返却
2. デフォルト値の設定
   └─ tenantsSlug, tenantFieldName, tenantsArrayFieldName等
3. 管理者ユーザーコレクションの特定
   └─ auth有効なコレクションを検索
4. ユーザーコレクションへのテナント配列フィールド追加
   └─ tenantsArrayField設定に基づく
5. ユーザーコレクションへのアクセス制御追加
   └─ addCollectionAccess関数で各操作にアクセス制御を適用
6. 有効化された各コレクションの処理
   └─ tenantField追加、baseFilter設定、アクセス制御適用
7. テナントコレクション自体の設定
   └─ アクセス制御、リストフィルター、削除時クリーンアップフック
8. 管理画面コンポーネントの注入
   └─ TenantSelectionProvider, TenantSelector, GlobalViewRedirect
9. 翻訳リソースのマージ
   └─ 多言語対応ラベルの統合
```

### フローチャート

```mermaid
flowchart TD
    A[プラグイン呼び出し] --> B{enabled?}
    B -->|No| C[設定変更なしで返却]
    B -->|Yes| D[デフォルト値設定]
    D --> E[管理者ユーザーコレクション特定]
    E --> F[ユーザーコレクションにtenantsArrayField追加]
    F --> G[ユーザーコレクションにアクセス制御追加]
    G --> H[対象コレクションをループ処理]
    H --> I{テナントコレクション?}
    I -->|Yes| J[テナントコレクション固有設定]
    I -->|No| K{プラグイン対象?}
    K -->|Yes| L[tenantField追加]
    K -->|No| H
    L --> M[baseFilter設定]
    M --> N[アクセス制御追加]
    N --> H
    J --> O[削除時クリーンアップフック追加]
    O --> P[テナントオプションエンドポイント追加]
    P --> Q[管理画面コンポーネント注入]
    Q --> R[翻訳リソースマージ]
    R --> S[変更された設定を返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | テナント分離 | ユーザーは自身が所属するテナントのドキュメントのみアクセス可能 | 常時 |
| BR-02 | スーパーアドミン | userHasAccessToAllTenants=trueのユーザーは全テナントにアクセス可能 | 関数がtrueを返す場合 |
| BR-03 | グローバルコレクション | isGlobal=trueのコレクションはテナントごとに1ドキュメントのみ（unique制約） | collection設定でisGlobal=true |
| BR-04 | テナント削除時クリーンアップ | テナント削除時に関連ドキュメントを削除し、ユーザーからテナント関連を除去 | cleanupAfterTenantDelete=true（デフォルト） |
| BR-05 | Cookie優先 | テナント選択はCookieに保存され、リクエスト時に優先的に使用される | テナントセレクターで選択時 |

### 計算ロジック

テナントアクセス制御のWhere条件生成:
```typescript
{
  [tenantFieldName]: {
    in: userAssignedTenantIDs // ユーザーに割り当てられたテナントIDの配列
  }
}
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| コレクション読み取り | 各コレクション | SELECT | テナントフィールドでフィルタリング |
| コレクション作成 | 各コレクション | INSERT | テナントフィールドに選択テナントを設定 |
| テナント削除 | tenants | DELETE | テナントドキュメントを削除 |
| テナント削除後処理 | 各コレクション | DELETE | 関連ドキュメントを削除 |
| テナント削除後処理 | users | UPDATE | ユーザーのtenants配列から該当テナントを除去 |

### テーブル別操作詳細

#### tenants（テナントコレクション）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name等 | ユーザーのテナントIDに基づくフィルタ | アクセス制御適用 |
| DELETE | - | afterDeleteフックでクリーンアップ実行 | 関連データも削除 |

#### users（ユーザーコレクション）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | tenants | テナントIDの配列 | 配列フィールドとして追加 |
| UPDATE | tenants | 削除テナントを除去 | テナント削除時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | 認証有効なコレクションが見つからない | コレクション設定を確認 |
| - | Error | テナントコレクションが見つからない | tenantsSlugの設定を確認 |
| - | Warning | プラグイン設定のコレクションが見つからない | プラグイン配置順序を確認（他プラグインの後に配置） |

### リトライ仕様

特になし（設定エラーは起動時に検出）

## トランザクション仕様

テナント削除時のクリーンアップ処理は、afterDeleteフックで実行される。各コレクションの削除処理は個別に実行されるため、一部失敗した場合は部分的なクリーンアップ状態になる可能性がある。

## パフォーマンス要件

- テナントフィールドにはインデックスが自動設定される（`index: true`）
- baseFilterによるクエリオーバーヘッドは最小限に抑えられている
- Cookie取得は軽量な処理として実装

## セキュリティ考慮事項

- テナント分離はアクセス制御レベルで実装され、データベースレベルでは論理分離
- ユーザーが所属しないテナントのデータにはAPIレベルでアクセス不可
- スーパーアドミン権限は`userHasAccessToAllTenants`関数で明示的に制御
- テナント選択Cookieはサーバーサイドで検証される

## 備考

- 本プラグインはPayload 3.x以降で動作
- フォルダ機能が有効な場合、フォルダコレクションにも自動適用可能
- 翻訳は40以上の言語に対応

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、プラグイン設定の型定義とデフォルト値を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/plugin-multi-tenant/src/types.ts` | MultiTenantPluginConfig型、UserWithTenantsField型の構造 |
| 1-2 | defaults.ts | `packages/plugin-multi-tenant/src/defaults.ts` | デフォルトのフィールド名、コレクションslug |

**読解のコツ**: `MultiTenantPluginConfig`は包括的な設定型で、各プロパティのJSDocコメントが詳細な説明を提供している。`CollectionTenantFieldConfigOverrides`は個別コレクションでのテナントフィールドカスタマイズに使用される。

#### Step 2: エントリーポイントを理解する

プラグインのメインロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/plugin-multi-tenant/src/index.ts` | multiTenantPlugin関数の全体フロー |

**主要処理フロー**:
1. **25-27行目**: enabled===falseの場合は早期リターン
2. **32-45行目**: デフォルト値の設定
3. **75-85行目**: 管理者ユーザーコレクションの特定
4. **90-99行目**: ユーザーコレクションへのtenantsArrayField追加
5. **101-109行目**: ユーザーコレクションへのアクセス制御追加
6. **248-428行目**: 各コレクションへの設定適用ループ
7. **453-462行目**: TenantSelectionProviderの注入
8. **485-496行目**: TenantSelectorコンポーネントの注入
9. **500-524行目**: 翻訳リソースのマージ

#### Step 3: アクセス制御を理解する

テナントベースのアクセス制御の実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | addCollectionAccess.ts | `packages/plugin-multi-tenant/src/utilities/addCollectionAccess.ts` | コレクションアクセス制御の追加 |
| 3-2 | withTenantAccess.ts | `packages/plugin-multi-tenant/src/utilities/withTenantAccess.ts` | アクセス制御ラッパー関数 |
| 3-3 | getTenantAccess.ts | `packages/plugin-multi-tenant/src/utilities/getTenantAccess.ts` | テナントアクセス条件の生成 |

**主要処理フロー**:
- **addCollectionAccess.ts 41-56行目**: 全アクセスキー（create/read/update/delete/readVersions/unlock）にwithTenantAccessを適用
- **getTenantAccess.ts 22-31行目**: ユーザーのテナントIDに基づくWhere条件を生成

#### Step 4: フィルタリングを理解する

ドキュメントフィルタリングの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | filterDocumentsByTenants.ts | `packages/plugin-multi-tenant/src/filters/filterDocumentsByTenants.ts` | baseFilterで使用されるフィルタ関数 |
| 4-2 | getTenantFromCookie.ts | `packages/plugin-multi-tenant/src/utilities/getTenantFromCookie.ts` | CookieからのテナントID取得 |

**主要処理フロー**:
- **filterDocumentsByTenants.ts 40-48行目**: Cookieまたは引数からテナントIDを取得しフィルタ条件生成
- **filterDocumentsByTenants.ts 50-57行目**: スーパーアドミンの場合はnullを返しフィルタなし
- **filterDocumentsByTenants.ts 59-70行目**: ユーザーの割り当てテナントでフィルタ

#### Step 5: テナントフィールドを理解する

コレクションに追加されるテナントフィールドの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | tenantField/index.ts | `packages/plugin-multi-tenant/src/fields/tenantField/index.ts` | テナントリレーションシップフィールドの定義 |
| 5-2 | tenantsArrayField/index.ts | `packages/plugin-multi-tenant/src/fields/tenantsArrayField/index.ts` | ユーザー用テナント配列フィールドの定義 |

**主要処理フロー**:
- **tenantField/index.ts 84-116行目**: defaultValueでCookieまたはユーザーの最初のテナントを設定
- **tenantField/index.ts 117-133行目**: filterOptionsでユーザーのテナントのみ選択可能に

### プログラム呼び出し階層図

```
multiTenantPlugin (index.ts)
    │
    ├─ defaults (defaults.ts)
    │      └─ デフォルト値の参照
    │
    ├─ tenantsArrayField (fields/tenantsArrayField/index.ts)
    │      └─ ユーザーコレクションへのフィールド追加
    │
    ├─ addCollectionAccess (utilities/addCollectionAccess.ts)
    │      └─ withTenantAccess (utilities/withTenantAccess.ts)
    │             └─ getTenantAccess (utilities/getTenantAccess.ts)
    │                    └─ getUserTenantIDs (utilities/getUserTenantIDs.ts)
    │
    ├─ tenantField (fields/tenantField/index.ts)
    │      ├─ getTenantFromCookie (utilities/getTenantFromCookie.ts)
    │      └─ getUserTenantIDs (utilities/getUserTenantIDs.ts)
    │
    ├─ combineFilters (utilities/combineFilters.ts)
    │      └─ filterDocumentsByTenants (filters/filterDocumentsByTenants.ts)
    │
    ├─ addTenantCleanup (hooks/afterTenantDelete.ts)
    │      └─ テナント削除時のクリーンアップ処理
    │
    └─ translations (translations/index.ts)
           └─ 多言語翻訳リソース
```

### データフロー図

```
[入力]                    [処理]                         [出力]

プラグイン設定 ─────────▶ multiTenantPlugin ─────────▶ 変更されたConfig
                              │
リクエスト ─────────────────▶ │
  │                           │
  ├─ Cookieのテナント ──────▶ filterDocumentsByTenants ─▶ Where条件
  │                           │
  └─ ユーザーのtenants ─────▶ getTenantAccess ──────────▶ アクセス制御条件
                              │
                              ▼
                         フィルタリングされた
                         ドキュメント一覧
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/plugin-multi-tenant/src/index.ts` | ソース | プラグインのエントリーポイント |
| types.ts | `packages/plugin-multi-tenant/src/types.ts` | ソース | 型定義 |
| defaults.ts | `packages/plugin-multi-tenant/src/defaults.ts` | ソース | デフォルト値 |
| tenantField/index.ts | `packages/plugin-multi-tenant/src/fields/tenantField/index.ts` | ソース | テナントフィールド定義 |
| tenantsArrayField/index.ts | `packages/plugin-multi-tenant/src/fields/tenantsArrayField/index.ts` | ソース | テナント配列フィールド定義 |
| addCollectionAccess.ts | `packages/plugin-multi-tenant/src/utilities/addCollectionAccess.ts` | ソース | アクセス制御追加 |
| withTenantAccess.ts | `packages/plugin-multi-tenant/src/utilities/withTenantAccess.ts` | ソース | アクセス制御ラッパー |
| getTenantAccess.ts | `packages/plugin-multi-tenant/src/utilities/getTenantAccess.ts` | ソース | アクセス条件生成 |
| filterDocumentsByTenants.ts | `packages/plugin-multi-tenant/src/filters/filterDocumentsByTenants.ts` | ソース | ドキュメントフィルタリング |
| getTenantFromCookie.ts | `packages/plugin-multi-tenant/src/utilities/getTenantFromCookie.ts` | ソース | Cookie取得 |
| getUserTenantIDs.ts | `packages/plugin-multi-tenant/src/utilities/getUserTenantIDs.ts` | ソース | ユーザーテナントID取得 |
| afterTenantDelete.ts | `packages/plugin-multi-tenant/src/hooks/afterTenantDelete.ts` | ソース | 削除時クリーンアップ |
| translations/index.ts | `packages/plugin-multi-tenant/src/translations/index.ts` | ソース | 翻訳リソース |
