# 機能設計書 41-マイルストーン管理

## 概要

本ドキュメントは、GitLabにおけるマイルストーン管理機能の設計仕様を定義する。マイルストーンは、リリースやスプリント単位でイシューやマージリクエストをグルーピングし、プロジェクトやグループの進捗を追跡するための機能である。

### 本機能の処理概要

**業務上の目的・背景**：ソフトウェア開発プロジェクトでは、リリース計画やスプリント管理のために、一定期間内に完了すべき作業を明確に区切る必要がある。マイルストーン機能は、複数のイシューやマージリクエストを特定の目標（バージョンリリース、スプリント完了など）に紐付けることで、進捗管理を視覚化し、チームの計画立案と進捗追跡を支援する。

**機能の利用シーン**：
- プロダクトマネージャーがリリースバージョン（v1.0、v2.0など）ごとにマイルストーンを作成し、対象機能を整理する場面
- スクラムマスターがスプリント単位でマイルストーンを設定し、チームの作業を管理する場面
- 開発者が自分に割り当てられたイシューの期限・目標を確認する場面
- プロジェクト管理者がグループ全体のリリース計画を統合管理する場面

**主要な処理内容**：
1. マイルストーンの作成（タイトル、説明、開始日、終了日の設定）
2. マイルストーンの編集・更新
3. マイルストーンの状態管理（Active/Closed）
4. マイルストーンの削除と関連イシュー・MRの紐付け解除
5. プロジェクトマイルストーンからグループマイルストーンへの昇格（Promote）
6. マイルストーンに紐付くイシュー・マージリクエストの集計表示

**関連システム・外部連携**：
- Webhook連携により、マイルストーンの作成・更新・削除時に外部システムへ通知可能
- GraphQL API経由で外部ツールからマイルストーン情報を取得・操作可能
- リリース機能との連携（マイルストーンとリリースの紐付け）

**権限による制御**：
- マイルストーンの閲覧：`read_milestone`権限（Reporter以上）
- マイルストーンの作成・編集・削除：`admin_milestone`権限（Developer以上）
- グループマイルストーンへの昇格：親グループの`admin_milestone`権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | マイルストーン一覧 | 主機能 | 関連マイルストーンの一覧表示 |
| 52 | マイルストーン一覧 | 主機能 | マイルストーンの一覧表示 |
| 53 | マイルストーン新規作成 | 主機能 | 新規マイルストーンの作成 |
| 54 | マイルストーン詳細 | 主機能 | マイルストーンの詳細表示 |
| 55 | マイルストーン編集 | 主機能 | マイルストーンの編集処理 |
| 148 | マイルストーン一覧 | 主機能 | グループマイルストーンの一覧表示 |
| 149 | マイルストーン新規作成 | 主機能 | グループマイルストーンの作成 |
| 150 | マイルストーン詳細 | 主機能 | グループマイルストーンの詳細表示 |
| 151 | マイルストーン編集 | 主機能 | グループマイルストーンの編集 |

## 機能種別

CRUD操作 / 状態管理 / データ集計

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | String | Yes | マイルストーンのタイトル | 必須、同一スコープ内でユニーク |
| description | Text | No | マイルストーンの説明 | なし |
| start_date | Date | No | 開始日 | 日付形式 |
| due_date | Date | No | 終了日（期限） | 日付形式 |
| state_event | String | No | 状態変更イベント | activate / close のいずれか |
| lock_version | Integer | No | 楽観的ロック用バージョン | 整数 |

### 入力データソース

- 画面入力（Webフォーム）
- REST API（POST/PUT リクエスト）
- GraphQL API（Mutation）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | マイルストーンの内部ID |
| iid | Integer | プロジェクト/グループ内でのマイルストーン番号 |
| title | String | タイトル |
| description | Text | 説明 |
| state | String | 状態（active/closed） |
| start_date | Date | 開始日 |
| due_date | Date | 終了日 |
| created_at | DateTime | 作成日時 |
| updated_at | DateTime | 更新日時 |
| project_id | Integer | 所属プロジェクトID（プロジェクトマイルストーンの場合） |
| group_id | Integer | 所属グループID（グループマイルストーンの場合） |

