# 機能設計書 37-タスク編集

## 概要

本ドキュメントは、Fat Free CRMシステムにおける「タスク編集」機能の設計を定義する。既存のタスク情報を更新するための機能である。

### 本機能の処理概要

タスク編集機能は、作成済みのタスクの内容や期限を変更するための機能である。

**業務上の目的・背景**：営業活動において、タスクの内容や期限は状況に応じて変更が必要となる。顧客の都合による予定変更、優先度の変更、担当者の変更などに対応するため、タスクの柔軟な編集が求められる。また、クイックリスケジュール機能により、ワンクリックで期限バケットを変更できる。

**機能の利用シーン**：タスクの期限を変更する場合、タスクの内容を修正する場合、担当者を再割り当てする場合、カテゴリや優先度を変更する場合、関連エンティティを変更する場合。

**主要な処理内容**：
1. タスク編集フォームの表示（edit アクション）
2. タスクの期限バケット・カテゴリ選択肢の取得
3. タスク情報のバリデーションと更新（update アクション）
4. 期限日時の再計算（バケット変更時）
5. バケット間のタスク移動処理
6. サイドバー情報の更新

**関連システム・外部連携**：タスクは取引先、連絡先、リード、商談、キャンペーンなどのエンティティにポリモーフィック関連で紐付け可能。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 30 | タスク編集フォーム | 主画面 | タスク情報更新フォームの表示と保存 |
| 28 | タスク一覧画面 | 参照画面 | 一覧からの編集呼び出し |

## 機能種別

CRUD操作（Update） / データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | タスクID | 存在チェック、アクセス権チェック |
| task[name] | String | Yes | タスク名 | 必須、最大255文字 |
| task[assigned_to] | Integer | No | 割り当て先ユーザーID | ユーザー存在チェック |
| task[bucket] | String | No | 期限バケット | 設定された選択肢のいずれか |
| task[due_at] | DateTime | No | 期限日時 | 日時形式 |
| task[calendar] | String | No | 特定日時（specific_time用） | 日時形式 |
| task[category] | String | No | カテゴリ | 設定された選択肢 |
| task[priority] | String | No | 優先度 | - |
| task[background_info] | String | No | 背景情報 | 最大255文字 |
| bucket | String | No | クイックリスケジュール用バケット | URLパラメータ |
| view | String | No | 現在の表示ビュー | pending/assigned/completed |
| previous | String | No | 前のタスクID（フォーム管理用） | - |

### 入力データソース

- 画面入力（タスク編集フォーム）
- セッション情報（current_user）
- URLパラメータ（bucket、view、previous）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @task | Task | 更新されたタスクオブジェクト |
| @task_before_update | Task | 更新前のタスク（バケット比較用） |
| @view | String | 現在の表示ビュー |
| @bucket | Array | 期限バケット選択肢 |
| @category | Array | カテゴリ選択肢 |
| @empty_bucket | String | 空になったバケット名 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. editアクション（編集フォーム表示）
   └─ GET /tasks/:id/edit
2. タスクデータの取得
   └─ Task.tracked_by(current_user).find(params[:id])
3. フォーム用データの準備
   └─ バケット・カテゴリ選択肢、関連エンティティ
4. updateアクション（更新処理）
   └─ PUT /tasks/:id
5. 更新前タスク状態の保存
   └─ @task_before_update.bucket
6. タスクレコードの更新
   └─ before_update: set_due_date（期限再計算）
7. バケット変更の検知
   └─ 空バケットの判定
8. サイドバーの更新（一覧画面からの呼び出し時）
   └─ update_sidebar
9. 応答の生成
   └─ JS形式での応答
