# マスタードキュメント：TODO管理機能

> **作成**: 2026-04-10
> **最終更新**: 2026-04-10（tractor_work 対応追記）
> **対象機能**: TODO管理（作業指示・優先順位管理・実績連携導線）
> **実装状況**: 設計完了・実装前
> **対象 Issue**: `akira/keinasystem#17`

---

## 概要

繁忙期に「どれから手を付けるか」を管理するための TODO 機能。
計画（施肥・田植え・運搬など）と実績の間に位置する「作業指示」レイヤー。

### 機能スコープ（IN / OUT）

| IN（MVP対象） | OUT（対象外） |
|---|---|
| TODO の作成・編集・削除 | 期日通知・リマインダー |
| ステータス管理（todo / doing / done / canceled） | 複数ユーザー割り当て |
| 優先順位管理（ドラッグ&ドロップ / 矢印移動） | コメント・添付ファイル |
| 圃場単位の対象紐づけ | 工数見積・実績時間記録 |
| 計画との紐づけ（施肥・田植え・運搬） | 完全な汎用ワークフローエンジン化 |
| 計画画面からの TODO 生成 | 実績アプリ未実装領域の詳細実績入力 UI |
| 完了時の実績入力画面への導線生成 | |
| 完了済み・キャンセル済みの表示切り替え | |

### TODO の位置づけ

```
計画（年間設計情報）
  ↓ 生成
TODO（実際に動く作業単位）
  ↓ 完了
実績（完了した事実）
```

---

## データモデル

### Todo（本体）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| year | integer | ✓ | 年度 |
| title | varchar(200) | ✓ | タイトル |
| description | text | | 説明 |
| status | enum | ✓ | `todo / doing / done / canceled` |
| priority | integer | ✓ | 小さいほど上位（1000刻み） |
| due_date | date | | 期日 |
| work_type | enum | ✓ | 作業種別（下記参照） |
| work_subtype | varchar(30) | | トラクター作業の細別。`work_type=tractor_work` のときのみ必須（下記参照） |
| should_link_record | boolean | ✓ | 完了時に実績連携導線を有効にするか |
| completed_at | datetime | | 完了日時（差し戻し後も保持） |
| canceled_at | datetime | | キャンセル日時 |
| created_at | datetime | ✓ | |
| updated_at | datetime | ✓ | |

#### ステータス遷移

- `todo` → `doing` → `done`（complete/ 専用エンドポイント経由）
- `done` → `todo / doing`（差し戻し許可。completed_at は履歴として保持）
- `canceled` への遷移は任意のタイミングで可

#### 作業種別（work_type）

MVP で採用する種別：

| 値 | 意味 |
|---|---|
| `general` | 一般（どれにも当てはまらない作業） |
| `fertilization` | 施肥 |
| `rice_transplant` | 田植え |
| `delivery` | 運搬 |
| `tractor_work` | トラクター作業（畔塗・荒代掻き・植代掻き・耕耘） |

将来追加（農薬散布管理アプリ実装時）：

| 値 | 意味 |
|---|---|
| `pesticide` | 防除 |

#### work_subtype（トラクター作業の細別）

`work_type = tractor_work` のときのみ使用する。それ以外は `null`。

| 値 | 意味 |
|---|---|
| `levee_work` | 畔塗 |
| `rough_harrowing` | 荒代掻き |
| `transplant_harrowing` | 植代掻き |
| `cultivation` | 耕耘 |

バリデーションルール：
- `work_type = tractor_work` → `work_subtype` 必須
- `work_type ≠ tractor_work` → `work_subtype` は null のみ許可

#### 並び順

- 基本は FILO（新規作成時は最上位へ）
- 初回作成時：最上位 TODO の `priority - 1000` を割り当て
- 一覧表示：`priority` 昇順
- 並び替え後：表示対象全体を 1000, 2000, 3000... と再採番して保存
- 完了・キャンセル済みも `priority` を保持

### TodoTargetField（対象圃場）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | CASCADE |
| field | FK(fields.Field) | ✓ | PROTECT |
| field_name_snapshot | varchar(100) | ✓ | 保存時点の圃場名 |
| group_name_snapshot | varchar(50) | | 保存時点の group_name |
| created_at | datetime | ✓ | |

- `unique_together = ['todo', 'field']`
- 圃場グループは独立モデル化しない（`Field.group_name` を参照するのみ）
- 計画に含まれる圃場の一部だけを対象にすることを許可する

### TodoCrop（分類補助：作物）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | CASCADE |
| crop | FK(plans.Crop) | ✓ | PROTECT |
| created_at | datetime | ✓ | |

