# 機能設計書 30-カードを「Not Now」に設定

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるカードの「Not Now」（延期）設定機能の設計を記載するものです。

### 本機能の処理概要

「Not Now」機能は、カードを一時的に非表示（延期）状態にする機能です。延期されたカードはボードの「Not Now」セクションに移動し、通常のカラムやストリームには表示されなくなります。

**業務上の目的・背景**：プロジェクト管理において、すぐには着手できないが後で対応が必要なタスクが存在します。このようなタスクを「Not Now」に設定することで、現在進行中のタスクに集中しながら、将来対応が必要なタスクを見失わないようにできます。これにより、ボードの視認性を維持しつつ、タスクの漏れを防止できます。

**機能の利用シーン**：
- 今は対応できないが、後で確認したいタスクを延期する場合
- 優先度の低いタスクを一時的に非表示にする場合
- 依存関係があり、他のタスク完了後に着手するタスクを延期する場合

**主要な処理内容**：
1. カードをトリアージ待ち（ストリーム）に戻す（カラム割り当て解除）
2. クローズ状態の解除（存在する場合）
3. Card::NotNowレコードを作成して延期状態を記録
4. 延期イベントの記録
5. 画面の動的更新（Turbo Stream）

**関連システム・外部連携**：Webhookが設定されている場合、延期イベントが通知されます。また、自動延期機能によりシステムが自動的に延期を実行する場合もあります。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | カード詳細画面 | 主画面 | 「Not Now」ボタンの表示と実行 |
| 24 | カードカラム変更画面 | 参照画面 | 「Not Now」ボタン |
| 10 | ボード詳細画面 | 結果表示画面 | 延期後の画面更新 |
| 15 | Not Nowカラム画面 | 結果表示画面 | 延期されたカードの一覧 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

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

### 入力データソース

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

## 出力仕様

### 出力データ

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

### 出力先

- Turbo Stream形式：カード、カラム/ストリーム、Not Nowセクションの動的更新
- JSON形式：HTTP 204 No Content

## 処理フロー

### 処理シーケンス

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

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

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

4. 延期処理実行
   └─ @card.postpone
       ├─ トリアージ待ちに戻す（カラム解除）
       ├─ クローズ状態を解除（存在する場合）
       ├─ アクティビティスパイクを削除
       ├─ Card::NotNowレコードを作成
       └─ 延期イベントを記録

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

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

### フローチャート

```mermaid
flowchart TD
    A[延期リクエスト受信] --> B[カード取得]
    B --> C[元の位置を記録]
    C --> D[トリアージ待ちに戻す]
    D --> E[クローズ解除]
    E --> F[アクティビティスパイク削除]
    F --> G{既に延期済み?}
    G -->|No| H[Card::NotNowレコード作成]
    G -->|Yes| I[スキップ]
    H --> J[イベント記録]
    I --> J
    J --> K[ストリーム更新]
    K --> L{レスポンス形式}
    L -->|Turbo Stream| M[画面動的更新]
    L -->|JSON| N[204 No Content]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-30-01 | カラム解除 | 延期時にカラム割り当ては解除される | すべての延期操作 |
| BR-30-02 | クローズ解除 | 延期時にクローズ状態は解除される | クローズ済みカードを延期した場合 |
| BR-30-03 | 冪等性 | 既に延期済みの場合はNotNowレコード作成をスキップ | 延期済みカードへの操作 |
| BR-30-04 | 延期者記録 | Card::NotNowレコードに延期したユーザーを記録 | すべての延期操作 |
| BR-30-05 | イベント記録 | :postponed イベントが記録される | すべての延期操作 |
| BR-30-06 | 自動延期 | システムによる自動延期は :auto_postponed イベントを記録 | 自動延期の場合 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 延期 | card_not_nows | INSERT | Card::NotNowレコードの作成 |
| カラム解除 | cards | UPDATE | column_id を null に更新 |
| クローズ解除 | closures | DELETE | Closureレコードの削除 |
| スパイク削除 | card_activity_spikes | DELETE | アクティビティスパイクの削除 |
| イベント記録 | events | INSERT | 延期イベントを記録 |

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

#### card_not_nows

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

#### cards

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | column_id | null | カラム解除 |

#### events

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

## エラー処理

### エラーケース一覧

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

### リトライ仕様

冪等性あり（既に延期済みの場合でも安全に再実行可能）

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

postpone メソッド内で transaction ブロックを使用し、トリアージ戻し、クローズ解除、スパイク削除、NotNow作成、イベント記録を一括で処理します。いずれかが失敗した場合はロールバックされます。

## パフォーマンス要件

- 延期処理は500ms以内に完了すること

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

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

## 備考

- 延期されたカードはボードの「Not Now」セクションに表示される
- 延期を解除するには、カードをカラムにトリアージするか、再度ストリームに戻す
- 自動延期機能（auto_postpone）は、ボードの設定に基づいてシステムが実行する

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | postponable.rb | `app/models/card/postponable.rb` | Postponableモジュールでpostponeメソッドを定義 |
| 1-2 | not_now.rb | `app/models/card/not_now.rb` | Card::NotNowモデルの定義 |

**読解のコツ**: Card::Postponable モジュールには `postpone` メソッドと `auto_postpone` メソッドがあります。両者の違いはイベント種別のみです。

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

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

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

#### Step 3: 延期処理を理解する

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

**主要処理フロー**:
- **31-38行目**: `postpone`メソッド
  - **32行目**: トランザクション開始
  - **33行目**: `send_back_to_triage(skip_event: true)` - トリアージ待ちに戻す
  - **34行目**: `reopen` - クローズ解除
  - **35行目**: `activity_spike&.destroy` - スパイク削除
  - **36行目**: `create_not_now!(user: user) unless postponed?` - NotNow作成
  - **37行目**: `track_event event_name` - イベント記録

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

```
Cards::NotNowsController#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.postpone
    │      └─ Card::Postponable#postpone
    │             ├─ transaction
    │             │      ├─ send_back_to_triage(skip_event: true)
    │             │      │      └─ Card::Triageable#send_back_to_triage
    │             │      │             ├─ resume
    │             │      │             └─ update!(column: nil)
    │             │      ├─ reopen
    │             │      │      └─ Card::Closeable#reopen
    │             │      ├─ activity_spike&.destroy
    │             │      ├─ create_not_now!(user: user)
    │             │      └─ track_event :postponed
    │             └─ end
    │
    └─ CardScoped#refresh_stream_if_needed
           └─ ストリーム更新（元がストリームの場合）
```

### データフロー図

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

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

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| not_nows_controller.rb | `app/controllers/cards/not_nows_controller.rb` | コントローラー | 延期リクエストの処理 |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | コントローラー(concern) | カード取得とヘルパーメソッド |
| postponable.rb | `app/models/card/postponable.rb` | モデル(concern) | 延期処理ロジック |
| not_now.rb | `app/models/card/not_now.rb` | モデル | Card::NotNowモデル定義 |
| triageable.rb | `app/models/card/triageable.rb` | モデル(concern) | トリアージ戻し処理 |
| closeable.rb | `app/models/card/closeable.rb` | モデル(concern) | クローズ解除処理 |
| create.turbo_stream.erb | `app/views/cards/not_nows/create.turbo_stream.erb` | ビュー | Turbo Streamレスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（84行目） |
