# 画面設計書 149-マイルストーン新規作成

## 概要

本ドキュメントは、GitLabにおけるグループマイルストーン新規作成画面の設計仕様を定義するものである。グループに新しいマイルストーンを作成するための入力フォームを提供する。

### 本画面の処理概要

グループマイルストーン新規作成画面は、グループに対して新しいマイルストーンを作成するためのフォーム画面である。マイルストーンは課題やマージリクエストを期限ベースでグルーピングするために使用され、スプリントやリリースの計画に活用される。

**業務上の目的・背景**：アジャイル開発におけるスプリント計画や、ウォーターフォール開発におけるフェーズ管理において、期限付きの作業区切りを設定する必要がある。グループマイルストーンを作成することで、複数プロジェクトにまたがる作業の進捗を一元管理できる。

**画面へのアクセス方法**：以下のいずれかの方法でアクセス可能である。
- グループマイルストーン一覧画面の「New milestone」ボタンをクリック
- URL直接アクセス: `/groups/{group_path}/-/milestones/new`

**主要な操作・処理内容**：
1. マイルストーンタイトルの入力（必須）
2. 開始日の入力（任意）
3. 期限日の入力（任意）
4. 説明の入力（Markdown対応）
5. マイルストーンの作成実行

**画面遷移**：
- 遷移元：グループマイルストーン一覧画面
- 遷移先：マイルストーン詳細画面（作成成功時）、同画面（バリデーションエラー時）

**権限による表示制御**：
- Maintainer以上：マイルストーン作成が可能（admin_milestone権限）
- 権限がない場合は404エラー

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 42 | マイルストーン管理 | 主機能 | グループマイルストーンの新規作成 |

## 画面種別

登録

## URL/ルーティング

- パス: `/groups/{group_path}/-/milestones/new`
- ルーティング: `groups/milestones#new`
- HTTPメソッド: GET（フォーム表示）、POST（作成処理）

## 入出力項目

| 項目名 | 種別 | 必須 | データ型 | 説明 |
|--------|------|------|----------|------|
| title | フォーム入力 | 必須 | String | マイルストーンタイトル（255文字以内） |
| start_date | フォーム入力 | 任意 | Date | 開始日 |
| due_date | フォーム入力 | 任意 | Date | 期限日 |
| description | フォーム入力 | 任意 | Text | 説明（Markdown対応） |

## 表示項目

| 項目名 | データ型 | 説明 | 表示条件 |
|--------|----------|------|----------|
| ページヘッダー | Component | 「New milestone」タイトル | 常時 |
| タイトル入力欄 | TextInput | マイルストーン名入力（255文字制限） | 常時 |
| 開始日入力欄 | DatePicker | 開始日選択 | 常時 |
| 期限日入力欄 | DatePicker | 期限日選択 | 常時 |
| 説明入力欄 | MarkdownEditor | 説明入力（プレビュー付き） | 常時 |
| 作成ボタン | Button | マイルストーン作成実行 | 常時 |
| キャンセルボタン | Button | マイルストーン一覧へ戻る | 常時 |
| バリデーションエラー | Alert | 入力エラー表示 | エラー時 |

## イベント仕様

### 1-フォーム表示（GET new）

1. `authorize_admin_milestones!`による権限チェック（7行目）
2. `@milestone = Milestone.new`で新規マイルストーンインスタンス作成（25行目）
3. `@noteable = @milestone`でノータブル設定（25行目）
4. フォームパーシャル（`_form`）をレンダリング

### 2-マイルストーン作成（POST create）

1. `authorize_admin_milestones!`による権限チェック
2. `Milestones::CreateService`による作成処理（29行目）
3. スパムチェック（before_create）
4. 作成成功時：`execute_hooks`でWebhook発火（12行目）
5. 永続化成功時：マイルストーン詳細へリダイレクト（32行目）
6. 永続化失敗時：フォームを再表示（34行目）

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| フォーム表示 | - | - | データベース操作なし |
| マイルストーン作成 | milestones | INSERT | マイルストーンレコード作成 |
| マイルストーン作成 | internal_ids | UPSERT | IID採番 |

### テーブル別更新項目詳細

#### milestones

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | 入力値 | 必須、255文字以内 |
| INSERT | description | 入力値 | 任意 |
| INSERT | start_date | 入力値 | 任意 |
| INSERT | due_date | 入力値 | 任意 |
| INSERT | group_id | @group.id | グループマイルストーン |
| INSERT | state | 'active' | 初期状態 |
| INSERT | iid | 自動採番 | グループ内連番 |
| INSERT | created_at | 現在時刻 | 自動設定 |
| INSERT | updated_at | 現在時刻 | 自動設定 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | エラー | Title can't be blank | タイトル未入力時 |
| MSG-002 | エラー | Title has already been taken | 同名マイルストーン存在時 |
| MSG-003 | エラー | Due date must be greater than start date | 期限日が開始日より前の場合 |

## 例外処理

| 例外条件 | 処理内容 | 遷移先 |
|----------|----------|--------|
| 管理権限なし | 404エラー | エラーページ |
| グループ不存在 | 404エラー | エラーページ |
| バリデーションエラー | エラーメッセージ表示 | 同画面 |
| スパム検出 | スパムエラー表示 | 同画面 |

## 備考