- `unique_together = ['todo', 'crop']`

### TodoVariety（分類補助：品種）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | CASCADE |
| variety | FK(plans.Variety) | ✓ | PROTECT |
| created_at | datetime | ✓ | |

- `unique_together = ['todo', 'variety']`
- 圃場が 0 件でも Crop / Variety だけの紐づけは許可（圃場未確定の準備作業など）

### TodoPlanLink（計画との紐づけ）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | CASCADE |
| plan_type | enum | ✓ | `fertilization / rice_transplant / delivery` |
| fertilization_plan | FK(fertilizer.FertilizationPlan) | | |
| rice_transplant_plan | FK(plans.RiceTransplantPlan) | | |
| delivery_plan | FK(分配計画モデル) | | |
| created_at | datetime | ✓ | |

- 1 行に 1 種別のリンクのみ保持
- `plan_type` に応じて対応 FK だけを埋める
- `levee_work` は MVP では「計画リンクなしで持てる work_type」として扱う
- 作付け計画（Plan）は TODO 生成元としては対象外

### TodoCompletionLink（完了時の実績連携索引）

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | |
| record_type | enum | ✓ | 実績種別（`fertilization` / `tractor_work` など） |
| work_record | FK(workrecords.WorkRecord) | | 共通索引 |
| spreading_session | FK(fertilizer.SpreadingSession) | | 施肥実績 |
| tractor_work_session | FK(tractor_work.TractorWorkSession) | | トラクター作業実績 |
| created_at | datetime | ✓ | |

- `todo` は OneToOne ではなく FK（1 TODO から複数実績への分割を許容）
- 実績アプリが未実装の種別は空でよい

---

## API 仕様

### エンドポイント一覧

| メソッド | パス | 説明 |
|---|---|---|
| GET | `/api/todos/` | 一覧取得 |
| POST | `/api/todos/` | 作成 |
| GET | `/api/todos/{id}/` | 詳細取得 |
| PATCH | `/api/todos/{id}/` | 更新（status=done への変更は不可） |
| DELETE | `/api/todos/{id}/` | 削除 |
| PATCH | `/api/todos/reorder/` | 並び替え |
| POST | `/api/todos/from-plan/` | 計画から TODO 生成 |
| POST | `/api/todos/{id}/complete/` | 完了処理（実績連携導線を返す） |

### 重要な設計ルール

**完了処理の一本化**
- `PATCH` での `status=done` 変更はバックエンドが拒否する
- 完了は必ず `POST /api/todos/{id}/complete/` を通る
- 理由：実績連携導線の生成を確実にするため。AI 実装者がセッションをまたいで実装する際のブレを防ぐ

**差し戻し時の挙動**
- `done → todo/doing` は許可
- `TodoCompletionLink` が存在する場合は、差し戻しを許可しつつ API レスポンスに警告と各実績レコードへの直リンクを返す
- 実績レコード自体の削除は行わない（各実績アプリ側の責務）

**削除時の挙動**
- `TodoCompletionLink` が存在する TODO を削除しようとした場合、警告と各実績レコードへの直リンクを返す
- ユーザーが確認した上で削除を実行した場合は物理削除を許可する
- `TodoCompletionLink` は TODO と一緒に削除（CASCADE）
- 実績レコード自体は削除しない

### 一覧 GET `/api/todos/`

主なクエリパラメータ：

| パラメータ | デフォルト | 説明 |
|---|---|---|
| `status` | `todo,doing` | カンマ区切りで複数指定可 |
| `include_closed` | `false` | true で完了・キャンセルも含む |
| `work_type` | - | 作業種別フィルター |
| `due` | - | `overdue / today / upcoming` |
| `year` | - | 年度フィルター |

### 作成 POST `/api/todos/`

```json
{
  "title": "西田エリアの追肥",
  "description": "週内に先行実施",
  "status": "todo",
  "year": 2026,
  "due_date": "2026-04-12",
  "work_type": "fertilization",
  "should_link_record": true,
  "field_ids": [12, 18, 21],
  "crop_ids": [1],
  "variety_ids": [4],
  "plan_links": [
    {"plan_type": "fertilization", "plan_id": 8}
  ]
}
```

`plan_links` の変換：API 入力は `plan_type + plan_id` の組で受け、Serializer で対応 FK へ変換する。

### 並び替え PATCH `/api/todos/reorder/`

```json
{
  "items": [
    {"id": 31, "priority": 1000},
    {"id": 27, "priority": 2000},
    {"id": 42, "priority": 3000}
  ]
}
```

### 計画から TODO 生成 POST `/api/todos/from-plan/`

