# TODO管理機能仕様書案

> 作成日: 2026-04-09
> 最終更新: 2026-04-09
> 対象プロジェクト: `keinasystem`
> 対象 Issue: `akira/keinasystem#17`
> 位置づけ: 実装前ドラフト（レビュー反映版）

---

## 1. 概要

繁忙期の作業を「どれから手を付けるか」の観点で整理するため、Redmine チケットライクな TODO 管理機能を追加する。

本機能は単なるメモではなく、以下の中間レイヤーとして位置付ける。

- 計画
- TODO
- 実績

将来的には、作付け計画を除く各種計画について、`計画 -> TODO -> 実績` の流れに挟める構造を目指す。
ただし MVP では、まず TODO 管理の基本機能、対象圃場の管理、計画との紐づけ、完了時の実績連携導線を整備する。

---

## 2. 背景

現状は施肥計画、田植え計画、運搬計画などの個別機能はあるが、「今日やること」「今週先に処理すべきこと」を横断的に管理する仕組みがない。

そのため、繁忙期には以下の問題が起こりやすい。

- 作業の優先順位が頭の中や紙メモに依存する
- 計画の一部だけを先に実行したい場合に管理しづらい
- 実績入力までの間に「作業待ち」「着手中」の状態を置けない
- 将来追加される作業系機能を共通の入口で扱えない

TODO 管理を導入し、計画単位ではなく「実際に動く作業単位」で優先順位と進行状態を管理できるようにする。

---

## 3. 目的

### 3.1 目指す状態

- 未着手・進行中の作業を優先順で一覧できる
- TODO は計画に紐づくものと、独立したものの両方を扱える
- 計画に紐づく TODO では、計画全体ではなく一部圃場だけを対象にできる
- 完了時に、必要なものは実績系アプリへ連携できる
- 将来増える作業系アプリでも同じ TODO 基盤を使える

### 3.2 今回の対象

- Django 新規アプリ `apps/todos`
- Next.js 画面 `frontend/src/app/todos`
- REST API `/api/todos/`
- 計画画面からの TODO 生成導線

### 3.3 今回やらないこと

- 期日通知、リマインダー、メール通知
- 複数ユーザー割り当て
- コメント、添付ファイル
- 工数見積、実績時間記録
- 完全な汎用ワークフローエンジン化

---

## 4. 基本方針

### 4.1 TODO の位置づけ

TODO は「作業指示」兼「実行待ちキュー」として扱う。

- 計画は年間またはまとまり単位の設計情報
- TODO は実際に動く単位の作業
- 実績は実際に完了した事実

### 4.2 計画との関係

- 1 計画に対して複数 TODO を紐づけられる
- 1 TODO は複数計画を参照できる
- ただし TODO の実際の対象圃場は TODO 側で明示管理する
- 計画に含まれる圃場の一部だけを TODO 対象にすることを許可する

### 4.3 実績との関係

- TODO 完了時に、実績アプリを持つ作業は実績生成の入口にする
- ただし、すべての TODO が実績アプリを持つとは限らない
- 計画なし TODO、実績なし TODO も許容する

### 4.4 圃場グループの扱い

圃場グループは独立モデル化しない。
既存の `Field.group_name` を参照用の属性として扱うにとどめ、TODO の正式な対象管理は圃場単位で保持する。

理由:

- 現状のデータモデルに独立したグループモデルが存在しない
- TODO 完了後に履歴の再現性を保つには、最終的に対象圃場を確定保持した方が安全

---

## 5. 機能スコープ

### 5.1 IN

- TODO の作成、編集、削除
- ステータス管理
- 優先順位管理
- 圃場単位の対象紐づけ
- 作物、品種の補助的な分類紐づけ
- 計画との紐づけ
- 計画画面から TODO を生成
- 完了済み、キャンセル済みの表示切り替え
- 期日の強調表示
- 並び替え API

### 5.2 OUT

- 通知
- 担当者管理
- 承認フロー
- 複数段階ステータス
- 実績アプリ未実装領域の詳細実績入力 UI

---

## 6. 用語整理

| 用語 | 意味 |
|---|---|
| TODO | 実際に着手・進行・完了する作業単位 |
| 計画リンク | TODO が参照する施肥計画、田植え計画など |
| 対象圃場 | その TODO で実際に作業対象となる圃場 |
| 実績連携 | TODO 完了時に各実績アプリへ情報を渡すこと |

---

## 7. データモデル方針

### 7.1 Todo

