# 機能設計書 85-バージョン履歴

## 概要

本ドキュメントは、Fat Free CRMにおける「バージョン履歴」機能の設計仕様を定義する。PaperTrail gemを活用したエンティティの変更履歴追跡機能である。

### 本機能の処理概要

バージョン履歴機能は、エンティティ（取引先、キャンペーン、リード、連絡先、商談等）に対する作成、更新、削除、閲覧などの操作履歴を自動的に記録し、いつ誰が何を行ったかを追跡できる機能である。

**業務上の目的・背景**：CRMシステムでは、顧客情報や商談情報の変更履歴を追跡することが業務監査やコンプライアンスの観点から重要である。また、誤った変更からの復元や、チームメンバーの活動状況の把握にも活用される。バージョン履歴により、データの変更履歴を透明に管理できる。

**機能の利用シーン**：
- 管理者がユーザーの活動状況を監査する場合
- 誤って変更されたデータの変更前の値を確認する場合
- ダッシュボードで最近の活動履歴を表示する場合
- エンティティ詳細画面でタイムライン（活動履歴）を表示する場合

**主要な処理内容**：
1. 変更履歴の記録：エンティティの作成/更新/削除/閲覧時に自動記録
2. 変更内容の保存：変更前後のデータをシリアライズして保存
3. 履歴の表示：ダッシュボードやエンティティ詳細画面で履歴を表示
4. 履歴のフィルタリング：期間、ユーザー、イベント種別でフィルタ

**関連システム・外部連携**：
- PaperTrail gem：変更履歴追跡の基盤提供
- Versionモデル：PaperTrail::Versionを拡張したカスタムモデル

**権限による制御**：変更履歴の表示は、元のエンティティのアクセス権限に従う。ユーザーがアクセスできないエンティティの履歴は表示されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | ホーム画面（ダッシュボード） | 主画面 | 最近のアクティビティ表示 |
| 8 | 取引先詳細画面 | 主画面 | 取引先の変更履歴表示 |
| 12 | キャンペーン詳細画面 | 主画面 | キャンペーンの変更履歴表示 |
| 16 | リード詳細画面 | 主画面 | リードの変更履歴表示 |
| 21 | 連絡先詳細画面 | 主画面 | 連絡先の変更履歴表示 |
| 25 | 商談詳細画面 | 主画面 | 商談の変更履歴表示 |

## 機能種別

監査・履歴管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| asset | String | No | アセット種別フィルタ | ASSETS定数内の値 |
| event | String | No | イベント種別フィルタ | EVENTS定数内の値 |
| user | Integer | No | ユーザーIDフィルタ | 有効なユーザーID |
| duration | String | No | 期間フィルタ | DURATION定数内の値 |

### 入力データソース

- 自動記録：エンティティ操作時のコールバック（PaperTrail）
- 画面入力：ダッシュボードのフィルタリングオプション

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| versions | Array[Version] | バージョン履歴の配列 |
| version.item_type | String | 対象エンティティのクラス名 |
| version.item_id | Integer | 対象エンティティのID |
| version.event | String | イベント種別（create/update/destroy/view） |
| version.whodunnit | String | 操作者のユーザーID |
| version.object | Text | 変更前のシリアライズされたデータ |
| version.object_changes | Text | 変更内容のシリアライズされたデータ |

### 出力先

- 画面表示：ダッシュボード、エンティティ詳細画面のタイムライン
- データベース：versionsテーブル

## 処理フロー

### 処理シーケンス

```
1. エンティティ操作
   └─ create/update/destroy/show アクション
2. PaperTrailコールバック
   └─ after_create/after_update/after_destroy
3. Versionレコード作成
   └─ item_type, item_id, event, whodunnit, object等を保存
4. 履歴表示
   └─ Version.latestまたはVersion.historyで取得
5. 権限フィルタリング
   └─ visible_toで表示可能な履歴のみ返却
```

### フローチャート

