Files
keinasystem/backend/apps/materials/serializers.py
Akira 72b4d670fe 完璧に動作しています。
テスト	結果
確定取消 API	 is_confirmed: false, confirmed_at: null
USE トランザクション削除	 current_stock が 27.5→32 に復帰
引当再作成	 reserved_stock = 5.000 に復帰
追加した変更:

stock_service.py:81-93 — unconfirm_spreading(): USE削除→確定フラグリセット→引当再作成
fertilizer/views.py — unconfirm アクション(POST /api/fertilizer/plans/{id}/unconfirm/)
fertilizer/page.tsx — 一覧に「確定取消」ボタン(確定済み計画のみ表示)
FertilizerEditPage.tsx — 編集画面ヘッダーに「確定取消」ボタン + 在庫情報再取得
2026-03-15 13:28:02 +09:00

216 lines
7.6 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.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)