品種ごとの種子在庫前提まで実装を進めました。

主な変更は、seed 資材種別の追加と Variety.seed_material の導入です。backend/apps/materials/models.py、backend/apps/plans/models.py、backend/apps/plans/serializers.py で、田植え計画が作物在庫ではなく品種に紐づく種子資材の現在庫を参照するように切り替えました。マイグレーションは backend/apps/materials/migrations/0005_material_seed_type.py と backend/apps/plans/migrations/0008_variety_seed_material.py を追加しています。

画面側は、frontend/src/app/materials/page.tsx と frontend/src/app/materials/masters/page.tsx に「種子」タブを追加し、frontend/src/app/allocation/page.tsx の品種管理モーダルで品種ごとに種子在庫資材を設定できるようにしました。田植え計画画面 frontend/src/app/rice-transplant/_components/RiceTransplantEditPage.tsx も、苗箱数 列中心に整理し、種もみkg 列を削除、反当苗箱枚数 の列反映と ≈ / ↩ の四捨五入トグルを施肥計画寄りの操作感に寄せています。仕様書 document/16_マスタードキュメント_田植え計画編.md も更新済みです。

確認できたのは python3 -m py_compile backend/apps/materials/models.py backend/apps/materials/serializers.py backend/apps/plans/models.py backend/apps/plans/serializers.py backend/apps/plans/views.py までです。frontend/node_modules が無いためフロントのビルド確認はまだできていません。Issue #2 にも反映内容をコメント済みです。必要なら次にコミットします。
This commit is contained in:
akira
2026-04-05 11:22:07 +09:00
parent 11b36b28a5
commit a38472e4a0
14 changed files with 473 additions and 236 deletions

View File

@@ -0,0 +1,26 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materials', '0005_material_seed_type'),
('plans', '0007_ricetransplantplan_seedling_boxes_per_tan'),
]
operations = [
migrations.AddField(
model_name='variety',
name='seed_material',
field=models.ForeignKey(
blank=True,
limit_choices_to={'material_type': 'seed'},
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='varieties',
to='materials.material',
verbose_name='種子在庫資材',
),
),
]

View File

@@ -29,6 +29,15 @@ class Variety(models.Model):
default=0,
verbose_name="反当苗箱枚数デフォルト",
)
seed_material = models.ForeignKey(
'materials.Material',
on_delete=models.SET_NULL,
related_name='varieties',
verbose_name='種子在庫資材',
blank=True,
null=True,
limit_choices_to={'material_type': 'seed'},
)
class Meta:
verbose_name = "品種マスタ"

View File

