- Backend: apps/fertilizer を新規追加 - Fertilizer(肥料マスタ)、FertilizationPlan、FertilizationEntry モデル - 肥料マスタ・施肥計画 CRUD API - 3方式の自動計算API(反当袋数・均等配分・反当チッソ成分量) - 作付け計画から圃場候補を取得する API - WeasyPrint による PDF 出力(圃場×肥料=袋数 マトリクス表) - Frontend: app/fertilizer を新規追加 - 施肥計画一覧(年度セレクタ・PDF出力・編集・削除) - 肥料マスタ管理(インライン編集) - 施肥計画編集(品種選択→圃場自動取得→肥料追加→自動計算→マトリクス手動調整) - Navbar に「施肥計画」メニューを追加(Sprout アイコン) - Cursor ルールファイル・連携ガイドを削除(Claude Code 単独運用へ) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
from rest_framework import serializers
|
||
from .models import Fertilizer, FertilizationPlan, FertilizationEntry
|
||
|
||
|
||
class FertilizerSerializer(serializers.ModelSerializer):
|
||
class Meta:
|
||
model = Fertilizer
|
||
fields = '__all__'
|
||
|
||
|
||
class FertilizationEntrySerializer(serializers.ModelSerializer):
|
||
field_name = serializers.CharField(source='field.name', read_only=True)
|
||
field_area_tan = serializers.DecimalField(
|
||
source='field.area_tan', max_digits=6, decimal_places=4, read_only=True
|
||
)
|
||
fertilizer_name = serializers.CharField(source='fertilizer.name', read_only=True)
|
||
|
||
class Meta:
|
||
model = FertilizationEntry
|
||
fields = ['id', 'field', 'field_name', 'field_area_tan', 'fertilizer', 'fertilizer_name', 'bags']
|
||
|
||
|
||
class FertilizationPlanSerializer(serializers.ModelSerializer):
|
||
variety_name = serializers.SerializerMethodField()
|
||
crop_name = serializers.SerializerMethodField()
|
||
entries = FertilizationEntrySerializer(many=True, read_only=True)
|
||
field_count = serializers.SerializerMethodField()
|
||
fertilizer_count = serializers.SerializerMethodField()
|
||
|
||
class Meta:
|
||
model = FertilizationPlan
|
||
fields = [
|
||
'id', 'name', 'year', 'variety', 'variety_name', 'crop_name',
|
||
'entries', 'field_count', 'fertilizer_count', 'created_at', 'updated_at'
|
||
]
|
||
|
||
def get_variety_name(self, obj):
|
||
return obj.variety.name
|
||
|
||
def get_crop_name(self, obj):
|
||
return obj.variety.crop.name
|
||
|
||
def get_field_count(self, obj):
|
||
return obj.entries.values('field').distinct().count()
|
||
|
||
def get_fertilizer_count(self, obj):
|
||
return obj.entries.values('fertilizer').distinct().count()
|
||
|
||
|
||
class FertilizationPlanWriteSerializer(serializers.ModelSerializer):
|
||
"""保存用(entries を一括で受け取る)"""
|
||
entries = serializers.ListField(child=serializers.DictField(), write_only=True, required=False)
|
||
|
||
class Meta:
|
||
model = FertilizationPlan
|
||
fields = ['id', 'name', 'year', 'variety', 'entries']
|
||
|
||
def create(self, validated_data):
|
||
entries_data = validated_data.pop('entries', [])
|
||
plan = FertilizationPlan.objects.create(**validated_data)
|
||
self._save_entries(plan, entries_data)
|
||
return plan
|
||
|
||
def update(self, instance, validated_data):
|
||
entries_data = validated_data.pop('entries', None)
|
||
for attr, value in validated_data.items():
|
||
setattr(instance, attr, value)
|
||
instance.save()
|
||
if entries_data is not None:
|
||
instance.entries.all().delete()
|
||
self._save_entries(instance, entries_data)
|
||
return instance
|
||
|
||
def _save_entries(self, plan, entries_data):
|
||
for entry in entries_data:
|
||
FertilizationEntry.objects.create(
|
||
plan=plan,
|
||
field_id=entry['field_id'],
|
||
fertilizer_id=entry['fertilizer_id'],
|
||
bags=entry['bags'],
|
||
)
|