249 lines
8.9 KiB
Python
249 lines
8.9 KiB
Python
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='袋数')
|
||
actual_bags = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=4,
|
||
null=True,
|
||
blank=True,
|
||
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}袋"
|
||
|
||
|
||
class SpreadingSession(models.Model):
|
||
"""散布日単位の実績"""
|
||
year = models.IntegerField(verbose_name='年度')
|
||
date = models.DateField(verbose_name='散布日')
|
||
name = models.CharField(max_length=100, blank=True, verbose_name='名前')
|
||
notes = models.TextField(blank=True, default='', 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 = ['-date', '-id']
|
||
|
||
def __str__(self):
|
||
label = self.name.strip() or f'{self.date}'
|
||
return f'{self.year} {label}'
|
||
|
||
|
||
class SpreadingSessionItem(models.Model):
|
||
"""散布実績明細:圃場×肥料ごとの実績"""
|
||
session = models.ForeignKey(
|
||
SpreadingSession,
|
||
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='肥料'
|
||
)
|
||
actual_bags = models.DecimalField(max_digits=10, decimal_places=4, verbose_name='実散布袋数')
|
||
planned_bags_snapshot = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=4,
|
||
verbose_name='計画袋数スナップショット',
|
||
)
|
||
delivered_bags_snapshot = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=4,
|
||
verbose_name='運搬済み袋数スナップショット',
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = '散布実績明細'
|
||
verbose_name_plural = '散布実績明細'
|
||
unique_together = [['session', 'field', 'fertilizer']]
|
||
ordering = ['field__display_order', 'field__id', 'fertilizer__name']
|
||
|
||
def __str__(self):
|
||
return (
|
||
f'{self.session} / {self.field.name} / '
|
||
f'{self.fertilizer.name}: {self.actual_bags}袋'
|
||
)
|