作成: 2026-04-10
最終更新: 2026-05-11(生産履歴中心設計に合わせて TODO の役割を明確化)
対象機能: TODO管理(予定管理・実績連携・横断ビュー)
実装状況: #47 完了(Phase 5 着手まで反映済み) / #48-#51 着手前
対象 Issue:akira/keinasystem#17(既存)、akira/keinasystem#47(完了)、#48#49#50#51(後続)
関連方針:docs/00_設計ポリシー_生産履歴中心設計.md
旧設計:TODO は計画と実績の間に存在する独立ドメインエンティティ
計画 → TodoPlanLink → Todo → TodoCompletionLink → 実績
新設計:「実施予定日のある計画 = TODO」という統合モデル
計画(scheduled_date 付き) ─→ TODO一覧に出る
↓
実績(TaskExecutionLink で紐づく)
GeneralTask モデルが受け持つ/todos/ ページは「全 TODO 一覧」ではなく、GeneralTask の作成・管理・履歴確認画面として扱うapps/todos は当面残し、GeneralTask 専用アプリとして段階的に縮退させる繁忙期に「どれから手を付けるか」を管理するための予定管理機能。
本機能の役割は、実施予定の可視化と実績入力への導線提供である。
「完了したかどうか」を示すことはできるが、「実際に何をしたか」を代替してはならない。
| 画面 | 役割 |
|---|---|
| ダッシュボード | 今週・今日やることを見る横断 TODO の正面入口 |
/todos/ |
GeneralTask の作成・編集・完了・履歴確認 |
この整理により、計画系 TODO は各計画画面で管理し、scheduled_date が入ったものだけがダッシュボードへ集約される。/todos/ は「一般作業管理」画面として扱う。
| IN(対象) | OUT(対象外) |
|---|---|
| 予定の作成・編集・削除 | 期日通知・リマインダー |
| 実施管理状態の管理(draft→scheduled→in_progress→partially_done→done/canceled) | 複数ユーザー割り当て |
| 優先順位管理 | コメント・添付ファイル |
| 圃場単位の対象紐づけ | 工数見積・実績時間記録 |
| 実績との 1:N 連携(TaskExecutionLink) | 完全な汎用ワークフローエンジン化 |
| 横断 TODO ビュー(施肥・田植え・運搬・一般を同列表示) | 圃場消化率による自動完了判定 |
| 完了後の実績作成画面への導線 |
計画(年間設計情報)
↓ scheduled_date を入れる
TODO一覧に表示(実施予定一覧)
↓ 実績を作成する
実績(TaskExecutionLink で紐づく・1:N)
↓ ユーザーが作業完了を確定
完了(task_status = done)
各計画モデルと GeneralTask が SchedulableMixin 経由で共通に持つ。
| 値 | 意味 | TODO一覧への表示 |
|---|---|---|
draft |
候補(予定日未設定可) | 表示しない |
scheduled |
予定あり(実施日確定) | 表示する |
in_progress |
実施中 | 表示する |
partially_done |
一部完了(実績 1件以上あり) | 表示する |
done |
完了(手動確定) | 表示しない |
canceled |
中止 | 表示しない |
[draft]
↓ scheduled_date を入れる
[scheduled]
↓ 作業開始(任意)
[in_progress]
↓ 実績を 1件以上保存 → 自動で partially_done
[partially_done]
↓ ユーザーが「作業完了にする」
[done]
任意の状態 → [canceled](中止)
ルール
scheduled_date が入り task_status=scheduled になると TODO一覧に出るpartially_done へ遷移done への遷移は必ず手動確定(予定詳細画面から)done / canceled は TODO一覧から消えるが、予定レコードは物理削除しないdone であっても、対応する実績が無ければ生産履歴としては不十分であるscheduled_date IS NOT NULL
AND task_status IN ('scheduled', 'in_progress', 'partially_done')
各計画モデルと GeneralTask に適用する共通フィールド群。
class SchedulableMixin(models.Model):
scheduled_date = models.DateField(null=True, blank=True, verbose_name='実施予定日')
task_status = models.CharField(max_length=20, choices=TaskStatus.choices,
default='draft', verbose_name='実施管理状態')
sort_priority = models.IntegerField(default=1000, verbose_name='TODO並び順')
should_link_record = models.BooleanField(default=True, verbose_name='実績導線フラグ') # 後続 Issue #49 で廃止予定
class Meta:
abstract = True
適用対象モデル(現行コードに存在するもの)
fertilizer.FertilizationPlanplans.RiceTransplantPlanfertilizer.DeliveryPlantodos.GeneralTask(旧 Todo)トラクター作業について: 現行コードベースには計画モデル(TractorWorkPlan)が存在しない(実績側の
TractorWorkSessionのみ実装済み)。
当面はトラクター作業の予定管理をGeneralTaskで代替する。
後続 Issue #48 でTractorWorkPlanを新設し、計画 → TODO → 実績フローへ統合する。
計画モデルに属さない作業タスクを管理する。旧 Todo モデルを GeneralTask として縮退させる。
当面は apps/todos 配下に存在し、既存データとマイグレーションを引き継ぐ。
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| year | integer | ✓ | 年度 |
| title | varchar(200) | ✓ | タイトル |
| description | text | 説明・メモ | |
| scheduled_date | date | 実施予定日(SchedulableMixin) | |
| task_status | enum | ✓ | 実施管理状態(SchedulableMixin) |
| sort_priority | integer | ✓ | TODO並び順(SchedulableMixin) |
| should_link_record | boolean | ✓ | 実績導線フラグ(SchedulableMixin、後続 Issue #49 で廃止予定) |
| created_at | datetime | ✓ | |
| updated_at | datetime | ✓ |
旧 Todo からの縮退方針
work_type = general に相当する作業を引き継ぐwork_type=fertilization などは各計画モデルの scheduled_date で代替TodoPlanLink は段階的に廃止status(todo/doing/done/canceled) は新 task_status へマッピング| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
| id | bigint | ✓ | PK |
| todo | FK(GeneralTask) | ✓ | 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']予定(各計画 or GeneralTask)と実績を結ぶ汎用リンクテーブル。旧 TodoCompletionLink を置き換える。
予定:実績 = 1:N を許容。
class TaskExecutionLink(models.Model):
source_type = models.CharField(max_length=50, choices=SourceType.choices)
source_id = models.PositiveIntegerField()
record_type = models.CharField(max_length=50, choices=RecordType.choices)
record_id = models.PositiveIntegerField()
linked_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['source_type', 'source_id', 'record_type', 'record_id'],
name='uq_task_execution_link_pair',
),
]
indexes = [
models.Index(fields=['source_type', 'source_id']),
models.Index(fields=['record_type', 'record_id']),
]
| 値 | 対応モデル | 状態 |
|---|---|---|
fertilization_plan |
FertilizationPlan | 現行実装済み |
rice_transplant_plan |
RiceTransplantPlan | 現行実装済み |
delivery_plan |
DeliveryPlan | 現行実装済み |
general_task |
GeneralTask | 現行実装済み |
tractor_work_plan |
TractorWorkPlan | 将来追加(モデル未実装のため当面 general_task で代替) |
| 値 | 対応モデル | 状態 |
|---|---|---|
spreading_session |
fertilizer.SpreadingSession | 現行実装済み |
rice_transplant_record |
rice_transplant_records.RiceTransplantSession | 現行実装済み |
delivery_record |
workrecords.WorkRecord (work_type='fertilizer_delivery') |
現行実装済み |
tractor_work_session |
tractor_work.TractorWorkSession | 現行実装済み |
work_record |
workrecords.WorkRecord | 現行実装済み |
| source_type | record_type |
|---|---|
fertilization_plan |
spreading_session |
rice_transplant_plan |
rice_transplant_record |
delivery_plan |
delivery_record |
general_task |
全 record_type(制限なし) |
tractor_work_plan → tractor_work_sessionはTractorWorkPlanモデル新設後に追加する。
当面のトラクター作業予定はgeneral_task → work_recordで代替する。
組み合わせ検証はアプリサービス層(link_task_to_record() 関数)で行い、DB には制約を置かない。
delivery_record は専用実績モデルを新設せず、workrecords.WorkRecord のうち work_type='fertilizer_delivery' のレコードとして扱う。
source_type / record_type は TextChoices で固定(自由文字列不可)GeneralTask の削除は task_status=draft かつ TaskExecutionLink が 0件の場合のみ物理削除を許可canceled への状態変更で代替し、API は 409 を返すTaskExecutionLink 削除 + task_status 再計算を一体で行う| メソッド | パス | 説明 |
|---|---|---|
| GET | /api/todos/unified/ |
横断 TODO ビュー(scheduled_date あり・未完了の全計画種別) |
| POST | /api/general-tasks/ |
GeneralTask 作成(新 API・段階移行) |
| GET | /api/general-tasks/{id}/ |
GeneralTask 詳細 |
| PATCH | /api/general-tasks/{id}/ |
GeneralTask 更新 |
| DELETE | /api/general-tasks/{id}/ |
GeneralTask 削除(draft かつ実績リンクなしの場合のみ物理削除。それ以外は 409 を返し canceled への変更を促す) |
| PATCH | /api/todos/reorder/ |
GeneralTask の表示順並び替え(計画系は対象外) |
| POST | /api/todos/unified/{source_type}/{id}/complete/ |
予定を done に(完了確定) |
| POST | /api/todos/unified/{source_type}/{id}/cancel/ |
予定を canceled に |
| POST | /api/task-execution-links/ |
予定と実績を紐づける |
| DELETE | /api/task-execution-links/{id}/ |
リンク解除(実績削除ではない) |
/api/todos/は互換のため当面残すが、横断 TODO の正本 API は/api/todos/unified/とする。
/api/todos/unified/scheduled_date IS NOT NULL AND task_status IN (scheduled, in_progress, partially_done) の全計画種別を横断集計して返す。
ソート順: scheduled_date ASC(全種別共通。sort_priority は横断ビューでは使用しない)
sort_priorityはGeneralTask内部の並び替えにのみ使う(reorder API 参照)。
計画系モデルでも mixin フィールドとして保有するが、横断ビューでは参照しない。
主なクエリパラメータ:
| パラメータ | 説明 |
|---|---|
year |
年度フィルター |
source_type |
種別フィルター(fertilization_plan / general_task など) |
due |
overdue / today / upcoming |
レスポンス形式(各アイテムの共通フィールド):
{
"source_type": "fertilization_plan",
"source_id": 8,
"title": "2026春肥の散布",
"scheduled_date": "2026-05-10",
"task_status": "partially_done",
"execution_count": 1,
"last_executed_at": "2026-05-10",
"detail_path": "/todos/fertilization_plan/8"
}
detail_path は source_type を含む形式にし、fertilization_plan/8 と general_task/8 のような ID 衝突を回避する。
/api/general-tasks/{
"year": 2026,
"title": "農機の点検",
"description": "田植え前に要実施",
"scheduled_date": "2026-05-05",
"should_link_record": false, // 後続 Issue #49 で廃止予定
"field_ids": []
}
task_status は scheduled(scheduled_date あり)または draft(なし)が自動設定される。
/api/todos/unified/{source_type}/{id}/complete/task_status を done に変更する。partially_done 状態からのみ呼び出し可能(実績が 1件以上あること)。
{
"source_type": "fertilization_plan",
"id": 8
}
/api/task-execution-links/{
"source_type": "fertilization_plan",
"source_id": 8,
"record_type": "spreading_session",
"record_id": 42
}
サービス層で実在確認・組み合わせ検証・task_status 更新(→ partially_done)を行う。
[Dashboard(横断 TODO) / 一般作業管理]
↓ 行クリック
[予定詳細画面]
├─ [予定を編集する]
├─ [実績を追加する] ──→ [実績作成画面]
│ ↓ 保存
│ [予定詳細へ戻る]
├─ [作業完了にする](task_status → done)
└─ [中止にする](task_status → canceled)
done への遷移は詳細画面から手動確定(自動確定しない)各行の表示:
| 表示項目 | 説明 |
|---|---|
| 種別バッジ | 施肥 / 田植え / 運搬 / 一般など |
| タイトル | |
| 実施予定日 | 期限超過は赤表示 |
| 進捗表示 | 未着手 / 進行中 / 実績 N件 / 完了 |
| 詳細ボタン | 予定詳細へ遷移 |
partially_done の一覧表示
ダッシュボードの完了チェックボタンは段階的に廃止し、「詳細を開く」ボタンに置き換える。
/todos/(一般作業管理)GeneralTask のみTODO管理 ではなく 一般作業管理 とする[予定詳細]
種別・タイトル・予定日・状態・メモ
▼ この予定から作成された実績
実績 #1 2026-05-10 5圃場
実績 #2 2026-05-12 3圃場
[実績を追加する] [作業完了にする] [中止にする]
[予定を編集する]
partially_done 状態での詳細表示
一部完了予定詳細から入った場合、関連フィールドを初期値としてセット:
TaskExecutionLink 生成 → 予定の task_status → partially_done → 予定詳細へ戻るapps/todos/
├── models.py # GeneralTask(旧 Todo), TodoTargetField, TaskExecutionLink
│ # TodoPlanLink, TodoCompletionLink は Phase 5 (#47) migration 0003 で削除済み
├── admin.py
├── serializers.py
├── views.py # 旧互換 /api/todos/ + GeneralTask CRUD
├── views_v2.py # /api/todos/unified/ + TaskExecutionLink CRUD
├── services.py # link_task_to_record(), complete_task() など
└── migrations/
SchedulableMixin は各計画アプリのモデルファイルに適用:
apps/fertilizer/models.py → FertilizationPlan, DeliveryPlanapps/plans/models.py → RiceTransplantPlanfrontend/src/app/todos/
├── page.tsx # 一般作業管理
└── [source_type]/[id]/page.tsx # 予定詳細(ハブ画面)
# 例: /todos/fertilization_plan/8
# /todos/general_task/12
source_type を URL に含めることで、異なる種別間の id 衝突(例: fertilization_plan/8 と general_task/8)を回避する。
FertilizationPlan(scheduled)→ SpreadingSession
source_type=fertilization_plan, record_type=spreading_sessionRiceTransplantPlan(scheduled)→ RiceTransplantSession
source_type=rice_transplant_plan, record_type=rice_transplant_recordDeliveryPlan(scheduled)→ delivery_record(WorkRecord の運搬種別)
source_type=delivery_plan, record_type=delivery_record現行:
TractorWorkPlanモデルが未実装のため、トラクター作業の予定はGeneralTaskで管理する。
GeneralTask → WorkRecordの経路で実績と紐づける。
TractorWorkPlan新設後はsource_type=tractor_work_plan,record_type=tractor_work_sessionに移行する。
GeneralTask(scheduled)→ WorkRecord
source_type=general_task, record_type=work_recordgeneral_task の record_type 制限はなく、用途に応じて delivery_record なども連携可能should_link_record は現行コードに残存する互換フィールドだが、後続 Issue #49 で廃止予定Issue #47 完了後、次の順で仕様を更新する。
/{domain}/plans / /{domain}/records / /{domain}/todo/[id])TractorWorkPlan 新設(general_task 暫定運用を解消)should_link_record 廃止注: URL ドメイン名(
/fertilizerを維持するか等)は #51 着手時に確定する。
注記: 現フェーズ(Phase 3)の実装。新設計(Phase 4)では GeneralTask 作成 UI に統合される予定。
現在の入力項目:
| 項目 | 説明 | 必須 |
|---|---|---|
| タイトル | 作業内容 | ✓ |
作業種別(work_type) |
一般 / 施肥 / 田植え / 運搬 / トラクター作業 | ✓(デフォルト: 一般) |
トラクター作業細別(work_subtype) |
tractor_work 選択時のみ表示 |
tractor_work 時 ✓ |
| 期日 | 任意 |
Phase 4 移行後は、「計画を作る」または「GeneralTask を作る」の選択肢に変化する。
| 項目 | 旧 | 新 | 移行方針 |
|---|---|---|---|
apps/todos アプリ |
残す | GeneralTask 専用に縮退 | app 名は当面変更しない |
/api/todos/ |
Todo CRUD | 一般作業管理向け旧互換 API | 新規の横断 TODO は /api/todos/unified/ を正本とする |
Todo モデル |
独立ドメイン | GeneralTask |
work_type=general 相当のみ残す |
TodoPlanLink |
計画紐づけ | 不要(計画モデル直接参照) | 段階廃止 |
TodoCompletionLink |
実績連携 | TaskExecutionLink に移行 |
新テーブルへ移行 |
| status(todo/doing/done/canceled) | Todo 固有 | task_status(draft〜done) | SchedulableMixin に統合 |
| 事項 | 方針 |
|---|---|
| 差し戻し(done → scheduled) | 初期実装では対応しない |
| 一覧からの直接実績化ショートカット | 将来追加(初期は詳細経由の一本導線) |
draft 候補リストの UI |
別途候補計画一覧画面として検討 |
| 圃場消化率による自動完了 | 対象外(手動確定を維持) |