ローカル更新済み:

13_マスタードキュメント_施肥計画編.md — 散布実績セクション整備、在庫連携・集計ルール・WorkRecord自動生成・前年度コピーのセクション追加、旧「散布確定モーダル」記述削除、型定義・ファイル構成・将来の拡張を更新
14_マスタードキュメント_分配計画編.md — 散布実績との連携・WorkRecord自動生成のセクション追加
CLAUDE.md — データモデル(SpreadingSession/Item, WorkRecord, actual_bags)追加、プロジェクト構造にfertilizer/workrecordsアプリ追加、実装状況に散布実績・作業記録索引を追記、更新履歴に2026-03-17エントリ追加
This commit is contained in:
Akira
2026-03-17 20:31:22 +09:00
parent daae1a42e5
commit 13c21ed7de
5 changed files with 366 additions and 57 deletions

View File

@@ -67,7 +67,10 @@
"Bash(git diff:*)",
"mcp__serena__find_symbol",
"mcp__serena__get_symbols_overview",
"Bash(git status:*)"
"Bash(git status:*)",
"Bash(npx next:*)",
"mcp__butler__butler__list_skills",
"mcp__butler__butler__get_skill_usage"
],
"additionalDirectories": [
"C:\\Users\\akira\\AppData\\Local\\Temp",

View File

@@ -83,9 +83,16 @@ keinasystem_t02/
│ │ ├── views.py # sync(APIキー), records, summary, gdd, similarity
│ │ ├── urls.py
│ │ └── management/commands/fetch_weather.py # 初回一括取得・差分取得
── reports/ # 申請書生成アプリ
├── views.py # PDF生成API
└── templates/ # PDF用HTMLテンプレート
── reports/ # 申請書生成アプリ
├── views.py # PDF生成API
└── templates/ # PDF用HTMLテンプレート
│ ├── fertilizer/ # 施肥計画・散布実績アプリ
│ │ ├── models.py # Fertilizer, FertilizationPlan, FertilizationEntry, SpreadingSession, SpreadingSessionItem
│ │ ├── services.py # actual_bags再集計、在庫USE連携
│ │ └── views.py # 施肥計画/散布実績/候補API
│ └── workrecords/ # 作業記録索引アプリ
│ ├── models.py # WorkRecord
│ └── services.py # sync_delivery_work_record, sync_spreading_work_record
└── frontend/
└── src/app/
├── allocation/ # 作付け計画編集画面(メイン)
@@ -96,6 +103,9 @@ keinasystem_t02/
│ ├── feedback/[token]/ # フィードバックページ(認証不要)
│ ├── history/ # メール処理履歴
│ └── rules/ # 送信者ルール管理
├── fertilizer/ # 施肥計画(一覧・編集・肥料マスタ)
│ └── spreading/ # 散布実績画面
├── distribution/ # 運搬計画(一覧・編集)
├── weather/ # 気象データ画面(年別集計・期間指定・グラフ)
└── settings/
└── password/ # パスワード変更
@@ -188,9 +198,34 @@ FertilizationEntry (施肥エントリ・中間テーブル)
├── plan (FK to FertilizationPlan, CASCADE)
├── field (FK to fields.Field, CASCADE)
├── fertilizer (FK to Fertilizer, PROTECT) ← 使用中の肥料は削除不可
├── bags袋数、Decimal
├── bags袋数、Decimal・計画値
├── actual_bags散布実績集計値、Decimal(10,4)、nullable
└── unique_together = ['plan', 'field', 'fertilizer']
SpreadingSession (散布実績セッション) ← 2026-03-17 追加
├── year年度
├── date散布日
├── nameセッション名、必須
├── notes備考
└── year+date の一意制約なし(同日複数記録可能)
SpreadingSessionItem (散布実績明細)
├── session (FK to SpreadingSession, CASCADE)
├── field (FK to fields.Field, PROTECT)
├── fertilizer (FK to Fertilizer, PROTECT)
├── actual_bags実散布袋数、Decimal(10,4)
├── planned_bags_snapshot / delivered_bags_snapshot表示時点のスナップショット
└── unique_together = ['session', 'field', 'fertilizer']
WorkRecord (作業記録索引) ← apps/workrecords/ 別アプリ、2026-03-17 追加
├── work_date作業日
├── work_typefertilizer_delivery / fertilizer_spreading
├── title一覧表示名
├── year年度
├── delivery_trip (OneToOne FK to DeliveryTrip, nullable)
├── spreading_session (OneToOne FK to SpreadingSession, nullable)
└── 詳細は各業務テーブル側に持つ(索引として機能)
DeliveryPlan (運搬計画) ← 旧 DistributionPlan を置き換え2026-03-16 再設計)
├── year年度← 施肥計画へのFK廃止、年度ベースで全施肥計画を横断
├── name計画名
@@ -343,21 +378,29 @@ DeliveryTripItem (運搬明細)
- **将来計画**: 開花・収穫予測品種ごとの目標GDD設定 → 到達日予測)
- マスタードキュメント: `document/12_マスタードキュメント_気象データ編.md`
10. **施肥計画機能**(本番稼働中):
- Django `apps/fertilizer` アプリFertilizer, FertilizationPlan, FertilizationEntry
- Django `apps/fertilizer` アプリFertilizer, FertilizationPlan, FertilizationEntry, SpreadingSession, SpreadingSessionItem
- API: `/api/fertilizer/fertilizers/`, `/api/fertilizer/plans/`, `/api/fertilizer/calculate/`, `/api/fertilizer/candidate_fields/`
- PDF出力: `/api/fertilizer/plans/{id}/pdf/`WeasyPrint、A4横向き
- FertilizationEntry.fertilizer は PROTECT使用中の肥料は削除不可・migration 0002
- FertilizationEntry.fertilizer は PROTECT使用中の肥料は削除不可
- 自動計算3方式: per_tan反当袋数/ even均等配分/ nitrogen反当チッソ
- 四捨五入トグル: `≈`(丸め)/ `↩`(元の計算値に戻す)
- フロントエンド: `/fertilizer`(一覧)、`/fertilizer/masters`(肥料マスタ)、`/fertilizer/new``/fertilizer/[id]/edit`(編集)
- 施肥機能全体で alert/confirm を廃止し、React インラインバナーでエラー表示
- マスタードキュメント: `document/13_マスタードキュメント_施肥計画編.md`
10. **施肥計画機能**:
- Django `apps/fertilizer` アプリFertilizer, FertilizationPlan, FertilizationEntry
- APIJWT認証: `GET/POST /api/fertilizer/fertilizers/`, `GET/POST /api/fertilizer/plans/?year=`, `GET /api/fertilizer/plans/{id}/pdf/`, `GET /api/fertilizer/candidate_fields/?year=&variety_id=`, `POST /api/fertilizer/calculate/`
- 自動計算3方式: 反当袋数(per_tan)、均等配分(even)、反当チッソ(nitrogen)
- フロントエンド: `/fertilizer/`(一覧), `/fertilizer/new``/fertilizer/[id]/edit`(編集・マトリクス表), `/fertilizer/masters/`(肥料マスタ)
- スコープ外(将来): 購入管理
- **散布実績機能**2026-03-17 追加):
- 散布実績API: `/api/fertilizer/spreading/`CRUD + candidates
- 散布日単位の記録(同日複数セッション可能)
- 運搬済み肥料ベースで候補を表示、圃場×肥料単位で実績袋数を記録
- 保存時に在庫USE連携・FertilizationEntry.actual_bags再集計・WorkRecord自動生成
- 施肥計画一覧に進捗表示(未散布/一部散布/完了/計画超過)
- 旧「散布確定」ボタン廃止is_confirmed/confirmed_at はDB残留、UI未使用
- フロントエンド: `/fertilizer/spreading`
12. **作業記録索引**2026-03-17 追加):
- Django `apps/workrecords` アプリWorkRecord モデル)
- 運搬・散布の作業を日付順に一覧する索引テーブル
- 明細は各業務テーブル側DeliveryTrip / SpreadingSessionに持つ
- API: `/api/workrecords/?year=`
- WorkRecord自動生成: 運搬回の日付保存時・散布実績保存時にupsert
11. **運搬計画機能**(旧・分配計画、本番稼働中):
- 旧 DistributionPlan/Group/GroupField → 新 DeliveryPlan/Group/GroupField/Trip/TripItem に移行
- 施肥計画への直接FK廃止 → 年度ベースで全施肥計画を横断
@@ -522,6 +565,8 @@ docker-compose exec backend python manage.py migrate
## 📝 更新履歴
- 2026-03-17: 施肥散布実績連携を実装・本番稼働。旧「散布確定」ボタン廃止→日付単位の散布実績記録SpreadingSession/Itemへ移行。FertilizationEntry.actual_bags追加、散布保存時に在庫USE連携・WorkRecord自動生成。apps/workrecords新設。StockTransaction.spreading_itemをCASCADE→SET_NULLに変更。マスタードキュメント13/14を更新
- 2026-03-16: 分配計画を「運搬計画」に再設計・本番稼働。実運用のワークフロー軽トラ複数回・複数施肥計画混在・肥料指定に合わせ、DeliveryPlan/Trip/TripItem モデルへ移行。施肥計画へのFK廃止→年度ベース。グループ一括割り当て・グループ単位の回間移動機能を追加。マスタードキュメント14を全面改訂
- 2026-03-05: メール通知機能を更新。MailEmail.account を xserver1〜xserver6 で識別可能に変更。Windmill mail_filter に To ヘッダー宛先補正を追加し、Gmail先行取り込みでも Xserver 宛先ラベルが崩れないよう修正。マスタードキュメント/仕様書を同期。

