# 施肥散布実績連携変更実装仕様 > 作成日: 2026-03-17 > 対象プロジェクト: `keinasystem_t02` > 目的: 実運用に合わせて、施肥計画の「散布確定」中心設計を見直し、運搬計画起点の散布実績管理と日付ベースの作業記録自動生成へ移行する --- ## 1. 結論 今回の変更では、施肥実績の確定主体を `FertilizationPlan` から外し、以下の流れに再設計する。 - 施肥計画は「計画」と「在庫引当」の責務に限定する - 運搬計画は「どの日に何をどこへ運んだか」の実績起点にする - 散布実績は、運搬済み肥料を元にした「日付単位の散布記録」で管理する - 作業記録は、運搬日・散布日から自動生成する - 在庫の `USE` は施肥計画の散布確定ではなく、散布実績の保存時に発生させる つまり、業務フローは以下に変更する。 1. 施肥計画を作る 2. 運搬計画で肥料を運ぶ 3. 運んだ肥料の全部または一部を、日付単位で散布する 4. 運搬日・散布日をもとに作業記録を自動作成する --- ## 2. 背景と現状課題 現状実装では、`FertilizationPlan` に「散布確定」ボタンがあり、ここで実績数量を入力して在庫の `RESERVE → USE` を行っている。 しかし実運用では、以下のギャップがある。 - 実際の散布は施肥計画から直接ではなく、運搬計画で現地へ運んだ肥料を元に行う - 散布時に参照するのは「1回目」「2回目」という順番ではなく、日付まで付いた運搬済み肥料全体である - 散布対象は運搬計画全体の中から「全部」または「一部の圃場」になる - 散布中に、運搬計画上の袋数から実際の散布袋数へ変更が入る - 作業記録は日付単位で残したい - 運搬計画にも日付が付いたため、運搬も作業記録として自動生成したい そのため、「施肥計画で一括散布確定する」設計は実態に合わない。 --- ## 3. 変更後の設計方針 ### 3.1 責務分離 - `FertilizationPlan` は計画データ - `DeliveryPlan / DeliveryTrip / DeliveryTripItem` は運搬計画と運搬実績 - 新設する `SpreadingSession` 系は散布実績 - 新設する `WorkRecord` 系は日付ベースの作業記録 ### 3.2 日付中心設計 作業記録は「年度中心」ではなく「日付中心」で扱う。 年度は `date` から導出できる補助情報とし、画面の年フィルタや施肥計画との照合に使う。 ### 3.3 散布元の考え方 散布元は「特定の運搬回の順番」ではなく、以下を満たす運搬済み明細の集合とする。 - `DeliveryTrip.date` が入っている - まだ未散布残がある - 年度が一致する つまり、散布画面では「その年度の運搬済み・未散布残あり」の明細を一覧化し、その中から対象圃場を選んで散布する。 ### 3.4 差異の扱い 運搬数量と実散布数量が一致しないことは許容する。 ただし整合性のため、以下の扱いにする。 - 実散布数量は運搬明細から初期値セットする - ユーザーは実散布数量を編集できる - 実散布数量が運搬済み残を超える場合は、そのまま黙って保存せず、差異として明示する - MVP では「運搬実績を先に修正してから再保存」を推奨フローとする --- ## 4. 機能スコープ ### 4.1 IN - 施肥計画一覧から「散布確定」ボタンを外す - 散布実績を日付単位で新設する - 散布実績は運搬済み明細を元に、全部または一部の圃場を対象に作成できる - 散布時に実績袋数を変更できる - 運搬日から作業記録を自動生成する - 散布日から作業記録を自動生成する - 在庫使用実績は散布実績保存時に作成する - 施肥計画一覧に「未散布 / 一部散布 / 完了」などの進捗状態を表示する ### 4.2 OUT - 肥料の置き場所管理 - 残肥の返却・再入庫 - ルート最適化 - 複数人同時編集のロック制御 - 汎用的な作業日誌機能の完成版 UI --- ## 5. 新しい業務フロー ### 5.1 施肥計画 1. 施肥計画を作成・更新する 2. 従来どおり `RESERVE` を張る 3. ここでは散布確定しない ### 5.2 運搬計画 1. 年度の施肥計画から運搬計画を作る 2. 各 `DeliveryTrip` に日付を入れる 3. 日付が入った `DeliveryTrip` は「運搬実績あり」と見なす 4. `DeliveryTrip` ごとに作業記録を自動生成する ### 5.3 散布実績 1. ユーザーは散布日を選ぶ 2. その年度の「運搬済みかつ未散布残あり」の明細を候補表示する 3. 候補の中から、全部または一部の圃場を選ぶ 4. 初期値として未散布残袋数を入れる 5. 実際の散布袋数へ修正して保存する 6. 保存時に `USE` 在庫履歴を散布日で作る 7. 散布日の作業記録を自動生成する --- ## 6. 推奨データモデル変更 ### 6.1 `FertilizationPlan` 現行の `is_confirmed` / `confirmed_at` は業務上の意味が薄くなるため、段階的に非推奨化する。 ### 方針 - DB カラムは互換性のため一旦残してよい - 新UIでは `confirm_spreading` / `unconfirm` を使わない - 一覧表示は以下の計算状態へ置き換える ### 追加する表示用状態 - `spread_status`: `unspread | partial | completed | over_applied` - `planned_total_bags` - `spread_total_bags` - `remaining_total_bags` - `spread_started_at` - `spread_completed_at` これらは serializer で計算する実装を推奨する。 ### 6.2 `DeliveryTripItem` 現在は `field + fertilizer + bags` のみを持つが、散布実績との照合性を上げるため、内部参照を追加する。 ### 追加推奨フィールド - `fertilization_entry`: `FK(FertilizationEntry, null=True, on_delete=SET_NULL)` ### 目的 - どの施肥計画エントリ由来かを内部的に追跡する - 施肥計画進捗の自動集計をしやすくする - 将来の差異分析をしやすくする ※ UI 上は引き続き「年度横断で選ぶ」挙動のままでよい。 ### 6.3 `SpreadingSession`(新規) 散布日単位の親レコード。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | int | PK | | | year | int | required | 年度フィルタ用 | | date | DateField | required | 散布日 | | name | varchar(100) | blank | 任意名 | | notes | text | blank | 備考 | | created_at / updated_at | datetime | auto | | ### 制約 - MVP では `unique_together = [['year', 'date']]` を推奨 同日に複数の散布イベントを分けたい場合は、将来 `sequence` を追加して拡張する。 ### 6.4 `SpreadingSessionItem`(新規) 散布日の圃場×肥料ごとの実績。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | int | PK | | | session | FK(SpreadingSession) | CASCADE | | | fertilization_entry | FK(FertilizationEntry) | SET_NULL, nullable | 元施肥計画エントリ | | field | FK(fields.Field) | PROTECT | | | fertilizer | FK(Fertilizer) | PROTECT | | | actual_bags | Decimal(10,4) | required | 実散布袋数 | | planned_bags_snapshot | Decimal(10,4) | required | 施肥計画時点の参照値 | | delivered_bags_snapshot | Decimal(10,4) | required | 初期表示に使った運搬残袋数 | | created_at / updated_at | datetime | auto | | ### 制約 - `unique_together = [['session', 'field', 'fertilizer']]` ### 6.5 `SpreadingAllocation`(新規) どの運搬明細残からどれだけ散布に充当したかを持つ中間テーブル。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | int | PK | | | spreading_item | FK(SpreadingSessionItem) | CASCADE | | | delivery_trip_item | FK(DeliveryTripItem) | PROTECT | | | allocated_bags | Decimal(10,4) | required | 充当袋数 | ### 目的 - 運搬順ではなく、運搬済み明細全体から散布したことを追跡する - どの運搬明細に未散布残がどれだけあるかを算出できる - 後から「どの便で運んだ肥料をいつ散布したか」を確認できる ### 6.6 `WorkRecord`(新規) 日付単位で自動生成される作業記録。 MVP では汎用化しすぎず、今回必要な 2 種類に絞る。 | フィールド | 型 | 制約 | 説明 | |---|---|---|---| | id | int | PK | | | work_date | DateField | required | 作業日 | | work_type | CharField | required | `fertilizer_delivery` / `fertilizer_spreading` | | title | varchar(200) | required | 表示名 | | auto_created | bool | default=True | 自動生成フラグ | | delivery_trip | OneToOne FK(DeliveryTrip) | nullable | 運搬由来の場合 | | spreading_session | OneToOne FK(SpreadingSession) | nullable | 散布由来の場合 | | created_at / updated_at | datetime | auto | | ### 方針 - 作業記録の明細は二重保持しない - 画面表示時は元の `DeliveryTrip` / `SpreadingSession` を参照して詳細を出す --- ## 7. 在庫連携の変更 ### 7.1 変更前 - 施肥計画保存時に `RESERVE` - 施肥計画の「散布確定」で `USE` ### 7.2 変更後 - 施肥計画保存時に `RESERVE` を継続 - 散布実績保存時に `USE` - `USE.occurred_on` は `SpreadingSession.date` - `RESERVE` は引き続き `FertilizationPlan` に紐づける - `USE` は `SpreadingSessionItem` に紐づける ### 7.3 `StockTransaction` 追加推奨フィールド - `spreading_item = FK(SpreadingSessionItem, null=True, blank=True, on_delete=SET_NULL)` ### 備考 `fertilization_plan` だけでは「どの圃場・どの散布日で使ったか」が追えないため、散布実績由来の FK を追加する。 --- ## 8. API 変更方針 ### 8.1 施肥計画 API ### 廃止対象 - `POST /api/fertilizer/plans/{id}/confirm_spreading/` - `POST /api/fertilizer/plans/{id}/unconfirm/` 即時削除ではなく、まずは UI から呼ばない状態にし、その後バックエンドも段階廃止する。 ### 追加する読み取り項目 - `spread_status` - `planned_total_bags` - `spread_total_bags` - `remaining_total_bags` - `spread_started_at` - `spread_completed_at` ### 8.2 運搬計画 API ### 既存の POST / PUT `DeliveryTrip.date` の保存時に、対応する `WorkRecord` を upsert する。 ### 詳細レスポンスへの追加推奨 - 各 `DeliveryTripItem` に `spread_bags` - 各 `DeliveryTripItem` に `remaining_bags` - 各 `DeliveryTrip` に `work_record_id` ### 8.3 散布実績 API(新規) | メソッド | URL | 説明 | |---|---|---| | GET | `/api/fertilizer/spreading/?year={year}` | 年度別一覧 | | POST | `/api/fertilizer/spreading/` | 新規作成 | | GET | `/api/fertilizer/spreading/{id}/` | 詳細 | | PUT | `/api/fertilizer/spreading/{id}/` | 更新 | | DELETE | `/api/fertilizer/spreading/{id}/` | 削除 | | GET | `/api/fertilizer/spreading/candidates/?year={year}&date={date}` | 散布候補一覧 | ### 候補一覧レスポンスイメージ ```json [ { "field": 5, "field_name": "田中上", "fertilizer": 1, "fertilizer_name": "電気炉さい", "planned_bags": "4.0000", "delivered_bags": "4.0000", "already_spread_bags": "1.5000", "remaining_bags": "2.5000", "source_trip_item_ids": [12, 18] } ] ``` ### 散布作成リクエストイメージ ```json { "year": 2026, "date": "2026-03-17", "items": [ { "field_id": 5, "fertilizer_id": 1, "actual_bags": "2.3000" }, { "field_id": 6, "fertilizer_id": 1, "actual_bags": "1.0000" } ] } ``` ### サーバー側の割当ルール - 同一 `field + fertilizer` の候補運搬明細に対して自動割当する - 割当順は `trip.date asc, trip.id asc, item.id asc` の固定順を推奨 - UI では「何回目」ではなく「残量」を見せる --- ## 9. フロントエンド変更方針 ### 9.1 施肥計画一覧 `/fertilizer` ### 変更点 - 「散布確定」ボタンを廃止 - `ConfirmSpreadingModal.tsx` は削除対象 - 各計画カードに進捗状態を表示 ### 表示例 - `未散布` - `一部散布 3.5 / 8.0袋` - `散布完了` - `計画超過` ### 9.2 運搬計画編集 `/distribution/new` `/distribution/[id]/edit` ### 追加する変更 - `DeliveryTripItem.bags` を画面上で直接編集できるようにする - 各明細に `残り未散布` を表示する - `date` 入力済みの回は「運搬実績あり」表示にする ### 理由 現状 UI では回の間の移動はできるが、袋数の直接編集ができない。 今回の業務に合わせるなら、運搬実績の修正も可能である必要がある。 ### 9.3 散布実績画面(新規) 新規ページ例: - `/fertilizer/spreading` - または `/distribution/spreading` ### 画面要件 - 日付を先に選ぶ - その年度の運搬済み未散布残を一覧表示 - 圃場ごとにチェックして「今日散布する対象」を選べる - 初期袋数は残量を自動セット - 実績袋数は編集可能 - 保存時に差異があればインライン警告を表示 ### UI イメージ ```text [散布日: 2026-03-17] [年度: 2026] 未散布残一覧 田中上 電気炉さい 計画4.0 運搬残2.5 実績[2.3] 田中下 電気炉さい 計画2.0 運搬残2.0 実績[2.0] [保存] ``` ### 9.4 作業記録画面(将来 UI) 今回の実装では、まずは自動生成と API を優先する。 一覧 UI は最小限でもよいが、少なくとも以下を表示できる形にする。 - 日付 - 作業種別 - タイトル - 元データへのリンク --- ## 10. 作業記録自動生成ルール ### 10.1 運搬 ### 作成契機 - `DeliveryTrip.date` が `null → 日付あり` になったとき - 既存レコード更新時に日付が変わったとき ### 作成内容 - `work_type = fertilizer_delivery` - `work_date = DeliveryTrip.date` - `title = 肥料運搬: {delivery_plan.name} {n}回目` ### 更新・削除 - 日付変更時は `WorkRecord.work_date` も更新 - 日付を空に戻したら、自動生成レコードを削除 ### 10.2 散布 ### 作成契機 - `SpreadingSession` の作成時 - `date` 更新時 ### 作成内容 - `work_type = fertilizer_spreading` - `work_date = SpreadingSession.date` - `title = 肥料散布: {date}` ### 更新・削除 - 日付変更時は upsert - `SpreadingSession` 削除時は対応 `WorkRecord` を削除 ### 10.3 実装上の注意 自動生成は view 層に直接書かず、サービス層関数に切り出して idempotent に実装する。 推奨関数: - `sync_delivery_work_record(trip)` - `sync_spreading_work_record(session)` - `delete_auto_work_record_for_delivery(trip)` - `delete_auto_work_record_for_spreading(session)` --- ## 11. 移行方針 ### 11.1 既存の確定済み施肥計画 既存の `confirm_spreading` 実装では、圃場別散布実績が永続化されていない。 `StockTransaction` には `material` 単位の `USE` は残るが、圃場別の再構成は正確にできない。 そのため、既存の確定済みデータは以下方針を推奨する。 - 既存 `USE` 在庫履歴はそのまま残す - 既存 `is_confirmed=True` の計画は「旧方式確定済み」と見なす - 新 `SpreadingSession` への自動変換はしない - 新方式の散布実績は、リリース後の新データから適用する ### 11.2 実装ステップ 1. `SpreadingSession` / `SpreadingSessionItem` / `SpreadingAllocation` / `WorkRecord` を追加 2. `DeliveryTripItem.fertilization_entry` と `StockTransaction.spreading_item` を追加 3. 散布実績 API を追加 4. 運搬日と散布日の作業記録同期を追加 5. フロントで「散布確定」ボタンを撤去 6. 散布実績画面を追加 7. 旧 `confirm_spreading` を非表示化 8. 十分移行後に旧 API を削除 --- ## 12. 影響ファイル(想定) ### Backend - `backend/apps/fertilizer/models.py` - `backend/apps/fertilizer/serializers.py` - `backend/apps/fertilizer/views.py` - `backend/apps/fertilizer/urls.py` - `backend/apps/fertilizer/admin.py` - `backend/apps/fertilizer/migrations/` - `backend/apps/materials/models.py` - `backend/apps/materials/stock_service.py` - `backend/apps/materials/serializers.py` - `backend/apps/materials/migrations/` - `backend/apps/workrecords/` または同等の新規 app ### Frontend - `frontend/src/app/fertilizer/page.tsx` - `frontend/src/app/fertilizer/_components/ConfirmSpreadingModal.tsx` - `frontend/src/app/distribution/_components/DeliveryEditPage.tsx` - `frontend/src/types/index.ts` - 散布実績の新規ページ群 - 作業記録の新規ページ群または既存画面への表示追加 ### ドキュメント - `document/13_マスタードキュメント_施肥計画編.md` - `document/14_マスタードキュメント_分配計画編.md` - `CLAUDE.md` ※ これらは実装着手時に正式仕様へ反映する。 --- ## 13. 受け入れ条件 - 施肥計画一覧に「散布確定」ボタンが表示されない - 運搬済み明細を元に、日付単位の散布実績を新規作成できる - 散布対象は、運搬計画全体から全部または一部の圃場を選べる - 運搬回の順番に依存せず散布できる - 散布時に実際袋数を変更できる - 散布保存時に `USE` 在庫履歴が散布日で作成される - `DeliveryTrip.date` 保存時に運搬作業記録が自動生成される - `SpreadingSession.date` 保存時に散布作業記録が自動生成される - 施肥計画一覧で未散布・一部散布・完了が分かる --- ## 14. 前提と留意点 - 本仕様では「残肥の返却」は扱わない。未散布残は運搬済み残として次回散布へ繰り越す - 同日複数散布の厳密な分離は MVP では不要とみなし、まずは 1日1レコードを採用する - 既存確定済みデータの完全移行は行わない - 作業記録 UI は最小構成でよいが、API と自動生成ロジックは今回入れる