# 機能設計書 40-タスク未完了に戻す

## 概要

本ドキュメントは、Fat Free CRMシステムにおける「タスク未完了に戻す」機能の設計を定義する。完了済みタスクを未完了状態に戻すための機能である。

### 本機能の処理概要

タスク未完了に戻す機能は、誤って完了にしたタスクや、再度対応が必要になったタスクを未完了状態に戻すための機能である。

**業務上の目的・背景**：営業活動において、一度完了とマークしたタスクを再度実行する必要が生じる場合がある。例えば、顧客から追加の要望があった場合や、完了を誤ってマークした場合など。この機能により、タスクを柔軟に管理でき、正確なタスク状態を維持できる。

**機能の利用シーン**：誤って完了にしたタスクを戻す場合、追加対応が必要になったタスクを再開する場合、完了タスクの確認中に未完了であることが判明した場合。

**主要な処理内容**：
1. タスクのcompleted_at（完了日時）をnullに設定
2. タスクのcompleted_by（完了者）をnullに設定
3. 空バケットの判定（completedビューから消えた場合）
4. サイドバー情報の更新
5. UI更新（タスクの非表示）

**関連システム・外部連携**：特になし。

**権限による制御**：ユーザーは自分が作成したタスク、または自分に割り当てられたタスクのみ未完了に戻すことが可能（tracked_byスコープ）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 28 | タスク一覧画面 | 主画面 | 完了ビューからの未完了戻し操作 |
| 30 | タスク編集フォーム | 補助画面 | 編集フォームからの未完了戻し操作 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象のタスクID | 存在チェック、アクセス権チェック |
| bucket | String | No | 未完了戻し元のバケット名 | 空バケット判定用 |
| view | String | No | 現在の表示ビュー | pending/assigned/completed |

### 入力データソース

- URLパラメータ（タスクID、bucket、view）
- セッション情報（current_user）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @task | Task | 未完了に戻したタスクオブジェクト |
| @empty_bucket | String | 空になったバケット名 |

### 出力先

- 画面表示（AJAX応答）
- データベース（tasksテーブル）

## 処理フロー

### 処理シーケンス

```
1. 未完了戻しリクエスト受信
   └─ PUT /tasks/:id/uncomplete
2. タスクの取得
   └─ Task.tracked_by(current_user).find(params[:id])
3. 完了情報のクリア
   └─ @task.update(completed_at: nil, completed_by: nil)
4. 空バケットの判定
   └─ Task.bucket_empty?(params[:bucket], current_user, @view)
5. サイドバーの更新
   └─ update_sidebar
6. 応答の生成
   └─ JS形式での応答
```

### フローチャート