### 出力先

- 画面表示（HTML/JSON）
- Webhook通知
- イベントログ

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ コントローラーでパラメータを受け取る
2. 認可チェック
   └─ 操作権限（read_milestone / admin_milestone）を検証
3. サービス層での処理実行
   └─ CreateService / UpdateService / DestroyService を呼び出し
4. スパムチェック（公開プロジェクトの場合）
   └─ タイトル・説明のスパム判定
5. データベース操作
   └─ milestone レコードの INSERT/UPDATE/DELETE
6. 関連処理
   └─ 削除時：関連イシュー・MRのmilestone_idをnullに更新
7. イベント発行
   └─ Webhook通知、EventStore へのイベントパブリッシュ
8. レスポンス返却
   └─ 成功時：リダイレクトまたはJSON、失敗時：エラーメッセージ
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|作成| C[CreateService実行]
    B -->|更新| D[UpdateService実行]
    B -->|削除| E[DestroyService実行]
    B -->|昇格| F[PromoteService実行]

    C --> G{バリデーション}
    D --> G

    G -->|成功| H[データベース保存]
    G -->|失敗| I[エラー返却]

    H --> J[イベント発行]
    J --> K[Webhook通知]
    K --> L[終了]

    E --> M[関連Issue/MR更新]
    M --> N[マイルストーン削除]
    N --> J

    F --> O[グループマイルストーン作成]
    O --> P[プロジェクトマイルストーン削除]
    P --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-41-001 | タイトル一意性 | マイルストーンタイトルは、プロジェクトおよびその親グループ階層内でユニークである必要がある | 作成・編集時 |
| BR-41-002 | 親タイプ排他 | マイルストーンはプロジェクトまたはグループのいずれかにのみ属する（両方には属せない） | 作成時 |
| BR-41-003 | 状態遷移 | Active状態からClosed状態への遷移、およびその逆が可能 | 状態変更時 |
| BR-41-004 | 削除時の紐付け解除 | マイルストーン削除時、関連するイシュー・MRのmilestone_idはnullに設定される | 削除時 |
| BR-41-005 | 昇格条件 | プロジェクトマイルストーンは、プロジェクトが属するグループがある場合のみグループマイルストーンに昇格可能 | 昇格時 |

### 計算ロジック

**進捗率の計算**：
- 完了率 = クローズ済みイシュー数 / 総イシュー数 × 100
- マイルストーンの状態判定：
  - `started`: start_date <= 現在日 かつ (due_date >= 現在日 または due_date が null)
  - `upcoming`: start_date > 現在日
  - `expired`: due_date < 現在日

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | milestones | INSERT | 新規マイルストーンレコードの挿入 |
| 作成 | events | INSERT | 作成イベントの記録 |
| 更新 | milestones | UPDATE | マイルストーン情報の更新 |
| 削除 | milestones | DELETE | マイルストーンレコードの削除 |
| 削除 | issues | UPDATE | milestone_id を null に更新 |
| 削除 | merge_requests | UPDATE | milestone_id を null に更新 |
| 削除 | events | UPDATE | target_id を null に更新 |

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

