# 画面設計書 305-招待確認

## 概要

本ドキュメントは、GitLabのプロジェクト・グループへの招待確認画面の設計仕様を定義する。

### 本画面の処理概要

本画面は、プロジェクトまたはグループへの招待を受けたユーザーが、その招待内容を確認し、承諾または辞退を行うための画面である。招待されたユーザーは、招待者、招待先（プロジェクト/グループ）、付与されるロールを確認した上で、参加の可否を決定できる。

**業務上の目的・背景**：GitLabでは、プロジェクトやグループへのメンバー追加を招待ベースで行うことができる。この機能により、既存のGitLabアカウントを持つユーザーだけでなく、まだアカウントを持っていないユーザーも招待できる。本画面は、招待を受けたユーザーが招待内容を確認し、適切な判断を行えるようにするためのインターフェースを提供する。これにより、意図しないプロジェクト・グループへの参加を防ぎ、ユーザーの意思決定を尊重する。

**画面へのアクセス方法**：
- 招待メールに含まれるリンクから遷移
- URLに直接アクセス（招待トークン付き）

**主要な操作・処理内容**：
1. 招待内容の確認（招待者、招待先、ロール）
2. 招待の承諾
3. 招待の辞退
4. 既存メンバーの場合は参加先への遷移案内

**画面遷移**：
- 遷移元：招待メールリンク
- 遷移先：招待先プロジェクト/グループ（承諾時）、招待辞退画面（辞退時）、ダッシュボード（条件付き）

**権限による表示制御**：
- 招待メールのアドレスと現在のログインユーザーのメールが一致する場合：招待の承諾/辞退ボタンが表示される
- メールが一致しない場合：アカウント切り替えの案内が表示される
- 既にメンバーの場合：参加先への直接リンクが表示される

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 86 | 招待機能 | 主機能 | 招待承認処理 |

## 画面種別

確認・承認画面

## URL/ルーティング

| メソッド | パス | アクション | 説明 |
|---------|------|----------|------|
| GET | `/-/invites/:id` | show | 招待確認画面表示 |
| POST | `/-/invites/:id/accept` | accept | 招待承諾 |
| GET/POST | `/-/invites/:id/decline` | decline | 招待辞退 |

## 入出力項目

### 入力項目（パスパラメータ）

| 項目名 | データ型 | 必須 | 説明 |
|--------|---------|------|------|
| id | String | Yes | 招待トークン（A-Za-z0-9_-の文字列） |

### 入力項目（クエリパラメータ）

| 項目名 | データ型 | 必須 | 説明 |
|--------|---------|------|------|
| invite_type | String | No | 招待種別（`initial_email`の場合トラッキング対象） |

## 表示項目

### 招待情報（招待メールとユーザーが一致する場合）

| 項目名 | データ型 | 説明 |
|--------|---------|------|
| inviter_name | String | 招待者の名前 |
| source_name | String | 招待先の名前（プロジェクト/グループ名） |
| source_type | String | 招待先の種別（project/group） |
| role | String | 付与されるロール（human_access） |

### 既存メンバーの場合

| 項目名 | データ型 | 説明 |
|--------|---------|------|
| member_source | String | 既に参加しているソース種別 |
| source_name | String | ソースの名前 |
| source_url | String | ソースへのリンク |

### メール不一致の場合

| 項目名 | データ型 | 説明 |
|--------|---------|------|
| invite_email | String | 招待されたメールアドレス |
| current_user_email | String | 現在のユーザーのメールアドレス |
| current_user_reference | String | 現在のユーザーの参照（@username） |

## イベント仕様

### 1-招待承諾

「Accept invitation」ボタン押下時の処理：
1. `POST /-/invites/:id/accept`にリクエスト送信
2. `Members::AcceptInviteService`が実行される
3. 成功時：招待先のパスへリダイレクト、成功メッセージ表示
4. 失敗時：前の画面に戻り、エラーメッセージ表示

### 2-招待辞退

「Decline」ボタン押下時の処理：
1. `POST /-/invites/:id/decline`にリクエスト送信
2. `member.decline_invite!`が実行される
3. 成功時：
   - 未ログインかつ未知のユーザーかつ招待者が存在する場合：招待辞退画面（ミニマルレイアウト）
   - ログイン済みの場合：ダッシュボードへリダイレクト
   - それ以外：ログイン画面へリダイレクト
4. 失敗時：前の画面に戻り、エラーメッセージ表示

### 3-招待先への遷移（既存メンバー）

「Go to {source_name}」ボタン押下時、招待先（プロジェクト/グループ）へ直接遷移する。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 承諾 | members | UPDATE | invite_tokenクリア、user_id設定 |
| 辞退 | members | DELETE | メンバーレコード削除 |

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

#### members（承諾時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | invite_token | NULL | トークンクリア |
| UPDATE | invite_accepted_at | 現在時刻 | 承諾日時記録 |
| UPDATE | user_id | current_user.id | ユーザー紐付け |

