Files
keinasystem/backend/apps/fertilizer/models.py
Akira 497bc87c24 在庫管理機能 Phase 1 実装(apps/materials + フロントエンド)
Backend:
- apps/materials 新規作成(Material, FertilizerProfile, PesticideProfile, StockTransaction)
- 資材マスタ CRUD API(/api/materials/materials/)
- 入出庫履歴 API(/api/materials/stock-transactions/)
- 在庫集計 API(/api/materials/stock-summary/)
- 既存 Fertilizer に material OneToOneField 追加(0005マイグレーション、データ移行込み)

Frontend:
- /materials: 在庫一覧画面(タブフィルタ、履歴展開、入出庫モーダル)
- /materials/masters: 資材マスタ管理(肥料/農薬/その他タブ、インライン編集)
- Navbar に「在庫管理」メニュー追加
- Material/StockTransaction/StockSummary 型定義追加

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 15:42:47 +09:00

140 lines
5.1 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='計算設定')
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 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}"