```json
{
  "plan_type": "fertilization",
  "plan_id": 8,
  "title": "2026春肥の散布",
  "field_ids": [12, 18],
  "due_date": "2026-04-15",
  "should_link_record": true
}
```

- `field_ids` 未指定時は計画内の全圃場を初期対象にする
- `work_type` は `plan_type` から自動補完する

### 完了処理 POST `/api/todos/{id}/complete/`

- `status=done` にする
- `should_link_record=true` かつ対応実績アプリがある場合、関連画面へ遷移するための情報を返す
- MVP では実績レコードの自動生成は行わず、導線情報の返却にとどめる

---

## バリデーション

- `done` 遷移時に `completed_at` を自動設定
- `canceled` 遷移時に `canceled_at` を自動設定
- `PATCH` で `status=done` を指定した場合は 400 エラーを返す
- `field_ids` が計画外圃場を含む場合は `plan_links` が 1 件以上あるときのみエラーにする
- `should_link_record=true` でも対応実績アプリが無い場合は保存を許可する
- `TodoTargetField.field` は `PROTECT`（過去 TODO の対象圃場履歴を保全するため）
- `work_type = tractor_work` の場合は `work_subtype` が必須（未指定時は 400 エラー）
- `work_type ≠ tractor_work` の場合は `work_subtype` に値を指定した場合は 400 エラー

---

## UI 仕様

### 一覧画面 `/todos`

- デフォルト表示：todo / doing を priority 昇順で表示
- 完了済み・キャンセル済みはフィルターで表示切り替え
- 期限超過は赤系で強調、当日期限も強調表示
- ドラッグ&ドロップで並び替え（難しければ矢印ボタンで代替）

表示カラム：タイトル / ステータス / 期日 / 作業種別 / 対象圃場数 / 紐づき計画

### 詳細画面 `/todos/{id}`

表示・編集：タイトル / 説明 / ステータス / 期日 / 作業種別 / 実績連携フラグ / 対象圃場 / 分類作物・品種 / 計画リンク

下部表示：実績連携先 / 完了日時 / 更新日時

### 作成導線

1. TODO 一覧から新規作成
2. 計画詳細または一覧から TODO 生成（施肥・田植え・運搬の各計画画面）

---

## 実装ファイル構成

### Backend

```
apps/todos/
├── models.py       # Todo, TodoTargetField, TodoCrop, TodoVariety, TodoPlanLink, TodoCompletionLink
├── admin.py
├── serializers.py
├── views.py
├── urls.py
└── migrations/
```

- `keinasystem/settings.py` に `apps.todos` を追加
- `keinasystem/urls.py` に `/api/todos/` を追加

### Frontend

```
frontend/src/app/todos/
├── page.tsx          # 一覧
├── [id]/page.tsx     # 詳細
└── new/page.tsx      # 作成
```

### 実装順

1. モデル・admin・migration
2. TODO CRUD API（一覧・詳細・作成・更新・削除）
3. TODO 一覧・詳細 UI
4. 並び替え API と UI
5. 計画から TODO 生成（from-plan API + 各計画画面への導線）
6. 完了処理 API と実績連携導線 UI

---

## 実績連携の考え方

### 施肥

`施肥計画 → 施肥TODO → 施肥実績`（SpreadingSession）の流れ。
完了時は `SpreadingSession` 作成画面への導線を返す。対象圃場は `TodoTargetField` を初期値として渡す。

### トラクター作業

`tractor_work` 種別の TODO 完了時は `TractorWorkSession` 作成画面への導線を返す。
`work_subtype` をクエリパラメータで渡し、作業種別セレクタの初期値として使う。
対象圃場は `TodoTargetField` を初期値として渡す。

### 田植え

田植え実績アプリは今後実装予定。MVP では：
- `rice_transplant` 種別の TODO を持てる
- 完了時は「完了済みだが実績アプリ未接続」の状態も許容する
- 将来の田植え実績導入時に `TodoCompletionLink` を拡張する

### 実績アプリが無い作業

`general` など、実績アプリに紐づかない TODO は `status=done` のみで完了とする。

---

## 未決定（実装時に判断）

以下は MVP 着手後に実装者が判断しながら決めてよい事項。

| 事項 | 方針 |
|---|---|
| 複数計画リンクの初回 UI | 内部構造は複数可。UI はまず 1 件中心で実装し、必要なら拡張する |
| 並び替え対象の範囲 | フィルター中（todo/doing のみ）を再採番対象とするのが自然 |
| 施肥完了時に渡す初期値の粒度 | SpreadingSession 作成画面の実装時に具体的な受け渡し仕様を決める |