#### members（辞退時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | id = member.id | レコード削除 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-305-001 | 通知 | Invitation | ページタイトル |
| MSG-305-002 | 情報 | You have been invited by %{inviter} to join %{source} as %{role} | 招待確認時 |
| MSG-305-003 | 情報 | You are already a member of this %{source}. | 既存メンバーの場合 |
| MSG-305-004 | 警告 | This invitation was sent to %{invite_email}, but you are signed in as %{current_user} with email %{current_email}. | メール不一致時 |
| MSG-305-005 | 案内 | Sign in as a user with the matching email address, add the email to this account, or sign-up for a new account using the matching email. | メール不一致時 |
| MSG-305-006 | 成功 | You have been granted %{access_level} access to %{source}. | 承諾成功時 |
| MSG-305-007 | エラー | The invitation could not be declined. | 辞退失敗時 |
| MSG-305-008 | 成功 | You have declined the invitation to join %{title} %{name}. | 辞退成功時 |
| MSG-305-009 | 案内 | To accept this invitation, create an account or sign in. | 未ログイン時（サインアップ可） |
| MSG-305-010 | 案内 | To accept this invitation, sign in. | 未ログイン時（サインアップ不可） |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| トークンが無効 | エラーメッセージ表示、前の画面またはダッシュボードへリダイレクト |
| 招待が存在しない | エラーメッセージ「The invitation can not be found with the provided invite token.」 |
| 承諾処理失敗 | エラーメッセージ表示、前の画面へリダイレクト |
| 辞退処理失敗 | エラーメッセージ表示、前の画面へリダイレクト |

## 備考

- 招待トークンは`A-Za-z0-9_-`の文字列で構成される
- 未ログイン状態でアクセスした場合、ログイン画面または登録画面へリダイレクト
- `invite_type=initial_email`の場合、Gitlabトラッキングで「join_clicked」イベントが記録される
- 新規ユーザーは登録時に自動的に保留中の招待を承諾する
- 既存ユーザーが招待メールと一致するメールで認証された場合、自動的に招待が承諾される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | member.rb | `app/models/member.rb` | 招待関連のスコープ・メソッド |

**読解のコツ**: `invite?`、`invite_token`、`invite_email`カラムの関係を理解する。`decline_invite!`メソッドで辞退処理のロジックを確認する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | invites_controller.rb | `app/controllers/invites_controller.rb` | show/accept/declineアクション |

**主要処理フロー**:
1. **行7-10**: before_actionでmember取得と存在確認
2. **行19-21**: `show`アクションで自動承諾条件確認
3. **行30-38**: `accept`アクションでAcceptInviteService呼び出し
4. **行40-58**: `decline`アクションで辞退処理

#### Step 3: ビューを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | show.html.haml | `app/views/invites/show.html.haml` | 条件分岐による表示制御 |

**主要処理フロー**:
- **行4-9**: `current_user_matches_invite?`かつ既存メンバーの場合
- **行11-23**: 招待確認・承諾/辞退ボタン表示
- **行25-32**: メール不一致時の案内表示

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | accept_invite_service.rb | `app/services/members/accept_invite_service.rb` | 承諾処理ロジック |

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

```
InvitesController
    │
    ├─ before_action :authenticate_user! (show のみ)
    │      └─ 未ログイン時：登録/ログインへリダイレクト
    │
    ├─ before_action :member
    │      └─ Member.find_by_invite_token(@token)
    │
    ├─ before_action :ensure_member_exists
    │      └─ member存在確認、なければリダイレクト
    │
    ├─ before_action :invite_details
    │      └─ Project/Groupに応じた詳細情報構築
    │
    ├─ #show (GET)
    │      ├─ skip_invitation_prompt?
    │      │      └─ !member? && current_user_matches_invite?
    │      │              └─ 自動的にaccept呼び出し
    │      │
    │      └─ View: show.html.haml
    │
    ├─ #accept (POST)
    │      └─ Members::AcceptInviteService#execute
    │              └─ member更新（user_id設定、トークンクリア）
    │
    └─ #decline (GET/POST)
           └─ member.decline_invite!
                  └─ member削除
```

### データフロー図

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

メールリンク ───▶ InvitesController#show ───▶ 確認画面表示
(invite_token)          │
                       ▼
               authenticate_user!
                       │
                       ├─ 未ログイン → 登録/ログイン画面
                       │
                       ▼
               member取得（find_by_invite_token）
                       │
                       ▼
               invite_details構築
                       │
                       ├─ current_user_matches_invite?
                       │      │
                       │      ├─ true かつ !member? → 自動承諾
                       │      │
                       │      └─ View表示
                       │             │
    ┌──────────────────┼─────────────┴─────────────────┐
    │                  │                               │
    ▼                  ▼                               ▼
既存メンバー       承諾ボタン ───▶ accept       辞退ボタン ───▶ decline
    │                  │                               │
    ▼                  ▼                               ▼
ソースへ遷移    AcceptInviteService           member.decline_invite!
                       │                               │
                       ▼                               ▼
               招待先へリダイレクト            招待辞退画面/ダッシュボード
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| invites_controller.rb | `app/controllers/invites_controller.rb` | コントローラー | 画面制御・API提供 |
| show.html.haml | `app/views/invites/show.html.haml` | テンプレート | 招待確認画面HTML |
| decline.html.haml | `app/views/invites/decline.html.haml` | テンプレート | 招待辞退画面HTML |
| member.rb | `app/models/member.rb` | モデル | メンバー管理 |
| accept_invite_service.rb | `app/services/members/accept_invite_service.rb` | サービス | 承諾処理 |
| routes.rb | `config/routes.rb` | ルーティング | URL定義（行227-232） |