View File

@@ -1,16 +1,16 @@
# マスタードキュメント:施肥計画機能
> **作成**: 2026-03-01
> **最終更新**: 2026-03-15
> **対象機能**: 施肥計画(年度×品種単位のマトリクス管理・在庫引当・散布確定
> **実装状況**: 実装完了・本番稼働中
> **最終更新**: 2026-03-17
> **対象機能**: 施肥計画(年度×品種単位のマトリクス管理・在庫引当・散布実績記録
> **実装状況**: 実装完了・本番稼働中(散布実績連携追加)
---
## 概要
農業生産者が「年度 × 品種」単位で施肥計画を立てる機能。
複数圃場 × 複数肥料 × 袋数をマトリクス形式で管理し、PDF出力に加えて在庫引当散布確定まで一連で扱う。
複数圃場 × 複数肥料 × 袋数をマトリクス形式で管理し、PDF出力在庫引当散布実績記録・作業記録索引生成まで一連で扱う。
### 機能スコープIN / OUT
@@ -18,11 +18,14 @@
|---|---|
| 肥料マスタ管理 | 肥料購入管理 |
| 施肥計画の作成・編集・削除 | 運搬計画(→ `14_マスタードキュメント_分配計画編.md` 参照) |
| 3方式の自動計算 | 個別作業日報の詳細管理 |
| 作付け計画からの圃場自動取得 | |
| PDF出力圃場×肥料マトリクス表 | |
| 3方式の自動計算 | 運搬便ごとの散布充当追跡 |
| 作付け計画からの圃場自動取得 | 相手先ごとのPDF様式実装 |
| PDF出力圃場×肥料マトリクス表 | 残肥返却・再入庫管理 |
| 在庫引当・引当解除 | |
| 散布確定(計画値確認 + 実績入力 | |
| 散布実績記録(日付単位・運搬済み肥料ベース | |
| 作業記録索引WorkRecord自動生成 | |
| 在庫USE連携散布実績保存時 | |
| 施肥計画進捗表示(未散布/一部散布/完了/計画超過) | |
---
@@ -49,11 +52,20 @@
| name | varchar(200) | required | 計画名(ユーザーが自由入力) |
| year | int | required | 年度 |
| variety | FK(plans.Variety) | PROTECT | 品種≠NULL |
| is_confirmed | bool | default=False | 散布確定済みフラグ |
| confirmed_at | datetime | nullable | 散布確定日時 |
| is_confirmed | bool | default=False | ~~散布確定済みフラグ~~deprecated: 新UIでは使用しない |
| confirmed_at | datetime | nullable | ~~散布確定日時~~deprecated: 新UIでは使用しない |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
#### 表示用計算項目APIレスポンスに含まれる
| 項目 | 型 | 説明 |
|---|---|---|
| spread_status | string | `unspread` / `partial` / `completed` / `over_applied` |
| planned_total_bags | decimal | 計画袋数合計全entries.bagsの合計 |
| spread_total_bags | decimal | 散布済み袋数合計全entries.actual_bagsの合計 |
| remaining_total_bags | decimal | 残袋数planned_total_bags - spread_total_bags |
### FertilizationEntry施肥エントリ圃場×肥料×袋数
| フィールド | 型 | 制約 | 説明 |
@@ -62,11 +74,60 @@
| plan | FK(FertilizationPlan) | CASCADE | |
| field | FK(fields.Field) | CASCADE | |
| fertilizer | FK(Fertilizer) | **PROTECT** | 施肥計画で使用中の肥料は削除不可 |
| bags | decimal(8,2) | required | 袋数 |
| bags | decimal(8,2) | required | 袋数(計画値) |
| actual_bags | decimal(10,4) | nullable | 散布実績集計値SpreadingSessionItemから自動集計 |
- `unique_together = ['plan', 'field', 'fertilizer']`
- 順序: `field__display_order, field__id, fertilizer__name`
### SpreadingSession散布実績セッション
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| year | int | required | 年度フィルタ用 |
| date | DateField | required | 散布日 |
| name | varchar(100) | required | セッション名(必須) |
| notes | text | blank | 備考 |
| created_at | datetime | auto | |
| updated_at | datetime | auto | |
- `year + date` の一意制約は付けない(同日に午前・午後やエリア別で複数記録可能)
### 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 | datetime | auto | |
| updated_at | datetime | auto | |
- `unique_together = ['session', 'field', 'fertilizer']`
### WorkRecord作業記録索引
別アプリ `apps/workrecords/` で管理。施肥・運搬の作業を日付順に一覧するための索引テーブル。
詳細の本体は各業務テーブル側DeliveryTrip / SpreadingSessionに持つ。
| フィールド | 型 | 制約 | 説明 |
|---|---|---|---|
| id | int | PK | |
| work_date | DateField | required | 作業日 |
| work_type | varchar | 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 | datetime | auto | |
| updated_at | datetime | auto | |
---
## API エンドポイント
@@ -106,8 +167,8 @@
| GET | `/api/fertilizer/plans/{id}/` | 詳細取得entries 含む) |
| PUT | `/api/fertilizer/plans/{id}/` | 更新entries 全置換) |
| DELETE | `/api/fertilizer/plans/{id}/` | 削除 |
| POST | `/api/fertilizer/plans/{id}/confirm_spreading/` | 散布確定(引当 → 使用へ変換 |
| POST | `/api/fertilizer/plans/{id}/unconfirm/` | 散布確定取消(使用 → 引当に戻す |
| POST | `/api/fertilizer/plans/{id}/confirm_spreading/` | ~~散布確定~~deprecated: UI上で廃止、バックエンドは互換維持 |
| POST | `/api/fertilizer/plans/{id}/unconfirm/` | ~~散布確定取消~~deprecated: UI上で廃止、バックエンドは互換維持 |
| GET | `/api/fertilizer/plans/{id}/pdf/` | PDF出力application/pdf |
一覧レスポンス例FertilizationPlan:
@@ -154,18 +215,60 @@ POST/PUT リクエスト例:
PUT 時は entries が全置換削除→再作成。entries を省略した場合は既存を維持。
散布確定 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}` | 散布候補一覧 |
散布候補一覧レスポンス例:
```json
[
{
"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"
}
]
```
散布実績 POST リクエスト例:
```json
{
"entries": [
{"field_id": 5, "fertilizer_id": 1, "actual_bags": 2.4},
{"field_id": 6, "fertilizer_id": 1, "actual_bags": 0}
"year": 2026,
"date": "2026-04-15",
"name": "午前・田中エリア",
"notes": "",
"items": [
{
"field": 5,
"fertilizer": 1,
"actual_bags": "2.5000",
"planned_bags_snapshot": "4.0000",
"delivered_bags_snapshot": "4.0000"
}
]
}
```
- `actual_bags > 0`: 対応する引当を使用実績へ変換
- `actual_bags = 0`: 未散布として引当解除
### 作業記録(新規・別アプリ)
| メソッド | URL | 説明 |
|---|---|---|
| GET | `/api/workrecords/?year={year}` | 一覧 |
| GET | `/api/workrecords/{id}/` | 詳細(元レコードへのリンク情報を返す) |
### 圃場候補取得
@@ -290,9 +393,11 @@ GET /api/plans/crops/
### 施肥計画一覧(`/fertilizer`
- 年度セレクタlocalStorage `fertilizerYear` で保持)
- 計画カード一覧: 計画名・作物/品種・圃場数・肥料数・散布確定状態
- 操作ボタン: PDF出力・編集・削除・散布確定
- 計画カード一覧: 計画名・作物/品種・圃場数・肥料数・散布進捗
- 操作ボタン: PDF出力・編集・削除
- ヘッダー: 「肥料マスタ」「新規作成」ボタン
- 進捗表示: `未散布` / `一部散布 3.5 / 8.0袋` / `散布完了` / `計画超過`
- 計画値と実績値を並べて表示
### 肥料マスタ(`/fertilizer/masters`
@@ -316,11 +421,11 @@ GET /api/plans/crops/
6. **手動調整**: マトリクス表のセルを直接編集
7. **保存**: 「保存」ボタンで entries を一括送信
#### 在庫連携・確定状態
#### 在庫連携・実績表示
- 肥料列ヘッダーに在庫 / 利用可能在庫 / 計画計 / 不足数を表示
- 散布確定済みの計画は情報バナーを表示し、編集操作をロック
- 「確定取消」で使用実績を引当に戻し、再編集できる
- マトリクス表で `bags`(計画値)を編集可能、`actual_bags`(実績値)は読み取り専用で参照表示
- 散布実績画面(`/fertilizer/spreading`)へのリンクを表示
#### マトリクスの表示仕様
@@ -329,16 +434,24 @@ GET /api/plans/crops/
- `↩` ボタン押下: 整数値を破棄し、元の計算値に戻る(参照グレー表示も消える)
- 編集中に計算を再実行すると、その肥料列の `adjusted``roundedColumns` がリセットされる
### 散布確定モーダル`/fertilizer` 一覧から起動
### 散布実績画面`/fertilizer/spreading`
- 全画面遷移ではなくモーダル表示
- 施肥計画編集と同じ視線移動になるよう、`圃場 = 行``肥料 = 列` のマトリクス表を採用
- 画面上部に計画名・年度・作物/品種・対象圃場数・肥料数を表示
- 各セルは「薄いグレーの計画値」+「実績入力欄」の2段表示
- 行末に圃場ごとの実績合計、表フッターに肥料別合計と総合計を表示
- `0` を入力したセルは未散布として扱い、対応する引当を解除する
- 年度セレクタlocalStorage `fertilizerYear` と連動)
- 散布日入力DateField
- セッション名入力(必須)
- 運搬済み・未散布候補一覧を表示(`candidates` APIから取得
- 圃場単位で選択可能(全部または一部)
- 実績袋数の編集
- 差異がある場合はインライン警告表示
- 保存時に在庫USE連携・WorkRecord自動生成・FertilizationEntry.actual_bags再集計を実行
#### State 構成
### 作業記録画面(`/workrecords`
- 年度セレクタ
- 日付・作業種別・タイトルの一覧表示
- 元データ(運搬回 / 散布セッション)への遷移リンク
#### State 構成(施肥計画編集画面)
```typescript
// 基本情報
@@ -374,13 +487,15 @@ backend/apps/fertilizer/
├── __init__.py
├── admin.py # Django admin 登録
├── apps.py # FertilizerConfig
├── models.py # Fertilizer, FertilizationPlan, FertilizationEntry
├── serializers.py # FertilizerSerializer, FertilizationPlanSerializer/WriteSerializer
├── views.py # FertilizerViewSet, FertilizationPlanViewSet, CandidateFieldsView, CalculateView
├── urls.py # DefaultRouter + candidate_fields/ + calculate/
├── models.py # Fertilizer, FertilizationPlan, FertilizationEntry, SpreadingSession, SpreadingSessionItem
├── serializers.py # FertilizerSerializer, FertilizationPlanSerializer/WriteSerializer, SpreadingSessionSerializer
├── services.py # actual_bags再集計、WorkRecord自動生成、在庫USE連携
├── views.py # FertilizerViewSet, FertilizationPlanViewSet, SpreadingSessionViewSet, CandidateFieldsView, CalculateView
├── urls.py # DefaultRouter + candidate_fields/ + calculate/ + spreading/
├── migrations/
│ ├── 0001_initial.py
── 0002_alter_fertilizationentry_fertilizer.py # CASCADE → PROTECT
── 0002_alter_fertilizationentry_fertilizer.py # CASCADE → PROTECT
│ └── ... # SpreadingSession, SpreadingSessionItem, actual_bags 追加
└── templates/
└── fertilizer/
└── pdf.html # WeasyPrint テンプレートA4横向き
@@ -398,25 +513,131 @@ frontend/src/app/fertilizer/
│ └── page.tsx # 編集FertilizerEditPage をラップ)
├── masters/
│ └── page.tsx # 肥料マスタ管理
├── spreading/
│ └── ... # 散布実績画面(一覧・作成・編集)
└── _components/
└── FertilizerEditPage.tsx # 新規/編集共通コンポーネント(複雑)
frontend/src/app/workrecords/
└── ... # 作業記録画面(一覧・詳細)
```
### 変更されたファイル
| ファイル | 変更内容 |
|---|---|
| `backend/keinasystem/settings.py` | `INSTALLED_APPS``'apps.fertilizer'` を追加 |
| `backend/keinasystem/urls.py` | `path('api/fertilizer/', include('apps.fertilizer.urls'))` を追加 |
| `frontend/src/types/index.ts` | `Fertilizer`, `FertilizationEntry`, `FertilizationPlan` 型を追加 |
| `backend/keinasystem/settings.py` | `INSTALLED_APPS``'apps.fertilizer'`, `'apps.workrecords'` を追加 |
| `backend/keinasystem/urls.py` | `api/fertilizer/`, `api/workrecords/` を追加 |
| `backend/apps/materials/models.py` | `StockTransaction.spreading_item` FK 追加(`on_delete=SET_NULL` |
| `backend/apps/workrecords/` | 作業記録索引アプリWorkRecord モデル・API・services |
| `frontend/src/types/index.ts` | 施肥・散布・作業記録の型を追加 |
| `frontend/src/components/Navbar.tsx` | Sprout アイコン + 施肥計画メニューを追加 |
---
## 在庫連携
### RESERVE施肥計画保存時
- 従来どおり計画値 `bags` ベースで維持
- 施肥計画の entries 保存時に RESERVE トランザクションを作成
### USE散布実績保存時
- `SpreadingSessionItem` ごとに USE を1件作成
- `material`: `item.fertilizer.material`
- `quantity`: `actual_bags`
- `occurred_on`: `session.date`
- `note`: `散布実績「{session.name or session.date}」`
### StockTransaction 追加フィールド
- `spreading_item = FK(SpreadingSessionItem, null=True, blank=True, on_delete=SET_NULL)`
### 更新・削除
- 散布実績更新時: その session に紐づく USE を全置換で作り直す
- 散布実績削除時: 対応 USE を削除する
### RESERVE と USE の整合
- RESERVE は計画値 `bags` ベース
- USE は散布実績 `actual_bags` ベース
- 計画値と実績値は併存する
---
## 集計ルール
### planned_total圃場×肥料×年度
`FertilizationEntry.bags` の合計
### delivered_total圃場×肥料×年度
`DeliveryTrip.date != null``DeliveryTripItem.bags` 合計
### spread_total圃場×肥料×年度
`SpreadingSessionItem.actual_bags` の合計
### actual_bags 再集計ルール
- `SUM(SpreadingSessionItem.actual_bags)` を同一 year, field, fertilizer で集計
- 散布実績の保存・更新・削除時に該当する `FertilizationEntry.actual_bags` を即時再計算
- `SUM(...) = 0` の場合は `actual_bags = null`
### remaining_bags表示用の残量
`delivered_total - spread_total`
### remaining_plan_bags計画進捗用の残量
`planned_total - spread_total`
### 差異の扱い
- `remaining_bags < 0`: 運搬実績不足
- `remaining_plan_bags < 0`: 計画超過
- 圃場+肥料単位で差異が分かることを優先する
---
## WorkRecord 自動生成ルール
### 運搬fertilizer_delivery
- `DeliveryTrip.date` 保存時に upsert
- `title = 肥料運搬: {delivery_plan.name} {n}回目`
- 日付削除時は対応 WorkRecord を削除
### 散布fertilizer_spreading
- `SpreadingSession` 保存時に upsert
- `title = 肥料散布: {session.name or session.date}`
- 削除時は対応 WorkRecord を削除
### 実装方針
自動生成は view に直書きせず、サービス層(`services.py`)で idempotent に実装する。
---
## 前年度コピー
`copy_from_previous_year` で前年度の `FertilizationEntry` をコピーする際のルール:
- `actual_bags` がある場合: `actual_bags` を新年度の `bags` 初期値として使用
- `actual_bags``null` の場合: 従来どおり `bags` をコピー
前年度に実際に散布した量を次年度計画の初期値として再利用できる。
---
## 型定義TypeScript
```typescript
// frontend/src/types/index.ts
// frontend/src/types/index.ts(主要な型のみ抜粋)
export interface Fertilizer {
id: number;
@@ -437,6 +658,7 @@ export interface FertilizationEntry {
fertilizer: number;
fertilizer_name: string;
bags: string;
actual_bags: string | null; // 散布実績集計値
}
export interface FertilizationPlan {
@@ -449,6 +671,10 @@ export interface FertilizationPlan {
field_count: number;
fertilizer_count: number;
entries: FertilizationEntry[];
spread_status: 'unspread' | 'partial' | 'completed' | 'over_applied';
planned_total_bags: string;
spread_total_bags: string;
remaining_total_bags: string;
}
```
@@ -487,6 +713,25 @@ plans アプリの `DefaultRouter(r'', PlanViewSet)` が `plans/get-crops-with-v
PUT 時は entries を全削除→再作成する「全置換」方式。
部分更新は非対応PATCH でも entries がある場合は全置換)。
### 散布実績の在庫連携
- 施肥計画保存時: `RESERVE`(計画値 `bags` ベース)
- 散布実績保存時: `USE`(実績値 `actual_bags` ベース)
- `RESERVE``USE` は併存する(計画値と実績値は別管理)
- 散布実績更新時は `session` に紐づく `USE` を全置換で作り直す
- 散布実績削除時は対応 `USE` を削除する(`StockTransaction.spreading_item``SET_NULL`
- `perform_destroy` で明示的に `StockTransaction` を削除してから `session.delete()` を呼ぶ
### 散布セッション名は必須
`SpreadingSession.name` は必須フィールド。WorkRecord のタイトル生成や一覧表示に使用するため、
空文字での保存は許可しない。
### useSearchParams と SuspenseNext.js 14
散布実績画面(`/fertilizer/spreading`)では `useSearchParams()` を使用するため、
`Suspense` boundary でラップする必要がある(本番ビルドで必須)。
### Next.js ホットリロードが効かない問題Windows + Docker
Windows 環境では Docker ボリュームマウント経由のファイル変更が inotify で検知されず、
@@ -499,6 +744,8 @@ Windows 環境では Docker ボリュームマウント経由のファイル変
## 将来の拡張(スコープ外)
- **配置計画**: 複数圃場分を一か所にまとめる時の置き場所割り当て(別機能として検討
- **相手先別PDF様式**: 客先ごとの提出資料フォーマット(元データは散布実績から取得可能
- **残肥返却・再入庫管理**: 散布後の残りを在庫に戻す処理
- **SpreadingAllocation**: 運搬便単位の散布充当追跡(現状は集計ベースで十分)
- **購入管理**: 肥料の購入・在庫管理(施肥計画の集計から購入数量を自動算出)
- **作業記録との連携**: 施肥計画の実施記録(実施日・実際の袋数
- **配置計画**: 複数圃場分を一か所にまとめる時の置き場所割り当て(別機能として検討

View File

@@ -396,3 +396,17 @@ PDF生成時のみサーバーサイドで同じ計算を実施。
### エラー表示方針
施肥計画機能と同じく alert/confirm 廃止・インラインバナーに統一。
### 散布実績との連携
- 運搬計画の `DeliveryTripItem` が散布実績画面(`/fertilizer/spreading`)の候補データソースとなる
- `DeliveryTrip.date != null` の明細のみを「運搬済み」とみなし、散布候補に含める
- 散布実績画面から運搬計画を指定して遷移する場合(`?delivery_plan_id=N`)、日付フィルタは適用されない(その計画の全明細が候補になる)
- 散布実績の保存時に在庫 `USE` が作成される(運搬時点では在庫変動なし)
### WorkRecord 自動生成
- `DeliveryTrip` に日付が保存されると、`WorkRecord``work_type=fertilizer_delivery`)が自動生成される
- 実装: `apps/workrecords/services.py``sync_delivery_work_record()`
- `DeliveryTrip` の日付が削除されると、対応する `WorkRecord` も削除される
- `WorkRecord` は索引として機能し、明細データは `DeliveryTrip` / `DeliveryTripItem` 側が保持する

File diff suppressed because one or more lines are too long