分配計画を運搬計画に再設計: 軽トラ1回分を基本単位とする運搬回モデルを導入

実運用のワークフロー(複数施肥計画混在・軽トラ複数回・肥料指定)に合わせ、
旧 DistributionPlan/Group/GroupField を DeliveryPlan/Group/GroupField/Trip/TripItem に置き換え。
施肥計画への直接FK廃止→年度ベースで全施肥計画を横断。
回ごとの日付記録、圃場の回間移動、対象肥料フィルタ、回ごとPDF出力に対応。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-03-16 16:29:01 +09:00
parent eba6267495
commit 1c27a66691
14 changed files with 1640 additions and 903 deletions

View File

@@ -80,51 +80,48 @@ class FertilizationEntry(models.Model):
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='施肥計画'
)
class DeliveryPlan(models.Model):
"""運搬計画:施肥計画の肥料を軽トラで運ぶ単位で計画・記録する"""
year = models.IntegerField(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']
verbose_name = '運搬計画'
verbose_name_plural = '運搬計画'
ordering = ['-year', 'name']
def __str__(self):
return f"{self.fertilization_plan.year} {self.name}"
return f"{self.year} {self.name}"
class DistributionGroup(models.Model):
"""配グループ:ある場所にまとめて置く圃場のグループ"""
distribution_plan = models.ForeignKey(
DistributionPlan, on_delete=models.CASCADE,
related_name='groups', verbose_name='分配計画'
class DeliveryGroup(models.Model):
"""送先グループ:まとめて運ぶ圃場のグループ"""
delivery_plan = models.ForeignKey(
DeliveryPlan, 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']]
verbose_name = '送先グループ'
verbose_name_plural = '送先グループ'
unique_together = [['delivery_plan', 'name']]
ordering = ['order', 'id']
def __str__(self):
return f"{self.distribution_plan} / {self.name}"
return f"{self.delivery_plan} / {self.name}"
class DistributionGroupField(models.Model):
"""圃場のグループへの割り当て1圃場=1グループ/1分配計画)"""
distribution_plan = models.ForeignKey(
DistributionPlan, on_delete=models.CASCADE, verbose_name='分配計画'
class DeliveryGroupField(models.Model):
"""圃場のグループへの割り当て1圃場=1グループ/1運搬計画)"""
delivery_plan = models.ForeignKey(
DeliveryPlan, on_delete=models.CASCADE, verbose_name='運搬計画'
)
group = models.ForeignKey(
DistributionGroup, on_delete=models.CASCADE,
DeliveryGroup, on_delete=models.CASCADE,
related_name='field_assignments', verbose_name='グループ'
)
field = models.ForeignKey(
@@ -134,8 +131,51 @@ class DistributionGroupField(models.Model):
class Meta:
verbose_name = 'グループ圃場割り当て'
verbose_name_plural = 'グループ圃場割り当て'
unique_together = [['distribution_plan', 'field']]
unique_together = [['delivery_plan', 'field']]
ordering = ['field__display_order', 'field__id']
def __str__(self):
return f"{self.group.name} / {self.field.name}"
class DeliveryTrip(models.Model):
"""運搬回軽トラ1回分の積載"""
delivery_plan = models.ForeignKey(
DeliveryPlan, on_delete=models.CASCADE,
related_name='trips', verbose_name='運搬計画'
)
order = models.PositiveIntegerField(default=0, verbose_name='何回目')
name = models.CharField(max_length=100, blank=True, verbose_name='名前')
date = models.DateField(null=True, blank=True, verbose_name='運搬日')
class Meta:
verbose_name = '運搬回'
verbose_name_plural = '運搬回'
ordering = ['order', 'id']
def __str__(self):
return f"{self.delivery_plan} / {self.order + 1}回目"
class DeliveryTripItem(models.Model):
"""運搬明細:圃場×肥料単位の袋数"""
trip = models.ForeignKey(
DeliveryTrip, on_delete=models.CASCADE,
related_name='items', verbose_name='運搬回'
)
field = models.ForeignKey(
'fields.Field', on_delete=models.PROTECT, verbose_name='圃場'
)
fertilizer = models.ForeignKey(
Fertilizer, on_delete=models.PROTECT, verbose_name='肥料'
)
bags = models.DecimalField(max_digits=10, decimal_places=4, verbose_name='袋数')
class Meta:
verbose_name = '運搬明細'
verbose_name_plural = '運搬明細'
unique_together = [['trip', 'field', 'fertilizer']]
ordering = ['field__display_order', 'field__id', 'fertilizer__name']
def __str__(self):
return f"{self.trip} / {self.field.name} / {self.fertilizer.name}: {self.bags}"