```mermaid
flowchart TD
    A[エンティティ操作] --> B{操作種別?}
    B -->|create| C[Version: event='create']
    B -->|update| D[Version: event='update']
    B -->|destroy| E[Version: event='destroy']
    B -->|show| F[Version: event='view']
    C --> G[PaperTrail callback]
    D --> G
    E --> G
    F --> G
    G --> H[Versionレコード作成]
    H --> I[item_type, item_id, event保存]
    I --> J[whodunnit=current_user.id保存]
    J --> K[object/object_changes保存]

    L[履歴表示リクエスト] --> M[Version.latest/history]
    M --> N[期間/ユーザー/イベントフィルタ]
    N --> O[visible_toで権限フィルタ]
    O --> P[履歴一覧表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-85-01 | 自動記録 | CRUD操作時に自動的に履歴を記録 | has_paper_trail設定時 |
| BR-85-02 | 閲覧記録 | showアクション時にevent='view'で記録 | update_recently_viewed呼び出し時 |
| BR-85-03 | 除外フィールド | subscribed_usersなど指定フィールドは履歴対象外 | ignore設定時 |
| BR-85-04 | 関連記録 | コメント等は親エンティティへの関連として記録 | meta: { related: :parent } 設定時 |
| BR-85-05 | 権限フィルタ | 履歴表示時は元エンティティの権限に従う | visible_to使用時 |
| BR-85-06 | 期間フィルタ | デフォルトは2日間の履歴を表示 | duration未指定時 |

### 計算ロジック

- **期間フィルタ**: `created_at >= Time.zone.now - duration`
- **権限フィルタ**: エンティティのaccess属性とpermissionsを確認

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 履歴記録 | versions | INSERT | 操作履歴の追加 |
| 履歴取得 | versions | SELECT | 履歴一覧の取得 |

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

#### versions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | item_type | エンティティクラス名 | 'Account', 'Lead'等 |
| INSERT | item_id | エンティティID | |
| INSERT | event | 'create'/'update'/'destroy'/'view' | |
| INSERT | whodunnit | 操作者のユーザーID | PaperTrail.request.whodunnit |
| INSERT | object | 変更前のシリアライズデータ | YAMLまたはJSON |
| INSERT | object_changes | 変更内容 | |
| INSERT | related_id, related_type | 関連エンティティ | コメント等の場合 |
| INSERT | created_at | 操作日時 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | エンティティ削除済み | 履歴のエンティティが削除されている | reifyで復元を試みる |
| - | アクセス権限なし | 権限のないエンティティの履歴へのアクセス | visible_toでフィルタ |

### リトライ仕様

履歴記録処理にリトライ機能はない。

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

エンティティの保存と同一トランザクション内で履歴が記録される。エンティティ保存が失敗した場合、履歴もロールバックされる。

## パフォーマンス要件

- versionsテーブルのインデックスによる効率的な検索（item_type, item_id, created_at）
- 最新履歴取得時のlimit指定による件数制限

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

- **認証**：ログインユーザーのみ履歴閲覧可能
- **認可**：visible_toメソッドでエンティティのアクセス権限に基づくフィルタリング
- **監査**：全操作が記録されるため監査証跡として利用可能

## 備考

- PaperTrail gemの機能を最大限活用
- Versionモデルで定義された定数（ASSETS, EVENTS, DURATION）でフィルタオプションを制御
- 削除されたエンティティもreifyで復元可能（ただし完全な復元は保証されない）

---

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

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

### 推奨読解順序

#### Step 1: Versionモデルを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | version.rb | `app/models/polymorphic/version.rb` | PaperTrail::Versionの拡張 |

**主要処理フロー**:
- **10行目**: `class Version < PaperTrail::Version`でPaperTrailを継承
- **11-13行目**: ASSETS, EVENTS, DURATION定数でフィルタオプション定義
- **15-16行目**: belongs_to関連（related, user）
- **18-21行目**: スコープ定義（default_order, include_events, exclude_events, for）
- **24-44行目**: `recent_for_user`で最近の操作履歴を取得
- **46-54行目**: `latest`で条件指定の履歴取得
- **56-59行目**: `related_to`で特定エンティティに関連する履歴取得
- **61-63行目**: `history`でview以外のイベント履歴取得
- **65-82行目**: `visible_to`でアクセス権限フィルタリング

**読解のコツ**: Versionモデルはクラスメソッド（self.xxx）が多く、これらが履歴取得の主要APIとなる。

#### Step 2: モデルでのPaperTrail設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | account.rb | `app/models/entities/account.rb` | has_paper_trailの設定例 |

**主要処理フロー**:
- **68行目**: `has_paper_trail versions: { class_name: 'Version' }, ignore: [:subscribed_users]`
  - `versions: { class_name: 'Version' }`でカスタムVersionモデルを指定
  - `ignore: [:subscribed_users]`で除外フィールドを指定

#### Step 3: コントローラーでの履歴記録を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entities_controller.rb | `app/controllers/entities_controller.rb` | 閲覧履歴の記録 |
| 3-2 | application_controller.rb | `app/controllers/application_controller.rb` | PaperTrailのwhodunnit設定 |

**主要処理フロー**:
- **entities_controller.rb 17行目**: `after_action :update_recently_viewed, only: :show`で閲覧時に履歴記録
- **entities_controller.rb 186-188行目**: `update_recently_viewed`メソッドでevent='view'として記録
- **application_controller.rb 13行目**: `before_action :set_paper_trail_whodunnit`で操作者を設定

#### Step 4: ダッシュボードでの履歴表示を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | home_controller.rb | `app/controllers/home_controller.rb` | アクティビティフィルタリング |

**主要処理フロー**:
- Version.latestメソッドを使用して最近のアクティビティを取得
- フィルタオプション（asset, event, user, duration）を適用

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

```
ブラウザ（エンティティ操作）
    │
    └─ EntityController (create/update/destroy/show)
           │
           ├─ set_paper_trail_whodunnit (before_action)
           │      └─ PaperTrail.request.whodunnit = current_user.id
           │
           ├─ entity.save / entity.destroy
           │      │
           │      └─ PaperTrail callback
           │             │
           │             └─ Version.create(
           │                    item_type: entity.class.name,
           │                    item_id: entity.id,
           │                    event: 'create'/'update'/'destroy',
           │                    whodunnit: current_user.id,
           │                    object: serialized_entity
           │                )
           │
           └─ update_recently_viewed (after_action, show only)
                  │
                  └─ entity.versions.create(
                         event: :view,
                         whodunnit: current_user.id
                      )


