From 60dca6aab1aedc2e9eeb14c44c98bc04a629bc4b Mon Sep 17 00:00:00 2001 From: Akira Date: Sun, 15 Feb 2026 10:41:55 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20=E4=BF=AE=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ 02_ユーザーストーリー.md - 作物マスタの定義を統一 ✅ 03_データ仕様書.md - 作物・品種マスタを更新 ✅ 04_画面設計書.md - 集計サイドバーと編集モーダルのUI ✅ 05_実装優先順位.md - Day 5-6に集計API・品種追加APIを追加 ✅ 06_Gemini向け統合指示書.md - コード例を全面更新 --- 00_Gemini向け統合指示書.md | 160 +++++++++++++++++++++++++++-- 02_ユーザーストーリー.md | 35 +++++-- 03_データ仕様書.md | 45 +++++--- 04_画面設計書.md | 205 +++++++++++++++++++++++++++++-------- 05_実装優先順位.md | 13 ++- 5 files changed, 381 insertions(+), 77 deletions(-) diff --git a/00_Gemini向け統合指示書.md b/00_Gemini向け統合指示書.md index a0826c2..1fbc5fb 100644 --- a/00_Gemini向け統合指示書.md +++ b/00_Gemini向け統合指示書.md @@ -291,18 +291,19 @@ from apps.fields.models import Field class Crop(models.Model): """作物マスタ""" name = models.CharField("作物名", max_length=50, unique=True) - is_planting = models.BooleanField("作付けする", default=True) + # is_planting フィールドは削除(「その他」で統一するため) class Meta: ordering = ['name'] class Variety(models.Model): - """品種マスタ""" + """品種マスタ(すべての作物で統一)""" crop = models.ForeignKey(Crop, on_delete=models.CASCADE, related_name='varieties') name = models.CharField("品種名", max_length=100) class Meta: ordering = ['crop', 'name'] + unique_together = [['crop', 'name']] # 同じ作物内で品種名は一意 class Plan(models.Model): """作付け計画""" @@ -391,13 +392,60 @@ def import_yoshida_fields(request): ### Step 5: 作付け計画API(Day 5) +#### 作物・品種の初期データ投入 + +**apps/plans/management/commands/init_crops.py** +```python +from django.core.management.base import BaseCommand +from apps.plans.models import Crop, Variety + +class Command(BaseCommand): + help = '作物・品種マスタの初期データを投入' + + def handle(self, *args, **options): + # 作物マスタと品種 + crops_data = { + '米': ['にこまる', 'たちはるか', 'たちはるか(特栽)'], + 'トウモロコシ': [], + 'エンドウ': ['久留米豊'], + '野菜': [], + 'その他': [ + '完全休耕', + '緑肥(ヘアリーベッチ)', + '緑肥(レンゲ)', + '景観作物(コスモス)', + '景観作物(ヒマワリ)' + ] + } + + for crop_name, varieties in crops_data.items(): + crop, created = Crop.objects.get_or_create(name=crop_name) + if created: + self.stdout.write(f'作物「{crop_name}」を作成') + + for variety_name in varieties: + variety, created = Variety.objects.get_or_create( + crop=crop, + name=variety_name + ) + if created: + self.stdout.write(f' 品種「{variety_name}」を追加') + + self.stdout.write(self.style.SUCCESS('初期データ投入完了')) +``` + +**実行:** +```bash +docker-compose exec backend python manage.py init_crops +``` + #### apps/plans/views.py ```python from rest_framework import viewsets -from rest_framework.decorators import action +from rest_framework.decorators import action, api_view from rest_framework.response import Response -from .models import Plan -from .serializers import PlanSerializer +from .models import Plan, Crop, Variety +from .serializers import PlanSerializer, CropSerializer, VarietySerializer class PlanViewSet(viewsets.ModelViewSet): queryset = Plan.objects.all() @@ -450,6 +498,79 @@ class PlanViewSet(viewsets.ModelViewSet): ) return Response({'status': 'success', 'copied': len(plans)}) + + @action(detail=False, methods=['get']) + def summary(self, request): + """集計(サイドバー用)""" + year = int(request.query_params.get('year')) + plans = Plan.objects.filter(year=year).select_related('crop', 'variety', 'field') + + # 作物別集計 + crop_summary = {} + total_area = 0 + + for plan in plans: + crop_name = plan.crop.name if plan.crop else '未設定' + variety_name = plan.variety.name if plan.variety else '(品種なし)' + area = plan.field.area_tan + total_area += area + + if crop_name not in crop_summary: + crop_summary[crop_name] = {'total': 0, 'varieties': {}} + + crop_summary[crop_name]['total'] += area + + if variety_name not in crop_summary[crop_name]['varieties']: + crop_summary[crop_name]['varieties'][variety_name] = 0 + + crop_summary[crop_name]['varieties'][variety_name] += area + + # 反を小数点1桁に丸める + for crop in crop_summary.values(): + crop['total'] = round(crop['total'], 1) + for variety in crop['varieties']: + crop['varieties'][variety] = round(crop['varieties'][variety], 1) + + return Response({ + 'total_area': round(total_area, 1), + 'crops': crop_summary + }) + +@api_view(['POST']) +def add_variety(request): + """新しい品種を追加""" + crop_id = request.data.get('crop_id') + name = request.data.get('name') + + # 重複チェック + if Variety.objects.filter(crop_id=crop_id, name=name).exists(): + return Response({'error': 'この品種は既に登録されています'}, status=400) + + variety = Variety.objects.create(crop_id=crop_id, name=name) + + return Response({ + 'id': variety.id, + 'name': variety.name, + 'crop_id': variety.crop_id + }) + +@api_view(['GET']) +def get_crops_with_varieties(request): + """作物と品種の一覧を取得""" + crops = Crop.objects.prefetch_related('varieties').all() + + data = [] + for crop in crops: + data.append({ + 'id': crop.id, + 'name': crop.name, + 'varieties': [ + {'id': v.id, 'name': v.name} + for v in crop.varieties.all() + ] + }) + + return Response(data) ``` ### Step 6: 申請書PDF生成(Day 7) @@ -803,22 +924,43 @@ export default function ReportsPage() { - **面積単位**: DB内部は全て `m2` で保存、表示時に `反` に変換 - **紐付けキー**: `raw_*` フィールドと外部キー `*_field` の両方を持つ - **ユニーク制約**: `(field, year)` で作付け計画は1つまで +- **品種マスタ**: `(crop, name)` で一意制約 -### 2. 申請書CSV生成 +### 2. 作物・品種の統一 +- **すべての作物で品種選択UIは統一**: 作物による操作の違いなし +- **「作付けしない」系も特別扱いしない**: 「その他」という作物に統一 +- **品種の追加**: その場で追加可能、データベースに永続化 +- **初期データ**: + ``` + 米: にこまる、たちはるか、たちはるか(特栽) + トウモロコシ: (ユーザーが追加) + エンドウ: 久留米豊 + 野菜: (ユーザーが追加) + その他: 完全休耕、緑肥(ヘアリーベッチ)、緑肥(レンゲ)、景観作物(コスモス)、景観作物(ヒマワリ) + ``` + +### 3. 集計サイドバー +- **リアルタイム更新**: 作付け計画を保存するたびに自動更新 +- **PC**: 開閉可能なサイドバー(幅200px固定) +- **スマホ**: モーダル表示 +- **集計API**: `/api/plans/summary/?year=2025` で作物別・品種別の面積を返す + +### 4. 申請書PDF生成 - **共済マスタをベースにループ**: 実圃場ベースではない - **作物の名寄せ**: Pythonのセットで重複排除 - **未割当の扱い**: 「未設定」として出力 -### 3. UI/UX +### 5. UI/UX - **未割当の強調**: 赤または黄色の背景色 - **スマホ対応**: 文字サイズ16px以上、タップ領域44px以上 - **検索のリアルタイム性**: `useState` + `filter` で実装 +- **品種追加のUI**: インライン入力、別画面に遷移しない -### 4. 認証 +### 6. 認証 - **JWT認証**: アクセストークン(1日)+ リフレッシュトークン(7日) - **シンプルな実装**: パスワードリセットはPhase 2 -### 5. パフォーマンス +### 7. パフォーマンス - **Phase 1では最適化不要**: 圃場数39筆、共済31区画、中山間71区画 → 十分軽い - **Phase 2で検討**: 栽培履歴が増えたらページネーション diff --git a/02_ユーザーストーリー.md b/02_ユーザーストーリー.md index ae8d049..82d9fd0 100644 --- a/02_ユーザーストーリー.md +++ b/02_ユーザーストーリー.md @@ -65,21 +65,34 @@ So that △△できる(目的・価値) **【作物マスタ】** ``` -作付けしない: - - 休耕 - - 緑肥 - - 景観作物 - - その他野菜 - -作付けする: +作物リスト: - 米 - └ 品種: にこまる、たちはるか、たちはるか(特栽) - トウモロコシ - └ 品種: (毎年変わる→自由入力) - エンドウ - └ 品種: 久留米豊 - 野菜 - └ 品種: (増減あり→自由入力) + - その他(休耕・緑肥・景観作物など) + +品種の登録方法: + - すべての作物で統一 + - プリセット品種 + その場で追加 + - 例: + ┌─────────────────────────┐ + │ 品種: [にこまる ▼] │ + │ - にこまる │ + │ - たちはるか │ + │ - たちはるか(特栽)│ + │ │ + │ [+ 新しい品種を追加] │ + └─────────────────────────┘ + +作物「その他」の品種例: + - 完全休耕 + - 緑肥(ヘアリーベッチ) + - 緑肥(レンゲ) + - 景観作物(コスモス) + - 景観作物(ヒマワリ) + +※「作付けしない」系も特別扱いせず、「その他」として統一 ``` --- diff --git a/03_データ仕様書.md b/03_データ仕様書.md index c3b3364..d48319c 100644 --- a/03_データ仕様書.md +++ b/03_データ仕様書.md @@ -179,28 +179,47 @@ ID 大字 字 地番 農地面積 作付け品目 交付金額 ## 5. 作物マスタ -### 作付けしない -- 休耕 -- 緑肥 -- 景観作物 -- その他野菜 +### 作物リスト +- 米 +- トウモロコシ +- エンドウ +- 野菜 +- その他 -### 作付けする +### 品種の登録方法 + +**すべての作物で統一されたUI:** +- プリセット品種から選択 +- その場で新しい品種を追加可能 +- 作物による操作の違いなし + +### プリセット品種の例 #### 米 -- 品種: - - にこまる - - たちはるか - - たちはるか(特栽) +- にこまる +- たちはるか +- たちはるか(特栽) #### トウモロコシ -- 品種: 自由入力(毎年変わるため) +- (ユーザーが追加) #### エンドウ -- 品種: 久留米豊 +- 久留米豊 #### 野菜 -- 品種: 自由入力(増減あり) +- (ユーザーが追加) + +#### その他 +- 完全休耕 +- 緑肥(ヘアリーベッチ) +- 緑肥(レンゲ) +- 景観作物(コスモス) +- 景観作物(ヒマワリ) + +### 実装上の注意 +- すべての作物で `Variety` テーブルに品種を登録 +- 「作付けしない」系を特別扱いしない +- UIは完全に統一 --- diff --git a/04_画面設計書.md b/04_画面設計書.md index f3a7871..82738da 100644 --- a/04_画面設計書.md +++ b/04_画面設計書.md @@ -110,28 +110,76 @@ ### レイアウト(PC) ``` -┌──────────────────────────────────────────────────────────────┐ -│ 🌾 Keina System > 作付け計画一覧 2025年度 ▼ 👤ログアウト │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ 🔍 [検索: 圃場名・住所_____________] 🔽絞込: [全て▼] [作物▼]│ -│ │ -│ ☐ 未割当のみ表示 [📋 前年度をコピー] [📊 申請書作成] │ -│ │ -├──────────────────────────────────────────────────────────────┤ -│ No. ☐ 名称 住所 面積 作付 品種 操作│ -├──────────────────────────────────────────────────────────────┤ -│ 1 ☐ おまけ 口神ノ川足川 351 0.2反 米 にこまる [編集]│ -│ 2 ☐ 口神1反2畝 口神ノ川198... 1.2反 米 にこまる [編集]│ -│ 3 ☐ 口神 北東 口神ノ川198... 0.4反 野菜 トマト [編集]│ -│ 4 ☐ 口神 北中 口神ノ川198... 0.4反 ❗未設定 [割当]│ -│ 5 ☐ 口神 北西 口神ノ川198... 0.5反 休耕 - [編集]│ -│ │ -│ ... (39行) │ -│ │ -├──────────────────────────────────────────────────────────────┤ -│ ページ: 1 / 2 表示件数: [25件▼] │ -└──────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────┐ +│ 🌾 Keina System > 作付け計画一覧 2025年度 ▼ 👤ログアウト │ +├──────┬───────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 │ 🔍 [検索: 圃場名・住所_____________] 🔽絞込: [全て▼] │ +│ 集計 │ │ +│ │ ☐ 未割当のみ表示 [📋 前年度をコピー] [📊 申請書作成] │ +│ ────────────────────────────────────────────────────────────────│ +│ 米 │ No. ☐ 名称 住所 面積 作付 品種 操作 │ +│15.3反│ ────────────────────────────────────────────────────────│ +│┣にこ │ 1 ☐ おまけ 口神ノ川... 0.2反 米 にこまる [編集]│ +││10.2反│ 2 ☐ 口神1反 口神ノ川... 1.2反 米 にこまる [編集]│ +│┗たち │ 3 ☐ 口神北東 口神ノ川... 0.4反 野菜 トマト [編集]│ +││5.1反│ 4 ☐ 口神北中 口神ノ川... 0.4反 ❗未設定 [割当]│ +│ │ 5 ☐ 口神北西 口神ノ川... 0.5反 その他 完全休耕 [編集]│ +│野菜 │ │ +│3.2反 │ ... (39行) │ +│ │ │ +│その他│ ────────────────────────────────────────────────────────│ +│1.5反 │ ページ: 1 / 2 表示件数: [25件▼] │ +│ │ │ +│未設定│ │ +│2.0反❗ │ +└──────┴───────────────────────────────────────────────────────────┘ +``` + +**サイドバー(開閉可能):** +``` +┌──────────────┐ +│ [≡] 集計 │← トグルボタンで開閉 +├──────────────┤ +│ 合計 40.0反 │ +│ │ +│ 米 │ +│ 15.3反 │ +│ ├にこまる │ +│ │ 10.2反 │ +│ └たちはるか │ +│ 5.1反 │ +│ │ +│ 野菜 │ +│ 3.2反 │ +│ ├トマト │ +│ │ 1.8反 │ +│ └キュウリ │ +│ 1.4反 │ +│ │ +│ トウモロコシ │ +│ 2.5反 │ +│ │ +│ エンドウ │ +│ 1.8反 │ +│ │ +│ その他 │ +│ 1.5反 │ +│ ├完全休耕 │ +│ │ 0.8反 │ +│ └緑肥 │ +│ 0.7反 │ +│ │ +│ ❗未設定 │ +│ 2.0反 │ +└──────────────┘ +``` + +**サイドバー閉じた状態:** +``` +┌───┐ +│[☰]│← クリックで開く +└───┘ ``` ### レイアウト(スマホ) @@ -141,6 +189,8 @@ │ 🌾 作付け計画 2025年度 ▼ ☰ │ ├────────────────────────────────┤ │ 🔍 [検索_______________] [🔽] │ +│ │ +│ [📊 集計を表示] [前年度コピー]│← 集計はモーダル ├────────────────────────────────┤ │ ┌────────────────────────────┐│ │ │ おまけ ││ @@ -157,7 +207,36 @@ │ └────────────────────────────┘│ │ │ │ ... (39圃場) │ +└────────────────────────────────┘ +``` + +**スマホ: 集計モーダル** +``` +┌────────────────────────────────┐ +│ 集計 [×] │ +├────────────────────────────────┤ │ │ +│ 合計面積: 40.0反 │ +│ │ +│ 米: 15.3反 │ +│ ├ にこまる: 10.2反 │ +│ └ たちはるか: 5.1反 │ +│ │ +│ 野菜: 3.2反 │ +│ ├ トマト: 1.8反 │ +│ └ キュウリ: 1.4反 │ +│ │ +│ トウモロコシ: 2.5反 │ +│ │ +│ エンドウ: 1.8反 │ +│ │ +│ その他: 1.5反 │ +│ ├ 完全休耕: 0.8反 │ +│ └ 緑肥: 0.7反 │ +│ │ +│ ❗未設定: 2.0反 │ +│ │ +│ [閉じる] │ └────────────────────────────────┘ ``` @@ -185,11 +264,21 @@ - 前年度の作付けを全圃場にコピー - [ ] [申請書作成]ボタン: - 画面6へ遷移 +- [ ] **集計サイドバー(PC)**: + - トグルボタンで開閉 + - 作物別の合計面積 + - 品種別の内訳(ツリー表示) + - 未設定の面積を警告表示 +- [ ] **集計モーダル(スマホ)**: + - [📊 集計を表示]ボタンでモーダル表示 + - PC版と同じ内容を縦スクロール表示 ### デザインノート - **未割当の強調**: 赤または黄色の背景色 - **チェックボックスの位置**: 行の左端(スマホでも押しやすい) - **スマホ版**: カード型レイアウト(1圃場1カード) +- **サイドバーの幅**: 200px(固定) +- **集計の更新**: 作付け計画を編集するたびにリアルタイム更新 --- @@ -216,10 +305,14 @@ │ │ [米 ▼] ││ │ └────────────────────────────────┘│ │ │ -│ 品種 │ +│ 品種 * │ │ ┌────────────────────────────────┐│ │ │ [にこまる ▼] ││ +│ │ - にこまる ││ +│ │ - たちはるか ││ +│ │ - たちはるか(特栽) ││ │ └────────────────────────────────┘│ +│ [+ 新しい品種を追加] │ │ │ │ 備考 │ │ ┌────────────────────────────────┐│ @@ -235,21 +328,15 @@ ``` ┌────────────────────────────────────┐ -│ 作付けしない │ -│ • 休耕 │ -│ • 緑肥 │ -│ • 景観作物 │ -│ • その他野菜 │ -│ │ -│ 作付けする │ -│ • 米 │ -│ • トウモロコシ │ -│ • エンドウ │ -│ • 野菜 │ +│ • 米 │ +│ • トウモロコシ │ +│ • エンドウ │ +│ • 野菜 │ +│ • その他 │ └────────────────────────────────────┘ ``` -### 品種選択(作物に応じて動的に変化) +### 品種選択(すべての作物で統一) **作物=「米」の場合:** ``` @@ -257,36 +344,68 @@ │ • にこまる │ │ • たちはるか │ │ • たちはるか(特栽) │ -│ • [その他: 自由入力_____________] │ +│ ──────────────────────────────── │ +│ [+ 新しい品種を追加] │ +└────────────────────────────────────┘ + +クリックすると: +┌────────────────────────────────────┐ +│ 新しい品種名を入力: │ +│ [___________________________] │ +│ │ +│ [キャンセル] [追加] │ └────────────────────────────────────┘ ``` **作物=「トウモロコシ」の場合:** ``` ┌────────────────────────────────────┐ -│ [自由入力_____________________] │ -│ (品種は毎年変わるため) │ +│ (まだ品種が登録されていません) │ +│ ──────────────────────────────── │ +│ [+ 新しい品種を追加] │ └────────────────────────────────────┘ ``` -**作物=「休耕」などの場合:** +**作物=「その他」の場合:** ``` -(品種選択フィールドは非表示) +┌────────────────────────────────────┐ +│ • 完全休耕 │ +│ • 緑肥(ヘアリーベッチ) │ +│ • 緑肥(レンゲ) │ +│ • 景観作物(コスモス) │ +│ • 景観作物(ヒマワリ) │ +│ ──────────────────────────────── │ +│ [+ 新しい品種を追加] │ +└────────────────────────────────────┘ ``` ### 機能要件 - [ ] 作物をドロップダウンで選択 -- [ ] 作物に応じて品種選択が動的に変化 -- [ ] 「作付けしない」系の作物は、品種選択を非表示 -- [ ] 自由入力欄を用意(トウモロコシ、野菜など) +- [ ] **すべての作物で品種選択UIは統一** + - プリセット品種のドロップダウン + - [+ 新しい品種を追加]ボタンは常に表示 + - 作物による操作の違いなし +- [ ] 新しい品種の追加: + - [+ 新しい品種を追加]をクリック + - インライン入力フィールド表示 + - 入力後、その場でプリセットに追加 + - データベースに永続化(次回から選択可能) +- [ ] 備考欄(任意) - [ ] [保存]ボタン → 作付け計画を保存して一覧に戻る - [ ] [キャンセル]ボタン → 変更を破棄して一覧に戻る +- [ ] **集計のリアルタイム更新**: + - 保存すると、サイドバーの集計が即座に更新される ### 一括割当の場合 - モーダルのタイトルを「一括割当」に変更 - 「圃場: 5件選択中」と表示 - 保存時、選択中の全圃場に同じ作付けを適用 +### デザインノート +- **UIの統一性**: どの作物を選んでも操作フローが同じ +- **品種の追加**: その場で追加できる(別画面に遷移しない) +- **プリセットの管理**: 一度追加した品種は、全ユーザーで共有(マスタ化) + --- ## 画面5: 圃場詳細(スマホ参照用) diff --git a/05_実装優先順位.md b/05_実装優先順位.md index d9c74a0..354a792 100644 --- a/05_実装優先順位.md +++ b/05_実装優先順位.md @@ -85,11 +85,15 @@ **Day 5: 作付け計画API** - [ ] Django: 作物・品種マスタの初期データ投入 + - 作物: 米、トウモロコシ、エンドウ、野菜、その他 + - 品種: 米(にこまる等)、その他(完全休耕等) - [ ] Django: 作付け計画API - 一覧取得 (`GET /api/plans/?year=2025`) - 作成・更新 (`POST /api/plans/`, `PATCH /api/plans/{id}/`) - 一括更新 (`POST /api/plans/bulk/`) - 前年度コピー (`POST /api/plans/copy_from_previous_year/`) + - **品種追加API** (`POST /api/varieties/`) - その場で品種を追加 + - **集計API** (`GET /api/plans/summary/?year=2025`) - サイドバー用 - [ ] API動作確認 (Postman or curl) **Day 6: 作付け計画UI** @@ -97,9 +101,16 @@ - テーブル表示 - 検索・フィルタ機能 - 未割当のハイライト + - **集計サイドバー(開閉可能)** + - 作物別・品種別の合計面積 + - リアルタイム更新 + - **スマホ: 集計モーダル表示** - [ ] Next.js: 作付け計画編集モーダル - 作物選択(ドロップダウン) - - 品種選択(動的に変化) + - **品種選択(統一UI)** + - プリセット品種のドロップダウン + - [+ 新しい品種を追加]ボタン + - すべての作物で同じ操作 - 一括割当対応 - [ ] Next.js: 前年度コピーボタン - [ ] 動作確認: 作付け計画を実際に入力