Compare commits

..

7 Commits

Author SHA1 Message Date
akira
f04e1884d9 butlerを使えるようにする 2026-04-10 16:57:22 +09:00
akira
cc6823b071 docs: levee_work をトラクター作業(tractor_work)に再設計
- doc/15 を畔塗作業編からトラクター作業編に改訂
  荒代掻き・植代掻き・耕耘を追加、TractorWorkSession モデル導入
- doc/19 TODO管理編: work_type の levee_work → tractor_work 置換、
  work_subtype フィールド追加、TodoCompletionLink に tractor_work 追記
- Issue #21(代掻き実績登録)の仕様策定に対応
2026-04-10 13:39:47 +09:00
akira
b7b5ce3943 docs: TODO管理機能のマスタードキュメントを作成し仕様書案を更新
- 論点1-5の決定を仕様書案に反映
- document/19_マスタードキュメント_TODO管理編.md を新規作成
- CLAUDE.md のマスタードキュメント一覧に TODO管理を追加
2026-04-10 13:14:07 +09:00
Akira
8de1ae70aa .codex .mcpを除外 2026-04-09 20:40:30 +09:00
akira
3901caf668 docs: reflect todo spec review feedback 2026-04-09 16:36:19 +09:00
akira
5f58c2c686 指摘を反映して 改善案/TODO管理機能仕様書案.md を更新しました。
主な修正は、Todo.year の追加、TodoCrop / TodoVariety の具体定義追加、priority の 1000 刻み再採番ルール明記、plan_links がある場合だけ対象圃場整合性を検証する形への明確化、levee_work を「MVP では計画リンクなしの work_type」として整理、plan_type + plan_id を Serializer で各 FK に変換する方針追記、admin.py と migrations の追加、見出し階層の修正です。

あわせて、実績連携フラグ名は should_link_record に寄せました。まだコミットはしていないので、必要ならこの修正分もそのままコミットします。
2026-04-09 16:34:30 +09:00
akira
83525c2f59 docs: add todo management spec draft 2026-04-09 16:27:42 +09:00
7 changed files with 1541 additions and 557 deletions

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@ postgres_data/
nul nul
*.tsbuildinfo *.tsbuildinfo
.mcp.json
.codex

22
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"mcpServers": {
"butler": {
"command": "uv",
"args": ["run", "python", "-m", "butler.mcp_facade"],
"cwd": "../butler2"
},
"serena": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/oraios/serena",
"serena",
"start-mcp-server",
"--context",
"ide-assistant",
"--project",
"."
]
}
}
}

View File

@@ -109,6 +109,7 @@ ssh keinafarm-claude 'cd /home/keinasystem/keinasystem_t02 && \
| 運搬計画 | `document/14_マスタードキュメント_分配計画編.md` | | 運搬計画 | `document/14_マスタードキュメント_分配計画編.md` |
| 田植え計画 | `document/16_マスタードキュメント_田植え計画編.md` | | 田植え計画 | `document/16_マスタードキュメント_田植え計画編.md` |
| 農薬散布管理 | `document/18_マスタードキュメント_農薬散布管理編.md` | | 農薬散布管理 | `document/18_マスタードキュメント_農薬散布管理編.md` |
| TODO管理 | `document/19_マスタードキュメント_TODO管理編.md` |
| データモデル全体 | `document/03_データ仕様書.md` | | データモデル全体 | `document/03_データ仕様書.md` |
--- ---

View File

