# 機能設計書 28-カードをクローズ

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるカードクローズ機能の設計を記載するものです。

### 本機能の処理概要

カードクローズ機能は、カードを完了（クローズ）状態に設定する機能です。クローズされたカードはボードの「Closed」セクションに移動し、アクティブなタスク一覧から除外されます。

**業務上の目的・背景**：プロジェクト管理において、完了したタスクを明確に区別することは重要です。カードをクローズすることで、進行中のタスクと完了したタスクを視覚的に分離し、チームの作業状況を正確に把握できます。また、完了したタスクを記録として残すことで、プロジェクトの進捗履歴を追跡できます。

**機能の利用シーン**：
- タスクが完了した場合
- タスクが不要になり、アーカイブする場合
- タスクの進捗状況を「完了」として記録する場合

**主要な処理内容**：
1. カードの延期状態（Not Now）を解除（存在する場合）
2. Closureレコードを作成してクローズ状態を記録
3. クローズイベントの記録
4. 画面の動的更新（Turbo Stream）

**関連システム・外部連携**：Webhookが設定されている場合、クローズイベントが通知されます。

**権限による制御**：カードにアクセス可能なユーザーであれば誰でもクローズ操作を実行できます。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | カード詳細画面 | 主画面 | クローズボタンの表示と実行 |
| 24 | カードカラム変更画面 | 参照画面 | 「Done」ボタンでクローズ |
| 10 | ボード詳細画面 | 結果表示画面 | クローズ後の画面更新 |

## 機能種別

CRUD操作（CREATE） / ステータス変更

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | String | Yes | カード番号（number） | 存在するカードであること |

### 入力データソース

- URL パラメータからカード番号を取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Turbo Stream | HTML | 画面の動的更新（HTML形式） |
| HTTP Status | Integer | 成功時: 204 No Content（JSON形式） |

### 出力先

- Turbo Stream形式：カード、カラム、クローズドセクションの動的更新
- JSON形式：HTTP 204 No Content

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（POST /cards/:card_id/closure）
   └─ カード番号をURLパラメータから取得

2. カード取得
   └─ CardScoped concernによりカードを取得

3. 元の位置を記録
   └─ capture_card_location で元カラム/ストリーム状態を保存

4. クローズ処理実行
   └─ @card.close
       ├─ 延期状態（Not Now）を解除
       ├─ Closureレコードを作成
       └─ クローズイベントを記録

5. ストリーム更新
   └─ refresh_stream_if_needed で必要に応じてストリームを更新

6. レスポンス返却
   └─ Turbo Stream: カードとカラム/ストリームを更新
   └─ JSON: HTTP 204 No Content
```

### フローチャート

```mermaid
flowchart TD
    A[クローズリクエスト受信] --> B[カード取得]
    B --> C[元の位置を記録]
    C --> D{既にクローズ済み?}
    D -->|Yes| E[何もしない]
    D -->|No| F[Not Now解除]
    F --> G[Closureレコード作成]
    G --> H[イベント記録]
    H --> I[ストリーム更新]
    E --> I
    I --> J{レスポンス形式}
    J -->|Turbo Stream| K[画面動的更新]
    J -->|JSON| L[204 No Content]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-28-01 | 冪等性 | 既にクローズ済みの場合は何もしない | クローズ済みカードへの操作 |
| BR-28-02 | 延期解除 | クローズ時にNot Now状態は自動解除される | 延期中のカードをクローズした場合 |
| BR-28-03 | クローズ者記録 | Closureレコードにクローズしたユーザーを記録 | すべてのクローズ操作 |
| BR-28-04 | イベント記録 | :closed イベントが記録される | すべてのクローズ操作 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| クローズ | closures | INSERT | Closureレコードの作成 |
| 延期解除 | card_not_nows | DELETE | 延期レコードを削除 |
| イベント記録 | events | INSERT | クローズイベントを記録 |

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

#### closures

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | card_id | カードのID | 対象カード |
| INSERT | user_id | Current.user.id | クローズしたユーザー |
| INSERT | account_id | カードのaccount_id | アカウント |

