Files
keinasystem/backend/apps/fertilizer/models.py
Akira 1c27a66691 分配計画を運搬計画に再設計: 軽トラ1回分を基本単位とする運搬回モデルを導入
実運用のワークフロー(複数施肥計画混在・軽トラ複数回・肥料指定)に合わせ、
旧 DistributionPlan/Group/GroupField を DeliveryPlan/Group/GroupField/Trip/TripItem に置き換え。
施肥計画への直接FK廃止→年度ベースで全施肥計画を横断。
回ごとの日付記録、圃場の回間移動、対象肥料フィルタ、回ごとPDF出力に対応。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 16:29:01 +09:00

182 lines
6.6 KiB
Python
Raw 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.
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='備考')
material = models.OneToOneField(
'materials.Material',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='legacy_fertilizer',
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='品種'
)
calc_settings = models.JSONField(default=list, blank=True, verbose_name='計算設定')
is_confirmed = models.BooleanField(default=False, verbose_name='散布確定済み')
confirmed_at = models.DateTimeField(null=True, blank=True, 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.PROTECT, 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}"
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 = ['-year', 'name']
def __str__(self):
return f"{self.year} {self.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 = [['delivery_plan', 'name']]
ordering = ['order', 'id']
def __str__(self):
return f"{self.delivery_plan} / {self.name}"
class DeliveryGroupField(models.Model):
"""圃場のグループへの割り当て1圃場=1グループ/1運搬計画"""
delivery_plan = models.ForeignKey(
DeliveryPlan, on_delete=models.CASCADE, verbose_name='運搬計画'
)
group = models.ForeignKey(
DeliveryGroup, 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 = [['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}"