TODO 本体。

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| year | integer | ✓ | 年度 |
| title | varchar(200) | ✓ | タイトル |
| description | text |  | 説明 |
| status | enum | ✓ | `todo / doing / done / canceled` |
| priority | integer | ✓ | 小さいほど上位 |
| due_date | date |  | 期日 |
| work_type | enum | ✓ | 作業種別 |
| should_link_record | boolean | ✓ | 完了時に実績連携導線を有効にするか |
| completed_at | datetime |  | 完了日時 |
| canceled_at | datetime |  | キャンセル日時 |
| created_at | datetime | ✓ | |
| updated_at | datetime | ✓ | |

### 7.1.1 ステータス

- `todo`: 未着手
- `doing`: 進行中
- `done`: 完了
- `canceled`: キャンセル

### 7.1.2 並び順

- 基本は FILO とする
- 新規作成時は最上位へ入る
- `priority` は 1000 刻みの整数で保存する
- 初回作成時は最上位 TODO の `priority - 1000` を新規 TODO に割り当てる
- 一覧では `priority` 昇順で表示する
- ユーザーが並び替えた後は、表示順に 1000, 2000, 3000... と振り直して保存する
- 既存レコードの一括インクリメントや小数 priority は採用しない
- 完了、キャンセル済みも `priority` は保持する
- 一覧のデフォルト表示は `todo / doing` のみを `priority` 昇順で表示する

補足:

- 1000 刻みは API の中間挿入余地ではなく、再採番時の可読性のために採用する
- 並び順変更は常に表示対象全体を受け取って再採番する前提とする

### 7.1.3 作業種別

作業種別は「計画に対応するもの」と「計画に対応しないもの」の両方を含める。

初期採用（MVP）:

- `general`: 一般
- `fertilization`: 施肥
- `rice_transplant`: 田植え
- `delivery`: 運搬
- `levee_work`: 畔塗

将来追加（アプリ実装時）:

- `pesticide`: 防除（農薬散布管理アプリ実装時に追加）

補足:

- `general` はどれにも当てはまらない作業用に必須
- 現行アプリに対応する種別のみで始め、新しい計画機能追加時に `work_type` を拡張する

### 7.2 TodoTargetField

TODO が実際に対象とする圃場。

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| 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']`

方針:

- TODO の対象管理は最終的に圃場単位で保持する
- グループ、作物、品種から一括選択する UI は許可する
- ただし保存時は対象圃場へ展開して保持する

### 7.3 TodoCrop / TodoVariety

TODO の分類補助用。

#### 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']`

注意:

- 対象圃場の実体は `TodoTargetField` を正とする
- `Crop` や `Variety` だけ紐づいていて圃場が 0 件の TODO は許可する
- これにより、圃場未確定の準備作業も登録できる

### 7.4 TodoPlanLink

TODO と既存計画との紐づけ。

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | CASCADE |
| plan_type | enum | ✓ | 計画種別 |
| fertilization_plan | FK |  | 施肥計画 |
| rice_transplant_plan | FK |  | 田植え計画 |
| delivery_plan | FK |  | 運搬計画 |
| created_at | datetime | ✓ | |

方針:

- 1 行に 1 種別のリンクだけを保持する
- `plan_type` に応じて対応する FK だけを埋める
- MVP は汎用 `GenericForeignKey` を使わず、明示 FK を優先する
- 理由は API と serializer を単純に保ちやすいため

初期対象:

- 施肥計画 `FertilizationPlan`
- 田植え計画 `RiceTransplantPlan`
- 運搬計画 `DeliveryPlan`
- 畔塗 `levee_work` は MVP では「計画リンクなしで持てる work_type」として扱う
- 将来、畔塗に計画モデルが導入された時点で `TodoPlanLink` に追加する

補足:

- 作付け計画 `Plan` は「年内の計画情報」であり、TODO 生成元としては必須ではない
- 当面は Issue 回答に合わせ、`作付け計画以外のすべての計画` を TODO の対象候補とする

### 7.5 TodoCompletionLink

完了時の実績連携先を記録する索引。

| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(Todo) | ✓ | TODO |
| record_type | enum | ✓ | 実績種別 |
| work_record | FK(workrecords.WorkRecord) |  | 共通索引 |
| spreading_session | FK(fertilizer.SpreadingSession) |  | 施肥実績 |
| rice_transplant_record_id | 将来 |  | 田植え実績 |
| created_at | datetime | ✓ | |

方針:

- 完了時に何へ連携したかを TODO 側から追えるようにする
- `todo` は OneToOne に固定せず FK とする
- 理由は 1 TODO から複数実績へ分割される可能性を残すため
- 実績アプリが未実装の種別は空でよい
- 将来の田植え実績導入時に拡張できる形にする

---

## 8. API 仕様案

### 8.1 一覧

- `GET /api/todos/`

主な query:

