Files
keinasystem/document/10_マスタードキュメント_圃場管理編.md
2026-02-21 12:26:11 +09:00

755 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# マスタードキュメント - 圃場管理編
> **最終更新**: 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可 | インポート時の中山間IDraw値、カンマ区切り複数可 |
| 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` への FKCASCADE
- `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行 | PlanViewSetCRUD、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完了時点の全機能を網羅