施肥計画機能を追加(年度×品種単位のマトリクス管理)

- Backend: apps/fertilizer を新規追加
  - Fertilizer(肥料マスタ)、FertilizationPlan、FertilizationEntry モデル
  - 肥料マスタ・施肥計画 CRUD API
  - 3方式の自動計算API(反当袋数・均等配分・反当チッソ成分量)
  - 作付け計画から圃場候補を取得する API
  - WeasyPrint による PDF 出力(圃場×肥料=袋数 マトリクス表)
- Frontend: app/fertilizer を新規追加
  - 施肥計画一覧(年度セレクタ・PDF出力・編集・削除)
  - 肥料マスタ管理(インライン編集)
  - 施肥計画編集(品種選択→圃場自動取得→肥料追加→自動計算→マトリクス手動調整)
- Navbar に「施肥計画」メニューを追加(Sprout アイコン)
- Cursor ルールファイル・連携ガイドを削除(Claude Code 単独運用へ)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-03-01 12:14:29 +09:00
parent 371e40236c
commit f207f5de27
23 changed files with 1695 additions and 174 deletions

View File

@@ -0,0 +1,69 @@
from django.db import models
class Fertilizer(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name='肥料名')
maker = models.CharField(max_length=100, blank=True, null=True, verbose_name='メーカー')
capacity_kg = models.DecimalField(
max_digits=8, decimal_places=3, blank=True, null=True, verbose_name='1袋重量(kg)'
)
nitrogen_pct = models.DecimalField(
max_digits=5, decimal_places=2, blank=True, null=True, verbose_name='窒素含有率(%)'
)
phosphorus_pct = models.DecimalField(
max_digits=5, decimal_places=2, blank=True, null=True, verbose_name='リン酸含有率(%)'
)
potassium_pct = models.DecimalField(
max_digits=5, decimal_places=2, blank=True, null=True, verbose_name='カリ含有率(%)'
)
notes = models.TextField(blank=True, null=True, verbose_name='備考')
class Meta:
verbose_name = '肥料マスタ'
verbose_name_plural = '肥料マスタ'
ordering = ['name']
def __str__(self):
return self.name
class FertilizationPlan(models.Model):
name = models.CharField(max_length=200, verbose_name='計画名')
year = models.IntegerField(verbose_name='年度')
variety = models.ForeignKey(
'plans.Variety', on_delete=models.PROTECT,
related_name='fertilization_plans', verbose_name='品種'
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '施肥計画'
verbose_name_plural = '施肥計画'
ordering = ['-year', 'variety']
def __str__(self):
return f"{self.year} {self.name}"
class FertilizationEntry(models.Model):
"""圃場 × 肥料 × 袋数 の中間テーブル"""
plan = models.ForeignKey(
FertilizationPlan, on_delete=models.CASCADE, related_name='entries'
)
field = models.ForeignKey(
'fields.Field', on_delete=models.CASCADE, verbose_name='圃場'
)
fertilizer = models.ForeignKey(
Fertilizer, on_delete=models.CASCADE, verbose_name='肥料'
)
bags = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='袋数')
class Meta:
verbose_name = '施肥エントリ'
verbose_name_plural = '施肥エントリ'
unique_together = [['plan', 'field', 'fertilizer']]
ordering = ['field', 'fertilizer']
def __str__(self):
return f"{self.plan} / {self.field} / {self.fertilizer}: {self.bags}"