ブラウザ（履歴表示リクエスト）
    │
    └─ HomeController#index / EntityController#show
           │
           └─ Version.latest(asset:, event:, user:, duration:)
                  │
                  ├─ .where(item_type: asset)
                  ├─ .where(event: event)
                  ├─ .where(whodunnit: user)
                  ├─ .where('created_at >= ?', Time.zone.now - duration)
                  │
                  └─ visible_to(current_user)
                         │
                         └─ item.access / item.permissions チェック
```

### データフロー図

```
[エンティティ操作]              [処理]                          [出力]

entity.save         ──────▶  PaperTrail callback          ──────▶  Versionレコード
                                │
                                ▼
                          Version.create
                                │
                    ┌───────────┴───────────┐
                    ▼                       ▼
               event='create/           object=
               update/destroy'          serialized(entity)


[履歴表示]                      [処理]                          [出力]

filter params       ──────▶  Version.latest               ──────▶  versions配列
                                │
                                ▼
                          .where(conditions)
                                │
                                ▼
                          visible_to(user)              ──────▶  権限フィルタ済み配列
                                │
                                ▼
                          View Rendering               ──────▶  履歴一覧表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| version.rb | `app/models/polymorphic/version.rb` | ソース | Versionモデル（PaperTrail拡張） |
| account.rb | `app/models/entities/account.rb` | ソース | has_paper_trail設定（68行目） |
| campaign.rb | `app/models/entities/campaign.rb` | ソース | has_paper_trail設定（56行目） |
| contact.rb | `app/models/entities/contact.rb` | ソース | has_paper_trail設定（90行目） |
| lead.rb | `app/models/entities/lead.rb` | ソース | has_paper_trail設定（68行目） |
| opportunity.rb | `app/models/entities/opportunity.rb` | ソース | has_paper_trail設定（76行目） |
| entities_controller.rb | `app/controllers/entities_controller.rb` | ソース | update_recently_viewed（186-188行目） |
| application_controller.rb | `app/controllers/application_controller.rb` | ソース | set_paper_trail_whodunnit |
| home_controller.rb | `app/controllers/home_controller.rb` | ソース | アクティビティ表示 |
| 20120216031616_create_versions.rb | `db/migrate/` | マイグレーション | versionsテーブル作成 |
| 20120220233724_add_versions_object_changes.rb | `db/migrate/` | マイグレーション | object_changes追加 |
| 20120309070209_add_versions_related.rb | `db/migrate/` | マイグレーション | related関連追加 |
