From 8ac3a00737e86aaeead7d263af6843f7aa37d0ec Mon Sep 17 00:00:00 2001 From: Akira Date: Sun, 1 Mar 2026 12:16:42 +0900 Subject: [PATCH] =?UTF-8?q?=E6=96=BD=E8=82=A5=E8=A8=88=E7=94=BB=E3=81=AE?= =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=E3=83=BC=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit document/13_マスタードキュメント_施肥計画編.md を新規作成 データモデル・全API仕様・自動計算ロジック・フロントエンド画面・ファイル構成・注意点を網羅 Co-Authored-By: Claude Sonnet 4.6 --- .../13_マスタードキュメント_施肥計画編.md | 434 ++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 document/13_マスタードキュメント_施肥計画編.md diff --git a/document/13_マスタードキュメント_施肥計画編.md b/document/13_マスタードキュメント_施肥計画編.md new file mode 100644 index 0000000..e8252ae --- /dev/null +++ b/document/13_マスタードキュメント_施肥計画編.md @@ -0,0 +1,434 @@ +# マスタードキュメント:施肥計画機能 + +> **作成**: 2026-03-01 +> **対象機能**: 施肥計画(年度×品種単位のマトリクス管理) +> **実装状況**: 実装完了(commit f207f5d) + +--- + +## 概要 + +農業生産者が「年度 × 品種」単位で施肥計画を立てる機能。 +複数圃場 × 複数肥料 × 袋数をマトリクス形式で管理し、PDF出力する。 + +### 機能スコープ(IN / OUT) + +| IN(実装済み) | OUT(対象外) | +|---|---| +| 肥料マスタ管理 | 肥料購入管理 | +| 施肥計画の作成・編集・削除 | 圃場への配置計画(置き場所割り当て) | +| 3方式の自動計算 | 施肥作業の実績記録 | +| 作付け計画からの圃場自動取得 | | +| PDF出力(圃場×肥料マトリクス表) | | + +--- + +## データモデル + +### Fertilizer(肥料マスタ) + +| フィールド | 型 | 制約 | 説明 | +|---|---|---|---| +| id | int | PK | | +| name | varchar(100) | unique, required | 肥料名 | +| maker | varchar(100) | nullable | メーカー | +| capacity_kg | decimal(8,3) | nullable | 1袋重量(kg) ← nitrogen計算に必須 | +| nitrogen_pct | decimal(5,2) | nullable | 窒素含有率(%) ← nitrogen計算に必須 | +| phosphorus_pct | decimal(5,2) | nullable | リン酸含有率(%) | +| potassium_pct | decimal(5,2) | nullable | カリ含有率(%) | +| notes | text | nullable | 備考 | + +### FertilizationPlan(施肥計画) + +| フィールド | 型 | 制約 | 説明 | +|---|---|---|---| +| id | int | PK | | +| name | varchar(200) | required | 計画名(ユーザーが自由入力) | +| year | int | required | 年度 | +| variety | FK(plans.Variety) | PROTECT | 品種(≠NULL) | +| created_at | datetime | auto | | +| updated_at | datetime | auto | | + +### FertilizationEntry(施肥エントリ:圃場×肥料×袋数) + +| フィールド | 型 | 制約 | 説明 | +|---|---|---|---| +| id | int | PK | | +| plan | FK(FertilizationPlan) | CASCADE | | +| field | FK(fields.Field) | CASCADE | | +| fertilizer | FK(Fertilizer) | CASCADE | | +| bags | decimal(8,2) | required | 袋数 | + +- `unique_together = ['plan', 'field', 'fertilizer']` +- 順序: `field__display_order, field__id, fertilizer__name` + +--- + +## API エンドポイント + +すべて JWT 認証(`Authorization: Bearer `)が必要。 + +### 肥料マスタ + +| メソッド | URL | 説明 | +|---|---|---| +| GET | `/api/fertilizer/fertilizers/` | 一覧取得 | +| POST | `/api/fertilizer/fertilizers/` | 新規作成 | +| GET | `/api/fertilizer/fertilizers/{id}/` | 詳細取得 | +| PUT/PATCH | `/api/fertilizer/fertilizers/{id}/` | 更新 | +| DELETE | `/api/fertilizer/fertilizers/{id}/` | 削除 | + +レスポンス例(Fertilizer): +```json +{ + "id": 1, + "name": "コシヒカリ専用一発肥料", + "maker": "JA", + "capacity_kg": "20.000", + "nitrogen_pct": "14.00", + "phosphorus_pct": "12.00", + "potassium_pct": "12.00", + "notes": null +} +``` + +### 施肥計画 + +| メソッド | URL | 説明 | +|---|---|---| +| GET | `/api/fertilizer/plans/?year={year}` | 年度別一覧 | +| POST | `/api/fertilizer/plans/` | 新規作成(entries 含む) | +| GET | `/api/fertilizer/plans/{id}/` | 詳細取得(entries 含む) | +| PUT | `/api/fertilizer/plans/{id}/` | 更新(entries 全置換) | +| DELETE | `/api/fertilizer/plans/{id}/` | 削除 | +| GET | `/api/fertilizer/plans/{id}/pdf/` | PDF出力(application/pdf) | + +一覧レスポンス例(FertilizationPlan): +```json +{ + "id": 1, + "name": "2025年コシヒカリ施肥計画", + "year": 2025, + "variety": 3, + "variety_name": "コシヒカリ", + "crop_name": "米", + "field_count": 12, + "fertilizer_count": 2, + "entries": [ + { + "id": 1, + "field": 5, + "field_name": "田中上", + "field_area_tan": "1.2000", + "fertilizer": 1, + "fertilizer_name": "コシヒカリ専用一発肥料", + "bags": "2.40" + } + ], + "created_at": "2025-03-01T10:00:00Z", + "updated_at": "2025-03-01T10:00:00Z" +} +``` + +POST/PUT リクエスト例: +```json +{ + "name": "2025年コシヒカリ施肥計画", + "year": 2025, + "variety": 3, + "entries": [ + {"field_id": 5, "fertilizer_id": 1, "bags": 2.4}, + {"field_id": 6, "fertilizer_id": 1, "bags": 1.6} + ] +} +``` + +PUT 時は entries が全置換(削除→再作成)。entries を省略した場合は既存を維持。 + +### 圃場候補取得 + +``` +GET /api/fertilizer/candidate_fields/?year={year}&variety_id={variety_id} +``` + +作付け計画(Planモデル)から year + variety で圃場を検索して返す。 + +レスポンス例: +```json +[ + {"id": 5, "name": "田中上", "area_tan": "1.2000", "area_m2": 1200, "group_name": "田中"}, + {"id": 6, "name": "田中下", "area_tan": "0.8000", "area_m2": 800, "group_name": "田中"} +] +``` + +### 自動計算 + +``` +POST /api/fertilizer/calculate/ +``` + +計算結果を返すのみ(DB保存なし)。 + +#### 方式 1: per_tan(反当袋数) + +```json +{ + "method": "per_tan", + "param": 2.0, + "field_ids": [5, 6] +} +``` + +計算式: `bags = Sa × A`(Sa: 反当袋数, A: 圃場面積[反]) + +#### 方式 2: even(均等配分) + +```json +{ + "method": "even", + "param": 50, + "field_ids": [5, 6] +} +``` + +計算式: `bags = (SS / ΣA) × A`(SS: 総袋数, A: 圃場面積[反]) + +#### 方式 3: nitrogen(反当チッソ成分量) + +```json +{ + "method": "nitrogen", + "param": 3.0, + "fertilizer_id": 1, + "field_ids": [5, 6] +} +``` + +計算式: `bags = (Nr / (C × Nd/100)) × A` +- Nr: 反当チッソ成分量(kg/反) +- C: 1袋重量(kg) ← Fertilizer.capacity_kg 必須 +- Nd: 窒素含有率(%) ← Fertilizer.nitrogen_pct 必須 +- A: 圃場面積[反] + +nitrogen 方式は capacity_kg・nitrogen_pct が未設定の肥料に対してはエラー(400)。 + +レスポンス(共通): +```json +[ + {"field_id": 5, "bags": 2.40}, + {"field_id": 6, "bags": 1.60} +] +``` + +--- + +## 品種・作物の取得 + +品種一覧は既存の plans アプリの CropViewSet を使用: + +``` +GET /api/plans/crops/ +``` + +レスポンス例: +```json +[ + { + "id": 1, + "name": "米", + "base_temp": "0.0", + "varieties": [ + {"id": 1, "name": "コシヒカリ", "crop": 1}, + {"id": 2, "name": "ヒノヒカリ", "crop": 1} + ] + } +] +``` + +**注意**: plans アプリの DefaultRouter が `r''` に登録されているため、 +`/api/plans/get-crops-with-varieties/` のようなカスタムパスは 404 になる(URLルーティング競合)。 +`/api/plans/crops/` を使うこと。 + +--- + +## PDF 出力 + +`GET /api/fertilizer/plans/{id}/pdf/` + +- WeasyPrint を使用(reports アプリと同パターン) +- テンプレート: `backend/apps/fertilizer/templates/fertilizer/pdf.html` +- フォーマット: A4横向き +- 内容: 圃場(行)× 肥料(列)のマトリクス表、行合計・列合計・総合計 +- ファイル名: `fertilization_{year}_{plan_id}.pdf` + +--- + +## フロントエンド画面 + +### 施肥計画一覧(`/fertilizer`) + +- 年度セレクタ(localStorage `fertilizerYear` で保持) +- 計画カード一覧: 計画名・作物/品種・圃場数・肥料数 +- 操作ボタン: PDF出力・編集・削除 +- ヘッダー: 「肥料マスタ」「新規作成」ボタン + +### 肥料マスタ(`/fertilizer/masters`) + +- 肥料一覧テーブル(名前・メーカー・容量・窒素・リン酸・カリ・備考) +- インライン行編集(EditRow コンポーネント) +- 新規追加フォーム +- 削除確認ダイアログ + +### 施肥計画編集(`/fertilizer/new` / `/fertilizer/[id]/edit`) + +`FertilizerEditPage.tsx`(`fertilizer/_components/`)を共有コンポーネントとして使用。 + +#### 操作フロー + +1. **計画基本情報入力**: 計画名・年度・品種(ドロップダウン) +2. **圃場選択**: 品種選択後に候補圃場が自動取得(`candidate_fields` API)。チップ形式で追加/解除。候補外の圃場は「全圃場から追加」で手動選択 +3. **肥料追加**: 「+肥料を追加」で肥料マスタからドロップダウン選択 +4. **自動計算**: 各肥料に方式(per_tan/even/nitrogen)とパラメータを設定し「計算」ボタンでマトリクスに反映(上書き確認あり) +5. **手動調整**: マトリクス表のセルを直接編集 +6. **保存**: 「保存」ボタンで entries を一括送信 + +#### State 構成 + +```typescript +// 基本情報 +const [planName, setPlanName] = useState('') +const [planYear, setPlanYear] = useState(currentYear) +const [varietyId, setVarietyId] = useState('') + +// 圃場・肥料 +const [selectedFields, setSelectedFields] = useState([]) +const [planFertilizers, setPlanFertilizers] = useState([]) + +// 自動計算設定(肥料ごと) +const [calcSettings, setCalcSettings] = useState([]) +// CalcSetting: { fertilizer_id, method: 'per_tan'|'even'|'nitrogen', param: string } + +// マトリクス(fieldId → fertilizerId → 袋数文字列) +const [matrix, setMatrix] = useState>>({}) +``` + +--- + +## ファイル構成 + +### Backend + +``` +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/ +├── migrations/ +│ └── 0001_initial.py +└── templates/ + └── fertilizer/ + └── pdf.html # WeasyPrint テンプレート(A4横向き) +``` + +### Frontend + +``` +frontend/src/app/fertilizer/ +├── page.tsx # 施肥計画一覧 +├── new/ +│ └── page.tsx # 新規作成(FertilizerEditPage をラップ) +├── [id]/ +│ └── edit/ +│ └── page.tsx # 編集(FertilizerEditPage をラップ) +├── masters/ +│ └── page.tsx # 肥料マスタ管理 +└── _components/ + └── FertilizerEditPage.tsx # 新規/編集共通コンポーネント(複雑) +``` + +### 変更されたファイル + +| ファイル | 変更内容 | +|---|---| +| `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` 型を追加 | +| `frontend/src/components/Navbar.tsx` | Sprout アイコン + 施肥計画メニューを追加 | + +--- + +## 型定義(TypeScript) + +```typescript +// frontend/src/types/index.ts + +export interface Fertilizer { + id: number; + name: string; + maker: string | null; + capacity_kg: string | null; + nitrogen_pct: string | null; + phosphorus_pct: string | null; + potassium_pct: string | null; + notes: string | null; +} + +export interface FertilizationEntry { + id: number; + field: number; + field_name: string; + field_area_tan: string; + fertilizer: number; + fertilizer_name: string; + bags: string; +} + +export interface FertilizationPlan { + id: number; + name: string; + year: number; + variety: number; + variety_name: string; + crop_name: string; + field_count: number; + fertilizer_count: number; + entries: FertilizationEntry[]; +} +``` + +--- + +## 注意点・既知の問題 + +### URL ルーティング競合(解決済み) + +plans アプリの `DefaultRouter(r'', PlanViewSet)` が `plans/get-crops-with-varieties/` を +`{pk}/` パターンとして解釈して 404 になる問題があった。 +`/api/plans/crops/`(CropViewSet)を使うことで回避。 + +### nitrogen 計算の前提条件 + +反当チッソ成分量方式(nitrogen)は、指定した肥料に `capacity_kg` と `nitrogen_pct` が +両方登録されていないと 400 エラーになる。肥料マスタ登録時にユーザーへ案内が必要。 + +### 袋数の精度 + +袋数は `decimal(8,2)`(小数点以下2桁)。0.01 刻みで四捨五入。 +自動計算も `Decimal.quantize(Decimal('0.01'))` で丸める。 + +### entries の更新方式 + +PUT 時は entries を全削除→再作成する「全置換」方式。 +部分更新は非対応(PATCH でも entries がある場合は全置換)。 + +--- + +## 将来の拡張(スコープ外) + +- **配置計画**: 複数圃場分を一か所にまとめる時の置き場所割り当て(別機能として検討) +- **購入管理**: 肥料の購入・在庫管理(施肥計画の集計から購入数量を自動算出) +- **作業記録との連携**: 施肥計画の実施記録(実施日・実際の袋数)