@@ -0,0 +1,411 @@
# マスタードキュメント:トラクター作業記録機能
> **作成**: 2026-04-04
> **最終更新**: 2026-04-10
> **対象機能**: トラクター作業記録(畔塗・荒代掻き・植代掻き・耕耘)
> **実装状況**: 畔塗のみ実装済み。荒代掻き・植代掻き・耕耘は設計中Issue #21
> **対象 Issue**: `akira/keinasystem#21`
---
## 概要
農業生産者が、水稲作付け圃場に対して実施したトラクター作業を日付単位で記録する機能。
対象圃場をまとめて選択し、保存時に作業記録一覧へ自動反映する。
対象作業種別:
| 種別 | 日本語名 | 説明 |
|---|---|---|
| `levee_work` | 畔塗 | 畦畔の補修・造成 |
| `rough_harrowing` | 荒代掻き | 田植え前の粗い代掻き |
| `transplant_harrowing` | 植代掻き | 田植え直前の仕上げ代掻き |
| `cultivation` | 耕耘 | 土起こし・耕起 |
これらはいずれも**トラクターを用いた資材なし作業**であり、同一のデータモデルで管理する。
本機能は、施肥計画の散布実績と同様に
「作業本体を専用テーブルで持ち、作業記録一覧には索引を自動生成する」
という設計方針を採用する。
### 機能スコープIN / OUT
| IN本機能で扱う | OUT本機能では扱わない |
|---|---|
| 日付単位の作業記録作成全4種別 | 作業の工程管理 |
| 水稲作付け圃場の候補抽出 | 作業者別の工数集計 |
| 複数圃場の一括選択・保存 | 機械・資材の在庫管理 |
| 作業記録一覧WorkRecordへの自動反映 | 写真添付 |
| 記録の編集・削除 | GPS軌跡連携 |
| 対象圃場一覧の参照画面 | 汎用作業日誌への完全統合 |
---
## 背景と目的
現状システムには畔塗の記録機能があるが、同じトラクター作業である荒代掻き・植代掻き・耕耘は登録できない。
これらは以下の共通点を持つため、統一モデルで管理する。
- 1日で複数圃場をまとめて実施することが多い
- 対象圃場は当年の作付け計画と密接に関係する
- 後から「いつ、どの圃場を実施したか」を一覧で見返したい
- 使用資材がない(施肥・農薬とは区別される)
---
## データモデル
### TractorWorkSessionトラクター作業記録本体
日付単位のトラクター作業記録。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| work_type | varchar(30) | required | 作業種別(下記参照) |
| year | int | required | 年度フィルタ用。原則 `date.year` と一致させる |
| date | DateField | required | 作業日 |
| title | varchar(100) | required | 一覧表示タイトル。未指定時はサーバー側で work_type に応じたデフォルト値を補完する |
| notes | text | blank | 備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
#### work_type の値とデフォルトタイトル
| work_type | 表示名 | デフォルトタイトル |
|---|---|---|
| `levee_work` | 畔塗 | 水稲畔塗 |
| `rough_harrowing` | 荒代掻き | 水稲荒代掻き |
| `transplant_harrowing` | 植代掻き | 水稲植代掻き |
| `cultivation` | 耕耘 | 水稲耕耘 |
- `year + date` の一意制約は付けない
- 同日に種別違い・地区違いで複数記録を持てるようにする
### TractorWorkSessionItem対象圃場明細
トラクター作業記録に紐づく対象圃場一覧。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| session | FK(TractorWorkSession) | CASCADE | 親の作業記録 |
| field | FK(fields.Field) | PROTECT | 対象圃場 |
| plan | FK(plans.Plan) | SET_NULL, nullable | 保存時点の作付け計画参照 |
| crop_name_snapshot | varchar(100) | required | 保存時点の作物名 |
| variety_name_snapshot | varchar(100) | blank | 保存時点の品種名 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
- `unique_together = ['session', 'field']`
- 圃場名は `Field` を参照して表示する
- 作物・品種は履歴保全のためスナップショット保持
### WorkRecord作業記録索引
既存 `apps/workrecords``WorkRecord` をトラクター作業に対応させる。
| 変更点 | 内容 |
|---|---|
| `work_type` enum | `TRACTOR_WORK = 'tractor_work'` を追加(`LEVEE_WORK` を置換) |
| FK | `tractor_work_session = OneToOneField('tractor_work.TractorWorkSession', ...)` に改名 |
制約:
- `on_delete=CASCADE`
- `null=True`, `blank=True`
- `related_name='work_record'`
一覧表示時の想定値:
| 項目 | 値 |
|---|---|
| 作業日 | 作業記録の日付 |
| 種別 | トラクター作業work_type の日本語表示) |
| タイトル | session.title |
| 参照先 | 対象圃場一覧画面 |
---
## 候補圃場抽出ルール
候補は作付け計画 `Plan` から抽出する。
### 基本条件
- 指定年度の `Plan` であること
- `crop.name = "水稲"` の圃場であること
### 補足
- 品種未設定でも `crop=水稲` なら候補に含める
- 並び順は `field.display_order`, `field.id`
### 候補レスポンスで返す情報
| 項目 | 説明 |
|---|---|
| field_id | 圃場ID |
| field_name | 圃場名 |
| field_area_tan | 面積(反) |
| group_name | グループ名 |
| plan_id | 対応する作付け計画ID |
| crop_name | 作物名 |
| variety_name | 品種名 |
| selected | 初期選択状態(原則 `true` |
---
## 画面仕様
### 画面の位置づけ
日付と作業種別を先に決めて対象圃場を選ぶ「日報型UI」。
1回の保存で複数圃場をまとめて記録する。
### 主要画面
#### 1. トラクター作業記録一覧画面(`/tractor-work`
- 年度内の記録を一覧する
- 作業種別でフィルター可能
- 新規作成・既存記録の編集・削除
表示項目: 作業日 / 作業種別 / タイトル / 対象圃場数 / 面積合計 / 備考
#### 2. 作成・編集画面
入力項目:
- **作業種別**(畔塗 / 荒代掻き / 植代掻き / 耕耘)← 新規追加
- 日付
- タイトルwork_type に連動したデフォルト値を自動セット)
- 備考
- 対象圃場一覧(チェックボックス)
### 推奨UIイメージ
```text
トラクター作業記録作成
[作業種別 荒代掻き ▼]
[日付 2026-04-20]
[タイトル 水稲荒代掻き]
[備考 __________________ ]
対象圃場一覧
[全選択] [全解除]
☑ 田中上 1.2反 上エリア コシヒカリ
☑ 田中下 0.8反 上エリア あきたこまち
☐ 山の前 1.5反 南エリア (未設定)
[保存]
```
---
## API エンドポイント
すべて JWT 認証必須。
### トラクター作業記録
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/tractor-work/sessions/?year={year}` | 年度別一覧 |
| POST | `/api/tractor-work/sessions/` | 新規作成 |
| GET | `/api/tractor-work/sessions/{id}/` | 詳細取得 |
| PUT/PATCH | `/api/tractor-work/sessions/{id}/` | 更新 |
| DELETE | `/api/tractor-work/sessions/{id}/` | 削除 |
### 候補圃場取得
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/tractor-work/candidates/?year={year}` | 水稲作付け圃場候補を返す |
### リクエスト例(新規作成)
```json
{
"work_type": "rough_harrowing",
"year": 2026,
"date": "2026-04-20",
"title": "水稲荒代掻き",
"notes": "",
"items": [
{ "field": 5, "plan": 12 },
{ "field": 6, "plan": 13 }
]
}
```
- `crop_name_snapshot` / `variety_name_snapshot` はクライアント送信不要。サーバーが `plan` から自動設定する
- `plan``null` の場合は `field` に対応する当年 `Plan` から補完を試みる
### レスポンス例(詳細)
```json
{
"id": 3,
"work_type": "rough_harrowing",
"year": 2026,
"date": "2026-04-20",
"title": "水稲荒代掻き",
"notes": "",
"work_record_id": 15,
"item_count": 2,
"total_area_tan": "2.0000",
"items": [
{
"id": 11,
"field": 5,
"field_name": "田中上",
"plan": 12,
"crop_name_snapshot": "水稲",
"variety_name_snapshot": "コシヒカリ"
}
],
"created_at": "2026-04-20T08:00:00Z",
"updated_at": "2026-04-20T08:00:00Z"
}
```
---
## 業務フロー
### 1. 新規作成
1. ユーザーが作業種別・年度・日付を選ぶ
2. システムが当年の水稲作付け圃場を候補表示する
3. ユーザーが対象圃場を選択する
4. 保存時に `TractorWorkSession` を作成する
5. 明細として `TractorWorkSessionItem` を一括作成する
6. 各明細の `crop_name_snapshot` / `variety_name_snapshot` をサーバー側で自動設定する
7. `WorkRecord` を自動生成する(`update_or_create`
### 2. 編集
1. ユーザーが既存の作業記録を開く
2. 作業種別・日付・タイトル・備考・対象圃場を変更する
3. 保存時に明細を再構成する
4. `WorkRecord` 側の作業日・タイトルも同期更新する
### 3. 削除
1. ユーザーが作業記録を削除する
2. 紐づく `TractorWorkSessionItem``CASCADE` で削除される
3. 紐づく `WorkRecord``tractor_work_session``on_delete=CASCADE` により削除される
---
## 作業記録連携仕様
### 追加する種別
| enum値 | 表示名 |
|---|---|
| `tractor_work` | トラクター作業 |
### 自動生成ルール
- `work_date` = `session.date`
- `work_type` = `tractor_work`
- `title` = `session.title`work_type 別デフォルトで補完済み)
- `year` = `session.year`
- `auto_created` = `True`
- `tractor_work_session` = 対応する作業記録
### 同期タイミング
- 作成時・更新時: `update_or_create`
- 削除時: `on_delete=CASCADE` により自動削除
---
## バリデーションルール
### 必須
- `work_type`
- `year`
- `date`
- `items`1件以上
### 保存時チェック
- 選択圃場が0件の保存を禁止する
- 同一セッション内で同じ圃場を重複登録しない
- `year` は原則 `date.year` と一致しなければならない
- `plan` が指定されている場合、`plan.field``field` は一致しなければならない
- `plan.year``session.year` と一致しなければならない
### 業務上の許容
- 品種未設定の水稲圃場は保存可
- 同日に別種別・別地区で複数記録を持てる
- 一度作業した圃場を別日に再度記録することは可
---
## 実装方針
### 移行方針levee_work → tractor_work
既存 `apps/levee_work``apps/tractor_work` にアプリごと改名する。
- Django の `RenameModel` migration でテーブルを改名する
- `work_type` フィールドを追加し、既存レコードは `levee_work` で埋める
- `workrecords` の FK名・enum値も migration で更新する
- API パスを `/levee-work/``/tractor-work/` に変更する
- フロントエンドの `app/levee-work/``app/tractor-work/` に移動する
### バックエンド
- `Session` / `SessionItem` 構成を維持する
- Serializer は `read``write` を分離する
- 候補取得 API は `Plan` を起点に組み立てる
- `sync_tractor_work_record(session)``WorkRecord` と同期する
### フロントエンド
- 既存の levee-work ページを tractor-work に移植する
- 作業種別セレクタを追加し、選択に応じてデフォルトタイトルを自動セットする
---
## ソースファイル構成
### バックエンド
```
backend/apps/tractor_work/
├── models.py # TractorWorkSession, TractorWorkSessionItem
├── serializers.py
├── views.py
├── urls.py
├── admin.py
└── migrations/
├── 0001_initial.py # levee_work から移行)
└── 0002_rename_and_add_work_type.py
```
変更ファイル:
- `backend/apps/workrecords/models.py` — FK名・enum更新
- `backend/apps/workrecords/services.py` — sync関数改名
- `backend/keinasystem/settings.py` — INSTALLED_APPS更新
- `backend/keinasystem/urls.py` — URLパス更新
### フロントエンド
```
frontend/src/app/tractor-work/
└── page.tsx
```
変更ファイル:
- `frontend/src/types/index.ts` — 型定義更新
- `frontend/src/components/Navbar.tsx` — リンク更新
- `frontend/src/app/workrecords/page.tsx` — 遷移先更新

