分配計画機能を実装

施肥計画の圃場を配置場所単位でグループ化し、グループ×肥料の集計表を
表示・PDF出力できる機能を追加。

- Backend: DistributionPlan/Group/GroupField モデル (migration 0003)
- API: GET/POST/PUT/DELETE/PDF (/api/fertilizer/distribution/)
- Frontend: 一覧・新規作成・編集画面 (/distribution)
- Navbar に分配計画メニューを追加
- 集計プレビューはクライアントサイド計算(API不要)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-03-02 09:43:20 +09:00
parent 0d321df1c4
commit 466eef128c
15 changed files with 1656 additions and 5 deletions

View File

@@ -67,3 +67,64 @@ class FertilizationEntry(models.Model):
def __str__(self):
return f"{self.plan} / {self.field} / {self.fertilizer}: {self.bags}"
class DistributionPlan(models.Model):
"""分配計画:施肥計画の圃場をカスタムグループに割り当て、配置場所単位で集計する"""
fertilization_plan = models.ForeignKey(
FertilizationPlan, on_delete=models.CASCADE,
related_name='distribution_plans', verbose_name='施肥計画'
)
name = models.CharField(max_length=200, 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 = ['-fertilization_plan__year', 'name']
def __str__(self):
return f"{self.fertilization_plan.year} {self.name}"
class DistributionGroup(models.Model):
"""分配グループ:ある場所にまとめて置く圃場のグループ"""
distribution_plan = models.ForeignKey(
DistributionPlan, on_delete=models.CASCADE,
related_name='groups', verbose_name='分配計画'
)
name = models.CharField(max_length=100, verbose_name='グループ名')
order = models.PositiveIntegerField(default=0, verbose_name='表示順')
class Meta:
verbose_name = '分配グループ'
verbose_name_plural = '分配グループ'
unique_together = [['distribution_plan', 'name']]
ordering = ['order', 'id']
def __str__(self):
return f"{self.distribution_plan} / {self.name}"
class DistributionGroupField(models.Model):
"""圃場のグループへの割り当て1圃場=1グループ/1分配計画"""
distribution_plan = models.ForeignKey(
DistributionPlan, on_delete=models.CASCADE, verbose_name='分配計画'
)
group = models.ForeignKey(
DistributionGroup, on_delete=models.CASCADE,
related_name='field_assignments', verbose_name='グループ'
)
field = models.ForeignKey(
'fields.Field', on_delete=models.PROTECT, verbose_name='圃場'
)
class Meta:
verbose_name = 'グループ圃場割り当て'
verbose_name_plural = 'グループ圃場割り当て'
unique_together = [['distribution_plan', 'field']]
ordering = ['field__display_order', 'field__id']
def __str__(self):
return f"{self.group.name} / {self.field.name}"