```

### フローチャート

```mermaid
flowchart TD
    A[編集リクエスト] --> B[タスク取得]
    B --> C{追跡可能?}
    C -->|No| D[404エラー]
    C -->|Yes| E[編集フォーム表示]
    E --> F[ユーザー入力]
    F --> G[更新リクエスト]
    G --> H[更新前状態保存]
    H --> I{バリデーション}
    I -->|失敗| J[エラー表示]
    J --> F
    I -->|成功| K[set_due_date]
    K --> L[タスク更新]
    L --> M{バケット変更?}
    M -->|Yes| N[空バケット判定]
    M -->|No| O[サイドバー更新]
    N --> O
    O --> P[UI更新]
    P --> Q[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-37-01 | タスク名必須 | タスク名は必須入力 | 常時 |
| BR-37-02 | 追跡可能条件 | user_id = current_user OR assigned_to = current_user | 常時 |
| BR-37-03 | 期限再計算 | バケット変更時はdue_atを再計算 | bucket変更時 |
| BR-37-04 | バケット移動 | 更新後のバケットに応じてUI上でタスクを移動 | 一覧画面から |
| BR-37-05 | 空バケット非表示 | タスクがなくなったバケットは非表示 | 一覧画面から |
| BR-37-06 | 担当者変更時移動 | pending/assignedビュー間でタスクを移動 | assigned_to変更時 |

### 計算ロジック

**バケット変更時の期限再計算（set_due_date）**:
- 完了済みタスク（completed_at != nil）の場合はスキップ
- バケット値に基づいて新しいdue_atを計算
- specific_timeの場合はcalendarパラメータをパース

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| タスク取得 | 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 | name | フォーム入力値 | 必須 |
| UPDATE | assigned_to | フォーム入力値 | 任意 |
| UPDATE | bucket | フォーム入力値 | 期限バケット |
| UPDATE | due_at | 計算値（set_due_date） | 自動計算 |
| UPDATE | category | フォーム入力値 | カテゴリ |
| UPDATE | priority | フォーム入力値 | 優先度 |
| UPDATE | background_info | フォーム入力値 | 背景情報 |
| UPDATE | updated_at | 現在日時 | 自動設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | タスクが存在しない、またはアクセス権がない | エラー画面表示 |
| 422 | Validation Error | バリデーション失敗 | エラーメッセージ表示 |
| - | missing_task_name | タスク名未入力 | エラーメッセージ表示 |
| - | invalid_date | specific_time時のcalendar形式不正 | エラーメッセージ表示 |

### リトライ仕様

バリデーションエラー時はフォームを再表示し、ユーザーが修正して再送信可能。

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

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

## パフォーマンス要件

- 編集フォーム表示: 500ミリ秒以内
- 更新処理: 1秒以内

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

- tracked_byスコープにより、ユーザーは自分に関連するタスクのみ編集可能
- Strong Parametersによる入力パラメータのホワイトリスト制御
- CSRF対策（Railsデフォルト）
- XSS対策（出力時のエスケープ）

## 備考

- クイックリスケジュール機能: 編集フォーム下部のリンクでワンクリックでバケット変更可能
- 担当者変更時、pendingビューからassignedビューへ（またはその逆）タスクが移動
- 期限切れタスクを更新すると、適切なバケットに自動移動

---

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

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

### 推奨読解順序

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

タスクモデルのバケット計算とバリデーションを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | task.rb | `app/models/polymorphic/task.rb` | computed_bucketとset_due_date |

**読解のコツ**:
- **157-173行目**: `computed_bucket` メソッド - 期限に基づくバケット計算
- **217-234行目**: `set_due_date` メソッド - バケットから期限を計算
- **122行目**: `before_update :set_due_date, unless: :completed?` - 完了タスクは期限再計算しない

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

TasksControllerのeditとupdateアクションが処理の起点。

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

**主要処理フロー**:
1. **52-64行目**: editアクション全体
2. **56行目**: `Task.tracked_by(current_user).find(params[:id])` - タスク取得
3. **59行目**: `@asset = @task.asset if @task.asset_id?` - 関連エンティティ取得
4. **79-101行目**: updateアクション全体
5. **83行目**: `@task = Task.tracked_by(current_user).find(params[:id])`
6. **84行目**: `@task_before_update = @task.dup` - 更新前状態の保存
7. **86-90行目**: 更新前バケットの計算（期限切れ判定含む）
8. **93行目**: `@task.update(task_params)` - 更新処理
9. **94行目**: `@task.bucket = @task.computed_bucket` - 新バケット計算
10. **96行目**: `@empty_bucket` - 空バケット判定

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

編集フォームとクイックリスケジュールを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/tasks/_edit.html.haml` | 編集フォーム |
| 3-2 | update.js.haml | `app/views/tasks/update.js.haml` | 更新後JS処理 |

**主要処理フロー**:
- **_edit.html.haml 16-18行目**: クイックリスケジュールリンク
- `crm.reschedule_task(id, due_day)` - JSでバケット変更

#### Step 4: ヘルパーを理解する

タスク更新後のUI処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | tasks_helper.rb | `app/helpers/tasks_helper.rb` | reassign、rescheduleヘルパー |

**主要処理フロー**:
- **119-132行目**: `reassign` - 担当者変更時の処理
- **134-140行目**: `reschedule` - バケット変更時の処理

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

```
TasksController#edit
    │
    ├─ Task.tracked_by(current_user).find(params[:id])
    │
    ├─ Setting.unroll(:task_bucket)
    │
    ├─ Setting.unroll(:task_category)
    │
    ├─ @task.asset (関連エンティティ)
    │
    └─ respond_with(@task)
           └─ render "edit.js.haml"
                  └─ render "_edit.html.haml"

TasksController#update
    │
    ├─ Task.tracked_by(current_user).find(params[:id])
    │
    ├─ @task_before_update = @task.dup
    │      └─ computed_bucket (期限切れ判定)
    │
    ├─ @task.update(task_params)
    │      ├─ validates_presence_of :name
    │      ├─ before_update :set_due_date (unless completed?)
    │      └─ UPDATE tasks SET ...
    │
    ├─ @task.bucket = @task.computed_bucket
    │
    ├─ Task.bucket_empty? (空バケット判定)
    │
    ├─ update_sidebar (if called_from_index_page?)
    │
    └─ respond_with(@task)
           └─ render "update.js.haml"
                  ├─ reassign (担当者変更時)
                  └─ reschedule (バケット変更時)
```

### データフロー図

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

GET /tasks/:id/edit    TasksController#edit
                               │
                               ▼
                       tracked_by(current_user)
                               │
                               ▼
                       @task, @bucket, @category
                               │
                               ▼
                       編集フォーム表示
                               │
                               ▼
PUT /tasks/:id         TasksController#update
                               │
                               ▼
                       @task_before_update.bucket
                       (更新前バケット保存)
                               │
                               ▼
                       @task.update(task_params)
                               │
                               ▼
                       set_due_date (期限再計算)
                               │
                               ▼
                       @task.computed_bucket
                       (新バケット計算)
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
            バケット変更あり        バケット変更なし
                    │                     │
                    ▼                     │
            空バケット判定               │
            @empty_bucket                 │
                    │                     │
                    └──────────┬──────────┘
                               ▼
                       update_sidebar
                               │
                               ▼
                       UI更新（AJAX）
                       reassign/reschedule
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tasks_controller.rb | `app/controllers/tasks_controller.rb` | コントローラ | edit/updateアクション |
| task.rb | `app/models/polymorphic/task.rb` | モデル | computed_bucket、set_due_date |
| _edit.html.haml | `app/views/tasks/_edit.html.haml` | ビュー | 編集フォームテンプレート |
| edit.js.haml | `app/views/tasks/edit.js.haml` | ビュー | フォーム表示AJAX |
| update.js.haml | `app/views/tasks/update.js.haml` | ビュー | 更新後AJAX処理 |
| tasks_helper.rb | `app/helpers/tasks_helper.rb` | ヘルパー | reassign、reschedule |
| routes.rb | `config/routes.rb` | 設定 | PUT /tasks/:id ルーティング |