#### milestones

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | ユーザー入力値 | 必須 |
| INSERT | description | ユーザー入力値 | 任意 |
| INSERT | start_date | ユーザー入力値 | 任意 |
| INSERT | due_date | ユーザー入力値 | 任意 |
| INSERT | state | 'active' | デフォルト値 |
| INSERT | project_id | 対象プロジェクトのID | プロジェクトマイルストーンの場合 |
| INSERT | group_id | 対象グループのID | グループマイルストーンの場合 |
| INSERT | iid | 自動採番 | スコープ内でユニーク |
| UPDATE | state | 'active' / 'closed' | 状態変更時 |
| UPDATE | lock_version | +1 | 楽観的ロック |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定されたマイルストーンが存在しない | マイルストーンの存在を確認 |
| 403 | Forbidden | 操作権限がない | 適切な権限を持つユーザーで操作 |
| 422 | Validation Error | タイトルが空、または重複 | 入力値を修正 |
| 409 | Conflict | 楽観的ロックの競合 | ページをリロードして再操作 |
| - | PromoteMilestoneError | グループが存在しない場合の昇格 | プロジェクトがグループに属していることを確認 |

### リトライ仕様

- 楽観的ロック競合（StaleObjectError）の場合、ユーザーに再操作を促す
- データベース一時エラーの場合、サービス層で自動リトライは行わない

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

- 削除処理はトランザクション内で実行
  - 関連イシューの更新、関連MRの更新、マイルストーン削除を一括でコミット
  - いずれかが失敗した場合はロールバック
- 作成・更新処理は単一レコード操作のため、暗黙のトランザクション

## パフォーマンス要件

- マイルストーン一覧表示：1秒以内
- マイルストーン作成・更新：500ms以内
- 削除処理（関連Issue/MR更新含む）：関連件数に依存（バッチサイズ500件ごとに処理）

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

- **認証・認可**：すべての操作で認証必須、操作ごとに権限チェック
- **スパム対策**：公開プロジェクトの場合、タイトル・説明のスパムチェックを実行
- **入力検証**：Strong Parametersによるパラメータフィルタリング
- **CSRF対策**：標準のRails CSRF保護を適用

## 備考

- グループマイルストーンとプロジェクトマイルストーンは同じテーブル（milestones）で管理され、project_id または group_id のいずれかが設定される
- リリース機能との連携は milestone_releases テーブルで管理

---

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

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

### 推奨読解順序

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

マイルストーンのデータモデルと関連モデルを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | milestone.rb | `app/models/milestone.rb` | マイルストーンの属性、関連、状態管理、スコープ定義を理解 |
| 1-2 | milestoneish.rb | `app/models/concerns/milestoneish.rb` | イシュー・MR集計のconcernを理解 |
| 1-3 | milestoneable.rb | `app/models/concerns/milestoneable.rb` | マイルストーンを持つモデルのconcernを理解 |

**読解のコツ**:
- `state_machine`ブロック（93-105行目）でActiveとClosedの状態遷移を定義
- `belongs_to :project`と`belongs_to :group`の両方があり、排他的に使用される
- `has_internal_id`でプロジェクト/グループスコープのiidを自動採番

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

リクエストを受け付けるコントローラーの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | milestones_controller.rb | `app/controllers/projects/milestones_controller.rb` | プロジェクトマイルストーンのCRUD操作 |
| 2-2 | milestones_controller.rb | `app/controllers/groups/milestones_controller.rb` | グループマイルストーンの操作 |

**主要処理フロー**:
1. **27-44行目**: index - マイルストーン一覧取得、ソート、ページネーション
2. **46-48行目**: new - 新規マイルストーンフォーム表示
3. **61-73行目**: create - CreateServiceを呼び出してマイルストーン作成
4. **75-115行目**: update - UpdateServiceを呼び出して更新、楽観的ロック競合対応
5. **117-131行目**: promote - グループマイルストーンへの昇格処理
6. **138-147行目**: destroy - DestroyServiceを呼び出して削除

#### Step 3: サービス層を理解する

ビジネスロジックを担当するサービスクラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_service.rb | `app/services/milestones/create_service.rb` | マイルストーン作成ロジック |
| 3-2 | update_service.rb | `app/services/milestones/update_service.rb` | 更新ロジック、状態変更、イベント発行 |
| 3-3 | destroy_service.rb | `app/services/milestones/destroy_service.rb` | 削除ロジック、関連データ更新 |
| 3-4 | promote_service.rb | `app/services/milestones/promote_service.rb` | グループへの昇格ロジック |