```mermaid
flowchart TD
    A[未完了戻しリクエスト] --> B[タスク取得]
    B --> C{追跡可能?}
    C -->|No| D[404エラー]
    C -->|Yes| E[完了情報クリア]
    E --> F[completed_at = nil]
    F --> G[completed_by = nil]
    G --> H{バケット指定あり?}
    H -->|Yes| I[空バケット判定]
    H -->|No| J[サイドバー更新]
    I --> J
    J --> K[応答生成]
    K --> L[UI更新]
    L --> M[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-40-01 | 追跡可能条件 | user_id = current_user OR assigned_to = current_user | 常時 |
| BR-40-02 | 完了日時クリア | completed_at を nil に設定 | 常時 |
| BR-40-03 | 完了者クリア | completed_by を nil に設定 | 常時 |
| BR-40-04 | 空バケット非表示 | 未完了戻し後にバケットが空になった場合は非表示 | bucket指定時 |
| BR-40-05 | ビュー移動 | 未完了タスクはcompletedビューからpendingビューへ移動 | 一覧表示時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| タスク取得 | tasks | SELECT | 対象タスクの取得 |
| タスク更新 | tasks | UPDATE | 完了情報のクリア |
| 履歴記録 | versions | INSERT | PaperTrailによる履歴記録 |

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

#### tasks

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | id = params[:id] AND (user_id = current_user OR assigned_to = current_user) | tracked_by |
| UPDATE | completed_at | nil | 完了日時クリア |
| UPDATE | completed_by | nil | 完了者クリア |
| UPDATE | updated_at | 現在日時 | 自動設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | タスクが存在しない、またはアクセス権がない | エラー画面表示 |

### リトライ仕様

特になし。

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

- タスクの更新は暗黙的なトランザクション内で実行
- PaperTrailによる履歴記録も同一トランザクション内で実行

## パフォーマンス要件

- 未完了戻し処理: 500ミリ秒以内

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

- tracked_byスコープにより、ユーザーは自分に関連するタスクのみ未完了に戻すことが可能
- CSRF対策（Railsデフォルト）

## 備考

- 未完了に戻すとpendingスコープで取得可能になる
- 期限バケットは既存のdue_atに基づいてcomputed_bucketで計算される
- completedビューでのみ「未完了に戻す」リンクが表示される

---

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

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

### 推奨読解順序

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

タスクモデルの完了/未完了関連スコープを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | task.rb | `app/models/polymorphic/task.rb` | pendingスコープとcompleted関連 |

**読解のコツ**:
- **84行目**: `scope :pending, -> { where('completed_at IS NULL') }` - 未完了タスク
- **86行目**: `scope :completed, -> { where('completed_at IS NOT NULL') }` - 完了タスク
- **138-140行目**: `completed?` メソッド - 完了判定

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

TasksControllerのuncompleteアクションが処理の起点。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | tasks_controller.rb | `app/controllers/tasks_controller.rb` | uncompleteアクションの実装 |

**主要処理フロー**:
1. **130-141行目**: uncompleteアクション全体
2. **133行目**: `Task.tracked_by(current_user).find(params[:id])` - タスク取得
3. **134行目**: `@task.update(completed_at: nil, completed_by: nil)` - 完了情報クリア
4. **137行目**: `Task.bucket_empty?(params[:bucket], current_user, @view)` - 空バケット判定
5. **139行目**: `update_sidebar` - サイドバー更新

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

未完了戻し後のUI処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | uncomplete.js.haml | `app/views/tasks/uncomplete.js.haml` | 未完了戻し後JS処理 |

**主要処理フロー**:
- **1-2行目**: エラーがない場合はタスクをスライドアップ
- **4-5行目**: 空バケットの場合はバケット全体をフェードアウト
- **7-8行目**: 一覧からの呼び出し時はサイドバー更新
- **9-11行目**: エラー時はフォームにフォーカス

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

```
TasksController#uncomplete
    │
    ├─ Task.tracked_by(current_user).find(params[:id])
    │      └─ includes(:assignee)
    │             .where('user_id = ? OR assigned_to = ?', user.id, user.id)
    │
    ├─ @task.update(completed_at: nil, completed_by: nil)
    │      └─ UPDATE tasks SET completed_at = NULL, completed_by = NULL WHERE id = ?
    │
    ├─ Task.bucket_empty?(params[:bucket], current_user, @view)
    │      └─ my(user).send(bucket).send(view).count == 0
    │
    ├─ update_sidebar
    │      └─ Task.totals(current_user, @view)
    │
    └─ respond_with(@task)
           └─ render "uncomplete.js.haml"
                  ├─ タスクスライドアップ
                  ├─ 空バケットフェードアウト
                  └─ サイドバー更新
```

### データフロー図

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

PUT /tasks/:id/uncomplete  TasksController#uncomplete
                               │
                               ▼
                       tracked_by(current_user)
                               │
                               ▼
                       find(params[:id])
                               │
                               ▼
                       @task.update(
                         completed_at: nil,
                         completed_by: nil
                       )
                               │
                               ▼
                       UPDATE tasks
                               │
                               ▼
                       bucket_empty?判定
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
              空でない               空になった
                    │                     │
                    │                     ▼
                    │             @empty_bucket設定
                    │                     │
                    └──────────┬──────────┘
                               ▼
                       update_sidebar
                               │
                               ▼
                       UI更新（AJAX）
                       タスクスライドアップ
                       (バケット非表示)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tasks_controller.rb | `app/controllers/tasks_controller.rb` | コントローラ | uncompleteアクション |
| task.rb | `app/models/polymorphic/task.rb` | モデル | pendingスコープ、completed? |
| uncomplete.js.haml | `app/views/tasks/uncomplete.js.haml` | ビュー | 未完了戻し後AJAX処理 |
| tasks_helper.rb | `app/helpers/tasks_helper.rb` | ヘルパー | link_to_task_uncomplete |
| routes.rb | `config/routes.rb` | 設定 | PUT /tasks/:id/uncomplete ルーティング（151行目） |
