昨日運んだ肥料を散布してきました。 それで、今は施肥計画に「散布確定」ボタンがあるのですが、それだと実態に合わない事がわかりました。 実際には運搬計画を元に、運んだ肥料を散布します。 順序は、運搬計画の1回目2回目などの順序には関係がなく 運搬計画のすべての中から、全部または一部の圃場に対して散布します。 散布中に、運搬計画から実際の散布袋数が変更になる場合があるので、変更に対処できなければなりません。。散布は日付単位で行い、その日付を元に作業記録が自動的に作成されるようにしたいです。 運搬計画にも日付をつけたので、それも作業記録が自動的に作成されるようにしたいです。 以上のような感じで、変更実装仕様を作成してもらえますか?
18 KiB
施肥散布実績連携変更実装仕様
作成日: 2026-03-17 対象プロジェクト:
keinasystem_t02目的: 実運用に合わせて、施肥計画の「散布確定」中心設計を見直し、運搬計画起点の散布実績管理と日付ベースの作業記録自動生成へ移行する
1. 結論
今回の変更では、施肥実績の確定主体を FertilizationPlan から外し、以下の流れに再設計する。
- 施肥計画は「計画」と「在庫引当」の責務に限定する
- 運搬計画は「どの日に何をどこへ運んだか」の実績起点にする
- 散布実績は、運搬済み肥料を元にした「日付単位の散布記録」で管理する
- 作業記録は、運搬日・散布日から自動生成する
- 在庫の
USEは施肥計画の散布確定ではなく、散布実績の保存時に発生させる
つまり、業務フローは以下に変更する。
- 施肥計画を作る
- 運搬計画で肥料を運ぶ
- 運んだ肥料の全部または一部を、日付単位で散布する
- 運搬日・散布日をもとに作業記録を自動作成する
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 施肥計画
- 施肥計画を作成・更新する
- 従来どおり
RESERVEを張る - ここでは散布確定しない
5.2 運搬計画
- 年度の施肥計画から運搬計画を作る
- 各
DeliveryTripに日付を入れる - 日付が入った
DeliveryTripは「運搬実績あり」と見なす DeliveryTripごとに作業記録を自動生成する
5.3 散布実績
- ユーザーは散布日を選ぶ
- その年度の「運搬済みかつ未散布残あり」の明細を候補表示する
- 候補の中から、全部または一部の圃場を選ぶ
- 初期値として未散布残袋数を入れる
- 実際の散布袋数へ修正して保存する
- 保存時に
USE在庫履歴を散布日で作る - 散布日の作業記録を自動生成する
6. 推奨データモデル変更
6.1 FertilizationPlan
現行の is_confirmed / confirmed_at は業務上の意味が薄くなるため、段階的に非推奨化する。
方針
- DB カラムは互換性のため一旦残してよい
- 新UIでは
confirm_spreading/unconfirmを使わない - 一覧表示は以下の計算状態へ置き換える
追加する表示用状態
spread_status:unspread | partial | completed | over_appliedplanned_total_bagsspread_total_bagsremaining_total_bagsspread_started_atspread_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.dateRESERVEは引き続き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_statusplanned_total_bagsspread_total_bagsremaining_total_bagsspread_started_atspread_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} |
散布候補一覧 |
候補一覧レスポンスイメージ
[
{
"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]
}
]
散布作成リクエストイメージ
{
"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 イメージ
[散布日: 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_deliverywork_date = DeliveryTrip.datetitle = 肥料運搬: {delivery_plan.name} {n}回目
更新・削除
- 日付変更時は
WorkRecord.work_dateも更新 - 日付を空に戻したら、自動生成レコードを削除
10.2 散布
作成契機
SpreadingSessionの作成時date更新時
作成内容
work_type = fertilizer_spreadingwork_date = SpreadingSession.datetitle = 肥料散布: {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 実装ステップ
SpreadingSession/SpreadingSessionItem/SpreadingAllocation/WorkRecordを追加DeliveryTripItem.fertilization_entryとStockTransaction.spreading_itemを追加- 散布実績 API を追加
- 運搬日と散布日の作業記録同期を追加
- フロントで「散布確定」ボタンを撤去
- 散布実績画面を追加
- 旧
confirm_spreadingを非表示化 - 十分移行後に旧 API を削除
12. 影響ファイル(想定)
Backend
backend/apps/fertilizer/models.pybackend/apps/fertilizer/serializers.pybackend/apps/fertilizer/views.pybackend/apps/fertilizer/urls.pybackend/apps/fertilizer/admin.pybackend/apps/fertilizer/migrations/backend/apps/materials/models.pybackend/apps/materials/stock_service.pybackend/apps/materials/serializers.pybackend/apps/materials/migrations/backend/apps/workrecords/または同等の新規 app
Frontend
frontend/src/app/fertilizer/page.tsxfrontend/src/app/fertilizer/_components/ConfirmSpreadingModal.tsxfrontend/src/app/distribution/_components/DeliveryEditPage.tsxfrontend/src/types/index.ts- 散布実績の新規ページ群
- 作業記録の新規ページ群または既存画面への表示追加
ドキュメント
document/13_マスタードキュメント_施肥計画編.mddocument/14_マスタードキュメント_分配計画編.mdCLAUDE.md
※ これらは実装着手時に正式仕様へ反映する。
13. 受け入れ条件
- 施肥計画一覧に「散布確定」ボタンが表示されない
- 運搬済み明細を元に、日付単位の散布実績を新規作成できる
- 散布対象は、運搬計画全体から全部または一部の圃場を選べる
- 運搬回の順番に依存せず散布できる
- 散布時に実際袋数を変更できる
- 散布保存時に
USE在庫履歴が散布日で作成される DeliveryTrip.date保存時に運搬作業記録が自動生成されるSpreadingSession.date保存時に散布作業記録が自動生成される- 施肥計画一覧で未散布・一部散布・完了が分かる
14. 前提と留意点
- 本仕様では「残肥の返却」は扱わない。未散布残は運搬済み残として次回散布へ繰り越す
- 同日複数散布の厳密な分離は MVP では不要とみなし、まずは 1日1レコードを採用する
- 既存確定済みデータの完全移行は行わない
- 作業記録 UI は最小構成でよいが、API と自動生成ロジックは今回入れる