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, ) is_locked = serializers.SerializerMethodField() 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', 'spreading_item', 'is_locked', 'created_at', ] read_only_fields = ['created_at'] def get_is_locked(self, obj): return bool(obj.fertilization_plan_id or obj.spreading_item_id) 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)