改善案/issue_3_計画始動後の作付け変更_調査.md#L442 (line 442)

旧 plan の当該エントリに対応する RESERVE を削除 という書き方は、現行実装とずれています。RESERVE はエントリ単位ではなく fertilization_plan 単位で全置換管理です。backend/apps/materials/stock_service.py (line 10) の通り、実装上は「旧 plan 全体の RESERVE を再生成」「新 plan 全体の RESERVE を再生成」と書かないと誤実装されやすいです。

改善案/issue_3_計画始動後の作付け変更_調査.md#L437 (line 437)
未散布判定を actual_bags is NULL にしているのは危険です。actual_bags は散布実績再集計の結果で、将来のロジック変更や部分散布時に 0 や端数が入る可能性がありますし、「未散布」と「部分散布」を同一扱いできません。backend/apps/fertilizer/models.py (line 72) を踏まえると、少なくとも「actual_bags is null または 0」「一部散布済みは移動不可 or 分割対象」と明文化した方が安全です。

改善案/issue_3_計画始動後の作付け変更_調査.md#L367 (line 367) と 改善案/issue_3_計画始動後の作付け変更_調査.md#L449 (line 449)
田植え計画を「施肥と同様」とまとめていますが、田植え計画には actual_bags に相当する実績概念がまだありません。backend/apps/plans/serializers.py (line 177) 現状では「全 Entry 移動」なのか「将来の実績連携を見越して未実施分のみ移動」なのかを切り分けて書く必要があります。今の書き方だと、施肥と同じ判定軸があるように読めます。

改善案/issue_3_計画始動後の作付け変更_調査.md#L461 (line 461)
actual_bags 集計ロジックは「影響なし」と言い切らない方がいいです。今回の方針なら大きな改修は不要ですが、前提は「同一年・同圃場・同肥料の行が複数計画にまたがって共存しないこと」です。これは仕様上の制約なので、「影響なし」ではなく「現方針では再利用可能。ただし重複行を作らないことが前提」と書くのが正確です。
This commit is contained in:
akira
2026-04-05 14:07:59 +09:00
parent 4299c6eb4b
commit 429a98decb

View File

