Files
keinasystem/改善案/施肥散布実績連携変更実装仕様.md
Akira c9ae99ebc8 CODEX版
昨日運んだ肥料を散布してきました。
それで、今は施肥計画に「散布確定」ボタンがあるのですが、それだと実態に合わない事がわかりました。
実際には運搬計画を元に、運んだ肥料を散布します。
順序は、運搬計画の1回目2回目などの順序には関係がなく
運搬計画のすべての中から、全部または一部の圃場に対して散布します。
散布中に、運搬計画から実際の散布袋数が変更になる場合があるので、変更に対処できなければなりません。。散布は日付単位で行い、その日付を元に作業記録が自動的に作成されるようにしたいです。
運搬計画にも日付をつけたので、それも作業記録が自動的に作成されるようにしたいです。

以上のような感じで、変更実装仕様を作成してもらえますか?
2026-03-17 16:26:41 +09:00

568 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 施肥散布実績連携変更実装仕様
> 作成日: 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 と自動生成ロジックは今回入れる