@@ -2,14 +2,24 @@ from decimal import Decimal
from rest_framework import serializers
from apps.fields.models import Field
from apps.materials.models import StockTransaction
from .models import Crop, Variety, Plan
from .models import RiceTransplantEntry, RiceTransplantPlan
class VarietySerializer(serializers.ModelSerializer):
seed_material_name = serializers.CharField(source='seed_material.name', read_only=True)
class Meta:
model = Variety
fields = '__all__'
fields = [
'id',
'crop',
'name',
'default_seedling_boxes_per_tan',
'seed_material',
'seed_material_name',
]
class CropSerializer(serializers.ModelSerializer):
@@ -50,7 +60,6 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
)
planned_boxes = serializers.SerializerMethodField()
default_seedling_boxes = serializers.SerializerMethodField()
planned_seed_kg = serializers.SerializerMethodField()
class Meta:
model = RiceTransplantEntry
@@ -62,7 +71,6 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
'installed_seedling_boxes',
'default_seedling_boxes',
'planned_boxes',
'planned_seed_kg',
]
def get_default_seedling_boxes(self, obj):
@@ -73,21 +81,15 @@ class RiceTransplantEntrySerializer(serializers.ModelSerializer):
def get_planned_boxes(self, obj):
return str(obj.installed_seedling_boxes.quantize(Decimal('0.01')))
def get_planned_seed_kg(self, obj):
seed_kg = (
obj.installed_seedling_boxes * obj.plan.default_seed_grams_per_box / Decimal('1000')
).quantize(Decimal('0.001'))
return str(seed_kg)
class RiceTransplantPlanSerializer(serializers.ModelSerializer):
variety_name = serializers.CharField(source='variety.name', read_only=True)
crop_name = serializers.CharField(source='variety.crop.name', read_only=True)
seed_material_name = serializers.CharField(source='variety.seed_material.name', read_only=True)
entries = RiceTransplantEntrySerializer(many=True, read_only=True)
field_count = serializers.SerializerMethodField()
total_seedling_boxes = serializers.SerializerMethodField()
total_seed_kg = serializers.SerializerMethodField()
crop_seed_inventory_kg = serializers.SerializerMethodField()
variety_seed_inventory_kg = serializers.SerializerMethodField()
remaining_seed_kg = serializers.SerializerMethodField()
class Meta:
@@ -102,11 +104,12 @@ class RiceTransplantPlanSerializer(serializers.ModelSerializer):
'default_seed_grams_per_box',
'seedling_boxes_per_tan',
'notes',
'seed_material_name',
'entries',
'field_count',
'total_seedling_boxes',
'total_seed_kg',
'crop_seed_inventory_kg',
'variety_seed_inventory_kg',
'remaining_seed_kg',
'created_at',
'updated_at',
@@ -117,28 +120,58 @@ class RiceTransplantPlanSerializer(serializers.ModelSerializer):
def get_total_seedling_boxes(self, obj):
total = sum(
entry.installed_seedling_boxes
for entry in obj.entries.all()
(
entry.installed_seedling_boxes
for entry in obj.entries.all()
),
Decimal('0'),
)
return str(total.quantize(Decimal('0.01')))
def get_total_seed_kg(self, obj):
total = sum(
(
entry.installed_seedling_boxes
* obj.default_seed_grams_per_box
/ Decimal('1000')
)
for entry in obj.entries.all()
(
entry.installed_seedling_boxes
* obj.default_seed_grams_per_box
/ Decimal('1000')
)
for entry in obj.entries.all()
),
Decimal('0'),
)
return str(total.quantize(Decimal('0.001')))
def get_crop_seed_inventory_kg(self, obj):
return str(obj.variety.crop.seed_inventory_kg)
def get_variety_seed_inventory_kg(self, obj):
return str(self._get_seed_inventory_kg(obj).quantize(Decimal('0.001')))
def get_remaining_seed_kg(self, obj):
total_seed = Decimal(self.get_total_seed_kg(obj))
return str((obj.variety.crop.seed_inventory_kg - total_seed).quantize(Decimal('0.001')))
return str((self._get_seed_inventory_kg(obj) - total_seed).quantize(Decimal('0.001')))
def _get_seed_inventory_kg(self, obj):
material = obj.variety.seed_material
if material is None:
return Decimal('0')
transactions = list(material.stock_transactions.all())
increase = sum(
(
txn.quantity
for txn in transactions
if txn.transaction_type in StockTransaction.INCREASE_TYPES
),
Decimal('0'),
)
decrease = sum(
(
txn.quantity
for txn in transactions
if txn.transaction_type in StockTransaction.DECREASE_TYPES
),
Decimal('0'),
)
return increase - decrease
class RiceTransplantPlanWriteSerializer(serializers.ModelSerializer):

View File

@@ -19,7 +19,7 @@ class CropViewSet(viewsets.ModelViewSet):
class VarietyViewSet(viewsets.ModelViewSet):
queryset = Variety.objects.all()
queryset = Variety.objects.select_related('seed_material', 'crop').all()
serializer_class = VarietySerializer
@@ -134,7 +134,7 @@ class PlanViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def get_crops_with_varieties(self, request):
crops = Crop.objects.prefetch_related('varieties').all()
crops = Crop.objects.prefetch_related('varieties__seed_material').all()
return Response(CropSerializer(crops, many=True).data)
@@ -142,7 +142,12 @@ class RiceTransplantPlanViewSet(viewsets.ModelViewSet):
queryset = RiceTransplantPlan.objects.select_related(
'variety',
'variety__crop',
).prefetch_related('entries', 'entries__field')
'variety__seed_material',
).prefetch_related(
'variety__seed_material__stock_transactions',
'entries',
'entries__field',
)
def get_queryset(self):
queryset = self.queryset