@@ -228,7 +228,7 @@ issue にある「足川北上が圃場追加候補に出てこない」はこ
## 8. 追加提案に対する評価
ユーザーからの追加提案:
ユーザーからの追加提案(初期):
- 変更履歴は必要
- 散布済み Entry も新品種計画へ移動する案を第一候補
@@ -236,6 +236,9 @@ issue にある「足川北上が圃場追加候補に出てこない」はこ
- 圃場グループは対応不要
- 田植え計画も同様に移動
> **補足(最終確定)**: 散布済み Entry の扱いは後述 8-3 の検討を経て **(A) 旧計画に残す** に確定した。
> その他の方針は初期提案どおり採用。
この提案には良い点が多い一方で、現行実装のまま採ると危険な点もある。
### 8-1. 採用しやすい点
@@ -320,13 +323,11 @@ issue 本文にもあるように、同年度・同品種で複数計画があ
自動選択(最新)は暫定対応としてはあり得るが、本命仕様にはしにくい。
### 8-3. 私の現時点の推奨
### 8-3. 推奨と最終決定
#### 変更履歴
#### 変更履歴 ✅ 採用確定
採用推奨。
ただしモデルは次の方が使いやすい。
モデル設計:
```python
PlanVarietyChange
@@ -337,35 +338,39 @@ PlanVarietyChange
old_variety FK(Variety, SET_NULL, null=True)
new_variety FK(Variety, SET_NULL, null=True)
reason text blank
moved_entry_count int default=0 # 自動移動した未散布エントリ数(監査用)
```
#### 散布済み Entry の扱
- `plan FK` だけでなく `field_id``year` を冗長保持した方が将来参照しやす
- `reason` があると運用上かなり有用
- `moved_entry_count` で自動移動の件数を残すことで監査ログを兼ねる
現時点の推奨は `(A) 旧計画に残す`
#### 散布済み Entry の扱い ✅ **(A) 旧計画に残す** 確定
理由:
- 履歴解釈が明確
- 履歴解釈が明確(「にこまる用に施肥した」という文脈が旧計画に保持される)
- PDF/一覧での意味が崩れにくい
- 将来「既散布分も移したい」へ広げる余地を残せる
- `actual_bags` の二重反映リスクを避けられる(同 year+field+fertilizer に複数エントリを持たない)
- 将来「既散布分も移したい」要求が出た場合に後から対応できる
もし `(B) 新品種計画へ移動` を採るなら、
変更履歴表示と監査ログ表示を先に入れた方が安全。
#### 未散布 Entry の扱い ✅ 新品種計画へ移動RESERVE付け替えあり確定
#### 未散布 Entry の扱い
採用推奨。
「まだ実施していない将来計画」は新品種側へ寄せる。
RESERVE 付け替えもこの方針と整合する。
#### 圃場グループ
#### 圃場グループ ✅ 対応不要 確定
現時点では対応不要でよさそう。
少なくとも今回のコア問題ではない
圃場マスタの `group_name` は現在値として扱う。
帳票側でスナップショットが必要になれば別途検討
#### 田植え計画
#### 田植え計画 ✅ 対応確定(施肥とは判定軸が異なる)
採用推奨
ただし施肥より軽いので、実装順は施肥の後でよい。
施肥だけ直して田植えを放置すると整合しないため同時に対応する
ただし田植え計画には actual_bags 相当の実績概念がないため、
**対象圃場の Entry は全件移動**(未散布判定なし)。
将来、田植え実績との連携が実装された場合は改めて設計する。
実装順は施肥の後でよい。
### 8-4. 移動先計画の選び方への見解
@@ -391,19 +396,96 @@ RESERVE 付け替えもこの方針と整合する。
のような命名。
### 8-5. 実装ステップ案への見解
### 8-5. 実装ステップ(確定版)
提案の段階実装は良い
ただし Step 2 の前に「散布済みを残すか移すか」を固定した方がよい。
散布済みEntryの扱いが確定したため、以下の順で実装する
推奨順は次の通り。
1. `PlanVarietyChange` 追加
1. `PlanVarietyChange` モデル追加(履歴記録のみ・既存データに触らない)
2. 品種変更トリガーのサービス追加
3. まずは `未散布 Entry のみ移動` を施肥計画で実装
3. `未散布 Entry のみ` 新品種計画(常に新規作成)へ移動を施肥計画で実装
4. RESERVE 付け替えと `actual_bags` 再集計を確認
5. 田植え計画へ横展開
6. allocation 画面の履歴インジケータ追加
7. 必要なら散布済み Entry 移動案を再検討
この順だと、もっとも危険な「履歴の意味が壊れる変更」を後ろ倒しにできる。
散布済み Entry の移動は今後の要求が出た時点で別途検討する。
---
## 9. 確定仕様まとめ
> 更新日: 2026-04-05
### 9-1. 決定事項一覧
| 項目 | 決定内容 |
|---|---|
| 散布済み Entry | **旧計画に残すA確定** |
| 未散布 Entry | **新品種計画へ移動 + RESERVE付け替え** |
| 移動先計画の選び方 | **常に新規作成**(既存計画には集約しない) |
| 移動先計画の命名 | `{year}年度 {品種名} 施肥計画(品種変更移動)` |
| 変更履歴 | **PlanVarietyChange モデルを新設** |
| 圃場グループ | **対応不要**(現在値扱いのまま) |
| 田植え計画 | **施肥と同様に対応**(実装は施肥の後) |
### 9-2. 品種変更時の自動処理フロー
`Plan.variety``A → B` に変更されたとき:
```
1. PlanVarietyChange を記録
field, year, plan, changed_at, old_variety=A, new_variety=B, reason
2. 施肥計画エントリの移動
未散布の判定:
- actual_bags IS NULL → 未散布(移動対象)
- actual_bags IS NOT NULL かつ actual_bags < bags → 一部散布済み(移動不可・旧計画に残す)
- actual_bags >= bags → 散布完了(移動不可・旧計画に残す)
※ actual_bags = 0 は現行 services.py では null に丸められるため実運用上は出ないが、
将来のロジック変更に備えて「is NULL」単独ではなく上記3区分で判定する
対象: FertilizationPlan.variety=A かつ year=変更年度 かつ
FertilizationEntry.field=変更圃場 かつ actual_bags IS NULL未散布
処理:
a. variety=B, year=変更年度 の新 FertilizationPlan を作成
名前: "{year}年度 {B品種名} 施肥計画(品種変更移動)"
b. 対象 FertilizationEntry の plan FK を新 plan へ付け替え
c. 旧 plan 全体の RESERVE を再生成stock_service.create_reserves_for_plan(旧plan)
※ RESERVE は plan 単位で全置換管理のため、エントリ単位ではなく plan 単位で呼び出す
d. 新 plan 全体の RESERVE を生成stock_service.create_reserves_for_plan(新plan)
e. PlanVarietyChange.moved_entry_count に移動件数を記録
非対象(旧計画に残す):
actual_bags IS NOT NULL のエントリ(一部散布済み・散布完了)
3. 田植え計画エントリの移動
田植え計画には施肥計画の actual_bags に相当する実績概念がまだない
RiceTransplantEntry は installed_seedling_boxes のみ、散布済み/未散布の区別がない)。
そのため、現時点では 対象圃場の Entry を全件移動 とする。
将来、田植え実績(田植え日・実績箱数等)との連携が実装された場合は、
「実施済み Entry は旧計画に残す」方針に揃えて再設計すること。
対象: RiceTransplantPlan.variety=A かつ year=変更年度 かつ
RiceTransplantEntry.field=変更圃場(全件)
処理:
a. variety=B, year=変更年度 の新 RiceTransplantPlan を作成
名前: "{year}年度 {B品種名} 田植え計画(品種変更移動)"
b. 対象 RiceTransplantEntry の plan FK を新 plan へ付け替え
```
### 9-3. 変更しないもの(影響なし)
- `SpreadingSessionItem` — field+fertilizer リンクのため変更不要
- `actual_bags` 集計ロジック — 現方針では再利用可能。
ただし **同一 year+field+fertilizer の FertilizationEntry が複数計画にまたがって共存しないこと** が前提。
この制約は仕様上の invariant として守る必要がある(移動処理でエントリを複製しないこと)。
- `candidate_fields` API — Plan.variety 変更後は自然に新品種で候補が返る
- `WorkRecord` — 運搬/散布実績への 1:1 参照のため影響なし
### 9-4. 未解決・将来検討
- 散布済み Entry を新品種計画側にも見せたい要求が出た場合 → 別途設計
- allocation 画面の変更履歴インジケータ実装ステップ6
- `actual_bags` 集計を `year+field+fertilizer` から `plan単位` へ変更する大規模リファクタ(中長期)