View File

@@ -1,557 +0,0 @@
# マスタードキュメント:畔塗作業機能
> **作成**: 2026-04-04
> **最終更新**: 2026-04-04
> **対象機能**: 畔塗作業記録(日付単位の圃場選択・作業記録索引連携)
> **実装状況**: 実装予定(仕様策定版)
---
## 概要
農業生産者が、水稲作付け圃場に対して実施した「畔塗」作業を日付単位で記録する機能。
対象圃場をまとめて選択し、保存時に作業記録一覧へ自動反映する。
本機能は、施肥計画の散布実績と同様に
「作業本体を専用テーブルで持ち、作業記録一覧には索引を自動生成する」
という設計方針を採用する。
### 機能スコープIN / OUT
| IN本機能で扱う | OUT本機能では扱わない |
|---|---|
| 畔塗日単位の記録作成 | 畔塗作業の工程管理 |
| 水稲作付け圃場の候補抽出 | 作業者別の工数集計 |
| 複数圃場の一括選択・保存 | 機械・資材の在庫管理 |
| 作業記録一覧WorkRecordへの自動反映 | 写真添付 |
| 畔塗記録の編集・削除 | GPS軌跡連携 |
| 対象圃場一覧の参照画面 | 汎用作業日誌への完全統合 |
---
## 背景と目的
現状システムには、運搬や肥料散布のような作業実績を日付順に参照する仕組みがあるが、
春作業の一つである畔塗については記録先が存在しない。
畔塗は次の特徴を持つ。
- 1日で複数圃場をまとめて実施することが多い
- 対象圃場は当年の作付け計画と密接に関係する
- 後から「いつ、どの圃場を畔塗したか」を一覧で見返したい
そのため、圃場ごとに単発レコードを大量に作るのではなく、
`1日 = 1件の畔塗記録` とし、対象圃場を明細としてぶら下げる構成とする。
---
## データモデル
### LeveeWorkSession畔塗記録本体
日付単位の畔塗作業記録。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| year | int | required | 年度フィルタ用。既存機能に合わせて暦年を保持し、原則 `date.year` と一致させる |
| date | DateField | required | 畔塗日 |
| title | varchar(100) | required, default=`水稲畔塗` | 一覧表示タイトル。未指定時はサーバー側で `水稲畔塗` を補完する |
| notes | text | blank | 備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
- `year + date` の一意制約は付けない
- 同日に午前・午後や地区別で複数記録を持てるようにする
### LeveeWorkSessionItem畔塗対象圃場明細
畔塗記録に紐づく対象圃場一覧。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| session | FK(LeveeWorkSession) | CASCADE | 親の畔塗記録 |
| field | FK(fields.Field) | PROTECT | 対象圃場 |
| plan | FK(plans.Plan) | SET_NULL, nullable | 保存時点の作付け計画参照 |
| crop_name_snapshot | varchar(100) | required | 保存時点の作物名 |
| variety_name_snapshot | varchar(100) | blank | 保存時点の品種名 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
- `unique_together = ['session', 'field']`
- 圃場名そのものは `Field` を参照して表示する
- 作物・品種は履歴保全のためスナップショット保持を推奨する
### WorkRecord作業記録索引
既存 `apps/workrecords``WorkRecord` に畔塗種別を追加して連携する。
追加内容:
- `work_type``levee_work` を追加
- `levee_work_session` への `OneToOne FK('levee_work.LeveeWorkSession')` を追加
想定制約:
- `on_delete=models.CASCADE`
- `null=True`
- `blank=True`
- `related_name='work_record'`
削除方針:
- 親である `LeveeWorkSession` 削除時に、関連する `WorkRecord` は DB 制約の `CASCADE` で自動削除する
- アプリケーション側での「紐づく WorkRecord を削除する」は、この DB 制約により満たされるものとして扱う
一覧表示時の想定値:
| 項目 | 値 |
|---|---|
| 作業日 | 畔塗記録の日付 |
| 種別 | 畔塗 |
| タイトル | 水稲畔塗 |
| 参照先 | 畔塗した圃場一覧画面 |
---
## 候補圃場抽出ルール
畔塗対象候補は、作付け計画 `Plan` から抽出する。
### 基本条件
- 指定年度の `Plan` であること
- `crop.name = "水稲"` の圃場であること
- 圃場が存在すること
### 補足
- 判定条件は「品種が水稲」ではなく、原則として「作物が水稲」とする
- `variety` は任意項目のため、品種未設定でも `crop=水稲` なら候補に含める
- 並び順は `field.display_order`, `field.id`
### 候補レスポンスで返したい情報
| 項目 | 説明 |
|---|---|
| field_id | 圃場ID |
| field_name | 圃場名 |
| field_area_tan | 面積(反) |
| group_name | グループ名 |
| plan_id | 対応する作付け計画ID |
| crop_name | 作物名 |
| variety_name | 品種名 |
| selected | 初期選択状態。候補圃場は原則 `true` を返し、全選択をデフォルトとする |
### 初期選択ルール
- 候補として返す水稲圃場は、原則すべて `selected=true` とする
- 品種未設定の水稲圃場も `selected=true` とする
- UI 上のチェック解除は、ユーザーが今回畔塗しない圃場を明示的に外すための操作と位置づける
- 先行イメージ図にあった `☐ 山の前` は例示上の表現であり、初期ルールそのものではない
---
## 画面仕様
### 画面の位置づけ
畔塗機能は、日付を先に決めて対象圃場を選ぶ「日報型UI」とする。
圃場ごとの個別登録画面ではなく、1回の保存で複数圃場をまとめて記録する。
### 主要画面
#### 1. 畔塗記録一覧画面
目的:
- 年度内の畔塗記録を一覧する
- 新規作成画面へ遷移する
- 既存記録の編集・削除を行う
表示項目:
- 畔塗日
- タイトル
- 対象圃場数
- 対象圃場名の要約
- 備考
#### 2. 畔塗記録作成・編集画面
入力項目:
- 日付
- タイトル
- 備考
- 対象圃場一覧
対象圃場一覧の表示項目:
- 選択チェック
- 圃場名
- 面積
- グループ
- 作物
- 品種
操作:
- 全選択
- 全解除
- 個別選択
- 保存
初期表示ルール:
- 初回表示時は候補圃場を全選択状態で表示する
- 編集時は保存済み明細に含まれる圃場を選択状態で復元する
### 推奨UIイメージ
```text
畔塗記録作成
[日付 2026-04-20]
[タイトル 水稲畔塗]
[備考 __________________ ]
対象圃場一覧
[全選択] [全解除]
☑ 田中上 1.2反 上エリア 水稲 コシヒカリ
☑ 田中下 0.8反 上エリア 水稲 あきたこまち
☐ 山の前 1.5反 南エリア 水稲 (未設定)
[保存]
```
### 作業記録一覧への見え方
既存の作業記録一覧には次の形式で表示する。
| 列 | 表示内容 |
|---|---|
| 作業日 | 指定した日付 |
| 種別 | 畔塗 |
| タイトル | 水稲畔塗 |
| 参照先 | 畔塗記録 #ID または対象圃場要約 |
| 開く | 畔塗記録詳細画面へ遷移 |
---
## API エンドポイント
すべて JWT 認証必須。
### 畔塗記録
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/levee-work/sessions/?year={year}` | 年度別一覧 |
| POST | `/api/levee-work/sessions/` | 新規作成 |
| GET | `/api/levee-work/sessions/{id}/` | 詳細取得 |
| PUT/PATCH | `/api/levee-work/sessions/{id}/` | 更新 |
| DELETE | `/api/levee-work/sessions/{id}/` | 削除 |
### 候補圃場取得
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/levee-work/candidates/?year={year}` | 水稲作付け圃場候補を返す |
### レスポンス例(候補圃場)
```json
[
{
"field_id": 5,
"field_name": "田中上",
"field_area_tan": "1.2000",
"group_name": "上エリア",
"plan_id": 12,
"crop_name": "水稲",
"variety_name": "コシヒカリ",
"selected": true
}
]
```
### リクエスト例(新規作成)
```json
{
"year": 2026,
"date": "2026-04-20",
"title": "水稲畔塗",
"notes": "",
"items": [
{
"field": 5,
"plan": 12
},
{
"field": 6,
"plan": 13
}
]
}
```
備考:
- `crop_name_snapshot` / `variety_name_snapshot` はクライアント送信項目ではない
- サーバーが `plan``field` の整合を検証したうえで、保存時に `Plan` から自動設定する
- `plan``null` の場合は、保存時点で参照できる `field` に対応する当年 `Plan` から補完を試みる
### レスポンス例(詳細)
```json
{
"id": 3,
"year": 2026,
"date": "2026-04-20",
"title": "水稲畔塗",
"notes": "",
"work_record_id": 15,
"item_count": 2,
"items": [
{
"id": 11,
"field": 5,
"field_name": "田中上",
"plan": 12,
"crop_name_snapshot": "水稲",
"variety_name_snapshot": "コシヒカリ"
},
{
"id": 12,
"field": 6,
"field_name": "田中下",
"plan": 13,
"crop_name_snapshot": "水稲",
"variety_name_snapshot": "あきたこまち"
}
],
"created_at": "2026-04-20T08:00:00Z",
"updated_at": "2026-04-20T08:00:00Z"
}
```
---
## 業務フロー
### 1. 新規作成
1. ユーザーが年度と日付を選ぶ
2. システムが当年の水稲作付け圃場を候補表示する
3. ユーザーが対象圃場を選択する
4. 保存時に `LeveeWorkSession` を作成する
5. 明細として `LeveeWorkSessionItem` を一括作成する
6. 各明細の `crop_name_snapshot` / `variety_name_snapshot` をサーバー側で自動設定する
7. `WorkRecord` を自動生成または更新する
### 2. 編集
1. ユーザーが既存の畔塗記録を開く
2. 日付・タイトル・備考・対象圃場を変更する
3. 保存時に明細を再構成する
4. `WorkRecord` 側の作業日・タイトルも同期更新する
5. 明細のスナップショットも保存時点情報で再構成する
### 3. 削除
1. ユーザーが畔塗記録を削除する
2. 紐づく `LeveeWorkSessionItem``CASCADE` で削除される
3. 紐づく `WorkRecord``levee_work_session``on_delete=CASCADE` により削除される
---
## 作業記録連携仕様
畔塗記録保存時に `apps/workrecords` 側へ自動反映する。
### 追加する種別
| enum値 | 表示名 |
|---|---|
| `levee_work` | 畔塗 |
### 自動生成ルール
- `work_date` = `session.date`
- `work_type` = `levee_work`
- `title` = `session.title`
- `year` = `session.year`
- `auto_created` = `True`
- `levee_work_session` = 対応する畔塗記録
- `delivery_trip` = `None`
- `spreading_session` = `None`
実装メモ:
- 既存の `sync_spreading_work_record()` と同様に、`update_or_create()``defaults` 内で他系統 FK を明示的に `None` へそろえる
- `title` の未入力は `LeveeWorkSession` 保存時にサーバー側で `水稲畔塗` を補完するため、同期処理では補完済みの `session.title` をそのまま使う
### 同期タイミング
- 畔塗記録作成時: `update_or_create`
- 畔塗記録更新時: `update_or_create`
- 畔塗記録削除時: `levee_work_session``on_delete=CASCADE` により `WorkRecord` も自動削除される
---
## バリデーションルール
### 必須
- `year`
- `date`
- `items`1件以上
### 保存時チェック
- 選択圃場が0件の保存を禁止する
- 同一セッション内で同じ圃場を重複登録しない
- 候補外圃場の保存を原則禁止する
- `year` は原則 `date.year` と一致しなければならない
- `plan` が指定されている場合、その `plan.field``field` は一致しなければならない
- `plan` が指定されている場合、その `plan.year``session.year` と一致しなければならない
### 業務上の許容
- 品種未設定の水稲圃場は保存可
- 同日に別記録を複数作ることは可
- 一度畔塗した圃場を別日に再度記録することは可
---
## 実装方針
### バックエンド
- 新規アプリ `apps/levee_work` を追加する案を第一候補とする
- `Session` / `SessionItem` 構成でモデル化する
- Serializer は `read``write` を分離する
- 候補取得 API は `Plan` を起点に組み立てる
- `sync_levee_work_record(session)` を作成して `WorkRecord` と同期する
- `WorkRecord` から `LeveeWorkSession` への参照は、アプリ間循環参照を避けるため文字列参照 `OneToOneField('levee_work.LeveeWorkSession', ...)` を使う
### フロントエンド
- 画面候補: `frontend/src/app/levee-work/page.tsx`
- 1画面完結の一覧 + 作成/編集パネル、または一覧画面 + 詳細画面のどちらでも可
- 既存の `fertilizer/spreading` の「一覧 + 編集」導線を参考にする
- `workrecords/page.tsx` に遷移先判定を追加する
### 命名方針
- ユーザー向け表示は「畔塗」で統一
- コード上の英語名は `levee_work` または `levee_coating` が候補
- 既存の `WorkRecord.WorkType` に追加する値は、短く意味がぶれない `levee_work` を推奨する
---
## 画面遷移案
```text
作業記録一覧
└─ 畔塗レコードの「開く」
└─ 畔塗記録画面(該当セッションを編集状態で開く)
畔塗記録画面
├─ 新規作成
├─ 既存記録の編集
└─ 保存後、作業記録一覧に反映
```
---
## 将来拡張
- 作業者名の保持
- 使用機械の記録
- 実施済み圃場を地図で確認
- 写真添付
- 代かき、耕起、播種など他作業への横展開
- 汎用作業日誌基盤への統合
---
## 実装タスク案
1. `apps/levee_work` アプリ新設
2. `LeveeWorkSession` / `LeveeWorkSessionItem` モデル追加
3. migration 作成
4. serializer / view / url 実装
5. 候補圃場 API 実装
6. `WorkRecord` に畔塗種別と参照FK追加
7. `sync_levee_work_record` サービス実装
8. フロントエンド一覧・作成画面実装
9. 作業記録一覧の遷移先対応
10. テスト追加
---
## 注意点と設計判断
### なぜ「圃場ごと1件」ではなく「日付ごと1件」か
- 実際の作業単位が日付ベースである
- 一覧が見やすい
- 既存の散布実績機能と整合する
- 作業記録索引との親和性が高い
### なぜ作付け計画を参照するか
- 水稲圃場だけを自然に抽出できる
- 年度との整合が取りやすい
- 将来「未畔塗候補」や「前年比較」に発展させやすい
### スナップショットを持つ理由
- 後から作付け計画が変更されても、記録時点の情報を追える
- 作業記録としての監査性を保ちやすい
### なぜ snapshot をクライアント入力にしないか
- `plan``field` からサーバーが一意に導出できる情報だから
- クライアント送信にすると改ざんや不整合の余地が増えるから
- API 入力を最小限に保った方が UI 実装が単純になるから
---
## ソースファイル追加想定
### バックエンド
- `backend/apps/levee_work/models.py`
- `backend/apps/levee_work/serializers.py`
- `backend/apps/levee_work/views.py`
- `backend/apps/levee_work/urls.py`
- `backend/apps/levee_work/admin.py`
- `backend/apps/levee_work/migrations/0001_initial.py`
- `backend/apps/workrecords/models.py`
- `backend/apps/workrecords/services.py`
- `backend/apps/workrecords/serializers.py`
- `backend/apps/workrecords/views.py`
- `backend/keinasystem/urls.py`
### フロントエンド
- `frontend/src/app/levee-work/page.tsx`
- `frontend/src/types/index.ts`
- `frontend/src/app/workrecords/page.tsx`
---
## まとめ
畔塗作業機能は、
「当年の水稲作付け圃場を候補として出し、日付単位で複数圃場をまとめて記録し、作業記録一覧へ自動反映する」
というシンプルな構成を基本とする。
この構成により、既存の作付け計画・作業記録の設計を壊さずに、
春作業の記録を自然に追加できる。

View File

@@ -0,0 +1,392 @@
# マスタードキュメントTODO管理機能
> **作成**: 2026-04-10
> **最終更新**: 2026-04-10tractor_work 対応追記)
> **対象機能**: TODO管理作業指示・優先順位管理・実績連携導線
> **実装状況**: 設計完了・実装前
> **対象 Issue**: `akira/keinasystem#17`
---
## 概要
繁忙期に「どれから手を付けるか」を管理するための TODO 機能。
計画(施肥・田植え・運搬など)と実績の間に位置する「作業指示」レイヤー。
### 機能スコープIN / OUT
| INMVP対象 | 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 ではなく FK1 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 作成画面の実装時に具体的な受け渡し仕様を決める |

View File

@@ -0,0 +1,713 @@
# 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 優先、難しければ矢印移動で代替する