今回追加した内容は主にこの5点です。 FertilizationEntry.actual_bags の追加 SpreadingSessionItem 保存・更新・削除時の actual_bags 再集計ルール copy_from_previous_year で actual_bags があれば次年度 bags 初期値に使う方針 施肥計画一覧・編集画面での 計画値 / 実績値 併記 RESERVE = bags、USE = actual_bags の併存整理 受け入れ条件にも、 actual_bags が再集計されること 計画値と実績値の両方が見えること 前年度コピーで actual_bags を使えること を追加しています。 今回は仕様書更新のみで、コード変更やテストはしていません。必要なら次に、この内容をマスタードキュメント側へ反映します
18 KiB
施肥散布実績連携変更実装仕様
作成日: 2026-03-17 改訂日: 2026-03-17 対象プロジェクト:
keinasystem_t02位置づけ: 実運用反映版・MVP仕様
1. 結論
今回の変更では、施肥データの流れを以下の一気通貫に揃える。
- 施肥計画を作る
- 施肥計画を元に運搬計画を作る
- 運搬済み肥料を元に散布実績を記録する
- 散布実績を元に作業記録として参照できる
- 将来、必要な相手先提出資料へ変換できる
MVPの方針は以下とする。
- 施肥計画の
散布確定ボタンは廃止する - 散布実績は
SpreadingSessionとSpreadingSessionItemで管理する SpreadingAllocationは作らない- 在庫の
USEは散布実績保存時に発生させる WorkRecordは作るが、明細の二重管理はしないWorkRecordはDeliveryTripとSpreadingSessionへの索引として使うFertilizationEntry.bagsは計画値として維持し、actual_bagsを実績集計値として別保持する- 客先提出PDFは今回実装しない
- ただし、将来どの様式にも変換できるよう、元データを引き出せる構造にする
2. 背景
現状実装では、FertilizationPlan に「散布確定」ボタンがあり、施肥計画単位で一括確定する流れになっている。
しかし実運用では、実際に必要なのは以下である。
- どの圃場に
- どの肥料を
- どれだけ散布したか
- それがいつ行われたか
また、データは以下のように流用できる必要がある。
- 施肥計画作成
- 運搬計画作成
- 散布実績記録
- 作業記録参照
- 相手先提出資料への転用
この流れが切れると、従来どおり別DBや別表へ転記が必要になり、システム化の価値が大きく下がる。
3. 今回の判断
3.1 採用するもの
SpreadingSessionとSpreadingSessionItemWorkRecordの新設- 散布保存時の在庫
USE登録 FertilizationEntry.actual_bagsの集計反映- 施肥計画一覧からの
散布確定UI 廃止 - 施肥計画進捗の自動集計表示
3.2 今回は見送るもの
SpreadingAllocationDeliveryTripItem.fertilization_entryFK- 客先提出PDFの固定様式実装
3.3 理由
SpreadingAllocationが必要になる運用は今後も発生しない前提であるWorkRecordは必要だが、詳細を二重保持するのではなく索引で十分である- 在庫は実際に減るため、散布実績と同時に管理すべきである
- 提出資料は相手先ごとに違うため、今固定様式を作るより、元データ抽出可能性を優先すべきである
- 運搬計画は年度ベースで複数施肥計画を横断するため、
DeliveryTripItemに単一のfertilization_entryFK を持たせるのは不安がある
4. 機能スコープ
4.1 IN
- 施肥計画の
散布確定ボタン廃止 - 散布実績モデル追加
- 作業記録索引モデル追加
- 在庫
USE連携 FertilizationEntry.actual_bags集計- 散布実績一覧・作成・更新・削除 API
- 散布候補集計 API
- 施肥計画進捗表示
- 前年度コピー時の
actual_bags → bags初期化反映 - 作業記録一覧から運搬・散布の実績を参照可能にすること
4.2 OUT
- 運搬便ごとの散布充当追跡
- 相手先ごとのPDF様式実装
- 残肥返却・再入庫管理
- カレンダーUIの完成版
- 栽培管理全体を包含した汎用作業日誌の完成版
5. 新しい業務フロー
5.1 施肥計画
- 施肥計画を作成する
- 計画値として保持する
- この段階では散布確定しない
5.2 運搬計画
- 施肥計画を元に運搬計画を作成する
DeliveryTrip.dateに運搬日を入れる- 日付が入った運搬回を
運搬済みとみなす - 運搬日が保存されたら
WorkRecordを自動生成または更新する
5.3 散布実績
- ユーザーは散布日を選ぶ
- システムはその年度の
運搬済みデータを集計する 未散布残がある圃場×肥料を候補として表示する- ユーザーは全部または一部の圃場を選ぶ
- 実際に散布した袋数を入力して保存する
- 保存時に在庫
USEを作成する - 保存時に
WorkRecordを自動生成または更新する
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_bags
FertilizationEntry.actual_bags の追加
FertilizationEntry に以下のフィールドを追加する。
actual_bags = DecimalField(max_digits=10, decimal_places=4, null=True, blank=True)
役割
bags: 計画値actual_bags: 散布実績の集計値
bags はユーザーが立てた計画として保持し、散布後も自動で上書きしない。
実績は actual_bags に集約する。
actual_bags の更新契機
SpreadingSessionItem作成時SpreadingSessionItem更新時SpreadingSessionItem削除時
上記のたびに、同一年度・圃場・肥料の散布実績合計を再集計して反映する。
6.2 DeliveryTrip / DeliveryTripItem
MVPではモデル追加は行わない。
前提
DeliveryTrip.date != nullの明細のみを運搬済みとみなすDeliveryTripItemは今までどおりfield + fertilizer + bagsを保持するfertilization_entryFK は追加しない
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 |
制約
year + dateの一意制約は付けない
同日に午前・午後やエリア別で複数散布記録を分けられるようにする。
6.4 SpreadingSessionItem(新規)
散布日の圃場×肥料ごとの実績。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| session | FK(SpreadingSession) | CASCADE | |
| 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']]
補足
fertilization_entryFK は持たない- 圃場+肥料単位の事実記録を優先する
6.5 WorkRecord(新規)
作業記録参照用の索引テーブル。
目的
- 日付順に作業を一覧できるようにする
- 将来、播種・農薬散布・収穫なども同じ入口で見られるようにする
- ただし、詳細の本体は各業務テーブル側に持つ
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| work_date | DateField | required | 作業日 |
| work_type | CharField | required | fertilizer_delivery / fertilizer_spreading |
| title | varchar(200) | required | 一覧表示名 |
| year | int | required | 年度フィルタ補助 |
| auto_created | bool | default=True | 自動生成フラグ |
| delivery_trip | OneToOne FK(DeliveryTrip) | nullable | 運搬由来 |
| spreading_session | OneToOne FK(SpreadingSession) | nullable | 散布由来 |
| created_at / updated_at | datetime | auto |
方針
WorkRecord自体には圃場別明細を持たない- 明細参照時は
DeliveryTrip/SpreadingSession側を開く - 実体ではなく索引として使う
7. 在庫連携
7.1 変更前
- 施肥計画保存時に
RESERVE - 施肥計画の
散布確定でUSE
7.2 変更後
- 施肥計画保存時に
RESERVEを継続 - 散布実績保存時に
USE USE.occurred_onはSpreadingSession.date
7.3 StockTransaction 追加推奨フィールド
spreading_item = FK(SpreadingSessionItem, null=True, blank=True, on_delete=SET_NULL)
7.4 USE 作成ルール
SpreadingSessionItemごとにUSEを1件作るmaterialはitem.fertilizer.materialquantityはactual_bagsoccurred_onはsession.datenoteは散布実績「{session.name or session.date}」
7.5 更新・削除
- 散布実績更新時は、その
sessionに紐づくUSEを全置換で作り直す - 散布実績削除時は対応
USEを削除する
7.6 RESERVE との整合
RESERVEは従来どおり計画値bagsベースで維持するUSEは散布実績actual_bagsベースで発生する- 計画値と実績値は併存する
つまり、
- 計画:
bags - 実績集計:
actual_bags - 引当:
RESERVE - 使用:
USE
を分けて持つ。
8. 集計ルール
SpreadingAllocation を持たないため、残量は集計で求める。
8.1 計画値
planned_total(field, fertilizer, year)
FertilizationEntryの合計
8.2 運搬済み量
delivered_total(field, fertilizer, year)
DeliveryTrip.date != nullのDeliveryTripItem.bags合計
8.3 散布済み量
spread_total(field, fertilizer, year)
SpreadingSessionItem.actual_bagsの合計
8.4 FertilizationEntry.actual_bags 集計ルール
actual_bags(field, fertilizer, year)
SUM(SpreadingSessionItem.actual_bags)- 対象条件は
同一 year, field, fertilizer
実装上は、散布実績保存・更新・削除時に、該当する FertilizationEntry を再計算して更新する。
8.5 表示用の残量
remaining_bags = delivered_total - spread_total
8.6 計画進捗用の残量
remaining_plan_bags = planned_total - spread_total
8.7 差異の扱い
運搬数量と散布数量がずれることは許容する。
remaining_bags < 0の場合:運搬実績不足remaining_plan_bags < 0の場合:計画超過
まずは 圃場+肥料単位で差異が分かること を優先する。
9. API方針
9.1 施肥計画 API
UI上で廃止するもの
POST /api/fertilizer/plans/{id}/confirm_spreading/POST /api/fertilizer/plans/{id}/unconfirm/
追加する読み取り項目
spread_statusplanned_total_bagsspread_total_bagsremaining_total_bags- 各
FertilizationEntryのactual_bags
9.2 散布実績 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} |
散布候補一覧 |
9.3 作業記録 API(新規)
| メソッド | URL | 説明 |
|---|---|---|
| GET | /api/workrecords/?year={year} |
一覧 |
| GET | /api/workrecords/{id}/ |
詳細 |
詳細では元レコードへのリンク情報を返すだけでよい。
9.4 候補一覧レスポンス例
[
{
"field": 5,
"field_name": "田中上",
"fertilizer": 1,
"fertilizer_name": "電気炉さい",
"planned_bags": "4.0000",
"delivered_bags": "4.0000",
"spread_bags": "1.5000",
"remaining_bags": "2.5000",
"remaining_plan_bags": "2.5000",
"delivery_gap": "0.0000"
}
]
9.5 保存時の基本バリデーション
actual_bags > 0を必須- 同一
session内でfield + fertilizerの重複禁止 remaining_plan_bagsを大きく超える入力はエラーremaining_bagsを超える入力は警告を出した上で保存は許容してよい
9.6 actual_bags 同期APIルール
- 散布実績保存後、対象となる
FertilizationEntry.actual_bagsを即時再集計する SUM(...) = 0の場合はactual_bags = nullとしてよい- 一覧・編集画面では、再計算後の値を返す
10. フロントエンド変更
10.1 施肥計画一覧 /fertilizer
散布確定ボタンを削除ConfirmSpreadingModal.tsxは削除対象- カードに進捗状態を表示
- 計画値と実績値を並べて表示できるようにする
表示例:
未散布一部散布 3.5 / 8.0袋散布完了計画超過計画 4.0 → 実績 3.5
10.2 散布実績画面(新規)
/fertilizer/spreading
画面要件
- 散布日入力
- 年度選択
- 運搬済み・未散布候補一覧
- 圃場単位選択
- 実績袋数編集
- 差異のインライン警告
10.4 施肥計画編集画面
bagsを計画値として表示actual_bagsを実績値として参照表示- 編集時にも
計画 / 実績の差が分かるようにする
actual_bags は散布実績からの集計値なので、編集画面で直接入力はしない。
10.3 作業記録画面(新規または将来画面の土台)
最低限、以下を一覧できること。
- 日付
- 作業種別
- タイトル
- 元データへの遷移
11. 将来の資料生成に向けた要件
今回、固定形式のPDFは実装しない。
ただし、将来どの相手先様式にも対応できるよう、散布実績から少なくとも以下を引き出せる必要がある。
- 散布日
- 年度
- 圃場
- 肥料
- 散布袋数
- 備考
また、資料生成時に計画値と実績値を比較できるよう、少なくとも以下の対比を維持する。
bags(計画)actual_bags(実績)
つまり今回の要件は、PDFを作ること ではなく、後で資料化できる元データを崩さず持つこと である。
12. 前年度コピーへの影響
将来 copy_from_previous_year で前年度の FertilizationEntry をコピーする際は、以下のルールを適用する。
actual_bagsがある場合:actual_bagsを新年度のbags初期値として使うactual_bagsがnullの場合: 従来どおりbagsをコピーする
この方針により、前年度に実際に散布した量を次年度計画の初期値として再利用できる。
13. WorkRecord 自動生成ルール
12.1 運搬
DeliveryTrip.date保存時に upserttitle = 肥料運搬: {delivery_plan.name} {n}回目- 日付削除時は対応
WorkRecordを削除
12.2 散布
SpreadingSession保存時に upserttitle = 肥料散布: {session.name or session.date}- 削除時は対応
WorkRecordを削除
12.3 実装方針
自動生成は view に直書きせず、サービス層で idempotent に実装する。
14. 移行方針
13.1 既存の is_confirmed
- 既存カラムは残す
- 新UIでは参照しない
- 旧方式の確定データは
旧散布確定データとして扱う
13.2 既存 confirm_spreading API
- まずフロントから呼ばない状態にする
- しばらく互換維持
- 新散布実績画面へ完全移行後に削除を検討する
15. 実装ステップ
SpreadingSession/SpreadingSessionItemを追加apps/workrecords/を追加FertilizationEntry.actual_bagsを追加StockTransaction.spreading_itemを追加- 散布実績 API を追加
- 作業記録 API を追加
- 散布候補集計 API を追加
- 散布保存時の
actual_bags再集計を追加 - 施肥計画一覧に進捗表示を追加
- 施肥計画一覧から
散布確定ボタンを外す ConfirmSpreadingModal.tsxを撤去- 散布実績画面を追加
- WorkRecord 自動生成を追加
- 散布保存時の在庫
USE連携を追加 copy_from_previous_yearのactual_bags → bags反映を追加
16. 影響ファイル(想定)
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/fertilizer/services.pyまたは同等の集計ロジック配置先backend/apps/materials/models.pybackend/apps/materials/stock_service.pybackend/apps/materials/migrations/backend/apps/workrecords/
Frontend
frontend/src/app/fertilizer/page.tsxfrontend/src/app/fertilizer/_components/FertilizerEditPage.tsxfrontend/src/app/fertilizer/_components/ConfirmSpreadingModal.tsxfrontend/src/app/fertilizer/spreading/frontend/src/app/workrecords/frontend/src/types/index.ts
ドキュメント
document/13_マスタードキュメント_施肥計画編.mddocument/14_マスタードキュメント_分配計画編.mdCLAUDE.md
17. 受け入れ条件
- 施肥計画一覧に
散布確定ボタンが表示されない - 日付単位の散布実績を登録できる
- 散布対象は全部または一部の圃場を選べる
- 運搬回の順番に依存せず、運搬済みデータを元に散布できる
- 実際の散布袋数を入力できる
- 散布保存時に在庫
USEが作成される - 散布保存・更新・削除時に
FertilizationEntry.actual_bagsが再集計される - 作業記録一覧から運搬と散布を参照できる
- 施肥計画一覧で
未散布 / 一部散布 / 完了 / 計画超過が分かる - 施肥計画一覧・編集画面で
計画値 / 実績値の両方が分かる - 前年度コピー時に
actual_bagsがあればそれを次年度bags初期値として使える - 将来、散布実績データから資料化に必要な情報を取り出せる
18. 将来拡張
今回見送るが、将来必要になれば以下を追加できる。
SpreadingAllocationによる便単位追跡- 相手先別PDFテンプレート群
- 残肥返却や再入庫
- 栽培管理全体を包含した作業記録詳細