- タイトルはグループ内で一意である必要がある（uniqueness_of_title）
- 説明はMarkdown形式に対応（GFM: GitLab Flavored Markdown）
- 説明入力欄はMarkdownエディタ（プレビュー機能付き）
- 開始日・期限日は両方とも任意だが、両方設定した場合は開始日 <= 期限日の制約
- 作成時にスパムチェックが実行される（Spammable concern）
- IIDはグループ内で自動採番される（has_internal_id :iid, scope: :group）
- 作成成功時はWebhook（execute_hooks）が発火する（グループマイルストーンの場合は発火しない）

---

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

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

### 推奨読解順序

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

マイルストーンモデルの構造とバリデーションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | milestone.rb | `app/models/milestone.rb` | マイルストーンモデル、バリデーション |
| 1-2 | timebox.rb | `app/models/concerns/timebox.rb` | 期間管理のバリデーション |

**読解のコツ**: `validates :title, presence: true`（85行目）でタイトル必須を確認。`validate :uniqueness_of_title`（88行目）で一意性を確認。`has_internal_id :iid, scope: :group`（30行目）でIID採番を確認。

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

コントローラーの`new`と`create`アクションがエントリーポイント。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | milestones_controller.rb | `app/controllers/groups/milestones_controller.rb` | new/createアクション |

**主要処理フロー**:
1. **7行目**: `authorize_admin_milestones!`で権限チェック
2. **24-26行目**: newアクションでマイルストーンインスタンス生成
3. **28-36行目**: createアクションでMilestones::CreateService呼び出し
4. **32行目**: 成功時は`milestone_path(@milestone)`へリダイレクト

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_service.rb | `app/services/milestones/create_service.rb` | マイルストーン作成ロジック |

**主要処理フロー**:
- **6行目**: `parent.milestones.new(params)`でインスタンス生成
- **8行目**: `before_create(milestone)`でスパムチェック
- **10-13行目**: 保存成功時にイベント記録とフック実行

#### Step 4: ビューレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | new.html.haml | `app/views/groups/milestones/new.html.haml` | メインテンプレート |
| 4-2 | _form.html.haml | `app/views/groups/milestones/_form.html.haml` | フォームパーシャル |
| 4-3 | _form_dates.html.haml | `app/views/shared/milestones/_form_dates.html.haml` | 日付入力パーシャル |

**主要処理フロー**:
- new.html.haml **5行目**: PageHeadingComponentでヘッダー表示
- new.html.haml **8行目**: フォームパーシャルをレンダリング
- _form.html.haml **8-9行目**: タイトル入力（255文字制限、必須、autofocus）
- _form.html.haml **10行目**: 日付入力パーシャル
- _form.html.haml **14-22行目**: Markdownエディタで説明入力
- _form.html.haml **30行目**: 「Create milestone」ボタン

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

```
Groups::MilestonesController#new
    │
    ├─ authorize_admin_milestones! (before_action)
    │      └─ can?(current_user, :admin_milestone, group)
    │
    └─ @noteable = @milestone = Milestone.new
           │
           └─ render 'form'

Groups::MilestonesController#create
    │
    ├─ authorize_admin_milestones! (before_action)
    │
    └─ Milestones::CreateService.new(group, current_user, milestone_params).execute
           │
           ├─ parent.milestones.new(params)
           │
           ├─ before_create(milestone)
           │      └─ milestone.check_for_spam(user: current_user, action: :create)
           │
           └─ milestone.save
                  │
                  ├─ Milestone validations
                  │    ├─ title presence
                  │    ├─ title uniqueness (scope: group)
                  │    └─ dates validation (start_date <= due_date)
                  │
                  ├─ has_internal_id (IID採番)
                  │
                  └─ milestones table (INSERT)
                         │
                         ▼
                  [成功] → event_service.open_milestone (※project_milestone?時のみ)
                         → execute_hooks(milestone, 'create')
                         → redirect_to milestone_path(@milestone)
                  [失敗] → render :new
```

### データフロー図

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

Form params ─────────────▶ MilestonesController#create ─▶ Redirect/Render
(title, description,              │
 start_date, due_date)            │
                                  ▼
                          Milestones::CreateService
                                  │
                                  ├── spam check
                                  │
                                  └── milestone.save
                                          │
                                          ├── validations
                                          │    ├── title presence
                                          │    ├── uniqueness
                                          │    └── dates validation
                                          │
                                          ├── has_internal_id (IID)
                                          │
                                          └── milestones table (INSERT)
                                                  │
                                                  ▼
                                          [成功] → redirect_to milestone_path
                                          [失敗] → render :new
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| milestones_controller.rb | `app/controllers/groups/milestones_controller.rb` | コントローラー | new/createアクション定義 |
| milestone.rb | `app/models/milestone.rb` | モデル | マイルストーンモデル |
| new.html.haml | `app/views/groups/milestones/new.html.haml` | ビュー | メインテンプレート |
| _form.html.haml | `app/views/groups/milestones/_form.html.haml` | ビュー | フォームパーシャル |
| _form_dates.html.haml | `app/views/shared/milestones/_form_dates.html.haml` | ビュー | 日付入力パーシャル |
| create_service.rb | `app/services/milestones/create_service.rb` | サービス | マイルストーン作成ロジック |
| base_service.rb | `app/services/milestones/base_service.rb` | サービス | 基底サービス |
| timebox.rb | `app/models/concerns/timebox.rb` | Concern | 期間管理共通ロジック |
