# 機能設計書 153-ポータルカテゴリ

## 概要

本ドキュメントは、Chatwootのヘルプセンターにおける「ポータルカテゴリ」機能の設計仕様を定義する。カテゴリは記事を整理・分類するための階層構造を提供し、ユーザーが目的の情報を効率的に見つけられるようにする。

### 本機能の処理概要

**業務上の目的・背景**：
ヘルプセンターに多数の記事が存在する場合、適切な分類がなければユーザーは必要な情報を見つけることが困難になる。カテゴリ機能により、記事をトピック別に整理し、階層的なナビゲーション構造を構築できる。また、多言語対応により、各言語で独立したカテゴリ構造を持つことが可能。

**機能の利用シーン**：
- 管理者がヘルプセンターの記事を分類するためのカテゴリを作成する場面
- 多言語サイトで各言語用のカテゴリを設定する場面
- カテゴリの並び順を調整して、重要なカテゴリを上位に配置する場面
- 親子関係を持つサブカテゴリを作成する場面
- 関連カテゴリを設定して、ユーザーに関連情報を提示する場面

**主要な処理内容**：
1. カテゴリの作成：名前、スラッグ、説明、アイコン、ロケールなどの基本情報を登録
2. カテゴリの更新：設定情報の変更、関連カテゴリの設定
3. カテゴリの削除：カテゴリとフォルダの削除（記事は関連解除のみ）
4. カテゴリ一覧・詳細表示：ポータル内のカテゴリを取得
5. 親子カテゴリ管理：サブカテゴリの作成と階層化
6. 関連カテゴリ設定：関連するカテゴリ同士のリンク

**関連システム・外部連携**：
- ポータル：親コンテナとしてのポータル
- 記事：カテゴリに属する記事
- フォルダ：カテゴリ内の記事をさらに分類

**権限による制御**：
- 一覧表示・詳細表示：アカウントに所属する全ユーザーがアクセス可能
- 作成・更新・削除：管理者（Administrator）権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 93 | 記事一覧画面 | 補助画面 | カテゴリによるフィルタリング |
| 96 | カテゴリ一覧画面 | 主画面 | 記事カテゴリの一覧表示 |

## 機能種別

CRUD操作 / 階層構造管理 / 多言語対応

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | String | Yes | カテゴリ名 | presence: true |
| slug | String | Yes | URL識別子 | presence: true |
| description | Text | No | カテゴリ説明 | - |
| locale | String | No | ロケール | default: 'en', ポータルの許可ロケール内 |
| icon | String | No | アイコン識別子 | default: '' |
| position | Integer | No | 表示順序 | - |
| parent_category_id | Integer | No | 親カテゴリID | Category存在確認 |
| associated_category_id | Integer | No | 関連元カテゴリID | Category存在確認 |
| related_category_ids | Array | No | 関連カテゴリIDリスト | Category存在確認 |

### 入力データソース

- 画面入力：管理画面のカテゴリ設定フォーム
- API：`/api/v1/accounts/:account_id/portals/:portal_id/categories` エンドポイント

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | カテゴリID |
| name | String | カテゴリ名 |
| slug | String | URL識別子 |
| description | Text | カテゴリ説明 |
| locale | String | ロケール |
| icon | String | アイコン識別子 |
| position | Integer | 表示順序 |
| account_id | Integer | アカウントID |
| portal_id | Integer | ポータルID |
| parent_category_id | Integer | 親カテゴリID |
| associated_category_id | Integer | 関連元カテゴリID |
| articles | Array | カテゴリ内の記事一覧 |
| sub_categories | Array | サブカテゴリ一覧 |
| related_categories | Array | 関連カテゴリ一覧 |

### 出力先

- JSON API レスポンス
- JBuilder ビューテンプレート

## 処理フロー

### 処理シーケンス

