diff --git a/document/10_マスタードキュメント_圃場管理編.md b/document/10_マスタードキュメント_圃場管理編.md new file mode 100644 index 0000000..12617a7 --- /dev/null +++ b/document/10_マスタードキュメント_圃場管理編.md @@ -0,0 +1,754 @@ +# マスタードキュメント - 圃場管理編 + +> **最終更新**: 2026-02-20 +> **対象バージョン**: Phase 1 (MVP) 完了時点 +> **目的**: このドキュメントだけで圃場管理機能の全容を把握できること + +--- + +## 目次 + +1. [機能概要](#1-機能概要) +2. [データモデル](#2-データモデル) +3. [API仕様](#3-api仕様) +4. [画面仕様](#4-画面仕様) +5. [インポート仕様](#5-インポート仕様) +6. [エクスポート仕様](#6-エクスポート仕様) +7. [対応表(E-2)機能](#7-対応表e-2機能) +8. [PDF帳票との関係](#8-pdf帳票との関係) +9. [作付け計画との関係](#9-作付け計画との関係) +10. [設計判断と制約](#10-設計判断と制約) +11. [ソースファイル索引](#11-ソースファイル索引) + +--- + +## 1. 機能概要 + +### 圃場管理機能とは + +農業生産者が所有・管理する農地(圃場)の情報を管理する機能。本システムでは以下の3種類のデータを扱う。 + +| データ種別 | 説明 | レコード数 | ソース | +|---|---|---|---| +| **実圃場(Field)** | 農家が実際に管理している農地 | 39筆 | 吉田農地台帳.ods | +| **共済マスタ(OfficialKyosaiField)** | 水稲共済細目書の区画 | 31区画 | 水稲共済細目用.ods | +| **中山間マスタ(OfficialChusankanField)** | 中山間交付金の区画 | 71区画 | 中山間.ods | + +### 3つのデータの関係 + +``` +実圃場 (39筆) + ├── M:N ─── 共済マスタ (31区画) ※1つの実圃場が複数の共済区画に対応可 + └── M:N ─── 中山間マスタ (71区画) ※1つの実圃場が複数の中山間区画に対応可 +``` + +**M:N (多対多) 関係にした理由**: 当初は1:1を想定したが、実運用データで「1つの実圃場が複数の申請区画にまたがる」ケースが判明したため。 + +### ユーザーフロー + +1. **初回セットアップ**: データ取込画面から3つのODSファイルをインポート +2. **日常管理**: 圃場一覧で圃場情報の確認、圃場詳細で共済/中山間との紐付け確認・編集 +3. **年次作業**: 作付け計画画面で年度ごとの作付けを設定 +4. **申請書作成**: 帳票出力画面でPDFを生成・ダウンロード + +--- + +## 2. データモデル + +### 2.1 Field(実圃場) + +**テーブル名**: `fields_field` +**ソース**: `backend/apps/fields/models.py:48-78` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| name | CharField(100) | NOT NULL | 圃場名(例: 「田んぼA」)| +| address | CharField(255) | NOT NULL | 住所 | +| area_tan | DecimalField(6,4) | NOT NULL | 面積(反)※表示用 | +| area_m2 | IntegerField | NOT NULL | 面積(m2)※計算用 | +| owner_name | CharField(100) | NOT NULL | 地主名 | +| group_name | CharField(50) | NULL可 | グループ名(エリア・用途分け用) | +| display_order | IntegerField | default=0 | 表示順(カスタム並び替え用) | +| raw_kyosai_k_num | CharField(20) | NULL可 | インポート時の共済耕地番号(raw値) | +| raw_kyosai_s_num | CharField(20) | NULL可 | インポート時の共済分筆番号(raw値) | +| raw_chusankan_id | CharField(20) | NULL可 | インポート時の中山間ID(raw値、カンマ区切り複数可) | +| location | PointField | NULL可 | 位置情報(Phase 1未使用) | + +**M:N関係(中間テーブル)**: +- `fields_field_kyosai_fields` → OfficialKyosaiField との紐付け +- `fields_field_chusankan_fields` → OfficialChusankanField との紐付け + +**面積の変換ルール**: `area_m2 = area_tan × 1000`(1反 = 1000m2 で統一) + +### 2.2 OfficialKyosaiField(共済マスタ) + +**テーブル名**: `fields_officialkyosaifield` +**ソース**: `backend/apps/fields/models.py:5-18` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| k_num | CharField(20) | UNIQUE(k_num, s_num) | 耕地番号 | +| s_num | CharField(20) | UNIQUE(k_num, s_num) | 分筆番号(枝番) | +| address | CharField(255) | NOT NULL | 住所(地名地番) | +| kanji_name | CharField(100) | NOT NULL | 漢字地名 | +| area | IntegerField | default=0 | 本地面積(m2)| + +**ユニーク制約**: `(k_num, s_num)` の組み合わせで一意 + +### 2.3 OfficialChusankanField(中山間マスタ) + +**テーブル名**: `fields_officialchusankanfield` +**ソース**: `backend/apps/fields/models.py:21-45` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| c_id | CharField(20) | UNIQUE | 中山間ID | +| chusankan_flag | CharField(10) | NULL可 | 中山間フラグ | +| oaza | CharField(100) | NOT NULL | 大字 | +| aza | CharField(100) | NOT NULL | 字 | +| chiban | CharField(50) | NOT NULL | 地番 | +| branch_num | CharField(20) | NULL可 | 枝番 | +| land_type | CharField(20) | NULL可 | 地目 | +| area | IntegerField | default=0 | 農地面積(m2) | +| planting_area | IntegerField | NULL可 | 植栽面積(m2) | +| original_crop | CharField(100) | NULL可 | 作付け品目 | +| manager | CharField(100) | NULL可 | 協定管理者 | +| owner | CharField(100) | NULL可 | 所有者 | +| slope | CharField(20) | NULL可 | 傾斜度 | +| base_amount | IntegerField | NULL可 | 基本金額 | +| steep_slope_addition | IntegerField | NULL可 | 超急傾斜加算額 | +| smart_agri_addition | IntegerField | NULL可 | スマート農業加算額 | +| payment_amount | IntegerField | NULL可 | 交付金額 | + +**ユニーク制約**: `c_id` で一意 + +### 2.4 関連モデル(作付け計画系) + +**Crop(作物マスタ)** - `backend/apps/plans/models.py:5-13` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| name | CharField(100) | UNIQUE | 作物名(米、トウモロコシ等) | + +**Variety(品種マスタ)** - `backend/apps/plans/models.py:16-26` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| crop | FK(Crop) | CASCADE | 親の作物 | +| name | CharField(100) | UNIQUE(crop, name) | 品種名 | + +**Plan(作付け計画)** - `backend/apps/plans/models.py:29-43` + +| フィールド名 | 型 | 制約 | 説明 | +|---|---|---|---| +| id | BigAutoField | PK | 自動採番 | +| field | FK(Field) | CASCADE | 対象圃場 | +| year | IntegerField | UNIQUE(field, year) | 作付年度 | +| crop | FK(Crop) | CASCADE | 作物 | +| variety | FK(Variety) | SET_NULL, NULL可 | 品種 | +| notes | TextField | NULL可 | 備考 | + +### 2.5 ER図(テキスト形式) + +``` +┌──────────────────┐ M:N ┌──────────────────────┐ +│ Field │──────────────│ OfficialKyosaiField │ +│ (39筆) │ │ (31区画) │ +│ │ M:N ├──────────────────────┤ +│ id │──────────────│ OfficialChusankanField│ +│ name │ │ (71区画) │ +│ address │ └──────────────────────┘ +│ area_tan │ +│ area_m2 │ 1:N +│ owner_name │──────────────┐ +│ group_name │ │ +│ display_order │ ┌─────────┴─────┐ +└──────────────────┘ │ Plan │ + │ (年度×圃場) │ + │ │ + │ field (FK) │ + │ year │ N:1 ┌────────┐ + │ crop (FK) │─────────────│ Crop │ + │ variety (FK) │ N:1 ├────────┤ + └───────────────┘─────────────│Variety │ + └────────┘ +``` + +--- + +## 3. API仕様 + +### 3.1 圃場 CRUD + +**ベースURL**: `/api/fields/` +**ViewSet**: `FieldViewSet` (`backend/apps/fields/views.py:18-27`) +**認証**: JWT Bearer トークン必須 + +| メソッド | エンドポイント | 説明 | リクエスト/レスポンス | +|---|---|---|---| +| GET | `/api/fields/` | 圃場一覧取得 | → `Field[]`(group_name, display_order, id順) | +| GET | `/api/fields/{id}/` | 圃場詳細取得 | → `Field`(kyosai_fields, chusankan_fieldsをネスト含む) | +| POST | `/api/fields/` | 圃場新規作成 | ← `{name, address, area_tan, area_m2, owner_name}` | +| PUT | `/api/fields/{id}/` | 圃場全体更新 | ← 全フィールド | +| PATCH | `/api/fields/{id}/` | 圃場部分更新 | ← 変更フィールドのみ | +| DELETE | `/api/fields/{id}/` | 圃場削除 | → 204 No Content | + +**レスポンス例(GET /api/fields/{id}/)**: +```json +{ + "id": 1, + "name": "田んぼA", + "address": "○○市△△町123", + "area_tan": "1.2000", + "area_m2": 1200, + "owner_name": "吉田太郎", + "group_name": "上エリア", + "display_order": 1, + "kyosai_fields": [ + { + "id": 5, + "k_num": "12", + "s_num": "1", + "address": "○○ 123", + "kanji_name": "上田", + "area": 1200, + "linked_field_names": ["田んぼA"] + } + ], + "chusankan_fields": [ + { + "id": 10, + "c_id": "42", + "chusankan_flag": "○", + "oaza": "△△", + "aza": "□□", + "chiban": "123", + "branch_num": null, + "land_type": "田", + "area": 1200, + "planting_area": 1100, + "original_crop": "米", + "manager": "吉田太郎", + "owner": "吉田太郎", + "slope": "1/20", + "base_amount": 21000, + "steep_slope_addition": null, + "smart_agri_addition": null, + "payment_amount": 21000, + "linked_field_names": ["田んぼA"] + } + ] +} +``` + +**ソート**: デフォルトは `group_name ASC NULLS FIRST, display_order ASC, id ASC` +クエリパラメータ `ordering=area_tan` 等で変更可能。 + +### 3.2 共済/中山間マスタ(読み取り専用) + +| メソッド | エンドポイント | 説明 | +|---|---|---| +| GET | `/api/kyosai-fields/` | 共済マスタ一覧(k_num, s_num順) | +| GET | `/api/kyosai-fields/{id}/` | 共済マスタ詳細 | +| GET | `/api/chusankan-fields/` | 中山間マスタ一覧(c_id順) | +| GET | `/api/chusankan-fields/{id}/` | 中山間マスタ詳細 | + +**ViewSet**: `OfficialKyosaiFieldViewSet`, `OfficialChusankanFieldViewSet` (`backend/apps/fields/views.py:30-39`) +**ルーティング**: `backend/keinasystem/urls.py:23-25` の `master_router` で登録 + +各レスポンスに `linked_field_names: string[]` が含まれ、紐付いている実圃場の名前を返す。 + +### 3.3 M:N紐付け操作 + +**ソース**: `backend/apps/fields/views.py:42-77` + +| メソッド | エンドポイント | 説明 | +|---|---|---| +| POST | `/api/fields/{field_id}/kyosai-links/` | 共済リンク追加 | +| DELETE | `/api/fields/{field_id}/kyosai-links/{kyosai_id}/` | 共済リンク削除 | +| POST | `/api/fields/{field_id}/chusankan-links/` | 中山間リンク追加 | +| DELETE | `/api/fields/{field_id}/chusankan-links/{chusankan_id}/` | 中山間リンク削除 | + +**リンク追加リクエスト例**: +```json +// POST /api/fields/1/kyosai-links/ +{ "kyosai_field_ids": [5, 8] } + +// POST /api/fields/1/chusankan-links/ +{ "chusankan_field_ids": [10, 15, 20] } +``` + +### 3.4 作付け計画関連API + +**ベースURL**: `/api/plans/` +**ViewSet**: `PlanViewSet` (`backend/apps/plans/views.py:20-132`) + +| メソッド | エンドポイント | 説明 | +|---|---|---| +| GET | `/api/plans/?year=2026` | 年度指定で計画一覧取得 | +| POST | `/api/plans/` | 計画新規作成 | +| PATCH | `/api/plans/{id}/` | 計画更新 | +| DELETE | `/api/plans/{id}/` | 計画削除 | +| GET | `/api/plans/summary/?year=2026` | 年度別集計 | +| POST | `/api/plans/copy_from_previous_year/` | 前年度コピー | +| POST | `/api/plans/bulk_update/` | 一括更新 | +| GET | `/api/plans/get_crops_with_varieties/` | 作物+品種一覧 | + +**集計レスポンス例 (GET /api/plans/summary/?year=2026)**: +```json +{ + "year": 2026, + "total_fields": 39, + "assigned_fields": 35, + "unassigned_fields": 4, + "total_plans": 35, + "total_area": 25.5, + "by_crop": [ + { "crop": "米", "count": 20, "area": 15.3 }, + { "crop": "トウモロコシ", "count": 8, "area": 5.2 } + ] +} +``` + +**前年度コピー リクエスト**: +```json +// POST /api/plans/copy_from_previous_year/ +{ "from_year": 2025, "to_year": 2026 } +``` + +**一括更新 リクエスト**: +```json +// POST /api/plans/bulk_update/ +{ "field_ids": [1, 3, 5], "year": 2026, "crop": 2, "variety": 4 } +``` + +### 3.5 作物・品種マスタAPI + +| メソッド | エンドポイント | 説明 | +|---|---|---| +| GET | `/api/plans/crops/` | 作物一覧 | +| POST | `/api/plans/crops/` | 作物追加 | +| GET | `/api/plans/varieties/` | 品種一覧 | +| POST | `/api/plans/varieties/` | 品種追加 | +| DELETE | `/api/plans/varieties/{id}/` | 品種削除 | + +--- + +## 4. 画面仕様 + +### 4.1 圃場一覧画面 + +**URL**: `/fields` +**ソース**: `frontend/src/app/fields/page.tsx` + +**機能**: +- 全圃場をカード形式で一覧表示 +- グループ名でフィルタ(ドロップダウン) +- 各カードに: 圃場名、住所、面積(反)、地主名、共済紐付け数、中山間紐付け数 +- 「新規作成」ボタン → `/fields/new` に遷移 +- カードクリック → `/fields/{id}` に遷移 + +**データ取得**: `GET /api/fields/` → 全圃場をフロントで表示 + +**グループフィルタのロジック**: 全圃場の `group_name` からユニーク値を抽出し、ドロップダウンで選択。「すべて」選択時は全件表示。 + +### 4.2 圃場詳細画面 + +**URL**: `/fields/{id}` +**ソース**: `frontend/src/app/fields/[id]/page.tsx` + +**機能**: +- 圃場の全情報を表示(名前、住所、面積、地主、グループ) +- インライン編集(各フィールドの編集ボタンでトグル) +- **共済マスタ紐付けセクション**: 紐付いている共済区画の一覧表示 + 紐付け追加/削除 +- **中山間マスタ紐付けセクション**: 紐付いている中山間区画の一覧表示 + 紐付け追加/削除 +- 紐付け追加はモーダル(`LinkModal`コンポーネント)で実施 +- 圃場削除ボタン(確認ダイアログ付き) + +**紐付け追加モーダル(LinkModal)**: +- ソース: `frontend/src/components/LinkModal.tsx` +- 未紐付けのマスタ区画を一覧表示 +- チェックボックスで複数選択 → 一括追加 +- 既に他の圃場に紐付いている区画も表示(紐付け先圃場名を表示) + +**データフロー**: +``` +圃場詳細表示: GET /api/fields/{id}/ +紐付け候補取得: GET /api/kyosai-fields/ または /api/chusankan-fields/ +紐付け追加: POST /api/fields/{id}/kyosai-links/ or /chusankan-links/ +紐付け削除: DELETE /api/fields/{id}/kyosai-links/{kyosai_id}/ +圃場更新: PATCH /api/fields/{id}/ +圃場削除: DELETE /api/fields/{id}/ +``` + +### 4.3 圃場新規作成画面 + +**URL**: `/fields/new` +**ソース**: `frontend/src/app/fields/new/page.tsx` + +**入力フィールド**: +- 圃場名(必須) +- 住所(必須) +- 面積・反(必須)→ 入力時に自動でm2計算 +- 地主名(必須) +- グループ名(任意) + +**バリデーション**: フロント側で必須チェック。面積は数値チェック。 + +### 4.4 ダッシュボード画面 + +**URL**: `/dashboard` +**ソース**: `frontend/src/app/dashboard/page.tsx` + +圃場管理に関連する表示: +- **全圃場数** / **作付け済み圃場数** / **未割当圃場数** のサマリーカード +- 作物別集計テーブル(面積・筆数) +- クイックアクセスボタン(作付け計画、圃場管理、帳票出力、データ取込) + +### 4.5 作付け計画画面 + +**URL**: `/allocation` +**ソース**: `frontend/src/app/allocation/page.tsx` + +圃場管理に関連する機能: +- 全圃場を表形式で表示(圃場名、面積、作物、品種、備考) +- **グループ名**: インライン編集(datalistで既存グループから選択 or 自由入力) +- **表示順**: カスタム順モードで↑↓ボタンで変更(PATCH /api/fields/{id}/ で保存) +- **ソート**: カスタム順 / グループ順 / 作付け順 +- **検索・フィルタ**: 圃場名・住所テキスト検索、作物フィルタ、未割当のみ表示 +- **チェックボックス一括操作**: 複数圃場を選択して作物・品種を一括設定 +- **年度管理**: localStorageで年度保持、過去年度は参照モード(amber色表示) + +--- + +## 5. インポート仕様 + +### 5.1 インポート画面 + +**URL**: `/import` +**ソース**: `frontend/src/app/import/page.tsx` + +3種類のODSファイルをインポートする画面。**インポート順序が重要**: + +``` +1. 共済マスタ (水稲共済細目用.ods) ← 先にインポート +2. 中山間マスタ (中山間.ods) ← 先にインポート +3. 実圃場 (吉田農地台帳.ods) ← 最後(M:N紐付けのため) +``` + +### 5.2 共済マスタインポート + +**エンドポイント**: `POST /api/fields/import/kyosai/` +**ソース**: `backend/apps/fields/views.py:80-139` + +**ODSカラム → DBフィールド マッピング**: + +| ODSカラム名 | DBフィールド | 変換処理 | +|---|---|---| +| 耕地番号 | k_num | strip() | +| 分筆番号 | s_num | strip() | +| 地名 地番 | address | strip() | +| 漢字地名 | kanji_name | strip() | +| 本地面積 (m2) ※スペース有無両対応 | area | `float(値) × 100`(アール→m2変換) | + +**重要**: ODSファイルの面積値は**アール(a)単位**。m2への変換で `×100` している。 + +**upsertロジック**: `(k_num, s_num)` でマッチングし、`update_or_create`。 + +### 5.3 中山間マスタインポート + +**エンドポイント**: `POST /api/fields/import/chusankan/` +**ソース**: `backend/apps/fields/views.py:232-304` + +**ODSカラム → DBフィールド マッピング**: + +| ODSカラム名 | DBフィールド | 変換処理 | +|---|---|---| +| ID | c_id | strip() | +| 中山間 | chusankan_flag | safe_str() | +| 大字 | oaza | safe_str() | +| 字 | aza | safe_str() | +| 地番 | chiban | safe_str() | +| 枝番 | branch_num | safe_str() or None | +| 地目 | land_type | safe_str() or None | +| 農地面積 | area | safe_int() | +| 植栽面積 | planting_area | safe_int() | +| 作付け品目 | original_crop | safe_str() or None | +| 協定管理者 | manager | safe_str() or None | +| 所有者 | owner | safe_str() or None | +| 傾斜度 | slope | safe_str() or None | +| 基本金額 | base_amount | safe_int() | +| 超急傾斜加算額 | steep_slope_addition | safe_int() | +| スマート農業加算額 | smart_agri_addition | safe_int() | +| 交付金額 | payment_amount | safe_int() | + +**スキップ条件**: c_idが空、または数字を含まない行はスキップ。 + +**upsertロジック**: `c_id` でマッチングし、`update_or_create`。 + +### 5.4 実圃場インポート + +**エンドポイント**: `POST /api/fields/import/yoshida/` +**ソース**: `backend/apps/fields/views.py:142-229` + +**ODSカラム → DBフィールド マッピング**: + +| ODSカラム名 | DBフィールド | 変換処理 | +|---|---|---| +| 名称 | name | strip() | +| 住所 | address | strip() | +| 面積(反) | area_tan, area_m2 | area_m2 = area_tan × 1000 | +| 地主 | owner_name | strip() | +| 細目_耕地番号 | raw_kyosai_k_num | clean_int_str() | +| 細目_分筆番号 | raw_kyosai_s_num | clean_int_str() | +| 中山間_ID | raw_chusankan_id | clean_int_str(), カンマ区切り対応 | + +**upsertロジック**: `name` でマッチングし、`update_or_create`。 + +**M:N紐付け処理(インポート時に自動実行)**: +1. `raw_kyosai_k_num` と `raw_kyosai_s_num` の両方がある場合 → 共済マスタの `(k_num, s_num)` で検索 → `field.kyosai_fields.add()` +2. `raw_chusankan_id` がある場合 → カンマ区切りで分割 → 各IDで中山間マスタを検索 → `field.chusankan_fields.add()` + +**clean_int_str()関数**: NaN/空文字 → None、末尾 `.0` を除去。 + +--- + +## 6. エクスポート仕様 + +**エンドポイント**: `GET /api/fields/export/zip/` +**ソース**: `backend/apps/fields/views.py:307-380` + +全データをCSV形式でZIPアーカイブとしてエクスポート。 + +**含まれるCSVファイル**: + +| ファイル名 | 内容 | カラム | +|---|---|---| +| fields.csv | 実圃場 | id, name, address, area_tan, area_m2, owner_name, group_name, display_order | +| kyosai_fields.csv | 共済マスタ | id, k_num, s_num, address, kanji_name, area | +| chusankan_fields.csv | 中山間マスタ | id, c_id, chusankan_flag, oaza, aza, chiban, branch_num, land_type, area, planting_area, original_crop, manager, owner, slope, base_amount, steep_slope_addition, smart_agri_addition, payment_amount | +| plans.csv | 作付け計画 | id, field_id, field_name, year, crop_id, crop_name, variety_id, variety_name, notes | +| crops_varieties.csv | 作物・品種 | crop_id, crop_name, variety_id, variety_name | +| field_links.csv | M:N紐付け | field_id, field_name, link_type(kyosai/chusankan), linked_id | + +--- + +## 7. 対応表(E-2)機能 + +圃場詳細画面における共済/中山間マスタとの紐付け管理機能。 + +### 紐付け追加フロー + +1. 圃場詳細画面で「共済区画を追加」or「中山間区画を追加」ボタンをクリック +2. `LinkModal` が開く +3. 全マスタ区画が一覧表示される(既に紐付いている区画にはチェック済み表示) +4. 各区画に `linked_field_names` が表示され、どの実圃場に紐付いているか確認可能 +5. チェックボックスで選択 → 「追加」ボタンで `POST /api/fields/{id}/kyosai-links/` or `/chusankan-links/` + +### 紐付け削除フロー + +1. 圃場詳細画面の紐付きリストで「×」ボタンをクリック +2. 確認ダイアログ +3. `DELETE /api/fields/{id}/kyosai-links/{kyosai_id}/` or `/chusankan-links/{chusankan_id}/` + +--- + +## 8. PDF帳票との関係 + +圃場データは PDF帳票生成で以下のように使用される。 + +### 8.1 水稲共済細目書 PDF + +**エンドポイント**: `GET /api/reports/kyosai/{year}/` +**ソース**: `backend/apps/reports/views.py:38-66` + +**データ集約ロジック**: +``` +共済マスタ区画ごとにループ: + 1. 共済区画に紐づく実圃場群を取得 (kyosai.fields.all()) + 2. 各実圃場の当年度Planから作物・品種を集約 + 3. 行データとして出力 +``` + +### 8.2 中山間交付金申請書 PDF + +**エンドポイント**: `GET /api/reports/chusankan/{year}/` +**ソース**: `backend/apps/reports/views.py:69-105` + +**データ集約ロジック**: +``` +中山間マスタ区画ごとにループ: + 1. 中山間区画に紐づく実圃場群を取得 (ch.fields.all()) + 2. 各実圃場の当年度Planから作物・品種を集約 + 3. 所在地 = 大字 + 字 + 地番 (+ 枝番) + 4. 行データとして出力 +``` + +**共通の集約関数**: `_get_plan_info(related_fields, year)` (`backend/apps/reports/views.py:8-35`) + +--- + +## 9. 作付け計画との関係 + +### Plan → Field の関係 + +- `Plan.field` は `Field` への FK(CASCADE) +- `unique_together = ['field', 'year']` により、1つの圃場に年度あたり1つの計画のみ +- Field削除時、関連するPlanも全て削除される(CASCADE) + +### 作付け計画画面での圃場操作 + +作付け計画画面(`/allocation`)では以下の圃場関連操作が可能: + +| 操作 | API呼び出し | 説明 | +|---|---|---| +| グループ名変更 | `PATCH /api/fields/{id}/` | `{ "group_name": "上エリア" }` | +| 表示順変更 | `PATCH /api/fields/{id}/` | `{ "display_order": 5 }` | + +--- + +## 10. 設計判断と制約 + +### 10.1 絶対に変えてはいけない制約 + +1. **Field ↔ OfficialKyosaiField は M:N** — 決してFKに変更しない +2. **Field ↔ OfficialChusankanField は M:N** — 決してFKに変更しない +3. **Plan の (field, year) ユニーク制約** — 1圃場1年度1計画 +4. **共済マスタの (k_num, s_num) ユニーク制約** +5. **中山間マスタの c_id ユニーク制約** + +### 10.2 面積の扱い + +| 用途 | 単位 | フィールド | +|---|---|---| +| DB保存 | m2 (整数) | `area_m2` | +| 画面表示 | 反 (小数) | `area_tan` | +| 変換式 | 1反 = 1000m2 | 実際は991.736m2だが1000で統一 | +| 共済ODS | アール(a) | インポート時に `×100` でm2変換 | + +### 10.3 年度管理の設計方針 + +- **作付け計画画面**: `localStorage` で選択年度を保持(セッション跨ぎで記憶) +- **過去年度**: 参照モード(amber色表示、「現在年度に戻る」ボタン) +- **ダッシュボード/帳票**: 常に `new Date().getFullYear()` をデフォルト +- **Phase 2**: グローバル作業年度の導入予定 + +### 10.4 認証 + +- JWT認証(`rest_framework_simplejwt`) +- アクセストークン: 24時間有効 +- リフレッシュトークン: 7日間有効 +- フロントエンド: `localStorage` にトークン保存、`axios` interceptorで自動付与・リフレッシュ +- API設定: `DEFAULT_PERMISSION_CLASSES = IsAuthenticated` + +--- + +## 11. ソースファイル索引 + +### バックエンド + +| ファイル | 行数 | 内容 | +|---|---|---| +| `backend/apps/fields/models.py` | 79行 | Field, OfficialKyosaiField, OfficialChusankanField モデル定義 | +| `backend/apps/fields/views.py` | 381行 | ViewSet、インポート3種、エクスポート、M:Nリンク操作 | +| `backend/apps/fields/serializers.py` | 37行 | FieldSerializer(ネストあり)、マスタSerializer | +| `backend/apps/fields/urls.py` | 18行 | ルーティング定義 | +| `backend/apps/plans/models.py` | 44行 | Crop, Variety, Plan モデル定義 | +| `backend/apps/plans/views.py` | 133行 | PlanViewSet(CRUD、summary、copy、bulk_update) | +| `backend/apps/plans/serializers.py` | 37行 | Plan, Crop, Variety シリアライザ | +| `backend/apps/plans/urls.py` | 13行 | ルーティング定義 | +| `backend/apps/reports/views.py` | 106行 | PDF生成(共済・中山間) | +| `backend/apps/reports/urls.py` | 7行 | ルーティング定義 | +| `backend/keinasystem/urls.py` | 35行 | ルートURL設定(マスタルーター含む) | +| `backend/keinasystem/settings.py` | 151行 | Django設定(DB, JWT, CORS等) | + +### フロントエンド + +| ファイル | 内容 | +|---|---| +| `frontend/src/app/fields/page.tsx` | 圃場一覧画面 | +| `frontend/src/app/fields/[id]/page.tsx` | 圃場詳細画面(M:N紐付け管理含む) | +| `frontend/src/app/fields/new/page.tsx` | 圃場新規作成画面 | +| `frontend/src/app/allocation/page.tsx` | 作付け計画画面(圃場グループ・順序操作含む) | +| `frontend/src/app/dashboard/page.tsx` | ダッシュボード(圃場数サマリー) | +| `frontend/src/app/import/page.tsx` | データ取込画面(3種インポート) | +| `frontend/src/app/reports/page.tsx` | 帳票出力画面 | +| `frontend/src/components/Navbar.tsx` | ナビゲーションバー | +| `frontend/src/components/LinkModal.tsx` | M:N紐付け追加モーダル | +| `frontend/src/lib/api.ts` | Axiosインスタンス、JWT認証インターセプター | +| `frontend/src/types/index.ts` | TypeScript型定義(Field, Plan, Crop, Variety等) | + +### TypeScript型定義 + +```typescript +// frontend/src/types/index.ts + +interface Field { + id: number; + name: string; + address: string; + area_tan: string; // DecimalFieldはstring型で来る + area_m2: number; + owner_name: string; + group_name: string | null; + display_order: number; + kyosai_fields: OfficialKyosaiField[]; + chusankan_fields: OfficialChusankanField[]; +} + +interface OfficialKyosaiField { + id: number; + k_num: string; + s_num: string; + address: string; + kanji_name: string; + area: number; + linked_field_names?: string[]; +} + +interface OfficialChusankanField { + id: number; + c_id: string; + oaza: string; + aza: string; + chiban: string; + area: number; + payment_amount: number | null; + linked_field_names?: string[]; +} + +interface Plan { + id: number; + field: number; + field_name: string; + year: number; + crop: number; + crop_name: string; + variety: number; + variety_name: string; + notes: string | null; +} + +interface Crop { + id: number; + name: string; + varieties: Variety[]; +} + +interface Variety { + id: number; + crop: number; + name: string; +} +``` + +--- + +## 更新履歴 + +- 2026-02-20: 初版作成(Phase 1 MVP完了時点の全機能を網羅)