主な変更は、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 にも反映内容をコメント済みです。必要なら次にコミットします。
220 lines
7.7 KiB
Python
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)
|