Files
keinasystem/backend/apps/materials/serializers.py
akira a38472e4a0 品種ごとの種子在庫前提まで実装を進めました。
主な変更は、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 にも反映内容をコメント済みです。必要なら次にコミットします。
2026-04-05 11:22:07 +09:00

220 lines
7.7 KiB
Python

from decimal import Decimal
from django.db import transaction
from rest_framework import serializers
from .models import (
FertilizerProfile,
Material,
PesticideProfile,
StockTransaction,
)
class FertilizerProfileSerializer(serializers.ModelSerializer):
class Meta:
model = FertilizerProfile
fields = ['capacity_kg', 'nitrogen_pct', 'phosphorus_pct', 'potassium_pct']
class PesticideProfileSerializer(serializers.ModelSerializer):
class Meta:
model = PesticideProfile
fields = [
'registration_no',
'formulation',
'usage_unit',
'dilution_ratio',
'active_ingredient',
'category',
]
class MaterialReadSerializer(serializers.ModelSerializer):
material_type_display = serializers.CharField(
source='get_material_type_display',
read_only=True,
)
stock_unit_display = serializers.CharField(
source='get_stock_unit_display',
read_only=True,
)
fertilizer_profile = FertilizerProfileSerializer(read_only=True)
pesticide_profile = PesticideProfileSerializer(read_only=True)
current_stock = serializers.SerializerMethodField()
class Meta:
model = Material
fields = [
'id',
'name',
'material_type',
'material_type_display',
'maker',
'stock_unit',
'stock_unit_display',
'is_active',
'notes',
'fertilizer_profile',
'pesticide_profile',
'current_stock',
'created_at',
'updated_at',
]
def get_current_stock(self, obj):
transactions = list(obj.stock_transactions.all())
increase = sum(
transaction.quantity
for transaction in transactions
if transaction.transaction_type in StockTransaction.INCREASE_TYPES
)
decrease = sum(
transaction.quantity
for transaction in transactions
if transaction.transaction_type in StockTransaction.DECREASE_TYPES
)
return increase - decrease
class MaterialWriteSerializer(serializers.ModelSerializer):
fertilizer_profile = FertilizerProfileSerializer(required=False, allow_null=True)
pesticide_profile = PesticideProfileSerializer(required=False, allow_null=True)
class Meta:
model = Material
fields = [
'id',
'name',
'material_type',
'maker',
'stock_unit',
'is_active',
'notes',
'fertilizer_profile',
'pesticide_profile',
]
def validate(self, attrs):
material_type = attrs.get('material_type')
if self.instance is not None and material_type is None:
material_type = self.instance.material_type
fertilizer_profile = attrs.get('fertilizer_profile')
pesticide_profile = attrs.get('pesticide_profile')
if material_type == Material.MaterialType.FERTILIZER and pesticide_profile:
raise serializers.ValidationError(
{'pesticide_profile': '肥料には農薬詳細を設定できません。'}
)
if material_type == Material.MaterialType.PESTICIDE and fertilizer_profile:
raise serializers.ValidationError(
{'fertilizer_profile': '農薬には肥料詳細を設定できません。'}
)
if (
material_type in {
Material.MaterialType.SEED,
Material.MaterialType.SEEDLING,
Material.MaterialType.OTHER,
}
and (fertilizer_profile or pesticide_profile)
):
raise serializers.ValidationError(
'種子・種苗・その他には詳細プロファイルを設定できません。'
)
return attrs
@transaction.atomic
def create(self, validated_data):
fertilizer_profile_data = validated_data.pop('fertilizer_profile', None)
pesticide_profile_data = validated_data.pop('pesticide_profile', None)
material = Material.objects.create(**validated_data)
self._save_profiles(material, fertilizer_profile_data, pesticide_profile_data)
return material
@transaction.atomic
def update(self, instance, validated_data):
fertilizer_profile_data = validated_data.pop('fertilizer_profile', None)
pesticide_profile_data = validated_data.pop('pesticide_profile', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
self._save_profiles(instance, fertilizer_profile_data, pesticide_profile_data)
return instance
def to_representation(self, instance):
return MaterialReadSerializer(instance, context=self.context).data
def _save_profiles(self, material, fertilizer_profile_data, pesticide_profile_data):
if material.material_type == Material.MaterialType.FERTILIZER:
if fertilizer_profile_data is not None:
profile, _ = FertilizerProfile.objects.get_or_create(material=material)
for attr, value in fertilizer_profile_data.items():
setattr(profile, attr, value)
profile.save()
PesticideProfile.objects.filter(material=material).delete()
return
if material.material_type == Material.MaterialType.PESTICIDE:
if pesticide_profile_data is not None:
profile, _ = PesticideProfile.objects.get_or_create(material=material)
for attr, value in pesticide_profile_data.items():
setattr(profile, attr, value)
profile.save()
FertilizerProfile.objects.filter(material=material).delete()
return
FertilizerProfile.objects.filter(material=material).delete()
PesticideProfile.objects.filter(material=material).delete()
class StockTransactionSerializer(serializers.ModelSerializer):
material_name = serializers.CharField(source='material.name', read_only=True)
material_type = serializers.CharField(source='material.material_type', read_only=True)
stock_unit = serializers.CharField(source='material.stock_unit', read_only=True)
stock_unit_display = serializers.CharField(
source='material.get_stock_unit_display',
read_only=True,
)
transaction_type_display = serializers.CharField(
source='get_transaction_type_display',
read_only=True,
)
class Meta:
model = StockTransaction
fields = [
'id',
'material',
'material_name',
'material_type',
'transaction_type',
'transaction_type_display',
'quantity',
'stock_unit',
'stock_unit_display',
'occurred_on',
'note',
'fertilization_plan',
'created_at',
]
read_only_fields = ['created_at']
class StockSummarySerializer(serializers.Serializer):
material_id = serializers.IntegerField()
name = serializers.CharField()
material_type = serializers.CharField()
material_type_display = serializers.CharField()
maker = serializers.CharField()
stock_unit = serializers.CharField()
stock_unit_display = serializers.CharField()
is_active = serializers.BooleanField()
current_stock = serializers.DecimalField(max_digits=10, decimal_places=3)
reserved_stock = serializers.DecimalField(max_digits=10, decimal_places=3)
available_stock = serializers.DecimalField(max_digits=10, decimal_places=3)
last_transaction_date = serializers.DateField(allow_null=True)