**主要処理フロー**:
- **create_service.rb 5-16行目**: 作成処理、スパムチェック、イベント発行、Webhook実行
- **update_service.rb 5-19行目**: 状態変更（activate/close）、属性更新、イベントパブリッシュ
- **destroy_service.rb 7-18行目**: トランザクション内でイシュー・MR更新後に削除

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

マイルストーンの検索ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | milestones_finder.rb | `app/finders/milestones_finder.rb` | 検索条件の組み立てとソート処理 |

**主要処理フロー**:
- **28-41行目**: execute - 各フィルタを順次適用
- **87-95行目**: order - ソート処理（due_date_asc/descなど）

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

```
Projects::MilestonesController
    │
    ├─ #index
    │      └─ MilestonesFinder#execute
    │             └─ Milestone.for_projects_and_groups
    │
    ├─ #create
    │      └─ Milestones::CreateService#execute
    │             ├─ milestone.check_for_spam
    │             ├─ milestone.save
    │             ├─ EventService#open_milestone
    │             └─ execute_hooks (Webhook)
    │
    ├─ #update
    │      └─ Milestones::UpdateService#execute
    │             ├─ Milestones::ReopenService#execute (状態変更時)
    │             ├─ Milestones::CloseService#execute (状態変更時)
    │             ├─ milestone.save
    │             └─ Gitlab::EventStore.publish
    │
    ├─ #destroy
    │      └─ Milestones::DestroyService#execute
    │             ├─ Issues::UpdateService#execute (各イシュー)
    │             ├─ MergeRequests::UpdateService#execute (各MR)
    │             ├─ milestone.destroy
    │             └─ execute_hooks (Webhook)
    │
    └─ #promote
           └─ Milestones::PromoteService#execute
                  ├─ グループマイルストーン作成
                  └─ プロジェクトマイルストーン削除
```

### データフロー図

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

Webフォーム ─────────────▶ MilestonesController ─────────▶ HTML/JSON レスポンス
                                  │
REST API ──────────────────▶     │
                                  │
GraphQL API ───────────────▶     ▼
                           Milestones::*Service
                                  │
                                  ▼
                           milestones テーブル ──────────▶ Webhook通知
                                  │
                                  ▼
                           issues / merge_requests ──────▶ EventStore
                           (milestone_id更新)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| milestone.rb | `app/models/milestone.rb` | モデル | マイルストーンのデータモデル |
| milestones_controller.rb | `app/controllers/projects/milestones_controller.rb` | コントローラー | プロジェクトマイルストーンのCRUD |
| milestones_controller.rb | `app/controllers/groups/milestones_controller.rb` | コントローラー | グループマイルストーンのCRUD |
| create_service.rb | `app/services/milestones/create_service.rb` | サービス | 作成ロジック |
| update_service.rb | `app/services/milestones/update_service.rb` | サービス | 更新ロジック |
| destroy_service.rb | `app/services/milestones/destroy_service.rb` | サービス | 削除ロジック |
| promote_service.rb | `app/services/milestones/promote_service.rb` | サービス | 昇格ロジック |
| milestones_finder.rb | `app/finders/milestones_finder.rb` | ファインダー | 検索・フィルタリング |
| milestone_policy.rb | `app/policies/milestone_policy.rb` | ポリシー | 権限定義 |
| milestones_helper.rb | `app/helpers/milestones_helper.rb` | ヘルパー | ビューヘルパー |
| milestone_type.rb | `app/graphql/types/milestone_type.rb` | GraphQL | GraphQL型定義 |
| milestones_resolver.rb | `app/graphql/resolvers/milestones_resolver.rb` | GraphQL | GraphQLリゾルバ |