#### events

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | action | "closed" | イベント種別 |
| INSERT | creator_id | Current.user.id | クローズしたユーザー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | RecordNotFound | 指定されたカードが存在しない | 適切なエラー画面を表示 |

### リトライ仕様

冪等性あり（既にクローズ済みの場合は状態を変えない）

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

close メソッド内で transaction ブロックを使用し、延期解除、Closure作成、イベント記録を一括で処理します。いずれかが失敗した場合はロールバックされます。

## パフォーマンス要件

- クローズ処理は500ms以内に完了すること

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

- 認証：ログインユーザーのみアクセス可能
- 認可：カードにアクセス可能なユーザーのみ操作可能
- CSRF対策：Rails標準のCSRFトークン検証を適用

## 備考

- クローズされたカードはボードの「Closed」セクションに表示される
- クローズは再オープン機能（No.29）で取り消し可能

---

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

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

### 推奨読解順序

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

クローズ処理の仕組みを理解するために、Closeableモジュールを把握することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | closeable.rb | `app/models/card/closeable.rb` | Closeableモジュールでクローズ関連メソッドを定義 |
| 1-2 | closure.rb | `app/models/closure.rb` | Closureモデルの定義 |

**読解のコツ**: Card::Closeable モジュールには `close` メソッドがあり、これがメインの処理です。`has_one :closure` の関連も確認してください。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | closures_controller.rb | `app/controllers/cards/closures_controller.rb` | createアクションがクローズのエントリーポイント |
| 2-2 | card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | CardScopedでカード取得とヘルパーメソッド |

**主要処理フロー**:
1. **5行目**: `capture_card_location` - 元の位置を記録
2. **6行目**: `@card.close` - クローズ処理の実行
3. **7行目**: `refresh_stream_if_needed` - ストリームの更新

#### Step 3: クローズ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | closeable.rb | `app/models/card/closeable.rb` | closeメソッドの実装詳細 |

**主要処理フロー**:
- **31-39行目**: `close`メソッド
  - **32行目**: `unless closed?` - 既にクローズ済みなら何もしない
  - **33行目**: トランザクション開始
  - **34行目**: `not_now&.destroy` - 延期状態を解除
  - **35行目**: `create_closure!` - Closureレコードを作成
  - **36行目**: `track_event :closed` - イベント記録

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

```
Cards::ClosuresController#create
    │
    ├─ CardScoped#set_card (before_action)
    │      └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
    │
    ├─ CardScoped#capture_card_location
    │      ├─ @source_column = @card.column
    │      └─ @was_in_stream = @card.awaiting_triage?
    │
    ├─ @card.close
    │      └─ Card::Closeable#close
    │             ├─ unless closed? (冪等性チェック)
    │             ├─ transaction
    │             │      ├─ not_now&.destroy
    │             │      ├─ create_closure!(user: user)
    │             │      └─ track_event :closed
    │             └─ end
    │
    └─ CardScoped#refresh_stream_if_needed
           └─ ストリーム更新（元がストリームの場合）
```

### データフロー図

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

POST /cards/:id/closure ──▶ ClosuresController#create ──▶ Turbo Stream/204
  │                              │
  │ card_id                      ├─ capture_card_location
  │                              │    └─ 元カラム/ストリーム状態保存
  │                              │
  │                              ├─ @card.close
  │                              │    ├─ not_now 削除
  │                              │    ├─ closure 作成
  │                              │    └─ イベント記録
  │                              │
  │                              └─ refresh_stream_if_needed
  │
  └─ closures (INSERT), card_not_nows (DELETE), events (INSERT)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| closures_controller.rb | `app/controllers/cards/closures_controller.rb` | コントローラー | クローズリクエストの処理 |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | コントローラー(concern) | カード取得とヘルパーメソッド |
| closeable.rb | `app/models/card/closeable.rb` | モデル(concern) | クローズ処理ロジック |
| closure.rb | `app/models/closure.rb` | モデル | Closureモデル定義 |
| create.turbo_stream.erb | `app/views/cards/closures/create.turbo_stream.erb` | ビュー | Turbo Streamレスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（80行目） |