```
1. カテゴリ作成
   └─ リクエスト受信 → ポータル取得 → 認可チェック → パラメータ検証 → Category作成 → 関連カテゴリ追加 → レスポンス返却

2. カテゴリ更新
   └─ リクエスト受信 → ポータル・カテゴリ取得 → 認可チェック → パラメータ検証 → Category更新 → 関連カテゴリ更新 → レスポンス返却

3. カテゴリ削除
   └─ リクエスト受信 → ポータル・カテゴリ取得 → 認可チェック → フォルダ削除（非同期） → 記事関連解除 → Category削除 → レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[ポータル取得]
    B --> C{アクション判定}
    C -->|index| D[カテゴリ検索・取得]
    C -->|show| E[個別カテゴリ取得]
    C -->|create| F[パラメータ検証]
    C -->|update| G[カテゴリ取得 + 検証]
    C -->|destroy| H[カテゴリ取得]

    F --> I[Category作成]
    I --> J[関連カテゴリ追加]
    J --> K{バリデーション成功?}
    K -->|Yes| L[レスポンス返却]
    K -->|No| M[422 エラー]

    G --> N[Category更新]
    N --> O[関連カテゴリ更新]
    O --> K

    H --> P[destroy!実行]
    P --> Q[HTTP 200 OK]

    D --> L
    E --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-153-01 | slug+locale+portal_id一意性 | 同一ポータル・ロケール内でslugは一意 | Category作成・更新時 |
| BR-153-02 | 許可ロケール検証 | カテゴリのlocaleはポータルのallowed_localesに含まれる必要がある | Category作成・更新時 |
| BR-153-03 | account_id自動設定 | カテゴリのaccount_idはポータルのaccount_idから自動取得 | Category作成時 |
| BR-153-04 | 削除時の記事処理 | カテゴリ削除時、記事は削除せずcategory_idをnullに設定 | Category削除時 |
| BR-153-05 | フォルダ非同期削除 | カテゴリ削除時、関連フォルダは非同期で削除 | Category削除時 |
| BR-153-06 | 表示順序 | カテゴリはposition昇順でソート | 一覧取得時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| create | categories | INSERT | 新規カテゴリの作成 |
| create | related_categories | INSERT | 関連カテゴリの追加 |
| update | categories | UPDATE | カテゴリ情報の更新 |
| update | related_categories | DELETE/INSERT | 関連カテゴリの更新 |
| destroy | categories | DELETE | カテゴリの削除 |
| destroy | folders | DELETE | 関連フォルダの非同期削除 |
| destroy | articles | UPDATE | category_id を null に更新 |

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

#### categories

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | リクエストパラメータ | 必須 |
| INSERT | slug | リクエストパラメータ | 必須 |
| INSERT | locale | リクエストパラメータ | default: 'en' |
| INSERT | account_id | portal.account_id | 自動設定 |
| INSERT | portal_id | params[:portal_id] | 自動設定 |
| SELECT | - | locale, portal_id | 検索条件 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | UnprocessableEntity | slug+locale+portal_id重複 | 異なるslugまたはlocaleを指定 |
| 422 | UnprocessableEntity | localeがポータルの許可ロケール外 | 許可されたロケールを指定 |
| 404 | NotFound | 指定IDのCategoryが存在しない | 正しいIDを指定 |
| 404 | NotFound | 指定slugのPortalが存在しない | 正しいslugを指定 |
| 403 | Forbidden | 権限不足 | 管理者権限でアクセス |

### リトライ仕様

特になし

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

- create/update処理は暗黙的トランザクション
- 関連カテゴリの追加は同一トランザクション内で実行
- フォルダ削除は`dependent: :destroy_async`で非同期実行

## パフォーマンス要件

- カテゴリ一覧取得：100ms以内
- ページネーションはLimits::CATEGORIES_PER_PAGEで制御

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

- 管理者権限チェック必須
- ポータルスコープでのカテゴリ取得（他アカウントのカテゴリにはアクセス不可）

## 備考

- `accepts_nested_attributes_for :related_categories`により関連カテゴリのネスト作成が可能
- 多言語版カテゴリは`associated_category_id`で元カテゴリとリンク

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | category.rb | `app/models/category.rb` | スキーマ定義、バリデーション、関連モデル |

**読解のコツ**:
- **1-26行目**: Schema Informationコメントでテーブル構造を把握
- **28行目**: `paginates_per Limits::CATEGORIES_PER_PAGE` - ページネーション設定
- **29-55行目**: 関連モデル定義（portal, folders, articles, sub_categories, related_categories）
- **57-63行目**: バリデーション（account_id, slug, name, locale一意性）
- **66行目**: `scope :search_by_locale` - ロケール検索スコープ
- **68-74行目**: searchクラスメソッド - 検索とページネーション
- **82-89行目**: allowed_locales - ポータルの許可ロケール検証

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | categories_controller.rb | `app/controllers/api/v1/accounts/categories_controller.rb` | CRUD操作 |

**主要処理フロー**:
1. **7-10行目**: index - カテゴリ検索
2. **14-20行目**: create - カテゴリ作成と関連カテゴリ追加
3. **22-28行目**: update - カテゴリ更新と関連カテゴリ更新
4. **30-33行目**: destroy - カテゴリ削除

#### Step 3: 公開API側を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | categories_controller.rb | `app/controllers/public/api/v1/portals/categories_controller.rb` | 公開カテゴリAPI |

**主要処理フロー**:
- **7-9行目**: index - カテゴリ一覧（position順）
- **11-13行目**: show - 個別カテゴリ表示
- **17-21行目**: set_category - locale+slugでカテゴリ取得

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

```
Api::V1::Accounts::CategoriesController
    │
    ├─ before_action :portal
    │      └─ Current.account.portals.find_by(slug:)
    │
    ├─ before_action :check_authorization
    │
    ├─ before_action :fetch_category
    │      └─ @portal.categories.find(params[:id])
    │
    ├─ create
    │      ├─ @portal.categories.create!(category_params)
    │      └─ @category.related_categories << related_categories_records
    │
    ├─ update
    │      ├─ @category.update!(category_params)
    │      └─ @category.related_categories = related_categories_records
    │
    └─ destroy
           └─ @category.destroy!
                  ├─ folders.destroy_async
                  └─ articles.update(category_id: nil)
```

### データフロー図

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

API Request         →   CategoriesController     →   JSON Response
(params)                    │
                           ├─ portal取得
                           │     (slug → Portal)
                           │
                           ├─ Category Model
                           │     ├─ バリデーション
                           │     └─ locale検証
                           │
                           └─ related_categories
                                 (関連カテゴリ設定)
                                       ↓
                              [categories テーブル]
                              [related_categories テーブル]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| category.rb | `app/models/category.rb` | モデル | カテゴリのデータモデル定義 |
| categories_controller.rb | `app/controllers/api/v1/accounts/categories_controller.rb` | コントローラー | 管理API CRUD操作 |
| categories_controller.rb | `app/controllers/public/api/v1/portals/categories_controller.rb` | コントローラー | 公開API |
| portal.rb | `app/models/portal.rb` | モデル | 親ポータル |
| article.rb | `app/models/article.rb` | モデル | カテゴリに属する記事 |
| related_category.rb | `app/models/related_category.rb` | モデル | 関連カテゴリの中間テーブル |
