作成: 2026-05-06
最終更新: 2026-05-06(UI導線と TODO / 一般作業管理の役割整理を反映)
対象機能: 畝建て作業(計画・TODO・実績の一連管理)
実装状況: 実装完了(Issue #58)
対象 Issue:akira/keinasystem#58
農業生産者が畑作業として行う畝建てについて、計画・TODO・実績を登録・管理する機能。
「複数圃場をまとめて1つの計画にまとめ、実施予定日が決まったら TODO 一覧に出現し、実施後に実績を記録する」という施肥計画・トラクター作業計画と同じフローで扱う。
畝建ては水稲向けのトラクター作業(畔塗・代掻き・耕耘)とは別ドメインの畑作業であるため、専用の計画モデル RidgeBuildingPlan を新設する。
| IN(本機能で扱う) | OUT(本機能では扱わない) |
|---|---|
| 畝建て計画の作成・編集・削除 | 機械・アタッチメント管理 |
| 作付け品目による圃場絞り込み(任意) | 畝の GPS 位置情報 |
| 圃場ごとの畝パラメータ記録(畝間・本数・長さ) | 写真添付 |
| 圃場ごとの施肥計画リンク(任意) | 複数ユーザー割り当て |
| TODO 一覧への統合(SchedulableMixin) | 工数・稼働時間の記録 |
| 計画からの実績(TractorWorkSession)作成 | |
| 部分実施(複数回の実績を1計画に紐づけ) |
scheduled_date)を入れると TODO 一覧に出現するpartially_done に自動遷移し、手動で done に確定するTractorWorkSession(work_type=ridge_building)を使って記録するPOST /api/task-execution-links/ でリンク作成」 の 2 ステップを必須とし、省略してはならない実施予定日(scheduled_date)を入力して保存した時点で、task_status は scheduled となり、横断 TODO 一覧(/api/todos/unified/)の対象になる実施予定日 を空のまま保存した場合は task_status=draft のままで、TODO 一覧には出ない実施予定日 を後から入れて保存すれば TODO 化されるSchedulableMixin を継承する。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| year | int | required | 年度 |
| name | varchar(200) | required | 計画名(例: "5/3 大豆圃場") |
| scheduled_date | date | nullable | 実施予定日(SchedulableMixin) |
| task_status | enum | default=draft | 実施管理状態(SchedulableMixin) |
| sort_priority | int | default=1000 | TODO 並び順(SchedulableMixin) |
| notes | text | blank=True | 備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto |
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| plan | FK(RidgeBuildingPlan) | CASCADE | |
| field | FK(fields.Field) | PROTECT | |
| base_plan | FK(plans.Plan) | SET_NULL, nullable | 作付け計画(品目絞り込み用参照) |
| crop_name_snapshot | varchar(100) | blank=True | 保存時点の作物名(サーバー側で base_plan から導出) |
| variety_name_snapshot | varchar(100) | blank=True | 保存時点の品種名(サーバー側で base_plan から導出) |
| fertilization_plan | FK(fertilizer.FertilizationPlan) | SET_NULL, nullable | 連動する施肥計画(圃場ごと) |
| ridge_length | decimal(6,1) | nullable | 畝の長さ (m)、任意 |
| ridge_spacing | decimal(5,1) | required | 畝間 (cm) |
| ridge_count | int | required | 畝の本数 |
| notes | text | blank=True | 圃場ごと備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto |
unique_together = ['plan', 'field']スナップショット導出ルール(サーバー側):
base_plan が指定されている場合: crop_name_snapshot = base_plan.crop.name、variety_name_snapshot = base_plan.variety.name(品種なしなら空文字)base_plan が null の場合: 両フィールドとも空文字field と base_plan(任意)のみを送る既存 TractorWorkSession.WorkType に以下を追加する。新たなモデルは作らない。
RIDGE_BUILDING = 'ridge_building', '畝建て'
| source_type | record_type |
|---|---|
ridge_building_plan |
tractor_work_session |
TaskExecutionLink.SourceType に ridge_building_plan を追加する。
| メソッド | パス | 説明 |
|---|---|---|
| GET | /api/ridge-building-plans/ |
計画一覧(year フィルタ対応) |
| POST | /api/ridge-building-plans/ |
計画作成(entries 同時保存) |
| GET | /api/ridge-building-plans/{id}/ |
計画詳細(entries 含む) |
| PATCH | /api/ridge-building-plans/{id}/ |
計画更新 |
| DELETE | /api/ridge-building-plans/{id}/ |
計画削除(draft かつ実績リンクなしのみ物理削除、他は 409) |
| POST | /api/todos/unified/ridge_building_plan/{id}/complete/ |
完了確定(→ done) |
| POST | /api/todos/unified/ridge_building_plan/{id}/cancel/ |
中止(→ canceled) |
クライアントはスナップショットフィールドを送らない。field と base_plan(任意)を送ると、サーバー側で作付け計画を解決してスナップショットを導出する。
{
"year": 2026,
"name": "5/3 大豆圃場",
"scheduled_date": "2026-05-03",
"notes": "",
"entries": [
{
"field": 12,
"base_plan": 34,
"fertilization_plan": 8,
"ridge_length": 80.0,
"ridge_spacing": 75.0,
"ridge_count": 4,
"notes": ""
},
{
"field": 15,
"base_plan": null,
"fertilization_plan": null,
"ridge_length": null,
"ridge_spacing": 75.0,
"ridge_count": 6,
"notes": ""
}
]
}
| 対象 | ルール |
|---|---|
base_plan |
指定する場合、base_plan.year == 親 RidgeBuildingPlan.year かつ base_plan.field == entry.field であること |
fertilization_plan |
指定する場合、fertilization_plan.year == plan.year かつ FertilizationEntry に field が含まれること |
ridge_spacing / ridge_count |
必須。0以下は不可 |
field |
計画内で重複不可(unique_together で保証) |
| パラメータ | 説明 |
|---|---|
year |
年度フィルタ |
task_status |
状態フィルタ |
横断 TODO ビュー(GET /api/todos/unified/)に source_type=ridge_building_plan として出現する。
{
"source_type": "ridge_building_plan",
"source_id": 3,
"title": "5/3 大豆圃場",
"scheduled_date": "2026-05-03",
"task_status": "scheduled",
"execution_count": 0,
"last_executed_at": null,
"detail_path": "/todos/ridge_building_plan/3"
}
detail_path は /todos/ridge_building_plan/{id} とする。ダッシュボードはこのパスをそのまま開くため、専用の /ridge-building/[id] ページは作らない(詳細ハブは /todos/ ルートに統一)。
実績(TractorWorkSession)は source_type/source_id を受け取る API を持たないため、以下の 2ステップ で連携を完結させる。どちらか片方だけで終了してはならない。
[1] 実績の作成
POST /api/tractor-work/sessions/
{
"work_type": "ridge_building",
"year": 2026,
"date": "2026-05-03",
"items": [{"field": 12}, {"field": 15}]
}
→ TractorWorkSession が作成される(session_id を得る)
[2] 計画と実績のリンク
POST /api/task-execution-links/
{
"source_type": "ridge_building_plan",
"source_id": 3,
"record_type": "tractor_work_session",
"record_id": {session_id}
}
→ TaskExecutionLink が生成され、plan.task_status が partially_done に自動遷移
フロントエンド実装指針:
TaskExecutionLink 未作成のまま partially_done / done になった前提で UI を進めてはならないグローバルナビゲーション(Navbar)の 「計画」グループ に以下を追加する。
計画
- 一般作業管理 /todos
- 作付け計画 /allocation
- 施肥計画 /fertilizer/plans
- 田植え計画 /rice-transplant/plans
- 運搬計画 /delivery
- トラクター計画 /tractor-work/plans
+ 畝建て計画 /ridge-building/ ← 追加
実装ファイル: frontend/src/components/Navbar.tsx の planning グループに追記する。
補足:
/todos/ は「全 TODO 一覧」ではなく GeneralTask を扱う一般作業管理画面として扱う[Navbar: 畝建て計画]
↓
[/ridge-building/] 計画一覧
├─ [新規作成] ──────────────────────→ [/ridge-building/new]
│ ↓ 保存
│ [/ridge-building/]
├─ [詳細] ──────────────────────────→ [/todos/ridge_building_plan/[id]]
└─ [編集] ──────────────────────────→ [/ridge-building/[id]/edit]
↓ 保存
[/ridge-building/]
[ダッシュボード TODO一覧] ─ 行クリック → [/todos/ridge_building_plan/[id]]
| パス | 内容 | 主な遷移元 |
|---|---|---|
/ridge-building/ |
計画一覧(年度フィルタ) | Navbar |
/ridge-building/new |
計画作成 | 一覧の「新規作成」ボタン |
/ridge-building/[id]/edit |
計画編集 | 一覧の「編集」ボタン、詳細ハブの「計画を開く」 |
/todos/ridge_building_plan/[id] |
予定詳細ハブ | 一覧の「詳細」ボタン、ダッシュボード行クリック |
詳細ハブ URL の正本は /todos/ridge_building_plan/[id] とする。畝建て専用の詳細ページは作らない。既存 TodoDetailHubPage コンポーネントに ridge_building_plan を追加する形で対応する(SOURCE_TYPE_LABELS・PLAN_API_PATH・PLAN_DETAIL_PATH の各 Record に追記)。
1. 計画名・実施予定日・備考を入力
- 実施予定日を入れて保存すると TODO 一覧に出現する
- 実施予定日を空のまま保存すると下書き(draft)のまま保存される
2. 作付け品目(crop)で圃場を絞り込む(任意。絞り込みなしで全圃場も選択可)
3. 対象圃場をチェックして追加
- 新規作成: candidate.selected(作付け計画のある圃場)が初期チェック済み
- 編集: 既存エントリの圃場のみが初期チェック済み(全圃場が表示されるが未チェック)
4. 各圃場ごとに入力:
- 畝間 (cm)・畝の本数 ← 必須
- 畝の長さ (m) ← 任意
- 施肥計画リンク ← 任意(同年度・同圃場が含まれる施肥計画からプルダウン選択)
- 圃場ごと備考 ← 任意
5. 保存 → /ridge-building/ へ戻る
- scheduled_date があれば TODO 一覧に出現
/todos/ridge_building_plan/[id])[種別バッジ: 畝建て計画] [計画名] [状態バッジ]
[計画を開く → /ridge-building/[id]/edit]
予定内容セクション
計画名 / 実施予定日 / 年度
実績セクション(TaskExecutionLink N件)
record_type / record_id / リンク日付 / [解除]ボタン
操作セクション(task_status が done/canceled でない間表示)
[実績を追加する] ← tractor-work/records へ source_type/source_id/field_ids 付きで遷移
[完了のみ] ← done に遷移後 router.back()
[完了して実績入力へ] ← done に遷移後 tractor-work/records へ遷移(backend redirect_info 使用)
[中止にする] ← canceled に遷移
ボタン表示条件:
| ボタン | 表示条件 |
|---|---|
| 実績を追加する | task_status が scheduled / in_progress / partially_done |
| 完了のみ | 同上 |
| 完了して実績入力へ | 同上(done にしてから records ページへ遷移する。実績未登録のまま完了したい場合に使う) |
| 中止にする | task_status が done / canceled 以外 |
| 計画を開く | 常時(Navbar に相当する編集ページへのリンク) |
「実績を追加する」の遷移先:
/tractor-work/records?year={year}&work_type=ridge_building&source_type=ridge_building_plan&source_id={id}&field_ids={comma-separated}
保存完了後は /todos/ridge_building_plan/{id} に戻り、partially_done に遷移済みの状態が表示される。
ダッシュボードからの実績追加ショートカット(既知の制限):
ダッシュボード TODO 一覧の実績追加ショートカットは UnifiedTodoItem(entries を持たない)から生成するため、field_ids を渡せない。/tractor-work/records を開いたとき圃場が事前選択されず、ユーザーが手動で選択する必要がある。詳細ハブ経由(detail_path クリック → 実績を追加する)なら field_ids が渡るため、そちらが推奨導線。
実績表示(現行):
実績セクションは TaskExecutionLink の record_type(例: tractor_work_session)と record_id・リンク日付を表示する。TractorWorkSession の日付・圃場数への展開は将来検討とする。
/ridge-building/)各行の表示項目:
| 表示項目 | 説明 |
|---|---|
| 計画名 | |
| 実施予定日 | |
| 圃場数 | entries 件数 |
| 状態 | task_status_display(例: 予定 / 一部完了 / 完了) |
| 詳細ボタン | /todos/ridge_building_plan/[id] へ遷移 |
| 編集ボタン | /ridge-building/[id]/edit へ遷移 |
| 削除ボタン | 確認ダイアログ後に DELETE。draft かつ実績なしのみ成功(他は 409 をエラー表示) |
apps/plans/
├── models.py # RidgeBuildingPlan, RidgeBuildingEntry を追加
├── serializers.py # RidgeBuildingPlanSerializer(entries ネスト、スナップショット自動導出)
├── views.py # RidgeBuildingPlanViewSet
├── urls.py # /api/ridge-building-plans/
└── migrations/ # 0013_ridgebuildingplan_and_entries.py
apps/tractor_work/
└── models.py # WorkType に ridge_building を追加
apps/todos/
├── models.py # SourceType に ridge_building_plan を追加
├── services.py # get_schedulable() に ridge_building_plan ケースを追加
└── views_v2.py # UnifiedTodoCompleteView / CancelView の source_type 対応追加
frontend/src/app/
├── ridge-building/
│ ├── page.tsx # 計画一覧
│ ├── new/page.tsx # 計画作成
│ └── [id]/edit/page.tsx # 計画編集
└── todos/
└── ridge_building_plan/[id]/page.tsx # 詳細ハブ(TodoDetailHubPage を拡張して使用)
frontend/src/components/todos/
└── TodoDetailHubPage.tsx # ridge_building_plan を Props 型・各 Record に追記
| 事項 | 方針 |
|---|---|
| Navbar への「畝建て計画」リンク追加 | 未実装。frontend/src/components/Navbar.tsx の planning グループに追記する |
| 畝建て実績時の WorkRecord 自動生成 | 初期実装では対象外。TractorWorkSession 側の既存挙動に準じる |
| ダッシュボードショートカットへの field_ids 付与 | 初期は非対応。詳細ハブ経由を推奨導線とする |
| 実績一覧の詳細表示(日付・圃場数) | 初期は record_type + record_id のみ |
| candidate_fields の実装重複解消 | RidgeBuildingPlanViewSet.candidate_fields と TractorWorkCandidatesView の ridge_building 分岐が重複。将来整理 |