- `status=todo,doing`
- `include_closed=true|false`
- `work_type=...`
- `due=overdue|today|upcoming`
- `year=2026`

デフォルト:

- `include_closed=false`
- `status=todo,doing`
- `priority` 昇順

### 8.2 詳細取得

- `GET /api/todos/{id}/`

返却内容:

- TODO 本体
- 対象圃場
- 作物、品種
- 計画リンク
- 完了連携状況

### 8.3 作成

- `POST /api/todos/`

作成 payload 例:

```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 で `plan_type` を見て対応 FK へ変換する
- 例:
  - `fertilization` -> `fertilization_plan_id`
  - `rice_transplant` -> `rice_transplant_plan_id`
  - `delivery` -> `delivery_plan_id`
- DB 返却時は、フロントエンド向けに再び `plan_type + plan_id + plan_label` の形へ正規化して返す

### 8.4 更新

- `PATCH /api/todos/{id}/`

更新可能項目:

- タイトル
- 説明
- ステータス（`todo` / `doing` / `canceled` のみ。`done` への変更は不可）
- 期日
- 作業種別
- 実績連携フラグ
- 対象圃場
- 分類
- 計画リンク

**注意**: `status=done` への変更は `PATCH` では受け付けない。完了は必ず `POST /api/todos/{id}/complete/` を使うこと。理由は、完了時に実績連携導線の生成が必要なため、入口を一本化して実装のブレを防ぐ。

### 8.5 削除

- `DELETE /api/todos/{id}/`

ルール:

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

### 8.6 並び替え

- `PATCH /api/todos/reorder/`

payload 例:

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

方針:

- 一括更新で保存する
- DnD が難しい場合も、矢印移動 UI から同 API を呼ぶ

### 8.7 計画から TODO 生成

- `POST /api/todos/from-plan/`

payload 例:

```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` から自動補完する
- タイトルは自動生成可能にする

### 8.8 完了処理

- `POST /api/todos/{id}/complete/`

方針:

- `status=done` にする専用入口を用意する
- `should_link_record=true` かつ対応実績アプリがある場合、関連画面へ遷移するための情報を返す
- MVP で自動実績作成まで行うか、完了導線のみ返すかは実装時に選べるようにする

---

## 9. UI 仕様案

### 9.1 一覧画面 `/todos`

表示内容:

- 未着手、進行中 TODO を優先表示
- タイトル
- ステータス
- 期日
- 作業種別
- 対象圃場数
- 紐づき計画

操作:

- 新規作成
- ステータス変更
- 並び替え
- 完了済み、キャンセル済み表示切り替え
- 絞り込み

視覚表現:

- 期限超過は赤系
- 当日期限は強調
- 進行中は目立つバッジ表示

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

表示・編集項目:

- タイトル
- 説明
- ステータス
- 期日
- 作業種別
- 実績連携フラグ
- 対象圃場
- 分類作物、分類品種
- 計画リンク

下部表示:

- 実績連携先
- 完了日時
- 更新日時

### 9.3 作成導線

MVP では少なくとも以下の 2 導線を持つ。

1. TODO 一覧から新規作成
2. 計画詳細または一覧から TODO 生成

### 9.4 計画画面からの導線

対象候補:

- 施肥計画
- 田植え計画
- 運搬計画

ボタン例:

- `TODOを作成`
- `この計画からTODO生成`

初期値:

- タイトル
- 作業種別
- 対象圃場候補
- `should_link_record`

---

## 10. 実績連携の考え方

### 10.1 基本原則

- TODO は実績そのものではない
- ただし、実績入力の起点にはなる
- すべての TODO が実績へ行くわけではない

### 10.2 施肥

将来像:

1. 施肥計画を作る
2. TODO を生成する
3. TODO を実施する
4. 完了時に施肥実績へつなぐ

考え方:

- 従来の `施肥計画 -> 施肥実績` に対し、間に TODO が入れるようにする
- TODO 完了時は `SpreadingSession` 作成導線へつなぐ
- 対象圃場は TODO の `TodoTargetField` を初期値として渡す

### 10.3 田植え

田植え実績アプリは今後実装予定であるため、今回の TODO 側では以下を前提にする。

- `rice_transplant` 種別の TODO を持てる
- 完了時に将来の田植え実績へ接続できるよう索引設計を残す
- MVP 時点では「完了済みだが実績アプリ未接続」の状態も許容する

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

- `general` など、実績アプリに紐づかない TODO を許容する
- その場合は `status=done` のみで完了とする

---

## 11. バリデーション方針

- `done` に遷移したら `completed_at` を自動設定する
- `canceled` に遷移したら `canceled_at` を自動設定する
- `done` から `todo` または `doing` への差し戻しは MVP では許可する
- 差し戻し時も `completed_at` はクリアせず履歴値として保持する
- 差し戻し時に `TodoCompletionLink` が存在する場合は、差し戻し自体は許可しつつ、API レスポンスに警告と各実績レコードへの直リンクを返す
- フロントはその警告を表示し、ユーザーが実績を削除したい場合は直リンクから遷移できるようにする（実績レコード自体の削除は TODO 側では行わない）
- `plan_links` に紐づく計画の年度と TODO の利用年度が必要なら将来追加する
- `field_ids` が計画外圃場を含む場合は、`plan_links` が 1 件以上ある場合のみエラーにする
- 複数 `plan_links` がある場合は、それぞれの計画に対して対象圃場整合性を検証する
- `should_link_record=true` でも、対応実績アプリが無い場合は保存を許可する
- `TodoTargetField.field` は `PROTECT` を採用する
- 理由は、過去 TODO の対象圃場履歴を崩さないことを優先するため

### 11.1 レビュー反映済み判断

- `done -> todo/doing` の差し戻しは許可する
- 差し戻し後も `completed_at` は監査用の履歴値として保持する
- `TodoTargetField.field` は運用上の削除容易性より履歴保全を優先し、`PROTECT` を維持する
- 実績連携フラグ名は `should_link_record` で確定する

---

## 12. 実装方針

### 12.1 Backend

- `apps/todos/models.py`
- `apps/todos/admin.py`
- `apps/todos/serializers.py`
- `apps/todos/views.py`
- `apps/todos/urls.py`
- `apps/todos/migrations/`
- `keinasystem/settings.py` へ app 追加
- `keinasystem/urls.py` へ `/api/todos/` 追加

### 12.2 Frontend

- `frontend/src/app/todos/page.tsx`
- `frontend/src/app/todos/[id]/page.tsx`
- `frontend/src/app/todos/new/page.tsx`
- 必要に応じて `_components` 配下に分離
- ナビゲーションへ TODO 追加

### 12.3 実装順

1. モデル、admin、serializer、migration の作成
2. TODO 一覧と CRUD API
3. TODO 一覧と詳細 UI
4. 並び替え API と UI
5. 計画から TODO 生成
6. 完了時の実績連携導線
7. `makemigrations` と `migrate` を実行

---

## 13. テスト観点

- TODO を新規作成できる
- 対象圃場を複数紐づけできる
- 計画の一部圃場だけを対象にできる
- 完了済み、キャンセル済みの表示切り替えができる
- 並び替え後に順番が保持される
- 計画画面から TODO を生成できる
- 実績アプリ未接続の TODO でも完了できる
- 実績連携済み TODO の挙動が壊れない

---

## 14. 未確定事項

### 14.1 work_type enum の初回実装範囲（確定）

MVP は現行アプリ対応の5種別のみで開始する。

- `general`: 一般
- `fertilization`: 施肥
- `rice_transplant`: 田植え
- `delivery`: 運搬
- `levee_work`: 畔塗

`pesticide`（防除）は農薬散布管理アプリ実装時に追加する。

### 14.2 完了時の実績連携レベル（確定）

MVP は **B. 実績入力画面への導線生成** を採用する。

- A. 完了ステータス変更のみ → 採用しない
- **B. 実績入力画面への導線生成 → 採用**
- C. TODO 情報を使った実績レコード仮生成 → 後続検討

### 14.3 削除ポリシー

実績連携後の TODO をどう扱うか。

案:

- 物理削除禁止
- 論理削除
- 参照整合性チェック付き物理削除

### 14.4 work_type と計画種別の追加ルール

MVP では以下を前提とする。

- work_type は先に定義する
- plan_link は実在する計画モデルだけを持つ
- work_type が存在しても、対応する計画 FK が未実装のことはあり得る

将来、新しい計画機能が増えたときは以下を同時に更新する。

- `Todo.work_type` choices
- `TodoPlanLink.plan_type`
- 対応 FK
- 計画から TODO 生成 API

---

## 15. 提案する MVP 決定案

実装着手しやすさを優先し、MVP では以下を採用することを提案する。

- TODO は `year` を持つ
- 対象管理は `TodoTargetField` を正とする
- `work_type` は `general / fertilization / rice_transplant / delivery / levee_work` を初期採用する（`pesticide` は農薬散布管理アプリ実装時に追加）
- 計画リンクは明示 FK 方式で開始する
- 実績連携フラグ名は `should_link_record` を採用する
- 完了時はまず「実績入力画面への導線生成」を採用し、自動実績作成は後続検討とする
- 並び替えは API 先行、UI は DnD 優先、難しければ矢印移動で代替する
